Skip to content

Commit 1392172

Browse files
committed
feature: defer plan generation from add to work-implement
1 parent 79e77be commit 1392172

8 files changed

Lines changed: 477 additions & 93 deletions

File tree

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"status": "open"
2+
"status": "closed",
3+
"resolution": "implemented with final inline fixes (config reads, temp cleanup, hardcoded paths)",
4+
"targetBranch": "v2.1"
35
}

.cat/issues/v2/v2.1/defer-plan-generation-to-work-phase/plan.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,159 @@ Deferring full plan generation ensures the implementation plan reflects the curr
1515
(including implementation steps) at issue creation time
1616
- `cat:work-implement-agent` currently assumes a complete PLAN.md exists when it runs
1717

18+
## Approaches
19+
20+
### A: Inline lightweight plan in add + plan-builder invocation in work-implement
21+
22+
- **Risk:** LOW
23+
- **Scope:** 3 files (plugin/skills/add/first-use.md, plugin/skills/work-implement-agent/first-use.md,
24+
plugin/skills/plan-builder-agent/first-use.md and SKILL.md)
25+
- **Description:** Replace plan-builder invocation in add/first-use.md with inline lightweight plan generation.
26+
Add plan-builder invocation in work-implement-agent/first-use.md before spawning the implementation subagent
27+
(only when plan.md lacks implementation steps).
28+
29+
> Selected: Approach A — minimal, targeted changes; backward compatible with existing full plan.mds.
30+
31+
## Risk Assessment
32+
33+
- **Risk Level:** LOW
34+
- **Concerns:** Existing open issues have full PLAN.md files (with implementation steps). They must continue to work.
35+
- **Mitigation:** `cat:work-implement-agent` checks whether plan.md already contains `## Sub-Agent Waves` or
36+
`## Execution Steps` before invoking plan-builder-agent. If the section exists, skip the invocation. This
37+
preserves existing full plans unchanged.
38+
39+
## Files to Modify
40+
41+
- `plugin/skills/add/first-use.md` — Replace plan-builder invocation in `issue_create` step with inline
42+
lightweight plan.md generation (lines ~948–988)
43+
- `plugin/skills/work-implement-agent/first-use.md` — Add new "Generate implementation steps" section after
44+
displaying the implementing banner (after Step 3) and before "Read plan.md and Invoke Main Agent Waves"
45+
- `plugin/skills/plan-builder-agent/SKILL.md` — Update description field to remove `/cat:add` reference
46+
- `plugin/skills/plan-builder-agent/first-use.md` — Update "When to Use" section and Mode: `initial` description
47+
48+
## Pre-conditions
49+
50+
- [ ] All dependent issues are closed
51+
52+
## Sub-Agent Waves
53+
54+
### Wave 1
55+
56+
- **Modify `plugin/skills/add/first-use.md`**: Replace the plan-builder invocation in the `issue_create` step
57+
with lightweight inline plan generation.
58+
59+
Locate the block starting with `**Generate plan.md via plan-builder-agent:**` (around line 948) through
60+
the end of the plan-builder invocation (around line 989, just before `**Apply auto-detected skill dependency
61+
updates...`).
62+
63+
Replace with `**Generate lightweight plan.md:**` block that:
64+
1. Creates a unique temporary plan.md file using `planTempFile=$(mktemp --suffix=.md)` (multi-instance
65+
safe — avoids name collisions when multiple `/cat:add` invocations run concurrently). Writes content:
66+
- `# Plan` header
67+
- `## Goal` section containing `${ISSUE_DESCRIPTION}` verbatim
68+
- `## Post-conditions` section with POSTCONDITIONS items as a checklist
69+
70+
Use this bash approach (the agent writes the file using the Write tool, not a heredoc, to avoid
71+
quoting issues with variable content):
72+
73+
```
74+
First: planTempFile=$(mktemp --suffix=.md)
75+
Then the agent writes the lightweight plan.md to ${planTempFile} using the Write tool
76+
(or Bash with printf/echo) with the following structure:
77+
78+
# Plan
79+
80+
## Goal
81+
82+
${ISSUE_DESCRIPTION}
83+
84+
## Post-conditions
85+
86+
- [ ] ${postcondition_1}
87+
- [ ] ${postcondition_2}
88+
...
89+
```
90+
91+
2. Passes `${planTempFile}` to `create-issue` via `planFile` parameter — same as before, but
92+
pointing to the new lightweight file instead of the plan-builder output.
93+
3. Removes the PLAN_CONTEXT JSON file creation step.
94+
4. Removes the `cat:plan-builder-agent` Skill tool invocation.
95+
5. Keeps the `create-issue` bash call structure identical (only the `planFile` value changes).
96+
97+
Files: `plugin/skills/add/first-use.md`
98+
99+
- **Modify `plugin/skills/work-implement-agent/first-use.md`**: Insert a new "### Generate Implementation Steps"
100+
section between the "Step 3 (Implementing Banner)" content and the "### Read plan.md and Invoke Main Agent
101+
Waves" section (around line 153).
102+
103+
The new section:
104+
105+
```markdown
106+
### Generate Implementation Steps
107+
108+
Before reading Main Agent Waves, check whether plan.md already contains implementation steps:
109+
110+
```bash
111+
PLAN_MD="${ISSUE_PATH}/plan.md" && \
112+
grep -qE '^## (Sub-Agent Waves|Execution Steps)' "${PLAN_MD}" && \
113+
echo "hasSteps=true" || echo "hasSteps=false"
114+
```
115+
116+
**If `hasSteps=false`** (lightweight plan created by `/cat:add`): invoke `cat:plan-builder-agent` in
117+
revise mode to generate full implementation steps before spawning the implementation subagent:
118+
119+
1. Read EFFORT from config:
120+
```bash
121+
CONFIG=$("${CLAUDE_PLUGIN_ROOT}/client/bin/get-config-output" effective)
122+
EFFORT=$(echo "$CONFIG" | grep -o '"effort"[[:space:]]*:[[:space:]]*"[^"]*"' \
123+
| sed 's/.*"effort"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
124+
```
125+
126+
2. Invoke plan-builder-agent to add implementation steps:
127+
```
128+
Skill tool:
129+
skill: "cat:plan-builder-agent"
130+
args: "${CAT_AGENT_ID} ${EFFORT} revise ${ISSUE_PATH} Generate full implementation steps for
131+
this lightweight plan. Add Sub-Agent Waves or Execution Steps section with detailed step-by-step
132+
implementation guidance."
133+
```
134+
135+
3. After plan-builder-agent returns, re-read the updated plan.md in subsequent steps.
136+
137+
**If `hasSteps=true`** (full plan with implementation steps): skip plan-builder-agent invocation.
138+
Proceed directly to "Read plan.md and Invoke Main Agent Waves".
139+
```
140+
141+
Files: `plugin/skills/work-implement-agent/first-use.md`
142+
143+
- **Modify `plugin/skills/plan-builder-agent/SKILL.md`**: Update the `description` field:
144+
- Old: `Invoked by /cat:add for initial plans and by /cat:work for mid-work revisions.`
145+
- New: `Invoked by /cat:work to generate full implementation steps before spawning the implementation
146+
subagent, and for mid-work revisions when requirements change during implementation.`
147+
148+
Files: `plugin/skills/plan-builder-agent/SKILL.md`
149+
150+
- **Modify `plugin/skills/plan-builder-agent/first-use.md`**: Update two sections:
151+
152+
Section 1 — "When to Use" (around line 61-64):
153+
- Old line 63: `- **Initial planning** (\`/cat:add\`): Generate plan.md from issue description and context`
154+
- New line 63: `- **Initial implementation** (\`/cat:work\`): Generate full implementation steps from a
155+
lightweight plan.md created by \`/cat:add\` (which contains only goal and post-conditions)`
156+
157+
Section 2 — "### Mode: `initial`" (around line 33-48):
158+
- Old line 35: `Used by \`/cat:add\`. The \`contextPath\` points to a temporary JSON file containing:`
159+
- New line 35: `**Deprecated.** This mode was previously used by \`/cat:add\`. The \`revise\` mode is now
160+
used by \`/cat:work-implement-agent\` to generate full implementation steps from lightweight plan.mds.
161+
The \`contextPath\` points to a temporary JSON file containing:`
162+
163+
Files: `plugin/skills/plan-builder-agent/first-use.md`
164+
165+
- **Commit all three file changes in one commit** with message:
166+
`feature: defer plan generation from add to work-implement`
167+
168+
Also include index.json closure in this same commit:
169+
- Set status to `closed` and progress to `100%` in the issue's index.json
170+
18171
## Post-conditions
19172

20173
1. `/cat:add-agent` creates a lightweight PLAN.md containing only: goal description, pre-conditions, and

client/build-jlink.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ readonly -a HANDLERS=(
8787
"statusline-install:util.StatuslineInstall"
8888
"record-learning:util.RecordLearning"
8989
"benchmark-runner:skills.BenchmarkRunner"
90+
"verify-defer-plan-generation:util.VerifyDeferPlanGeneration"
9091
)
9192

9293
# --- Logging ---
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/*
2+
* Copyright (c) 2026 Gili Tzabari. All rights reserved.
3+
*
4+
* Licensed under the CAT Commercial License.
5+
* See LICENSE.md in the project root for license terms.
6+
*/
7+
package io.github.cowwoc.cat.hooks.util;
8+
9+
import io.github.cowwoc.cat.hooks.HookOutput;
10+
import io.github.cowwoc.cat.hooks.JvmScope;
11+
import io.github.cowwoc.cat.hooks.MainJvmScope;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
15+
import java.io.IOException;
16+
import java.io.PrintStream;
17+
import java.nio.file.Files;
18+
import java.nio.file.Path;
19+
import java.util.Objects;
20+
21+
/**
22+
* Manual E2E verification tool for the defer-plan-generation-to-work-phase feature.
23+
* <p>
24+
* Asserts that:
25+
* <ol>
26+
* <li>{@code plugin/skills/add/first-use.md} does NOT contain a {@code cat:plan-builder-agent} invocation</li>
27+
* <li>{@code plugin/skills/add/first-use.md} contains the lightweight plan generation block
28+
* ({@code planTempFile=$(mktemp})</li>
29+
* <li>{@code plugin/skills/work-implement-agent/first-use.md} contains the {@code hasSteps} check</li>
30+
* <li>{@code plugin/skills/work-implement-agent/first-use.md} invokes {@code cat:plan-builder-agent}</li>
31+
* </ol>
32+
* <p>
33+
* Usage: {@code verify-defer-plan-generation [PROJECT_ROOT]}
34+
* <p>
35+
* {@code PROJECT_ROOT} defaults to the {@code CLAUDE_PROJECT_DIR} environment variable.
36+
*/
37+
public final class VerifyDeferPlanGeneration
38+
{
39+
/**
40+
* Instances are not supported.
41+
*/
42+
private VerifyDeferPlanGeneration()
43+
{
44+
}
45+
46+
/**
47+
* Entry point for the verify-defer-plan-generation CLI tool.
48+
*
49+
* @param args optional first argument is the project root path; defaults to {@code CLAUDE_PROJECT_DIR}
50+
*/
51+
public static void main(String[] args)
52+
{
53+
try (MainJvmScope scope = new MainJvmScope())
54+
{
55+
try
56+
{
57+
run(args, scope, System.out);
58+
}
59+
catch (IOException e)
60+
{
61+
System.err.println("ERROR: " + e.getMessage());
62+
System.exit(1);
63+
}
64+
catch (RuntimeException | AssertionError e)
65+
{
66+
Logger log = LoggerFactory.getLogger(VerifyDeferPlanGeneration.class);
67+
log.error("Unexpected error", e);
68+
System.out.println(new HookOutput(scope).block(
69+
Objects.toString(e.getMessage(), e.getClass().getSimpleName())));
70+
}
71+
}
72+
}
73+
74+
/**
75+
* Runs the verification checks, writing results to {@code out}.
76+
*
77+
* @param args command-line arguments; optional first arg is PROJECT_ROOT
78+
* @param scope the JVM scope providing the project path when no argument is supplied
79+
* @param out the output stream for results
80+
* @throws IOException if a skill file cannot be read
81+
* @throws NullPointerException if {@code args}, {@code scope}, or {@code out} are null
82+
*/
83+
@SuppressWarnings("PMD.DoNotTerminateVM")
84+
public static void run(String[] args, JvmScope scope, PrintStream out) throws IOException
85+
{
86+
Path projectRoot;
87+
if (args.length >= 1)
88+
projectRoot = Path.of(args[0]);
89+
else
90+
projectRoot = scope.getProjectPath();
91+
92+
Path addSkill = projectRoot.resolve("plugin/skills/add/first-use.md");
93+
Path workImplementSkill = projectRoot.resolve("plugin/skills/work-implement-agent/first-use.md");
94+
95+
out.println("=== E2E Verification: defer-plan-generation-to-work-phase ===");
96+
97+
int passed = 0;
98+
int failed = 0;
99+
100+
// Check 1: add/first-use.md must NOT contain cat:plan-builder-agent invocation
101+
if (Files.notExists(addSkill))
102+
{
103+
out.println("FAIL: add/first-use.md has no cat:plan-builder-agent invocation");
104+
out.println(" File not found: " + addSkill);
105+
++failed;
106+
}
107+
else
108+
{
109+
String addContent = Files.readString(addSkill);
110+
if (addContent.contains("cat:plan-builder-agent"))
111+
{
112+
out.println("FAIL: add/first-use.md has no cat:plan-builder-agent invocation");
113+
out.println(" Found 'cat:plan-builder-agent' in " + addSkill);
114+
++failed;
115+
}
116+
else
117+
{
118+
out.println("PASS: add/first-use.md has no cat:plan-builder-agent invocation");
119+
++passed;
120+
}
121+
}
122+
123+
// Check 2: add/first-use.md must contain the lightweight plan generation block
124+
if (Files.notExists(addSkill))
125+
{
126+
out.println("FAIL: add/first-use.md contains lightweight plan block (planTempFile mktemp)");
127+
out.println(" File not found: " + addSkill);
128+
++failed;
129+
}
130+
else
131+
{
132+
String addContent = Files.readString(addSkill);
133+
if (addContent.contains("planTempFile=$(mktemp"))
134+
{
135+
out.println("PASS: add/first-use.md contains planTempFile=$(mktemp ...)");
136+
++passed;
137+
}
138+
else
139+
{
140+
out.println("FAIL: add/first-use.md contains lightweight plan block (planTempFile mktemp)");
141+
out.println(" Pattern 'planTempFile=$(mktemp' not found in " + addSkill);
142+
++failed;
143+
}
144+
}
145+
146+
// Check 3: work-implement-agent/first-use.md must contain hasSteps check
147+
if (Files.notExists(workImplementSkill))
148+
{
149+
out.println("FAIL: work-implement-agent/first-use.md contains hasSteps check");
150+
out.println(" File not found: " + workImplementSkill);
151+
++failed;
152+
}
153+
else
154+
{
155+
String workContent = Files.readString(workImplementSkill);
156+
if (workContent.contains("hasSteps"))
157+
{
158+
out.println("PASS: work-implement-agent/first-use.md contains hasSteps check");
159+
++passed;
160+
}
161+
else
162+
{
163+
out.println("FAIL: work-implement-agent/first-use.md contains hasSteps check");
164+
out.println(" Pattern 'hasSteps' not found in " + workImplementSkill);
165+
++failed;
166+
}
167+
}
168+
169+
// Check 4: work-implement-agent/first-use.md must invoke cat:plan-builder-agent
170+
if (Files.notExists(workImplementSkill))
171+
{
172+
out.println("FAIL: work-implement-agent/first-use.md invokes cat:plan-builder-agent");
173+
out.println(" File not found: " + workImplementSkill);
174+
++failed;
175+
}
176+
else
177+
{
178+
String workContent = Files.readString(workImplementSkill);
179+
if (workContent.contains("cat:plan-builder-agent"))
180+
{
181+
out.println("PASS: work-implement-agent/first-use.md invokes cat:plan-builder-agent");
182+
++passed;
183+
}
184+
else
185+
{
186+
out.println("FAIL: work-implement-agent/first-use.md invokes cat:plan-builder-agent");
187+
out.println(" Pattern 'cat:plan-builder-agent' not found in " + workImplementSkill);
188+
++failed;
189+
}
190+
}
191+
192+
out.println("=== Results: " + passed + " passed, " + failed + " failed ===");
193+
194+
if (failed > 0)
195+
System.exit(1);
196+
}
197+
}

0 commit comments

Comments
 (0)