devflow-kit 3.6.0 → 3.8.0

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.
Files changed (197) hide show
  1. package/CHANGELOG.md +438 -1
  2. package/README.md +15 -11
  3. package/dist/agents/code.md +9 -15
  4. package/dist/agents/design.md +1 -1
  5. package/dist/agents/fix.md +11 -13
  6. package/dist/agents/git.md +1 -1
  7. package/dist/agents/harden.md +1 -1
  8. package/dist/agents/refactor.md +7 -10
  9. package/dist/agents/review.md +6 -4
  10. package/dist/agents/skim.md +2 -2
  11. package/dist/agents/triage.md +2 -2
  12. package/dist/cli/commands/capture.js +1 -1
  13. package/dist/cli/commands/curation-move-io.js +102 -0
  14. package/dist/cli/commands/curation-move-step.js +374 -0
  15. package/dist/cli/commands/{learning.js → curation.js} +302 -99
  16. package/dist/cli/commands/gate-level-prompts.js +426 -0
  17. package/dist/cli/commands/gates.js +299 -0
  18. package/dist/cli/commands/init-seed.js +16 -5
  19. package/dist/cli/commands/init.js +263 -54
  20. package/dist/cli/commands/memory.js +20 -2
  21. package/dist/cli/commands/prompt-io.js +37 -0
  22. package/dist/cli/commands/uninstall.js +81 -9
  23. package/dist/cli.js +4 -2
  24. package/dist/commands/bug-analysis.md +25 -36
  25. package/dist/commands/code-review.md +77 -73
  26. package/dist/commands/debug.md +5 -5
  27. package/dist/commands/dynamic-build.md +76 -83
  28. package/dist/commands/dynamic-plan.md +5 -5
  29. package/dist/commands/dynamic-profile.md +5 -5
  30. package/dist/commands/dynamic-tickets.md +5 -5
  31. package/dist/commands/explore.md +6 -6
  32. package/dist/commands/implement.md +68 -102
  33. package/dist/commands/plan.md +19 -29
  34. package/dist/commands/release.md +5 -5
  35. package/dist/commands/research.md +6 -6
  36. package/dist/commands/resolve.md +29 -31
  37. package/dist/commands/self-review.md +5 -13
  38. package/dist/core/assets.js +20 -20
  39. package/dist/core/curate-tuning-config.js +5 -6
  40. package/dist/core/curation-move-notice.js +84 -0
  41. package/dist/core/curation-move-skip.js +36 -0
  42. package/dist/core/curation-move.js +35 -0
  43. package/dist/core/curation-queue-cleanup.js +134 -0
  44. package/dist/core/curation-store.js +98 -0
  45. package/dist/core/{learning-variants.js → curation-variants.js} +65 -41
  46. package/dist/core/echo-value.js +48 -0
  47. package/dist/core/evidence-policy.js +18 -6
  48. package/dist/core/feature-switch.js +95 -19
  49. package/dist/core/fs-atomic.js +1 -1
  50. package/dist/core/gate-level.js +221 -0
  51. package/dist/core/git.js +16 -14
  52. package/dist/core/ledger-root.js +82 -22
  53. package/dist/core/linked-path.js +4 -4
  54. package/dist/core/manifest-lock.js +238 -0
  55. package/dist/core/manifest.js +84 -24
  56. package/dist/core/migrations.js +1 -13
  57. package/dist/core/model-discovery.js +7 -1
  58. package/dist/core/observations.js +2 -2
  59. package/dist/core/plugins.js +15 -15
  60. package/dist/core/pool.js +26 -0
  61. package/dist/core/project-paths.js +103 -34
  62. package/dist/core/queue-drain.js +2 -2
  63. package/dist/core/tracked-areas.js +35 -0
  64. package/dist/core/tracker.js +6 -34
  65. package/dist/{learning-off → curation-off}/agents/code.md +9 -15
  66. package/dist/{learning-off → curation-off}/agents/fix.md +11 -13
  67. package/dist/{learning-off → curation-off}/agents/refactor.md +7 -10
  68. package/dist/{learning-off → curation-off}/agents/review.md +5 -3
  69. package/dist/{learning-off → curation-off}/agents/triage.md +1 -1
  70. package/dist/{learning-off → curation-off}/commands/bug-analysis.md +20 -31
  71. package/dist/{learning-off → curation-off}/commands/code-review.md +74 -70
  72. package/dist/{learning-off → curation-off}/commands/debug.md +2 -2
  73. package/dist/{learning-off → curation-off}/commands/dynamic-build.md +73 -80
  74. package/dist/{learning-off → curation-off}/commands/explore.md +2 -2
  75. package/dist/{learning-off → curation-off}/commands/implement.md +65 -99
  76. package/dist/{learning-off → curation-off}/commands/plan.md +15 -25
  77. package/dist/{learning-off → curation-off}/commands/resolve.md +26 -28
  78. package/dist/{learning-off → curation-off}/commands/self-review.md +2 -10
  79. package/dist/hud/components/{learning-counts.js → curation-counts.js} +36 -17
  80. package/dist/hud/config.js +1 -1
  81. package/dist/hud/index.js +6 -6
  82. package/dist/hud/render.js +3 -3
  83. package/dist/skills/git/references/pr/validate-branch.md +8 -5
  84. package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +20 -14
  85. package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +20 -20
  86. package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +20 -20
  87. package/dist/targets/claude-code/{learning-install.js → curation-install.js} +99 -67
  88. package/dist/targets/claude-code/installer.js +15 -15
  89. package/dist/targets/claude-code/language-stamp.js +5 -5
  90. package/dist/targets/claude-code/post-install.js +2 -2
  91. package/dist/targets/claude-code/tracker-servers.js +1 -1
  92. package/package.json +3 -2
  93. package/src/assets/agents/_partials/_code.mds +16 -17
  94. package/src/assets/agents/code.mds +4 -7
  95. package/src/assets/agents/curate.md +12 -14
  96. package/src/assets/agents/debug.mds +6 -6
  97. package/src/assets/agents/design.mds +18 -18
  98. package/src/assets/agents/fix.mds +6 -5
  99. package/src/assets/agents/git.mds +1 -1
  100. package/src/assets/agents/harden.mds +7 -7
  101. package/src/assets/agents/knowledge.mds +23 -23
  102. package/src/assets/agents/refactor.mds +2 -2
  103. package/src/assets/agents/research.mds +15 -15
  104. package/src/assets/agents/review.mds +45 -43
  105. package/src/assets/agents/skim.mds +11 -11
  106. package/src/assets/agents/synthesize.md +2 -2
  107. package/src/assets/agents/triage.mds +22 -22
  108. package/src/assets/agents/validate.md +13 -26
  109. package/src/assets/commands/_partials/_decisions.mds +13 -13
  110. package/src/assets/commands/_partials/_engine.mds +8 -8
  111. package/src/assets/commands/_partials/_knowledge.mds +29 -15
  112. package/src/assets/commands/_partials/_preamble.mds +4 -4
  113. package/src/assets/commands/_partials/_settings.mds +3 -3
  114. package/src/assets/commands/_partials/_wave.mds +17 -24
  115. package/src/assets/commands/bug-analysis.mds +37 -48
  116. package/src/assets/commands/code-review.mds +90 -86
  117. package/src/assets/commands/debug.mds +14 -14
  118. package/src/assets/commands/dynamic-build.mds +78 -78
  119. package/src/assets/commands/dynamic-plan.mds +22 -22
  120. package/src/assets/commands/dynamic-profile.mds +2 -2
  121. package/src/assets/commands/dynamic-tickets.mds +8 -8
  122. package/src/assets/commands/explore.mds +20 -20
  123. package/src/assets/commands/implement.mds +92 -118
  124. package/src/assets/commands/plan.mds +41 -51
  125. package/src/assets/commands/release.mds +12 -12
  126. package/src/assets/commands/research.mds +19 -19
  127. package/src/assets/commands/resolve.mds +56 -50
  128. package/src/assets/commands/self-review.mds +20 -20
  129. package/src/assets/mds/git/_ops.mds +3 -1
  130. package/src/assets/mds/git/_ops_pr.mds +1 -1
  131. package/src/assets/mds/git/_ops_steps.mds +13 -7
  132. package/src/assets/mds/git/_pr.mds +7 -4
  133. package/src/assets/mds/tracker/_common.mds +23 -12
  134. package/src/assets/mds/tracker/_github.mds +2 -9
  135. package/src/assets/mds/tracker/_jira.mds +2 -15
  136. package/src/assets/mds/tracker/_linear.mds +2 -15
  137. package/src/assets/rules/quality.md +1 -0
  138. package/src/assets/scripts/ci-wait.cjs +22 -4
  139. package/src/assets/scripts/claude-md-audit.cjs +66 -51
  140. package/src/assets/scripts/hooks/background-memory-update +406 -97
  141. package/src/assets/scripts/hooks/capture-prompt +12 -12
  142. package/src/assets/scripts/hooks/capture-question +11 -11
  143. package/src/assets/scripts/hooks/capture-turn +12 -12
  144. package/src/assets/scripts/hooks/{learning-lock → curation-lock} +2 -2
  145. package/src/assets/scripts/hooks/curation-move +293 -0
  146. package/src/assets/scripts/hooks/ensure-devflow-init +45 -13
  147. package/src/assets/scripts/hooks/ensure-proxy +4 -4
  148. package/src/assets/scripts/hooks/ensure-root-gitignore +4 -4
  149. package/src/assets/scripts/hooks/git-marker +81 -0
  150. package/src/assets/scripts/hooks/json-helper.cjs +145 -51
  151. package/src/assets/scripts/hooks/json-parse +19 -4
  152. package/src/assets/scripts/hooks/lib/curation-move.cjs +1582 -0
  153. package/src/assets/scripts/hooks/lib/{learning-store.cjs → curation-store.cjs} +242 -118
  154. package/src/assets/scripts/hooks/lib/decisions-format.cjs +3 -3
  155. package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +24 -7
  156. package/src/assets/scripts/hooks/lib/project-paths.cjs +132 -39
  157. package/src/assets/scripts/hooks/lib/render-decisions.cjs +34 -22
  158. package/src/assets/scripts/hooks/lib/tracked-areas.cjs +259 -0
  159. package/src/assets/scripts/hooks/memory-permission +358 -0
  160. package/src/assets/scripts/hooks/memory-worker +1 -1
  161. package/src/assets/scripts/hooks/pre-compact-memory +6 -16
  162. package/src/assets/scripts/hooks/queue-append +228 -103
  163. package/src/assets/scripts/hooks/resolve-project-root +2 -2
  164. package/src/assets/scripts/hooks/session-start-context +145 -71
  165. package/src/assets/scripts/hooks/session-start-memory +27 -46
  166. package/src/assets/scripts/lib/project-config.cjs +31 -9
  167. package/src/assets/scripts/resolve-base.cjs +1043 -0
  168. package/src/assets/scripts/resolve-settings.cjs +238 -46
  169. package/src/assets/skills/agent-references/SKILL.md +9 -2
  170. package/src/assets/skills/agent-references/references/commands/code-review-levels.md +19 -0
  171. package/src/assets/skills/agent-references/references/commands/implement-levels.md +27 -0
  172. package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +6 -4
  173. package/src/assets/skills/agent-references/references/commands/plan-levels.md +40 -0
  174. package/src/assets/skills/agent-references/references/commands/resolve-levels.md +24 -0
  175. package/src/assets/skills/agent-references/references/synthesize/planning.md +9 -22
  176. package/src/assets/skills/agent-references/references/synthesize/review.md +11 -11
  177. package/src/assets/skills/apply-decisions/SKILL.md +1 -1
  178. package/src/assets/skills/docs-framework/SKILL.md +7 -7
  179. package/src/assets/skills/docs-framework/references/patterns.md +3 -4
  180. package/src/assets/skills/quality-gates/SKILL.md +1 -1
  181. package/src/assets/skills/worktree-support/references/roots.md +2 -2
  182. package/templates/lint-pack/.lint-pack.conf +86 -0
  183. package/templates/lint-pack/README.md +190 -0
  184. package/templates/lint-pack/lint-pack.sh +905 -0
  185. package/dist/core/learning-queue-cleanup.js +0 -27
  186. package/dist/core/learning-store.js +0 -61
  187. /package/dist/{learning-off → curation-off}/agents/debug.md +0 -0
  188. /package/dist/{learning-off → curation-off}/agents/design.md +0 -0
  189. /package/dist/{learning-off → curation-off}/agents/harden.md +0 -0
  190. /package/dist/{learning-off → curation-off}/agents/knowledge.md +0 -0
  191. /package/dist/{learning-off → curation-off}/agents/research.md +0 -0
  192. /package/dist/{learning-off → curation-off}/agents/skim.md +0 -0
  193. /package/dist/{learning-off → curation-off}/commands/dynamic-plan.md +0 -0
  194. /package/dist/{learning-off → curation-off}/commands/dynamic-profile.md +0 -0
  195. /package/dist/{learning-off → curation-off}/commands/dynamic-tickets.md +0 -0
  196. /package/dist/{learning-off → curation-off}/commands/release.md +0 -0
  197. /package/dist/{learning-off → curation-off}/commands/research.md +0 -0
@@ -75,12 +75,12 @@ Pass it as `complianceFrameworks` when invoking the workflow; the engine builds
75
75
 
76
76
  `{integration worktree root}` is the toplevel of the checkout the integration branch is checked out in: `{worktree}` unless the wave runs in a linked worktree.
77
77
 
78
- <!-- learning:on -->
78
+ <!-- curation:on -->
79
79
  **1. Apply decisions context**
80
80
 
81
81
  Apply the `devflow:apply-decisions` algorithm to the DECISIONS_CONTEXT loaded by the decisions step above: scan the index, Read relevant entries, note the verbatim ADR/PF IDs you will inject into Code agent prompts.
82
82
 
83
- <!-- learning:end -->
83
+ <!-- curation:end -->
84
84
  **2. Read budget**
85
85
 
86
86
  Note the `budget` value from the Workflow tool context (or default to "medium" if not provided). This governs Review agent roster size and verification vote count.
@@ -158,9 +158,9 @@ const TICKET = args.ticket || args[0] || "see task description";
158
158
  const PLAN = args.plan || null;
159
159
  const CRITERIA = args.criteria || null;
160
160
  const TEST_PLAN = args.testPlan || null; // the plan's checked TP lines, one string (Pre-authoring step 4)
161
- <!-- learning:on -->
161
+ <!-- curation:on -->
162
162
  const DECISIONS_CONTEXT = args.decisionsContext || ""; // injected before authoring
163
- <!-- learning:end -->
163
+ <!-- curation:end -->
164
164
  const ISSUE_INPUT = args.issueInput || args.issueNumber || "(none)"; // this ticket's OWN raw reference (Pre-authoring step 5; a wave passes issueInput) — setup-task's input, never a Code agent's
165
165
  const ISSUE_REQUIRED = String(args.issueRequired) === "false" ? "false" : "true"; // Pre-authoring step 0; only an explicit false turns it off — absent or unrecognised fails closed, like the resolver
166
166
  const APPLY_CONVENTIONS = String(args.applyConventions) === "false" ? "false" : "true";
@@ -175,7 +175,7 @@ TASK_DESCRIPTION: ${TICKET}
175
175
  ISSUE_REQUIRED: ${ISSUE_REQUIRED}
176
176
  APPLY_CONVENTIONS: ${APPLY_CONVENTIONS}
177
177
  ${ISSUE_INPUT !== "(none)" ? "ISSUE_INPUT: " + ISSUE_INPUT : ""}
178
- Return: {"branch": "<the - **Branch name**: value under your ### Branch, or (none)>", "issueId": "<the - **Issue ID**: value under your ### Handoff Values, or (none)>", "prLinkLine": "<the - **PR link line**: value, or (none)>"}`, { agentType: "Git" })
178
+ Return: {"branch": "<the - **Branch name**: value under your ### Branch, or (none)>", "issueId": "<the - **Issue ID**: value under your ### Handoff Values, or (none)>", "prLinkLine": "<the - **PR link line**: value, or (none)>", "baseSha": "<the 40 or 64 hex git rev-parse HEAD prints before step 4>"}`, { agentType: "Git" })
179
179
  );
180
180
 
181
181
  // The ticket's own Handoff Values, read from setup-task's Output — never derived from ISSUE_INPUT.
@@ -185,6 +185,7 @@ const ISSUE_NUMBER = ISSUE_ID_SHAPE.test(String(setup?.issueId ?? "")) ? setup.i
185
185
  const ISSUE_PR_LINK = setup?.prLinkLine || "(none)"; // "(none)" ⇒ the wave PR carries no closing reference for this ticket
186
186
  // The branch setup-task created, as it reported it: every later phase and the merge use it. The engine never names one.
187
187
  const BRANCH = typeof setup?.branch === "string" && setup.branch.trim() !== "" && setup.branch.trim() !== "(none)" ? setup.branch : "(none)";
188
+ const BASE_SHA = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(String(setup?.baseSha ?? "")) ? setup.baseSha : "(none)"; // the commit the ticket branch was forked from
188
189
 
189
190
  // Ticket-link stop, before implement. The workflow cannot ask, so it never records an exception: it stops.
190
191
  if (ISSUE_REQUIRED === "true" && ISSUE_NUMBER === "(none)") {
@@ -196,6 +197,11 @@ if (BRANCH === "(none)") {
196
197
  return { ticket: TICKET, branch: BRANCH, verdict: "ESCALATED", issueId: ISSUE_NUMBER, issuePrLink: ISSUE_PR_LINK, escalations: [{ type: "branch-missing", description: "setup-task reported no branch name — check its Output, then re-run" }] };
197
198
  }
198
199
 
200
+ // Base stop: every review range starts at the commit the ticket branch was forked from.
201
+ if (BASE_SHA === "(none)") {
202
+ return { ticket: TICKET, branch: BRANCH, verdict: "ESCALATED", issueId: ISSUE_NUMBER, issuePrLink: ISSUE_PR_LINK, escalations: [{ type: "base-missing", description: "setup-task reported no base commit — check its Output, then re-run" }] };
203
+ }
204
+
199
205
  // Phase 2: Implement
200
206
  await phase("implement", () =>
201
207
  agent(`OPERATION: implement
@@ -205,11 +211,11 @@ ${TICKET}
205
211
 
206
212
  ${PLAN ? `Implementation plan:\n${PLAN}` : "No plan provided — use best judgment."}
207
213
 
208
- <!-- learning:on -->
214
+ <!-- curation:on -->
209
215
  Relevant architectural decisions (apply devflow:apply-decisions algorithm):
210
216
  ${DECISIONS_CONTEXT}
211
217
 
212
- <!-- learning:end -->
218
+ <!-- curation:end -->
213
219
  ${COMPLIANCE_DIRECTIVE}
214
220
 
215
221
  When you build or run tests to verify your work, follow your Running commands block.
@@ -244,9 +250,9 @@ Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate"
244
250
  await agent(`OPERATION: validation-fix
245
251
  Fix the validation failures on branch ${BRANCH}:
246
252
  ${failureDetails}
247
- <!-- learning:on -->
253
+ <!-- curation:on -->
248
254
  DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
249
- <!-- learning:end -->
255
+ <!-- curation:end -->
250
256
  ${COMPLIANCE_DIRECTIVE}
251
257
  Commit fixes with conventional-commit message.`, { agentType: "Fix" });
252
258
  const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} after fix attempt ${attempt} (follow your Running commands block).
@@ -287,9 +293,9 @@ Return: {"verdict": "PASS" | "FAIL", "rationale": "..."} — FAIL if either answ
287
293
  await agent(`OPERATION: alignment-fix
288
294
  Fix the alignment issues identified by the Evaluate agent on branch ${BRANCH}:
289
295
  ${evaluation?.rationale || "see the Evaluate agent's report"}
290
- <!-- learning:on -->
296
+ <!-- curation:on -->
291
297
  DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
292
- <!-- learning:end -->
298
+ <!-- curation:end -->
293
299
  ${COMPLIANCE_DIRECTIVE}
294
300
  Self-verify your fix compiles, following your Running commands block. Commit fixes.`, { agentType: "Fix" });
295
301
  evalVerdict = "FAIL-FIXED"; // issues found, fixes applied, not re-evaluated by design
@@ -309,9 +315,9 @@ Cover: functionality, API contracts, performance, and cover every TEST_PLAN scen
309
315
  await agent(`OPERATION: qa-fix
310
316
  Fix the failing acceptance test scenarios on branch ${BRANCH}:
311
317
  ${testResult.failures}
312
- <!-- learning:on -->
318
+ <!-- curation:on -->
313
319
  DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
314
- <!-- learning:end -->
320
+ <!-- curation:end -->
315
321
  ${COMPLIANCE_DIRECTIVE}
316
322
  Self-verify your fix compiles and the scenarios pass, following your Running commands block. Commit fixes.`, { agentType: "Fix" });
317
323
  testVerdict = "FAIL-FIXED"; // issues found, fixes applied, not re-evaluated by design
@@ -323,10 +329,9 @@ Self-verify your fix compiles and the scenarios pass, following your Running com
323
329
 
324
330
  // Phase 5: Review → verify → fix (single pass)
325
331
  const reviewResult = await phase("review", async () => {
326
- // Review scope: the entire branch diff from the base branch to HEAD. No base SHA is tracked —
327
- // Review agents diff the branch against its merge-base with the default branch.
332
+ // Review scope: the entire branch diff, from the commit the ticket branch was forked from to HEAD.
328
333
  // The pass runs exactly ONCE.
329
- const reviewScope = `Review the full branch diff on branch ${BRANCH}. Diff the branch against its merge-base with the default branch (do not guess a base SHA — compute the merge-base from git).`;
334
+ const reviewScope = `Review the full branch diff on branch ${BRANCH}.\nDIFF_RANGE: ${BASE_SHA}...HEAD\n`;
330
335
 
331
336
  // Review agent focus list (order = index mapping for dead-reviewer detection)
332
337
  const reviewFocuses = ["security", "architecture", "performance", "complexity", "consistency", "regression", "testing", "reliability"];
@@ -370,13 +375,12 @@ Focus discipline: ${focus}. Return: {"focus": "${focus}", "reviewed": true, "fil
370
375
  return { survivingFindings: [], fixedFindings: [], coverageGaps };
371
376
  }
372
377
 
373
- // Adversarial verification — bounded 2-3 agent panel, majority-survives
374
- // Panel asks perspective-diverse questions over the WHOLE finding set (not one agent per finding).
375
- // A finding survives if >50% of panel agents CONFIRM it.
378
+ // Adversarial panel over the WHOLE finding set (not one agent per finding): >50% CONFIRMED survives
376
379
  const VERIFY_PANEL = 3;
377
380
  const panels = await parallel(
378
381
  Array.from({ length: VERIFY_PANEL }, (_, lens) => () =>
379
382
  agent(`Adversarially verify all ${allFindings.length} findings on branch ${BRANCH}.
383
+ DIFF_RANGE: ${BASE_SHA}...HEAD
380
384
  Lens ${lens + 1} of ${VERIFY_PANEL}:
381
385
  ${["Does each finding actually reproduce in the current code? Hunt for findings that describe patterns not present in the diff.",
382
386
  "Is each finding a real issue or a false positive in context? Consider intent, surrounding code, and project conventions.",
@@ -394,16 +398,13 @@ ${JSON.stringify(allFindings.map((f, i) => ({ index: i, description: f.descripti
394
398
  panels.filter(p => (p.verdicts || []).find(v => v.index === i)?.verdict === "CONFIRMED").length > VERIFY_PANEL / 2
395
399
  );
396
400
 
397
- // No confirmed findings — coverageGaps carry forward and block PASS downstream; also eliminates the empty-batch parallel() path
401
+ // No confirmed findings: coverageGaps carry forward and block PASS downstream
398
402
  if (confirmedFindings.length === 0) {
399
403
  return { survivingFindings: [], fixedFindings: [], coverageGaps };
400
404
  }
401
405
 
402
- // Batched fix Code agents — one file per set of sub-batches; max 5 findings per sub-batch.
403
- // Sub-batches for the SAME file run sequentially (never two Code agents editing the same file concurrently —
404
- // concurrent same-file edits cause index contention and lost fixes).
405
- // Sub-batches for DISTINCT files run in parallel (different code areas — safe per concurrency doctrine).
406
- // A finding with no file field gets its own singleton group (unknown scope — never mix with known files).
406
+ // Sub-batches (max 5 findings): same file in sequence, distinct files in parallel.
407
+ // A finding with no file is a singleton group.
407
408
  const MAX_BATCH = 5; // max 5 findings per sub-batch
408
409
  const fileGroups = {};
409
410
  for (let fi = 0; fi < confirmedFindings.length; fi++) {
@@ -413,9 +414,7 @@ ${JSON.stringify(allFindings.map((f, i) => ({ index: i, description: f.descripti
413
414
  fileGroups[key].push(f);
414
415
  }
415
416
 
416
- // One entry per distinct file; each entry runs its sub-batches sequentially.
417
- // Distinct-file groups are paced in chunks of FIX_CHUNK — same pacing bar as the Review spawn
418
- // path — to avoid launching all Code agents at once on a large branch (reliability: explicit bound).
417
+ // One entry per file; FIX_CHUNK chunks bound concurrent Code agents
419
418
  const FIX_CHUNK = 5;
420
419
  // issue-fix SCOPE per finding: CRITICAL or HIGH takes the Careful protocol (failing regression test first); anything else is Standard.
421
420
  const CAREFUL_SEVERITIES = new Set(["critical", "high"]);
@@ -429,9 +428,9 @@ ISSUES:
429
428
  ${chunk.map((f, n) => `${n + 1}. ${f.file ? `${f.file}${f.line ? `:${f.line}` : ""} — ` : ""}${f.description} (${f.severity})`).join("\n")}
430
429
  SCOPE: ${chunk.map((f, n) => `${n + 1}=${CAREFUL_SEVERITIES.has(String(f.severity).toLowerCase()) ? "Careful" : "Standard"}`).join(", ")}
431
430
  PUSH: false
432
- <!-- learning:on -->
431
+ <!-- curation:on -->
433
432
  DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
434
- <!-- learning:end -->
433
+ <!-- curation:end -->
435
434
  ${COMPLIANCE_DIRECTIVE}
436
435
  Fix all findings in this batch. Self-verify your fix compiles, following your Running commands block. Commit with conventional-commit message.
437
436
  Return: {"status": "fixed"|"blocked", "commitShas": ["<sha>"], "unresolved": ["<description of any finding that could not be fixed>"]}`, { agentType: "Code" });
@@ -490,9 +489,9 @@ Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate"
490
489
  await agent(`OPERATION: validation-fix
491
490
  Fix the final validation failures on branch ${BRANCH}:
492
491
  ${failureDetails}
493
- <!-- learning:on -->
492
+ <!-- curation:on -->
494
493
  DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
495
- <!-- learning:end -->
494
+ <!-- curation:end -->
496
495
  ${COMPLIANCE_DIRECTIVE}
497
496
  Self-verify your fix compiles. Commit fixes with conventional-commit message.`, { agentType: "Fix" });
498
497
  const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (final gate, after fix attempt ${attempt}; follow your Running commands block).
@@ -575,22 +574,49 @@ When WAVE mode is detected, author a workflow that wraps the single-ticket engin
575
574
 
576
575
  **Wave workflow structure (author after the SINGLE engine blocks above):**
577
576
 
578
- The wave workflow uses the same phases as SINGLE but wraps them in a wave loop. The integration branch is `wave/<initiative>` — the initiative slug, referenced below as `{slug}` — (or the user's current branch). Each ticket works on the branch setup-task created. The Git agent manages worktrees for parallel-eligible tickets. After every merge the workflow spawns the Validate agent (build + test) and undoes a red merge. Escalations accumulate in a list; the final report lists all of them.
577
+ The wave workflow uses the same phases as SINGLE but wraps them in a wave loop. The integration branch is `wave/<initiative>` — the initiative slug, referenced below as `{slug}` — (or the user's current branch).
579
578
 
580
579
  Wave skeleton — compact reference (see `wave_loop()` doctrine for full semantics):
581
580
 
582
581
  ```js
583
- // Wave round loop: Design agent reader → per-ticket try/catch → cascade quarantine via next reader
584
582
  // runSingleTicketEngine(args) is the SINGLE skeleton above as a function of its OWN args: the wave's
585
583
  // args.issueNumber (the tracking issue, Pre-authoring step 5) is never in its scope.
586
584
  const WAVE_TICKETS = [...remainingTickets]; // the pre-fetch's refs, in input order — the wave block's row order
587
- const results = {}; // ticketId → its row of the workflow's return; a ticket with no entry never ran
585
+ const results = {}; // ticketId → its row of the workflow's return
588
586
  // A row from an engine result: the fields step 3 after the workflow renders
589
587
  const rowOf = (ticketId, r, merged, treeEqual = null) => ({ ticket: ticketId, ran: true, verdict: r?.verdict || r?.overallVerdict || null, merged, treeEqual, issuePrLink: r?.issuePrLink || "(none)", evaluateVerdict: r?.gate2?.evaluateVerdict, testVerdict: r?.gate2?.testVerdict, surviving: r?.survivingFindings?.length, coverageComplete: r?.reviewCoverage?.complete });
590
588
  const MAX_ROUNDS = Math.max(10, remainingTickets.length * 2 + 5); // heuristic; always finite
591
- const MERGE_SHA = /^[0-9a-f]{40}$/; // a merge commit as git prints it: the only Git-reported value that reaches a later prompt
589
+ const MERGE_SHA = /^[0-9a-f]{40}$/; // a SHA as git prints it; with the checked branch, the only Git-reported values that reach a merge, Validate or undo prompt
590
+ const BRANCH_NAME = /^(?![-#]|(?:@|(?:FETCH_|ORIG_|MERGE_)?HEAD)$)(?!.*\.\.)[A-Za-z0-9._/+@,#-]{1,200}$/; // STRICT_NAME_RE's deliberate variant, less its origin/ refusal; the name enters git lines
591
+ // One ticket's merge, post-merge Validate and undo (D-WAVE-MERGE-VALIDATE: Git validates nothing).
592
+ const mergeValidateUndo = async (ticketId, engineResult) => {
593
+ if (typeof engineResult.branch !== "string" || !BRANCH_NAME.test(engineResult.branch)) return { kept: false, reason: "ticket branch name refused: not a plain git ref name", halted: null, treeEqual: null };
594
+ const merge = await agent(`Merge ${engineResult.branch} to ${INTEGRATION_BRANCH} locally. Include ticket ID ${ticketId} in the merge commit message. Do not push, and run no build or test.
595
+ Check out ${INTEGRATION_BRANCH}, run git rev-parse ${INTEGRATION_BRANCH} as baseSha, run git rev-parse --verify --quiet refs/heads/${engineResult.branch} (on a non-zero exit return merged false, reason "no such local branch"), then run git merge --no-ff refs/heads/${engineResult.branch}. If the merge stops after it began (git rev-parse -q --verify MERGE_HEAD succeeds), run git merge --abort; if that abort fails, return merged true with no mergeSha. A merge that did not begin returns merged false with the reason.
596
+ Return: {"merged": true, "mergeSha": "<40-hex merge commit>", "treeEqual": <true when mergeSha^{tree} equals the tree of ${engineResult.branch}'s head>, "baseSha": "<40-hex integration HEAD before the merge>"} — or {"merged": false, "reason": "<why>"} when no merge is kept.`, { agentType: "Git" });
597
+ const treeEqual = merge?.merged === true && typeof merge.treeEqual === "boolean" ? merge.treeEqual : null;
598
+ const done = (kept, reason, halted = null) => ({ kept, reason, halted, treeEqual });
599
+ if (merge?.merged !== true) return done(false, merge?.reason || "merge failed");
600
+ const badSha = ![merge.mergeSha, merge.baseSha].every(v => MERGE_SHA.test(String(v)));
601
+ if (badSha || merge.mergeSha === merge.baseSha) {
602
+ // No usable SHA, or no commit added: stop taking merges. Fixed reasons; no rejected value is copied.
603
+ const reason = badSha ? "merge reported without a 40-hex mergeSha or baseSha; the integration branch may hold an unvalidated merge" : "merge added no commit, so the wave stopped: an unchanged HEAD cannot be checked. Check that the ticket branch holds its commits, then run the wave again.";
604
+ return done(false, reason, { ticket: ticketId, mergeSha: null, baseSha: null, reason, kind: badSha ? "bad-sha" : "unchanged" });
605
+ }
606
+ const post = await agent(`Run build, typecheck, lint, and tests on ${INTEGRATION_BRANCH} after merging ${engineResult.branch} (merge commit ${merge.mergeSha}).
607
+ Follow your Running commands block for every build and test command.
608
+ Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
609
+ if (post?.verdict === "PASS") {
610
+ return done(true, "");
611
+ }
612
+ const undo = await agent(`Undo the merge ${merge.mergeSha} on ${INTEGRATION_BRANCH}, only when no remote branch contains ${merge.mergeSha}. Run these read-only checks in order; a non-zero exit or a different value means not undone: git symbolic-ref --short HEAD must print ${INTEGRATION_BRANCH}; git rev-parse refs/heads/${INTEGRATION_BRANCH} must print ${merge.mergeSha}; git rev-parse ${merge.mergeSha}^1 must print ${merge.baseSha}. Then run exactly this one command and no other that changes a branch or a remote: git reset --keep ${merge.baseSha}
613
+ Return: {"undone": true, "head": "<40-hex from git rev-parse refs/heads/${INTEGRATION_BRANCH}>"} — or {"undone": false, "reason": "<why>"} when you did not undo it.`, { agentType: "Git" });
614
+ if (undo?.undone === true && undo.head === merge.baseSha) return done(false, "post-merge build red; merge undone");
615
+ const reason = `post-merge build red; merge not undone (${undo?.undone === true ? "undo reported a head other than the recorded pre-merge SHA" : undo?.reason || "no reason given"})`;
616
+ return done(false, reason, { ticket: ticketId, mergeSha: merge.mergeSha, baseSha: merge.baseSha, reason, kind: "red" });
617
+ };
592
618
  const waveState = { quarantined: [], round: 0, halted: null };
593
- let reAskedThisDeadlock = false; // re-ask guard: re-ask once on empty ready-set, then escalate
619
+ let reAskedThisDeadlock = false;
594
620
  while (remainingTickets.length > 0 && waveState.round < MAX_ROUNDS && waveState.halted === null) {
595
621
  waveState.round++;
596
622
 
@@ -614,56 +640,28 @@ Return: {"ready": [...ticket-ids], "blocked": [{"ticket": "id", "namedBlocker":
614
640
  reAskedThisDeadlock = true; // re-ask once with vacuous-truth rule quoted verbatim
615
641
  continue;
616
642
  }
617
- reAskedThisDeadlock = false; // reset on a non-empty ready set
643
+ reAskedThisDeadlock = false;
618
644
 
619
645
  for (const ticketId of ready) {
620
646
  try {
621
- // ticketId is the ISSUE_REF the pre-fetch heading printed — this ticket's OWN reference: the engine's `ticket` (its TICKET)
622
- // and its setup-task ISSUE_INPUT; the engine returns the branch that setup-task created, merged below; plans[ticketId] is its own plan, criteria and checked testPlan (Pre-authoring step 4)
623
- <!-- learning:on -->
647
+ // ticketId is this ticket's OWN ISSUE_REF (pre-fetch heading): the engine's `ticket` and setup-task ISSUE_INPUT; the engine returns the branch setup-task created, merged below; plans[ticketId] is its own plan (Pre-authoring step 4)
648
+ <!-- curation:on -->
624
649
  const engineResult = await runSingleTicketEngine({ ticket: ticketId, baseBranch: INTEGRATION_BRANCH, ...(plans[ticketId] || {}), decisionsContext: DECISIONS_CONTEXT, issueRequired: ISSUE_REQUIRED, applyConventions: APPLY_CONVENTIONS, complianceFrameworks: COMPLIANCE_FRAMEWORKS, issueInput: ticketId });
625
- <!-- learning:off -->
650
+ <!-- curation:off -->
626
651
  const engineResult = await runSingleTicketEngine({ ticket: ticketId, baseBranch: INTEGRATION_BRANCH, ...(plans[ticketId] || {}), issueRequired: ISSUE_REQUIRED, applyConventions: APPLY_CONVENTIONS, complianceFrameworks: COMPLIANCE_FRAMEWORKS, issueInput: ticketId });
627
- <!-- learning:end -->
628
- // Check both verdict (engine_output_schema) and overallVerdict (SINGLE skeleton alias).
629
- // PASS and UNVERIFIED merge; PARTIAL, FAIL, ESCALATED (the ticket-link and branch stops included) or no verdict quarantine.
652
+ <!-- curation:end -->
653
+ // PASS and UNVERIFIED merge (verdict, or overallVerdict as the SINGLE alias); PARTIAL, FAIL, ESCALATED (the ticket-link and branch stops included) or no verdict quarantine.
630
654
  if (["PASS", "UNVERIFIED"].includes(engineResult.verdict || engineResult.overallVerdict)) {
631
- // Git merges locally and validates nothing: the workflow owns the post-merge Validate (D-WAVE-MERGE-VALIDATE).
632
- const merge = await agent(`Merge ${engineResult.branch} to ${INTEGRATION_BRANCH} locally. Include ticket ID ${ticketId} in the merge commit message. Do not push, and run no build or test.
633
- Return: {"merged": true, "mergeSha": "<40-hex merge commit>", "treeEqual": <true when mergeSha^{tree} equals the tree of ${engineResult.branch}'s head>} — or {"merged": false, "reason": "<why>"} when no merge is kept.`, { agentType: "Git" });
634
- let kept = false;
635
- let reason = merge?.reason || "merge failed";
636
- if (merge?.merged === true && !MERGE_SHA.test(String(merge.mergeSha))) {
637
- // A merge may sit on the integration branch with no SHA to validate or undo it by: stop taking merges.
638
- waveState.halted = { ticket: ticketId, mergeSha: null, reason: "merge reported without a 40-hex mergeSha; the integration branch may hold an unvalidated merge" };
639
- reason = waveState.halted.reason;
640
- } else if (merge?.merged === true) {
641
- // A merge counts as kept only when this Validate returns PASS. It always runs: treeEqual is recorded, never a reason to skip.
642
- const post = await agent(`Run build, typecheck, lint, and tests on ${INTEGRATION_BRANCH} after merging ${engineResult.branch} (merge commit ${merge.mergeSha}).
643
- Follow your Running commands block for every build and test command.
644
- Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
645
- if (post?.verdict === "PASS") {
646
- kept = true;
647
- } else {
648
- const undo = await agent(`Undo the merge ${merge.mergeSha} on ${INTEGRATION_BRANCH}, only when no remote branch contains ${merge.mergeSha} and the HEAD of ${INTEGRATION_BRANCH} still equals ${merge.mergeSha}. On the integration branch run exactly this one command and no other that changes a branch or a remote: git reset --keep ${merge.mergeSha}^1
649
- Return: {"undone": true} — or {"undone": false, "reason": "<why>"} when you did not undo it.`, { agentType: "Git" });
650
- if (undo?.undone === true) {
651
- reason = "post-merge build red; merge undone";
652
- } else {
653
- // A merge that stays red would poison every later ticket: stop taking merges and rounds, and name the red HEAD.
654
- reason = `post-merge build red; merge not undone (${undo?.reason || "no reason given"})`;
655
- waveState.halted = { ticket: ticketId, mergeSha: merge.mergeSha, reason };
656
- }
657
- }
658
- }
659
- results[ticketId] = rowOf(ticketId, engineResult, kept, merge?.merged === true && typeof merge.treeEqual === "boolean" ? merge.treeEqual : null);
655
+ const { kept, reason, halted, treeEqual } = await mergeValidateUndo(ticketId, engineResult);
656
+ if (halted !== null) waveState.halted = halted;
657
+ results[ticketId] = rowOf(ticketId, engineResult, kept, treeEqual);
660
658
  if (!kept) waveState.quarantined.push({ ticket: ticketId, reason });
661
659
  } else {
662
660
  results[ticketId] = rowOf(ticketId, engineResult, false);
663
661
  waveState.quarantined.push({ ticket: ticketId, reason: engineResult.escalations?.[0]?.description || "engine fail/escalated" });
664
662
  }
665
663
  } catch (err) {
666
- // One ticket's crash/stall never kills the wave — quarantine it; cascade propagates to dependents via the next reader round
664
+ // Quarantine the ticket: the cascade reaches its dependents via the next reader round
667
665
  results[ticketId] = rowOf(ticketId, null, false);
668
666
  waveState.quarantined.push({ ticket: ticketId, reason: `engine crash: ${String(err)}` });
669
667
  }
@@ -697,7 +695,7 @@ A workflow cannot pause mid-run. After the build/wave workflow returns, you (the
697
695
  In WAVE mode, if no tracking-issue number was resolved in Pre-authoring step 5: state `TRACEABILITY: DEGRADED (no tracking issue for this run)` in the run summary and skip — never skip silently.
698
696
  3. **Compose the wave PR inputs** (WAVE mode only — skip this step entirely in SINGLE mode). The workflow returned `tickets`: one entry per wave ticket, in its input order, each `{ticket, ran, verdict, merged, issuePrLink, evaluateVerdict, testVerdict, surviving, coverageComplete}`. Every value in it is agent-reported and treated as untrusted: nothing below is repaired, and no reference is ever composed from a number.
699
697
  - **Branch check.** Run `git -C "{integration worktree root}" branch --show-current`. Only a name matching `^wave/[a-z0-9][a-z0-9-]{0,59}$` opens a wave PR, and its part after `wave/` is the `{slug}` below. Any other name: record `TRACEABILITY: DEGRADED (not a wave branch)` and go to step 4 with no wave PR.
700
- - **Halted** (the workflow returned a non-null `halted`: a post-merge build it could not undo, or a merge reported with no SHA): the integration HEAD may be red. Record `Wave PR: skipped (integration HEAD red, wave halted at <halted.ticket>)`, name `halted.mergeSha` (or `none reported`) and `halted.reason` as the red integration HEAD in the run summary, and go to step 4 with no wave PR.
698
+ - **Halted** (a non-null `halted`, whose `kind` is `red`, `bad-sha` or `unchanged`): record `Wave PR: skipped (<text>, wave halted at <halted.ticket>)` and go to step 4 with no wave PR. `<text>` is fixed by `kind`, and no other `halted` field is shown. `red` (a post-merge build it could not undo): `integration HEAD red at <halted.mergeSha>`. `bad-sha` (a merge without a usable SHA): `integration branch may hold an unvalidated merge`. `unchanged` (a merge that added no commit): `merge added no commit`, with the remedy to check that the ticket branch holds its commits, then run the wave again.
701
699
  - **Nothing merged** (no entry has `merged: true`): record `Wave PR: skipped (nothing merged)` and go to step 4.
702
700
  - Otherwise compose (a) and then (b).
703
701
 
@@ -762,12 +760,14 @@ Do NOT ask questions mid-workflow — that is impossible (F4). The workflow only
762
760
 
763
761
  7. **Wave PR evidence** — only when step 6 reported the wave PR, after it; it never blocks, and every outcome below goes into the run summary. Take `{n}` from step 6's `- **PR**: #{n}` line when `{n}` matches `^[1-9][0-9]{0,9}$` as a whole; no such line ⇒ record `TRACEABILITY: DEGRADED (wave PR number not captured)` and skip this step. `PR_TEST_PLAN_BLOCK` `(none)` ⇒ record `Wave evidence: skipped (no wave test plan)` and skip it.
764
762
 
763
+ **Changed files.** Take `{base_ref}` from step 6's `- **Base ref**:` line; only when it is 40 or 64 lowercase hex, run `git -C "{integration worktree root}" diff --name-only --diff-filter=d {base_ref}...HEAD; echo "exit=$?"`. Unless it ran and printed `exit=0`, record `TRACEABILITY: DEGRADED (wave base unusable)` and skip this step.
764
+
765
765
  **(a) Test the wave.** Spawn one Test agent on the integration worktree with the wave test plan — the TP lines of the `## Test Plan` section step 3(b) wrote to `{integration worktree root}/.devflow/docs/evidence-wave-{slug}.md`:
766
766
 
767
767
  ```
768
768
  Agent(subagent_type="Test"):
769
769
  "ORIGINAL_REQUEST: the merged tickets of wave/{slug}, as the wave test plan names them
770
- FILES_CHANGED: {the files wave/{slug} changes against the - **Base**: branch step 6 reported}
770
+ FILES_CHANGED: {the file list that diff printed}
771
771
  TEST_PLAN: {the TP lines of the wave evidence file's ## Test Plan section}
772
772
  WORKTREE_PATH: {integration worktree root}
773
773
  Cover every TEST_PLAN line on the integration branch as it stands. Report PASS or FAIL with evidence."
@@ -807,4 +807,4 @@ cd "{integration worktree root}" && node "$HOME/.devflow/scripts/verify-evidence
807
807
 
808
808
  ### Maintenance note
809
809
 
810
- This recipe encodes the current `/implement` + `/code-review` + `/resolve` orchestration shape as of the authoring date (2026-06-12). When those base commands change their orchestration, update this recipe to match. No tooling detects drift — by design (the LLM-vs-plumbing Iron Rule). The reminder lives in the design doc §16.
810
+ This recipe mirrors the `/implement` + `/code-review` + `/resolve` orchestration as of 2026-06-12. When those commands change, update it. No tooling detects drift (the LLM-vs-plumbing Iron Rule).
@@ -11,12 +11,12 @@ output-dir: dist/commands
11
11
  @import "./_partials/_settings.mds" as settings
12
12
  {{authoring_preamble()}}
13
13
 
14
- <!-- learning:on -->
14
+ <!-- curation:on -->
15
15
  ### Settings line
16
16
 
17
17
  {{settings.settings_resolve()}}
18
18
 
19
- <!-- learning:end -->
19
+ <!-- curation:end -->
20
20
  {{authoring_decisions()}}
21
21
  ---
22
22
 
@@ -52,12 +52,12 @@ Before authoring, verify:
52
52
 
53
53
  Pass `{worktree}` to the workflow as its `root` argument, and its `slug` argument: the `{slug}` of the ticket directory (`tickets/{slug}/{ts}/`) when that is the ticket source, otherwise `wave`.
54
54
 
55
- <!-- learning:on -->
55
+ <!-- curation:on -->
56
56
  **1. Apply decisions context**
57
57
 
58
58
  Apply the `devflow:apply-decisions` algorithm to the DECISIONS_CONTEXT loaded by the decisions step above: scan the index, Read relevant entries, note verbatim ADR/PF IDs to inject into Design agent prompts.
59
59
 
60
- <!-- learning:end -->
60
+ <!-- curation:end -->
61
61
  **2. Read the preference profile**
62
62
 
63
63
  Check whether `~/.devflow/preference-profile.md` exists:
@@ -122,9 +122,9 @@ export const meta = {
122
122
  // AskUserQuestion happens at the command boundary after this workflow returns — NOT here.
123
123
 
124
124
  const ticketSource = args.ticketSource || args[0] || "see task description";
125
- <!-- learning:on -->
125
+ <!-- curation:on -->
126
126
  const DECISIONS_CONTEXT = args.decisionsContext || ""; // injected before authoring
127
- <!-- learning:end -->
127
+ <!-- curation:end -->
128
128
  const PREFERENCE_PROFILE = args.preferenceProfile || ""; // injected before authoring
129
129
  const TP_CONTRACT = args.tpContract || ""; // the "Test-plan line (TP)" contract below — its paragraph and four bullets — passed verbatim as tpContract when invoking the workflow, never pasted into this script (it holds backticks)
130
130
  const slug = args.slug || "wave";
@@ -146,9 +146,9 @@ const plans = await phase("plan-parallel", () =>
146
146
  parallel((tickets || []).map(ticket => () =>
147
147
  agent(`Write an implementation plan for this ticket.
148
148
  Ticket: ${JSON.stringify(ticket)}
149
- <!-- learning:on -->
149
+ <!-- curation:on -->
150
150
  Decisions context (apply devflow:apply-decisions; a plan file can be posted to the tracker, so state each decision in words, never by ID): ${DECISIONS_CONTEXT}
151
- <!-- learning:end -->
151
+ <!-- curation:end -->
152
152
  The plan must cover: approach overview, affected files and modules, key design decisions, implementation sequence (what to build first), risks and mitigations, and any open questions you cannot resolve from the ticket alone.
153
153
  Write a thorough but tight plan — every section must earn its place for a Code agent who has no other context.
154
154
  Return: { ticketTitle, planMarkdown, openDecisions (array of genuine unknowns requiring user input) }.`, { agentType: "Design" })
@@ -192,9 +192,9 @@ Return: { ticketTitle, improvements (array), acceptanceCriteria (array of number
192
192
  const crossCritic = await phase("cross-plan-critic", () =>
193
193
  agent(`Cross-plan conflict audit.
194
194
  All ticket plans + challenged criteria: ${JSON.stringify(challenged)}
195
- <!-- learning:on -->
195
+ <!-- curation:on -->
196
196
  Decisions context: ${DECISIONS_CONTEXT}
197
- <!-- learning:end -->
197
+ <!-- curation:end -->
198
198
 
199
199
  Audit:
200
200
  1. API conflicts: two tickets defining the same API differently (different signatures, return types, error codes).
@@ -202,11 +202,11 @@ Audit:
202
202
  3. Undeclared dependencies: ticket A's plan implicitly requires something ticket B provides, but no dependency is declared.
203
203
  4. Scope overlap: two tickets both claim ownership of the same module or behavior.
204
204
 
205
- <!-- learning:on -->
205
+ <!-- curation:on -->
206
206
  For each conflict found: state which two tickets are involved, describe the conflict precisely, and propose a resolution aligned with the decisions ledger.
207
- <!-- learning:off -->
207
+ <!-- curation:off -->
208
208
  For each conflict found: state which two tickets are involved, describe the conflict precisely, and propose a resolution.
209
- <!-- learning:end -->
209
+ <!-- curation:end -->
210
210
 
211
211
  Return: { conflicts (array of {tickets, conflictDescription, proposedResolution}), planAmendments (array of {ticketTitle, amendment}) }.`, { agentType: "Design" })
212
212
  );
@@ -243,26 +243,26 @@ For each ticket, write ${OUTDIR}/{ticket-slug}-plan.md, {ticket-slug} being the
243
243
  - ## Acceptance Criteria (numbered, positive + negative)
244
244
  - ## Test Plan — the challenger's testPlan lines, verbatim, one per line, and nothing else: no prose, no blank line between them, no setup or outcome
245
245
  - ## Test Scenarios — one line per TP, in TP order: TP-n: its setup, then its expected outcome, from testScenarios
246
- <!-- learning:on -->
246
+ <!-- curation:on -->
247
247
  - ## Auto-Resolved Decisions (if any — list each as: decision → resolution → source, naming a recorded decision in words, never by ID)
248
- <!-- learning:off -->
248
+ <!-- curation:off -->
249
249
  - ## Auto-Resolved Decisions (if any — list each as: decision → resolution → source)
250
- <!-- learning:end -->
250
+ <!-- curation:end -->
251
251
 
252
252
  Then write ${OUTDIR}/DECISIONS-NEEDED.md:
253
- <!-- learning:on -->
253
+ <!-- curation:on -->
254
254
  - ## Auto-Resolved Decisions — list each silently-resolved decision as: decision → resolution → source (preference profile / ADR-NNN), so auto-resolution is auditable and reversible. If none, write "None."
255
- <!-- learning:off -->
255
+ <!-- curation:off -->
256
256
  - ## Auto-Resolved Decisions — list each silently-resolved decision as: decision → resolution → source (preference profile), so auto-resolution is auditable and reversible. If none, write "None."
257
- <!-- learning:end -->
257
+ <!-- curation:end -->
258
258
  - ## Decisions Needed
259
259
  - One section per open decision: what the decision is, why it matters, what options exist.
260
260
  - Include cross-plan conflicts that were not auto-resolved.
261
- <!-- learning:on -->
261
+ <!-- curation:on -->
262
262
  - If no decisions needed, write: "No open decisions — all settled by preference profile or existing ADRs."
263
- <!-- learning:off -->
263
+ <!-- curation:off -->
264
264
  - If no decisions needed, write: "No open decisions — all settled by the preference profile."
265
- <!-- learning:end -->
265
+ <!-- curation:end -->
266
266
 
267
267
  Return: { planPaths (array), decisionsNeededPath (string), decisionsNeededCount (number) }.`, { agentType: "Synthesize" })
268
268
  );
@@ -7,12 +7,12 @@ output-dir: dist/commands
7
7
  @import "./_partials/_settings.mds" as settings
8
8
  {{authoring_preamble()}}
9
9
 
10
- <!-- learning:on -->
10
+ <!-- curation:on -->
11
11
  ### Settings line
12
12
 
13
13
  {{settings.settings_resolve()}}
14
14
 
15
- <!-- learning:end -->
15
+ <!-- curation:end -->
16
16
  {{authoring_decisions()}}
17
17
  ---
18
18
 
@@ -12,12 +12,12 @@ output-dir: dist/commands
12
12
  @import "./_partials/_settings.mds" as settings
13
13
  {{authoring_preamble()}}
14
14
 
15
- <!-- learning:on -->
15
+ <!-- curation:on -->
16
16
  ### Settings line
17
17
 
18
18
  {{settings.settings_resolve()}}
19
19
 
20
- <!-- learning:end -->
20
+ <!-- curation:end -->
21
21
  {{authoring_decisions()}}
22
22
  ---
23
23
 
@@ -61,12 +61,12 @@ Before you write the workflow script:
61
61
 
62
62
  Pass `{worktree}` to the workflow as its `root` argument.
63
63
 
64
- <!-- learning:on -->
64
+ <!-- curation:on -->
65
65
  **1. Apply decisions context**
66
66
 
67
67
  Apply the `devflow:apply-decisions` algorithm to the DECISIONS_CONTEXT loaded by the decisions step above: scan the index, Read relevant entries, note the verbatim ADR/PF IDs you will inject into Design agent prompts.
68
68
 
69
- <!-- learning:end -->
69
+ <!-- curation:end -->
70
70
  **2. Read and distill the initiative**
71
71
 
72
72
  Read or note the user's input:
@@ -103,9 +103,9 @@ export const meta = {
103
103
  const candidates = args.candidates || []; // [{title, summary, wave, dependsOn}]
104
104
  const initiative = args.initiative || args[0] || "see task description";
105
105
  const constraints = args.constraints || "";
106
- <!-- learning:on -->
106
+ <!-- curation:on -->
107
107
  const DECISIONS_CONTEXT = args.decisionsContext || ""; // injected before authoring
108
- <!-- learning:end -->
108
+ <!-- curation:end -->
109
109
  const slug = args.slug || initiative.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+/, '').slice(0, 50).replace(/-+$/, '') || 'untitled';
110
110
  const now = new Date(), two = (n) => String(n).padStart(2, '0');
111
111
  const ts = `${now.getFullYear()}-${two(now.getMonth() + 1)}-${two(now.getDate())}_${two(now.getHours())}${two(now.getMinutes())}`;
@@ -118,9 +118,9 @@ const drafts = await phase("draft", () =>
118
118
  agent(`Draft ticket for initiative: "${initiative}"
119
119
  Ticket: ${JSON.stringify(c)}
120
120
  Constraints: ${constraints}
121
- <!-- learning:on -->
121
+ <!-- curation:on -->
122
122
  Decisions context (apply devflow:apply-decisions; the ticket is filed to the tracker, so state each decision in words, never by ID): ${DECISIONS_CONTEXT}
123
- <!-- learning:end -->
123
+ <!-- curation:end -->
124
124
  Write the ticket body following the ticket_body_template structure (Wave/Depends-on header, Summary, Scope with In/Out + anti-features, Invariants, numbered Acceptance Criteria with at least one negative criterion, Open Questions).
125
125
  Return a JSON object with: title (string), summary (string), wave (number), dependsOn (array), bodyMarkdown (string), openQuestions (array).`, { agentType: "Design" })
126
126
  ))
@@ -32,34 +32,34 @@ $ARGUMENTS
32
32
 
33
33
  ## Phases
34
34
 
35
- <!-- learning:on -->
35
+ <!-- curation:on -->
36
36
  ### Phase 1: Load Decisions (Orchestrator-Local)
37
- <!-- learning:off -->
37
+ <!-- curation:off -->
38
38
  ### Phase 1: Resolve Settings
39
- <!-- learning:end -->
39
+ <!-- curation:end -->
40
40
 
41
- <!-- learning:on -->
41
+ <!-- curation:on -->
42
42
  **Produces:** DECISIONS_CONTEXT
43
43
 
44
- <!-- learning:end -->
44
+ <!-- curation:end -->
45
45
  {{settings.settings_resolve()}}
46
46
 
47
47
  {{decisions_load()}}
48
- <!-- learning:on -->
48
+ <!-- curation:on -->
49
49
  The orchestrator uses `DECISIONS_CONTEXT` locally when framing exploration — prior decisions and pitfalls suggest specific areas to investigate. Follow `devflow:apply-decisions` to Read full entry bodies on demand. **Do NOT pass `DECISIONS_CONTEXT` to Explore sub-agents** — decisions context stays in the orchestrator, not in the investigation workers.
50
50
 
51
- <!-- learning:end -->
51
+ <!-- curation:end -->
52
52
  **No up-front feature knowledge load** — explore investigation workers read code directly to avoid confirmation bias. Feature knowledge is only created as a write-back at the end, after exploration is complete.
53
53
 
54
54
  ### Phase 2: Orient
55
55
 
56
56
  **Produces:** ORIENT_OUTPUT
57
57
 
58
- <!-- learning:on -->
59
- Spawn `Agent(subagent_type="Skim")` with `LEARNING` from the settings line, to get codebase overview relevant to the exploration question:
60
- <!-- learning:off -->
58
+ <!-- curation:on -->
59
+ Spawn `Agent(subagent_type="Skim")` with `CURATION` from the settings line, to get codebase overview relevant to the exploration question:
60
+ <!-- curation:off -->
61
61
  Spawn `Agent(subagent_type="Skim")` to get codebase overview relevant to the exploration question:
62
- <!-- learning:end -->
62
+ <!-- curation:end -->
63
63
 
64
64
  - File structure and module boundaries in the target area
65
65
  - Entry points and key abstractions
@@ -105,11 +105,11 @@ Present findings to user. Use AskUserQuestion to offer focused follow-up explora
105
105
 
106
106
  ### Phase 6: Suggest Feature Knowledge Creation (Conditional)
107
107
 
108
- <!-- learning:on -->
108
+ <!-- curation:on -->
109
109
  **Requires:** MERGED_FINDINGS, DECISIONS_CONTEXT
110
- <!-- learning:off -->
110
+ <!-- curation:off -->
111
111
  **Requires:** MERGED_FINDINGS
112
- <!-- learning:end -->
112
+ <!-- curation:end -->
113
113
  **Produces:** FEATURE_KNOWLEDGE_STATUS (created | skipped)
114
114
 
115
115
  1. Check if matching feature knowledge already exists by reading `{worktree}/.devflow/features/index.md` (or globbing frontmatter if absent). If covered → skip
@@ -143,11 +143,11 @@ Structured exploration findings with concrete code references:
143
143
  ```
144
144
  /explore (orchestrator)
145
145
  │
146
- <!-- learning:on -->
146
+ <!-- curation:on -->
147
147
  ├─ Phase 1: Load Decisions (Orchestrator-Local)
148
- <!-- learning:off -->
148
+ <!-- curation:off -->
149
149
  ├─ Phase 1: Resolve Settings
150
- <!-- learning:end -->
150
+ <!-- curation:end -->
151
151
  │
152
152
  ├─ Phase 2: Orient
153
153
  │ └─ Skim agent (codebase overview)
@@ -168,11 +168,11 @@ Structured exploration findings with concrete code references:
168
168
 
169
169
  1. **Structure over browsing** - Every claim must cite file:line references
170
170
  2. **Parallel execution** - All explorers run simultaneously for speed
171
- <!-- learning:on -->
171
+ <!-- curation:on -->
172
172
  3. **Knowledge-informed** - Prior decisions guide where to look; NO pre-loaded feature knowledge in sub-agents (avoids confirmation bias)
173
- <!-- learning:off -->
173
+ <!-- curation:off -->
174
174
  3. **Knowledge-informed** - NO pre-loaded feature knowledge in sub-agents (avoids confirmation bias)
175
- <!-- learning:end -->
175
+ <!-- curation:end -->
176
176
  4. **User-driven depth** - Present findings, then offer drill-down into specific areas
177
177
 
178
178
  ## Error Handling