work-kit-cli 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -51,49 +51,39 @@ function parseStateMd(stateMd: string): RawEntry[] {
51
51
  const out: RawEntry[] = [];
52
52
  if (!stateMd) return out;
53
53
 
54
- let section: "observations" | "decisions" | "deviations" | null = null;
54
+ // Only `## Observations` is auto-harvested. `## Decisions` and `## Deviations`
55
+ // are agent scratch space during normal phase work — they routinely contain
56
+ // test plans, acceptance-criteria checklists, and self-review dumps. Auto-
57
+ // routing them floods workflow.md with noise. Agents opt into harvesting by
58
+ // writing typed bullets (`- [lesson|convention|risk|workflow] text`) under
59
+ // `## Observations`.
60
+ let inObservations = false;
55
61
 
56
62
  for (const rawLine of stateMd.split("\n")) {
57
63
  const trimmed = rawLine.trim();
58
64
 
59
65
  if (trimmed.startsWith("## ")) {
60
- const header = trimmed.slice(3).trim().toLowerCase();
61
- if (header === "observations") section = "observations";
62
- else if (header === "decisions") section = "decisions";
63
- else if (header === "deviations") section = "deviations";
64
- else section = null;
66
+ inObservations = trimmed.slice(3).trim().toLowerCase() === "observations";
65
67
  continue;
66
68
  }
67
69
 
68
- if (section === null) continue;
70
+ if (!inObservations) continue;
69
71
  if (!trimmed.startsWith("-") || trimmed.startsWith("<!--")) continue;
70
72
 
71
- if (section === "observations") {
72
- const m = trimmed.match(OBSERVATION_RE);
73
- if (!m) continue;
74
- const tag = m[1].toLowerCase();
75
- if (!isKnowledgeType(tag)) continue;
76
- const phaseStep = m[2];
77
- const text = m[3].trim();
78
- if (text.length === 0) continue;
79
- const entry: RawEntry = { type: tag, text, source: "auto-state-md" };
80
- if (phaseStep) {
81
- const [p, s] = phaseStep.split("/");
82
- entry.phase = p;
83
- entry.step = s;
84
- }
85
- out.push(entry);
86
- continue;
87
- }
88
-
89
- const text = trimmed.replace(/^-\s*/, "").trim();
73
+ const m = trimmed.match(OBSERVATION_RE);
74
+ if (!m) continue;
75
+ const tag = m[1].toLowerCase();
76
+ if (!isKnowledgeType(tag)) continue;
77
+ const phaseStep = m[2];
78
+ const text = m[3].trim();
90
79
  if (text.length === 0) continue;
91
-
92
- if (section === "decisions") {
93
- out.push({ type: "convention", text, source: "auto-state-md" });
94
- } else if (section === "deviations") {
95
- out.push({ type: "workflow", text: `[deviation] ${text}`, source: "auto-state-md" });
80
+ const entry: RawEntry = { type: tag, text, source: "auto-state-md" };
81
+ if (phaseStep) {
82
+ const [p, s] = phaseStep.split("/");
83
+ entry.phase = p;
84
+ entry.step = s;
96
85
  }
86
+ out.push(entry);
97
87
  }
98
88
 
99
89
  return out;
@@ -176,6 +176,33 @@ describe("learnCommand", () => {
176
176
  assert.ok(workflowMd.includes("e2e step needs server"));
177
177
  });
178
178
 
179
+ it("ignores bullets under ## Decisions and ## Deviations", () => {
180
+ const tmp = makeTmpDir();
181
+ tmpDirs.push(tmp);
182
+ setupSession(tmp);
183
+
184
+ // Simulate an agent dumping test-plan noise into Decisions/Deviations.
185
+ // None of these should be harvested — only ## Observations is auto-routed.
186
+ const stateMdPath = path.join(tmp, ".work-kit", "state.md");
187
+ const original = fs.readFileSync(stateMdPath, "utf-8");
188
+ const injected = original
189
+ .replace(
190
+ "## Decisions\n<!-- Append here whenever you choose between real alternatives -->",
191
+ "## Decisions\n<!-- Append here whenever you choose between real alternatives -->\n- Picked Zod over Yup\n- Use Firestore onSnapshot\n- **Expected:** Badge renders X / Y"
192
+ )
193
+ .replace(
194
+ "## Deviations\n<!-- Append here whenever implementation diverges from the Blueprint -->",
195
+ "## Deviations\n<!-- Append here whenever implementation diverges from the Blueprint -->\n- Navigate to / and verify cards render\n- Check badge shows 0 / 0\n- **Flow 3:** Pass"
196
+ );
197
+ fs.writeFileSync(stateMdPath, injected);
198
+
199
+ const r = extractCommand({ worktreeRoot: tmp });
200
+ assert.equal(r.action, "extracted");
201
+ assert.equal(r.written, 0, "no bullets from Decisions/Deviations should be harvested");
202
+ assert.equal(r.byType.convention, 0);
203
+ assert.equal(r.byType.workflow, 0);
204
+ });
205
+
179
206
  it("extract is idempotent (re-run produces only duplicates)", () => {
180
207
  const tmp = makeTmpDir();
181
208
  tmpDirs.push(tmp);
@@ -243,7 +243,23 @@ function installPlaywrightPackage(pm: PackageManager, projectDir: string): boole
243
243
  pm === "yarn" ? ["add", "-D", "@playwright/test"] :
244
244
  ["install", "-D", "@playwright/test"];
245
245
  console.error(` ${dim(`$ ${pm} ${args.join(" ")}`)}`);
246
- return runStreamed(pm, args, projectDir);
246
+ if (runStreamed(pm, args, projectDir)) return true;
247
+
248
+ // The most common npm failure here is ERESOLVE — the user's project has
249
+ // a pre-existing peer-dep conflict that npm refuses to resolve. Retry with
250
+ // --legacy-peer-deps so Playwright still installs; the user's underlying
251
+ // conflict is left for them to fix separately.
252
+ if (pm === "npm") {
253
+ console.error(` ${yellow("!")} npm install failed (likely peer-dep conflict). Retrying with --legacy-peer-deps...`);
254
+ const fallbackArgs = [...args, "--legacy-peer-deps"];
255
+ console.error(` ${dim(`$ ${pm} ${fallbackArgs.join(" ")}`)}`);
256
+ if (runStreamed(pm, fallbackArgs, projectDir)) {
257
+ console.error(` ${dim("Note: installed with --legacy-peer-deps. Your project still has the original peer-dep conflict — fix it separately when convenient.")}`);
258
+ return true;
259
+ }
260
+ }
261
+
262
+ return false;
247
263
  }
248
264
 
249
265
  function installPlaywrightBrowsers(projectDir: string): boolean {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "work-kit-cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Structured development workflow for Claude Code. Two modes, 6 phases, 27 steps.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,7 +26,7 @@ The summary you write goes into `.work-kit/summary.md`; the CLI archives it into
26
26
  4. **Run** `work-kit complete wrap-up/summary --outcome done`
27
27
 
28
28
  ### Step 2: knowledge
29
- 5. **Run `work-kit extract`** — mechanically routes Observations / Decisions / Deviations / loopbacks into `.work-kit-knowledge/` files
29
+ 5. **Run `work-kit extract`** — routes typed `## Observations` bullets and tracker loopbacks/skipped/failed steps into `.work-kit-knowledge/` files. `## Decisions` and `## Deviations` are not auto-harvested (they're scratch space).
30
30
  6. **Review the summary you just wrote** for subjective additions the parser would miss. For each, call `work-kit learn --type <lesson|convention|risk|workflow> --text "..."`.
31
31
  7. **Run** `work-kit complete wrap-up/knowledge --outcome done`
32
32
 
@@ -15,12 +15,12 @@ After `wrap-up/summary`. By now you've just re-read the full `state.md` and dist
15
15
  work-kit extract
16
16
  ```
17
17
  This parses `.work-kit/state.md` and `.work-kit/tracker.json` and routes entries to `.work-kit-knowledge/{lessons,conventions,risks,workflow}.md`. It pulls from:
18
- - `## Observations` typed bullets (`- [lesson|convention|risk|workflow] text`)
19
- - `## Decisions` → conventions
20
- - `## Deviations` → workflow feedback
18
+ - `## Observations` typed bullets (`- [lesson|convention|risk|workflow] text`) — the only section that is auto-harvested
21
19
  - `tracker.json.loopbacks[]` → workflow feedback
22
20
  - Skipped/failed steps → workflow feedback
23
21
 
22
+ `## Decisions` and `## Deviations` are **not** auto-harvested — they're agent scratch space and routinely contain test plans and review notes. If something in there is worth preserving, restate it as a typed bullet under `## Observations` (or call `work-kit learn` directly in step 2).
23
+
24
24
  The output JSON tells you how many entries were `written` vs `duplicates`. Re-running is idempotent.
25
25
 
26
26
  2. **Read your `.work-kit/summary.md`** (the one you just wrote). For each non-obvious thing in it that the parser would NOT have captured automatically, call `work-kit learn`: