devflow-kit 3.2.0 → 3.4.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 (188) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/dist/agents/code.md +330 -0
  4. package/{src/assets → dist}/agents/design.md +11 -1
  5. package/{src/assets → dist}/agents/diagnose.md +17 -13
  6. package/dist/agents/git.md +29 -56
  7. package/{src/assets → dist}/agents/knowledge.md +8 -5
  8. package/{src/assets → dist}/agents/research.md +21 -2
  9. package/{src/assets → dist}/agents/review.md +18 -7
  10. package/{src/assets → dist}/agents/scrutinize.md +35 -11
  11. package/dist/agents/skim.md +148 -0
  12. package/{src/assets → dist}/agents/triage.md +8 -2
  13. package/dist/cli/commands/init.js +62 -0
  14. package/dist/cli/commands/learning.js +63 -8
  15. package/dist/cli/commands/uninstall.js +42 -1
  16. package/dist/commands/bug-analysis.md +30 -8
  17. package/dist/commands/code-review.md +142 -61
  18. package/dist/commands/debug.md +15 -13
  19. package/dist/commands/dynamic-build.md +176 -130
  20. package/dist/commands/dynamic-plan.md +31 -19
  21. package/dist/commands/dynamic-profile.md +27 -13
  22. package/dist/commands/dynamic-tickets.md +28 -14
  23. package/dist/commands/explore.md +16 -14
  24. package/dist/commands/implement.md +163 -158
  25. package/dist/commands/plan.md +37 -24
  26. package/dist/commands/release.md +69 -4
  27. package/dist/commands/research.md +33 -11
  28. package/dist/commands/resolve.md +61 -47
  29. package/dist/commands/self-review.md +50 -32
  30. package/dist/core/agent-models.js +53 -7
  31. package/dist/core/assets.js +55 -10
  32. package/dist/core/claude-md-audit.js +190 -0
  33. package/dist/core/feature-switch.js +20 -1
  34. package/dist/core/flags.js +28 -0
  35. package/dist/core/fs-atomic.js +8 -3
  36. package/dist/core/learning-tuning-config.js +8 -0
  37. package/dist/core/learning-variants.js +213 -0
  38. package/dist/core/manifest.js +62 -0
  39. package/dist/core/mds-variants.js +38 -1
  40. package/dist/core/plugins.js +78 -11
  41. package/{src/assets → dist/learning-off}/agents/code.md +77 -28
  42. package/dist/learning-off/agents/design.md +119 -0
  43. package/dist/learning-off/agents/diagnose.md +210 -0
  44. package/dist/learning-off/agents/knowledge.md +90 -0
  45. package/dist/learning-off/agents/research.md +149 -0
  46. package/dist/learning-off/agents/review.md +228 -0
  47. package/dist/learning-off/agents/scrutinize.md +117 -0
  48. package/{src/assets → dist/learning-off}/agents/skim.md +5 -10
  49. package/dist/learning-off/agents/triage.md +163 -0
  50. package/dist/learning-off/commands/bug-analysis.md +420 -0
  51. package/dist/learning-off/commands/code-review.md +525 -0
  52. package/dist/learning-off/commands/debug.md +294 -0
  53. package/dist/learning-off/commands/dynamic-build.md +1255 -0
  54. package/dist/learning-off/commands/dynamic-plan.md +424 -0
  55. package/dist/learning-off/commands/dynamic-profile.md +214 -0
  56. package/dist/learning-off/commands/dynamic-tickets.md +632 -0
  57. package/dist/learning-off/commands/explore.md +210 -0
  58. package/dist/learning-off/commands/implement.md +808 -0
  59. package/dist/learning-off/commands/plan.md +664 -0
  60. package/dist/learning-off/commands/release.md +310 -0
  61. package/dist/learning-off/commands/research.md +222 -0
  62. package/dist/learning-off/commands/resolve.md +837 -0
  63. package/dist/learning-off/commands/self-review.md +266 -0
  64. package/dist/skills/git/references/tracker/_contract.md +33 -0
  65. package/dist/skills/git/references/tracker/github/fetch-issue.md +2 -0
  66. package/dist/skills/git/references/tracker/github/fetch-issues-batch.md +2 -0
  67. package/dist/skills/git/references/tracker/github/gather-release-evidence.md +5 -1
  68. package/dist/skills/git/references/tracker/github/post-wave-report.md +2 -0
  69. package/dist/skills/git/references/tracker/github/setup-task.md +12 -0
  70. package/dist/skills/git/references/tracker/jira/associate-release.md +1 -1
  71. package/dist/skills/git/references/tracker/jira/fetch-issue.md +2 -0
  72. package/dist/skills/git/references/tracker/jira/fetch-issues-batch.md +2 -0
  73. package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +5 -1
  74. package/dist/skills/git/references/tracker/jira/post-wave-report.md +2 -0
  75. package/dist/skills/git/references/tracker/jira/setup-task.md +14 -2
  76. package/dist/skills/git/references/tracker/linear/associate-release.md +1 -1
  77. package/dist/skills/git/references/tracker/linear/fetch-issue.md +2 -0
  78. package/dist/skills/git/references/tracker/linear/fetch-issues-batch.md +2 -0
  79. package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +5 -1
  80. package/dist/skills/git/references/tracker/linear/post-wave-report.md +2 -0
  81. package/dist/skills/git/references/tracker/linear/setup-task.md +14 -2
  82. package/dist/targets/claude-code/installer.js +72 -36
  83. package/dist/targets/claude-code/language-stamp.js +185 -0
  84. package/dist/targets/claude-code/learning-install.js +489 -0
  85. package/package.json +1 -1
  86. package/src/assets/agents/code.mds +339 -0
  87. package/src/assets/agents/design.mds +149 -0
  88. package/src/assets/agents/diagnose.mds +225 -0
  89. package/src/assets/agents/evaluate.md +12 -25
  90. package/src/assets/agents/git.mds +29 -56
  91. package/src/assets/agents/knowledge.mds +125 -0
  92. package/src/assets/agents/learning.md +4 -6
  93. package/src/assets/agents/research.mds +176 -0
  94. package/src/assets/agents/review.mds +286 -0
  95. package/src/assets/agents/scrutinize.mds +132 -0
  96. package/src/assets/agents/simplify.md +20 -0
  97. package/src/assets/agents/skim.mds +161 -0
  98. package/src/assets/agents/synthesize.md +12 -0
  99. package/src/assets/agents/test.md +2 -2
  100. package/src/assets/agents/triage.mds +194 -0
  101. package/src/assets/agents/validate.md +15 -8
  102. package/src/assets/commands/_partials/_compliance.mds +5 -4
  103. package/src/assets/commands/_partials/_decisions.mds +31 -0
  104. package/src/assets/commands/_partials/_engine.mds +29 -24
  105. package/src/assets/commands/_partials/_knowledge.mds +26 -13
  106. package/src/assets/commands/_partials/_plan_contract.mds +1 -1
  107. package/src/assets/commands/_partials/_preamble.mds +33 -9
  108. package/src/assets/commands/_partials/_publication.mds +5 -4
  109. package/src/assets/commands/_partials/_settings.mds +13 -5
  110. package/src/assets/commands/_partials/_wave.mds +16 -6
  111. package/src/assets/commands/bug-analysis.mds +24 -2
  112. package/src/assets/commands/code-review.mds +148 -45
  113. package/src/assets/commands/debug.mds +17 -1
  114. package/src/assets/commands/dynamic-build.mds +144 -65
  115. package/src/assets/commands/dynamic-plan.mds +36 -6
  116. package/src/assets/commands/dynamic-profile.mds +9 -1
  117. package/src/assets/commands/dynamic-tickets.mds +16 -2
  118. package/src/assets/commands/explore.mds +27 -1
  119. package/src/assets/commands/implement.mds +171 -138
  120. package/src/assets/commands/plan.mds +47 -8
  121. package/src/assets/commands/{release.md → release.mds} +27 -24
  122. package/src/assets/commands/research.mds +28 -4
  123. package/src/assets/commands/resolve.mds +69 -17
  124. package/src/assets/commands/self-review.mds +43 -13
  125. package/src/assets/mds/tracker/_common.mds +1 -1
  126. package/src/assets/mds/tracker/_contract.mds +72 -0
  127. package/src/assets/mds/tracker/_github.mds +13 -2
  128. package/src/assets/mds/tracker/_jira.mds +17 -5
  129. package/src/assets/mds/tracker/_linear.mds +17 -5
  130. package/src/assets/mds/tracker/_mcp.mds +2 -2
  131. package/src/assets/mds/tracker/_steps.mds +97 -0
  132. package/src/assets/rules/context-economy.md +10 -0
  133. package/src/assets/rules/go.md +1 -0
  134. package/src/assets/rules/java.md +1 -0
  135. package/src/assets/rules/python.md +1 -0
  136. package/src/assets/rules/rust.md +1 -0
  137. package/src/assets/rules/typescript.md +1 -0
  138. package/src/assets/scripts/ci-wait.cjs +636 -0
  139. package/src/assets/scripts/claude-md-audit.cjs +611 -0
  140. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +2 -3
  141. package/src/assets/scripts/hooks/background-memory-update +290 -9
  142. package/src/assets/scripts/hooks/json-helper.cjs +13 -5
  143. package/src/assets/scripts/hooks/json-parse +34 -10
  144. package/src/assets/scripts/hooks/session-start-context +315 -7
  145. package/src/assets/scripts/release-trace.cjs +27 -10
  146. package/src/assets/skills/accessibility/SKILL.md +1 -1
  147. package/src/assets/skills/apply-decisions/SKILL.md +12 -82
  148. package/src/assets/skills/apply-feature-knowledge/SKILL.md +9 -43
  149. package/src/assets/skills/architecture/SKILL.md +1 -1
  150. package/src/assets/skills/boundary-validation/SKILL.md +1 -1
  151. package/src/assets/skills/complexity/SKILL.md +1 -1
  152. package/src/assets/skills/compliance/SKILL.md +1 -1
  153. package/src/assets/skills/consistency/SKILL.md +1 -1
  154. package/src/assets/skills/database/SKILL.md +1 -1
  155. package/src/assets/skills/dependencies/SKILL.md +1 -1
  156. package/src/assets/skills/dependency-research/SKILL.md +3 -6
  157. package/src/assets/skills/design-review/SKILL.md +1 -1
  158. package/src/assets/skills/docs-framework/SKILL.md +1 -1
  159. package/src/assets/skills/documentation/SKILL.md +1 -1
  160. package/src/assets/skills/feature-knowledge/SKILL.md +43 -12
  161. package/src/assets/skills/gap-analysis/SKILL.md +1 -1
  162. package/src/assets/skills/git/SKILL.md +1 -1
  163. package/src/assets/skills/go/SKILL.md +1 -1
  164. package/src/assets/skills/java/SKILL.md +1 -1
  165. package/src/assets/skills/patterns/SKILL.md +1 -1
  166. package/src/assets/skills/performance/SKILL.md +1 -1
  167. package/src/assets/skills/python/SKILL.md +1 -1
  168. package/src/assets/skills/qa/SKILL.md +1 -3
  169. package/src/assets/skills/quality-gates/SKILL.md +10 -13
  170. package/src/assets/skills/quality-gates/references/report-template.md +20 -20
  171. package/src/assets/skills/react/SKILL.md +1 -1
  172. package/src/assets/skills/regression/SKILL.md +1 -1
  173. package/src/assets/skills/reliability/SKILL.md +1 -1
  174. package/src/assets/skills/research-codebase/SKILL.md +1 -1
  175. package/src/assets/skills/research-competitor/SKILL.md +1 -1
  176. package/src/assets/skills/research-external/SKILL.md +1 -1
  177. package/src/assets/skills/research-technology/SKILL.md +1 -1
  178. package/src/assets/skills/review-methodology/SKILL.md +1 -1
  179. package/src/assets/skills/rust/SKILL.md +1 -1
  180. package/src/assets/skills/security/SKILL.md +1 -1
  181. package/src/assets/skills/software-design/SKILL.md +1 -1
  182. package/src/assets/skills/test-driven-development/SKILL.md +9 -29
  183. package/src/assets/skills/testing/SKILL.md +1 -1
  184. package/src/assets/skills/typescript/SKILL.md +1 -1
  185. package/src/assets/skills/ui-design/SKILL.md +1 -1
  186. package/src/assets/skills/worktree-support/SKILL.md +3 -55
  187. package/src/assets/skills/worktree-support/references/discovery.md +48 -0
  188. package/src/assets/skills/worktree-support/references/roots.md +2 -2
@@ -3,7 +3,7 @@ description: Dependency-aware build engine — implement, review, and verify a s
3
3
  argument-hint: "[ticket | issue-url | plan-doc]"
4
4
  output-dir: dist/commands
5
5
  ---
6
- @import { authoring_preamble } from "./_partials/_preamble.mds"
6
+ @import { authoring_preamble, authoring_decisions } from "./_partials/_preamble.mds"
7
7
  @import { evidence_policy } from "./_partials/_evidence_policy.mds"
8
8
  @import { docs_root } from "./_partials/_docs_root.mds"
9
9
  @import { agent_roster, agent_caveats } from "./_partials/_roster.mds"
@@ -13,8 +13,14 @@ output-dir: dist/commands
13
13
  @import { issue_ref_grammar, issue_capture_contract } from "./_partials/_tracker.mds"
14
14
  @import "./_partials/_publication.mds" as pub
15
15
  @import "./_partials/_compliance.mds" as compliance
16
+ @import "./_partials/_settings.mds" as settings
16
17
  {{authoring_preamble()}}
17
18
 
19
+ ### Settings line
20
+
21
+ {{settings.settings_resolve()}}
22
+
23
+ {{authoring_decisions()}}
18
24
  ---
19
25
 
20
26
  ## dynamic-build — author and run a build workflow
@@ -69,10 +75,12 @@ Pass it as `complianceFrameworks` when invoking the workflow; the engine hands i
69
75
 
70
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.
71
77
 
78
+ <!-- learning:on -->
72
79
  **1. Apply decisions context**
73
80
 
74
- Apply the `devflow:apply-decisions` algorithm to the DECISIONS_CONTEXT loaded per the preamble above: scan the index, Read relevant entries, note the verbatim ADR/PF IDs you will inject into Code agent and Evaluate agent prompts.
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.
75
82
 
83
+ <!-- learning:end -->
76
84
  **2. Read budget**
77
85
 
78
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.
@@ -150,7 +158,9 @@ const TICKET = args.ticket || args[0] || "see task description";
150
158
  const PLAN = args.plan || null;
151
159
  const CRITERIA = args.criteria || null;
152
160
  const TEST_PLAN = args.testPlan || null; // the plan's checked TP lines, one string (Pre-authoring step 4)
161
+ <!-- learning:on -->
153
162
  const DECISIONS_CONTEXT = args.decisionsContext || ""; // injected before authoring
163
+ <!-- learning:end -->
154
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
155
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
156
166
  const APPLY_CONVENTIONS = String(args.applyConventions) === "false" ? "false" : "true";
@@ -187,15 +197,18 @@ if (BRANCH === "(none)") {
187
197
 
188
198
  // Phase 2: Implement
189
199
  await phase("implement", () =>
190
- agent(`Implement the following ticket on branch ${BRANCH}:
200
+ agent(`OPERATION: implement
201
+ Implement the following ticket on branch ${BRANCH}:
191
202
 
192
203
  ${TICKET}
193
204
 
194
205
  ${PLAN ? `Implementation plan:\n${PLAN}` : "No plan provided — use best judgment."}
195
206
 
207
+ <!-- learning:on -->
196
208
  Relevant architectural decisions (apply devflow:apply-decisions algorithm):
197
209
  ${DECISIONS_CONTEXT}
198
210
 
211
+ <!-- learning:end -->
199
212
  ISSUE_NUMBER: ${ISSUE_NUMBER}
200
213
  ISSUE_PR_LINK: ${ISSUE_PR_LINK}
201
214
  COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
@@ -208,39 +221,54 @@ After implementing, commit your changes with a conventional-commit message and r
208
221
  - Any open questions or blockers`, { agentType: "Code" })
209
222
  );
210
223
 
211
- // Phase 3: Gate 1 — post-code pipeline
224
+ // Phase 3: Gate 1 #1 — post-code pipeline: Simplify agent → Scrutinize agent → Validate agent.
225
+ // Validate runs last and unconditionally, so its one full run covers every commit the pass made (see gate1_postcode()).
212
226
  const gate1 = await phase("gate1", async () => {
213
- // Gate 1 #1 — runs once, immediately after the initial implementation.
227
+ await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
228
+
229
+ const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Fix P0/P1 issues and commit the fixes.
230
+ Return: {"status": "PASS" | "FIXED" | "BLOCKED"}`, { agentType: "Scrutinize" });
231
+ // Anything but an explicit PASS or FIXED is a stop: a Scrutinize that returned no status, or one this skeleton does not know, has not accepted the code.
232
+ if (!["PASS", "FIXED"].includes(scrutiny?.status)) {
233
+ return { verdict: "ESCALATED", type: "scrutiny-blocked", reason: scrutiny?.status === "BLOCKED" ? "Scrutinize agent returned BLOCKED: a P0 issue cannot be fixed in scope" : "Scrutinize agent returned no recognised status: counted as BLOCKED" };
234
+ }
235
+
214
236
  const validation = await agent(`Run build, typecheck, lint, and tests on branch ${BRANCH}.
215
237
  Follow your Running commands block for every build and test command.
216
- Report: PASS or FAIL with details.`, { agentType: "Validate" });
238
+ Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
217
239
 
218
- if (validation.verdict === "FAIL") {
219
- // Up to 2 fix attempts
240
+ // Anything but an explicit PASS is a failure: a Validate that returned no verdict has not validated the branch.
241
+ if (validation?.verdict !== "PASS") {
242
+ let failureDetails = validation?.details || "Validate agent returned no PASS verdict";
243
+ // Up to 2 fix attempts, each followed by a Validate re-run
220
244
  for (let attempt = 1; attempt <= 2; attempt++) {
221
- await agent(`Fix the validation failures on branch ${BRANCH}:
222
- ${validation.details}
245
+ await agent(`OPERATION: validation-fix
246
+ Fix the validation failures on branch ${BRANCH}:
247
+ ${failureDetails}
248
+ <!-- learning:on -->
249
+ DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
250
+ <!-- learning:end -->
223
251
  ISSUE_NUMBER: ${ISSUE_NUMBER}
224
252
  ISSUE_PR_LINK: ${ISSUE_PR_LINK}
225
253
  COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
226
254
  Commit fixes with conventional-commit message.`, { agentType: "Code" });
227
- const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH}. Report: PASS or FAIL.`, { agentType: "Validate" });
228
- if (recheck.verdict === "PASS") break;
229
- if (attempt === 2) return { verdict: "ESCALATED", reason: "Validation exhausted after 2 Code agent fix attempts" };
255
+ const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} after fix attempt ${attempt} (follow your Running commands block).
256
+ Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
257
+ if (recheck?.verdict === "PASS") break;
258
+ failureDetails = recheck?.details || failureDetails;
259
+ if (attempt === 2) return { verdict: "ESCALATED", type: "validation-exhausted", reason: "Validation exhausted after 2 Code agent fix attempts" };
230
260
  }
231
261
  }
232
262
 
233
- await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
234
-
235
- const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Report any code you changed and your findings.`, { agentType: "Scrutinize" });
236
-
237
- if (scrutiny.codeChanged) {
238
- await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (Scrutinize agent made changes). Report: PASS or FAIL.`, { agentType: "Validate" });
239
- }
240
-
241
263
  return { verdict: "PASS" };
242
264
  });
243
265
 
266
+ // Gate 1 #1 stop, before Gate 2: an exhausted Validate or a BLOCKED Scrutinize leaves a broken or unfinished build,
267
+ // so Gate 2 and the review pass never run on it — a correctness stop, like the ticket-link and branch stops.
268
+ if (gate1.verdict === "ESCALATED") {
269
+ return { ticket: TICKET, branch: BRANCH, verdict: "ESCALATED", issueId: ISSUE_NUMBER, issuePrLink: ISSUE_PR_LINK, escalations: [{ type: gate1.type, description: gate1.reason }] };
270
+ }
271
+
244
272
  // Phase 4: Gate 2 — acceptance gate (once, before review pass)
245
273
  const gate2 = await phase("gate2", async () => {
246
274
  if (!PLAN && !CRITERIA && !TEST_PLAN) {
@@ -249,21 +277,22 @@ const gate2 = await phase("gate2", async () => {
249
277
 
250
278
  let evalVerdict = "SKIPPED";
251
279
  if (PLAN) {
252
- const panel = await parallel([
253
- () => agent(`Acceptance-criteria evaluator: does the implementation on branch ${BRANCH} satisfy each numbered criterion, INCLUDING negative criteria?
280
+ const evaluation = await agent(`Acceptance and scope check on branch ${BRANCH}. Answer both questions:
281
+ 1. Does the implementation satisfy each numbered criterion, INCLUDING negative criteria (what it must NOT do)?
282
+ 2. Did the Code agent introduce any unplanned changes, smuggled anti-features, or drift from the plan's intent?
254
283
  Plan: ${PLAN}
255
284
  Criteria: ${CRITERIA || "see plan"}
256
- Report: PASS or FAIL per criterion with rationale.`, { agentType: "Evaluate" }),
257
- () => agent(`Scope/intent-drift evaluator: did the Code agent on branch ${BRANCH} introduce any unplanned changes, smuggled anti-features, or drift from the plan's intent?
258
- Plan: ${PLAN}
259
- Report: PASS or FAIL with rationale.`, { agentType: "Evaluate" }),
260
- ]);
261
- evalVerdict = panel.every(p => p.verdict === "PASS") ? "PASS" : "FAIL";
285
+ Return: {"verdict": "PASS" | "FAIL", "rationale": "..."} — FAIL if either answer is no, naming the failing criterion or the drift.`, { agentType: "Evaluate" });
286
+ evalVerdict = evaluation?.verdict === "PASS" ? "PASS" : "FAIL";
262
287
  if (evalVerdict === "FAIL") {
263
288
  // fix-and-continue — no re-evaluate, no inline Gate 1. The final Gate 1 (#2)
264
289
  // after the review pass is the build gate before the branch is handed back.
265
- await agent(`Fix the alignment issues identified by the Evaluate agent panel on branch ${BRANCH}:
266
- ${panel.filter(p => p.verdict === "FAIL").map(p => p.rationale).join("\n")}
290
+ await agent(`OPERATION: alignment-fix
291
+ Fix the alignment issues identified by the Evaluate agent on branch ${BRANCH}:
292
+ ${evaluation?.rationale || "see the Evaluate agent's report"}
293
+ <!-- learning:on -->
294
+ DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
295
+ <!-- learning:end -->
267
296
  ISSUE_NUMBER: ${ISSUE_NUMBER}
268
297
  ISSUE_PR_LINK: ${ISSUE_PR_LINK}
269
298
  COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
@@ -282,8 +311,12 @@ Cover: functionality, API contracts, performance, and cover every TEST_PLAN scen
282
311
  testVerdict = testResult.verdict;
283
312
  if (testVerdict === "FAIL") {
284
313
  // fix-and-continue — no re-test, no inline Gate 1.
285
- await agent(`Fix the failing acceptance test scenarios on branch ${BRANCH}:
314
+ await agent(`OPERATION: qa-fix
315
+ Fix the failing acceptance test scenarios on branch ${BRANCH}:
286
316
  ${testResult.failures}
317
+ <!-- learning:on -->
318
+ DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
319
+ <!-- learning:end -->
287
320
  ISSUE_NUMBER: ${ISSUE_NUMBER}
288
321
  ISSUE_PR_LINK: ${ISSUE_PR_LINK}
289
322
  COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
@@ -309,7 +342,7 @@ const reviewResult = await phase("review", async () => {
309
342
  const chunkSize = 5;
310
343
  const reviewThunks = reviewFocuses.map(focus =>
311
344
  () => agent(`${focus.charAt(0).toUpperCase() + focus.slice(1)} review. ${reviewScope}
312
- Focus discipline: ${focus}. Return: {"focus": "${focus}", "reviewed": true, "filesExamined": ["<path>"], "findings": [{"file": "<path-or-null>", "description": "<one-sentence issue>", "severity": "critical|high|medium|low"}]}`, { agentType: "Review" })
345
+ Focus discipline: ${focus}. Return: {"focus": "${focus}", "reviewed": true, "filesExamined": ["<path>"], "findings": [{"file": "<path-or-null>", "line": <number-or-null>, "description": "<one-sentence issue>", "severity": "critical|high|medium|low"}]}`, { agentType: "Review" })
313
346
  );
314
347
 
315
348
  let rawReviews = [];
@@ -326,7 +359,7 @@ Focus discipline: ${focus}. Return: {"focus": "${focus}", "reviewed": true, "fil
326
359
  const focus = reviewFocuses[i];
327
360
  if (!r || r.reviewed !== true) {
328
361
  // Dead Review agent: retry once sequentially
329
- const retry = await agent(`Retry ${focus} review. ${reviewScope} Focus: ${focus} discipline. Return: {"focus": "${focus}", "reviewed": true, "filesExamined": ["<path>"], "findings": [{"file": "<path-or-null>", "description": "<one-sentence issue>", "severity": "critical|high|medium|low"}]}`, { agentType: "Review" });
362
+ const retry = await agent(`Retry ${focus} review. ${reviewScope} Focus: ${focus} discipline. Return: {"focus": "${focus}", "reviewed": true, "filesExamined": ["<path>"], "findings": [{"file": "<path-or-null>", "line": <number-or-null>, "description": "<one-sentence issue>", "severity": "critical|high|medium|low"}]}`, { agentType: "Review" });
330
363
  if (!retry || retry.reviewed !== true) {
331
364
  coverageGaps.push(focus);
332
365
  log(`review coverage incomplete: ${focus}`);
@@ -391,13 +424,21 @@ ${JSON.stringify(allFindings.map((f, i) => ({ index: i, description: f.descripti
391
424
  // Distinct-file groups are paced in chunks of FIX_CHUNK — same pacing bar as the Review spawn
392
425
  // path — to avoid launching all Code agents at once on a large branch (reliability: explicit bound).
393
426
  const FIX_CHUNK = 5;
394
- const fixThunks = Object.entries(fileGroups).map(([fileKey, findings]) => async () => {
427
+ // issue-fix SCOPE per finding: CRITICAL or HIGH takes the Careful protocol (failing regression test first); anything else is Standard.
428
+ const CAREFUL_SEVERITIES = new Set(["critical", "high"]);
429
+ const fixThunks = Object.values(fileGroups).map(findings => async () => {
395
430
  const chunkResults = [];
396
431
  for (let i = 0; i < findings.length; i += MAX_BATCH) {
397
432
  const chunk = findings.slice(i, i + MAX_BATCH);
398
- const r = await agent(`Fix the following confirmed review findings on branch ${BRANCH}${fileKey.startsWith("__nofile") ? "" : ` in ${fileKey}`}:
399
- ${chunk.map(f => `- ${f.description} (${f.severity})`).join("\n")}
400
-
433
+ const r = await agent(`OPERATION: issue-fix
434
+ Fix the following confirmed review findings on branch ${BRANCH}:
435
+ ISSUES:
436
+ ${chunk.map((f, n) => `${n + 1}. ${f.file ? `${f.file}${f.line ? `:${f.line}` : ""} — ` : ""}${f.description} (${f.severity})`).join("\n")}
437
+ SCOPE: ${chunk.map((f, n) => `${n + 1}=${CAREFUL_SEVERITIES.has(String(f.severity).toLowerCase()) ? "Careful" : "Standard"}`).join(", ")}
438
+ PUSH: false
439
+ <!-- learning:on -->
440
+ DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
441
+ <!-- learning:end -->
401
442
  ISSUE_NUMBER: ${ISSUE_NUMBER}
402
443
  ISSUE_PR_LINK: ${ISSUE_PR_LINK}
403
444
  COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
@@ -436,38 +477,43 @@ Return: {"status": "fixed"|"blocked", "commitShas": ["<sha>"], "unresolved": ["<
436
477
  return { survivingFindings: notAddressed, fixedFindings, coverageGaps };
437
478
  });
438
479
 
439
- // Phase 5.5: Gate 1 #2 — FINAL post-fix gate (full Validate agent → Simplify agent → Scrutinize agent).
480
+ // Phase 5.5: Gate 1 #2 — FINAL post-fix gate (Simplify agent → Scrutinize agent → full Validate agent, Validate last and unconditional).
440
481
  // Runs ONCE, after the review pass. Inside the pass the Code agent self-verified its own fixes;
441
482
  // this is the build gate before the branch is handed back. See gate1_postcode() cadence.
442
483
  const gate1Final = await phase("gate1-final", async () => {
484
+ await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
485
+
486
+ const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Fix P0/P1 issues and commit the fixes.
487
+ Return: {"status": "PASS" | "FIXED" | "BLOCKED"}`, { agentType: "Scrutinize" });
488
+ if (!["PASS", "FIXED"].includes(scrutiny?.status)) {
489
+ return { verdict: "ESCALATED", type: "scrutiny-blocked", reason: scrutiny?.status === "BLOCKED" ? "Final Gate 1 Scrutinize agent returned BLOCKED: a P0 issue cannot be fixed in scope" : "Final Gate 1 Scrutinize agent returned no recognised status: counted as BLOCKED" };
490
+ }
491
+
443
492
  const validation = await agent(`Run build, typecheck, lint, and tests on branch ${BRANCH} (final gate after all fixing).
444
493
  Follow your Running commands block for every build and test command.
445
- Report: PASS or FAIL with details.`, { agentType: "Validate" });
494
+ Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
446
495
 
447
- if (validation.verdict === "FAIL") {
448
- let failureDetails = validation.details;
496
+ if (validation?.verdict !== "PASS") {
497
+ let failureDetails = validation?.details || "Validate agent returned no PASS verdict";
449
498
  for (let attempt = 1; attempt <= 2; attempt++) {
450
- await agent(`Fix the final validation failures on branch ${BRANCH}:
499
+ await agent(`OPERATION: validation-fix
500
+ Fix the final validation failures on branch ${BRANCH}:
451
501
  ${failureDetails}
502
+ <!-- learning:on -->
503
+ DECISIONS_CONTEXT: ${DECISIONS_CONTEXT}
504
+ <!-- learning:end -->
452
505
  ISSUE_NUMBER: ${ISSUE_NUMBER}
453
506
  ISSUE_PR_LINK: ${ISSUE_PR_LINK}
454
507
  COMPLIANCE_FRAMEWORKS: ${COMPLIANCE_FRAMEWORKS}
455
508
  Self-verify your fix compiles. Commit fixes with conventional-commit message.`, { agentType: "Code" });
456
- const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (follow your Running commands block). Report: PASS or FAIL.`, { agentType: "Validate" });
457
- if (recheck.verdict === "PASS") break;
458
- failureDetails = recheck.details || failureDetails;
459
- if (attempt === 2) return { verdict: "ESCALATED", reason: "Final Gate 1 validation exhausted after 2 Code agent fix attempts" };
509
+ const recheck = await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (final gate, after fix attempt ${attempt}; follow your Running commands block).
510
+ Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
511
+ if (recheck?.verdict === "PASS") break;
512
+ failureDetails = recheck?.details || failureDetails;
513
+ if (attempt === 2) return { verdict: "ESCALATED", type: "validation-exhausted", reason: "Final Gate 1 validation exhausted after 2 Code agent fix attempts" };
460
514
  }
461
515
  }
462
516
 
463
- await agent(`Simplify and reduce complexity of recent changes on branch ${BRANCH}. Commit any improvements.`, { agentType: "Simplify" });
464
-
465
- const scrutiny = await agent(`9-pillar self-review of recent changes on branch ${BRANCH}. Report any code you changed and your findings.`, { agentType: "Scrutinize" });
466
-
467
- if (scrutiny.codeChanged) {
468
- await agent(`Re-run build, typecheck, lint, tests on branch ${BRANCH} (Scrutinize agent made changes; follow your Running commands block). Report: PASS or FAIL.`, { agentType: "Validate" });
469
- }
470
-
471
517
  return { verdict: "PASS" };
472
518
  });
473
519
 
@@ -496,7 +542,7 @@ Write a concise report. Present only surviving findings as outstanding — never
496
542
  survivingFindings: reviewResult.survivingFindings || [], fixedFindings: reviewResult.fixedFindings || [],
497
543
  reviewCoverage: { failedFocuses: coverageGaps, complete: coverageGaps.length === 0 },
498
544
  escalations: [
499
- ...(gate1Final.verdict === "ESCALATED" ? [{ type: "validation-exhausted", description: gate1Final.reason || "final Gate 1 escalated" }] : []),
545
+ ...(gate1Final.verdict === "ESCALATED" ? [{ type: gate1Final.type || "validation-exhausted", description: gate1Final.reason || "final Gate 1 escalated" }] : []),
500
546
  ...coverageGaps.map(focus => ({ type: "review-coverage-incomplete", description: `review coverage incomplete: ${focus}` })),
501
547
  ],
502
548
  gate2, report,
@@ -540,7 +586,7 @@ When WAVE mode is detected, author a workflow that wraps the single-ticket engin
540
586
 
541
587
  **Wave workflow structure (author after the SINGLE engine blocks above):**
542
588
 
543
- 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: Validate agent (build + test). Escalations accumulate in a list; the final report lists all of them.
589
+ 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.
544
590
 
545
591
  Wave skeleton — compact reference (see `wave_loop()` doctrine for full semantics):
546
592
 
@@ -551,11 +597,12 @@ Wave skeleton — compact reference (see `wave_loop()` doctrine for full semanti
551
597
  const WAVE_TICKETS = [...remainingTickets]; // the pre-fetch's refs, in input order — the wave block's row order
552
598
  const results = {}; // ticketId → its row of the workflow's return; a ticket with no entry never ran
553
599
  // A row from an engine result: the fields step 3 after the workflow renders
554
- const rowOf = (ticketId, r, merged) => ({ ticket: ticketId, ran: true, verdict: r?.verdict || r?.overallVerdict || null, merged, issuePrLink: r?.issuePrLink || "(none)", evaluateVerdict: r?.gate2?.evaluateVerdict, testVerdict: r?.gate2?.testVerdict, surviving: r?.survivingFindings?.length, coverageComplete: r?.reviewCoverage?.complete });
600
+ 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 });
555
601
  const MAX_ROUNDS = Math.max(10, remainingTickets.length * 2 + 5); // heuristic; always finite
556
- const waveState = { quarantined: [], round: 0 };
602
+ const MERGE_SHA = /^[0-9a-f]{40}$/; // a merge commit as git prints it: the only Git-reported value that reaches a later prompt
603
+ const waveState = { quarantined: [], round: 0, halted: null };
557
604
  let reAskedThisDeadlock = false; // re-ask guard: re-ask once on empty ready-set, then escalate
558
- while (remainingTickets.length > 0 && waveState.round < MAX_ROUNDS) {
605
+ while (remainingTickets.length > 0 && waveState.round < MAX_ROUNDS && waveState.halted === null) {
559
606
  waveState.round++;
560
607
 
561
608
  // Design agent reader (opus) applies vacuous-truth rule: a ticket with no unmet dependencies is ALWAYS ready.
@@ -584,14 +631,44 @@ Return: {"ready": [...ticket-ids], "blocked": [{"ticket": "id", "namedBlocker":
584
631
  try {
585
632
  // ticketId is the ISSUE_REF the pre-fetch heading printed — this ticket's OWN reference: the engine's `ticket` (its TICKET)
586
633
  // 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)
634
+ <!-- learning:on -->
587
635
  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 });
636
+ <!-- learning:off -->
637
+ const engineResult = await runSingleTicketEngine({ ticket: ticketId, baseBranch: INTEGRATION_BRANCH, ...(plans[ticketId] || {}), issueRequired: ISSUE_REQUIRED, applyConventions: APPLY_CONVENTIONS, complianceFrameworks: COMPLIANCE_FRAMEWORKS, issueInput: ticketId });
638
+ <!-- learning:end -->
588
639
  // Check both verdict (engine_output_schema) and overallVerdict (SINGLE skeleton alias).
589
640
  // PASS and UNVERIFIED merge; PARTIAL, FAIL, ESCALATED (the ticket-link and branch stops included) or no verdict quarantine.
590
641
  if (["PASS", "UNVERIFIED"].includes(engineResult.verdict || engineResult.overallVerdict)) {
591
- const merge = await agent(`Merge ${engineResult.branch} to ${INTEGRATION_BRANCH}. Include ticket ID ${ticketId} in the merge commit message. Run Validate agent (build + test) after merge.
592
- Return: {"merged": true} — or {"merged": false, "reason": "<why>"} when the merge or the post-merge build failed and the merge was not kept.`, { agentType: "Git" });
593
- results[ticketId] = rowOf(ticketId, engineResult, merge?.merged === true);
594
- if (merge?.merged !== true) waveState.quarantined.push({ ticket: ticketId, reason: merge?.reason || "merge or post-merge build failed" });
642
+ // Git merges locally and validates nothing: the workflow owns the post-merge Validate (D-WAVE-MERGE-VALIDATE).
643
+ 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.
644
+ 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" });
645
+ let kept = false;
646
+ let reason = merge?.reason || "merge failed";
647
+ if (merge?.merged === true && !MERGE_SHA.test(String(merge.mergeSha))) {
648
+ // A merge may sit on the integration branch with no SHA to validate or undo it by: stop taking merges.
649
+ waveState.halted = { ticket: ticketId, mergeSha: null, reason: "merge reported without a 40-hex mergeSha; the integration branch may hold an unvalidated merge" };
650
+ reason = waveState.halted.reason;
651
+ } else if (merge?.merged === true) {
652
+ // A merge counts as kept only when this Validate returns PASS. It always runs: treeEqual is recorded, never a reason to skip.
653
+ const post = await agent(`Run build, typecheck, lint, and tests on ${INTEGRATION_BRANCH} after merging ${engineResult.branch} (merge commit ${merge.mergeSha}).
654
+ Follow your Running commands block for every build and test command.
655
+ Return: {"verdict": "PASS" | "FAIL", "details": "..."}`, { agentType: "Validate" });
656
+ if (post?.verdict === "PASS") {
657
+ kept = true;
658
+ } else {
659
+ 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
660
+ Return: {"undone": true} — or {"undone": false, "reason": "<why>"} when you did not undo it.`, { agentType: "Git" });
661
+ if (undo?.undone === true) {
662
+ reason = "post-merge build red; merge undone";
663
+ } else {
664
+ // A merge that stays red would poison every later ticket: stop taking merges and rounds, and name the red HEAD.
665
+ reason = `post-merge build red; merge not undone (${undo?.reason || "no reason given"})`;
666
+ waveState.halted = { ticket: ticketId, mergeSha: merge.mergeSha, reason };
667
+ }
668
+ }
669
+ }
670
+ results[ticketId] = rowOf(ticketId, engineResult, kept, merge?.merged === true && typeof merge.treeEqual === "boolean" ? merge.treeEqual : null);
671
+ if (!kept) waveState.quarantined.push({ ticket: ticketId, reason });
595
672
  } else {
596
673
  results[ticketId] = rowOf(ticketId, engineResult, false);
597
674
  waveState.quarantined.push({ ticket: ticketId, reason: engineResult.escalations?.[0]?.description || "engine fail/escalated" });
@@ -602,11 +679,12 @@ Return: {"merged": true} — or {"merged": false, "reason": "<why>"} when the me
602
679
  waveState.quarantined.push({ ticket: ticketId, reason: `engine crash: ${String(err)}` });
603
680
  }
604
681
  remainingTickets = remainingTickets.filter(t => t !== ticketId);
682
+ if (waveState.halted !== null) break; // a red integration HEAD takes no further merge in this round
605
683
  }
606
684
  }
607
685
 
608
686
  // After the wave report is written: one row per wave ticket, in input order. No entry ⇒ never ran (cascade, deadlock, MAX_ROUNDS).
609
- return { tickets: WAVE_TICKETS.map(t => results[t] || { ticket: t, ran: false, verdict: null, merged: false, issuePrLink: "(none)" }), quarantined: waveState.quarantined };
687
+ return { tickets: WAVE_TICKETS.map(t => results[t] || { ticket: t, ran: false, verdict: null, merged: false, treeEqual: null, issuePrLink: "(none)" }), quarantined: waveState.quarantined, halted: waveState.halted };
610
688
  ```
611
689
 
612
690
  ---
@@ -630,6 +708,7 @@ A workflow cannot pause mid-run. After the build/wave workflow returns, you (the
630
708
  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.
631
709
  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.
632
710
  - **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.
711
+ - **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.
633
712
  - **Nothing merged** (no entry has `merged: true`): record `Wave PR: skipped (nothing merged)` and go to step 4.
634
713
  - Otherwise compose (a) and then (b).
635
714
 
@@ -3,13 +3,21 @@ description: Parallel wave planning — plan-challenge every ticket, produce acc
3
3
  argument-hint: "[tickets-dir | issue-list]"
4
4
  output-dir: dist/commands
5
5
  ---
6
- @import { authoring_preamble } from "./_partials/_preamble.mds"
6
+ @import { authoring_preamble, authoring_decisions } from "./_partials/_preamble.mds"
7
7
  @import { docs_root } from "./_partials/_docs_root.mds"
8
8
  @import { agent_roster, agent_caveats } from "./_partials/_roster.mds"
9
9
  @import { acceptance_criteria_contract } from "./_partials/_plan_contract.mds"
10
10
  @import { issue_ref_grammar, issue_capture_contract } from "./_partials/_tracker.mds"
11
+ @import "./_partials/_settings.mds" as settings
11
12
  {{authoring_preamble()}}
12
13
 
14
+ <!-- learning:on -->
15
+ ### Settings line
16
+
17
+ {{settings.settings_resolve()}}
18
+
19
+ <!-- learning:end -->
20
+ {{authoring_decisions()}}
13
21
  ---
14
22
 
15
23
  ## dynamic-plan — author and run a parallel planning + plan-challenge workflow
@@ -44,10 +52,12 @@ Before authoring, verify:
44
52
 
45
53
  Pass `{worktree}` to the workflow as its `root` argument.
46
54
 
55
+ <!-- learning:on -->
47
56
  **1. Apply decisions context**
48
57
 
49
- Apply the `devflow:apply-decisions` algorithm to the DECISIONS_CONTEXT loaded per the preamble above: scan the index, Read relevant entries, note verbatim ADR/PF IDs to inject into Design agent and Evaluate agent prompts.
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.
50
59
 
60
+ <!-- learning:end -->
51
61
  **2. Read the preference profile**
52
62
 
53
63
  Check whether `~/.devflow/preference-profile.md` exists:
@@ -112,7 +122,9 @@ export const meta = {
112
122
  // AskUserQuestion happens at the command boundary after this workflow returns — NOT here.
113
123
 
114
124
  const ticketSource = args.ticketSource || args[0] || "see task description";
125
+ <!-- learning:on -->
115
126
  const DECISIONS_CONTEXT = args.decisionsContext || ""; // injected before authoring
127
+ <!-- learning:end -->
116
128
  const PREFERENCE_PROFILE = args.preferenceProfile || ""; // injected before authoring
117
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)
118
130
  const slug = args.slug || "wave";
@@ -133,7 +145,9 @@ const plans = await phase("plan-parallel", () =>
133
145
  parallel((tickets || []).map(ticket => () =>
134
146
  agent(`Write an implementation plan for this ticket.
135
147
  Ticket: ${JSON.stringify(ticket)}
148
+ <!-- learning:on -->
136
149
  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}
150
+ <!-- learning:end -->
137
151
  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.
138
152
  Write a thorough but tight plan — every section must earn its place for a Code agent who has no other context.
139
153
  Return: { ticketTitle, planMarkdown, openDecisions (array of genuine unknowns requiring user input) }.`, { agentType: "Design" })
@@ -149,7 +163,6 @@ const challenged = await phase("plan-challenge", () =>
149
163
 
150
164
  Plan under review: ${JSON.stringify(plan)}
151
165
  Ticket: ${JSON.stringify((tickets || [])[i])}
152
- Decisions context: ${DECISIONS_CONTEXT}
153
166
 
154
167
  Produce:
155
168
  1. List of improvements / gaps / edge cases / side-effects identified.
@@ -160,7 +173,7 @@ Produce:
160
173
  - its verification method is its method: a test committed to the suite is ci; a command run and read (a load test, a script) is local; a step performed and observed is manual;
161
174
  - the paths it exercises, from the plan's affected files, are its files: globs.
162
175
  Setup and expected outcome never go in a line: give them per TP in testScenarios.
163
- 4. A list of genuine design decisions that require user input (not settled by the plan, the preference profile, or existing ADRs).
176
+ 4. A list of genuine design decisions that require user input (not settled by the plan or the preference profile).
164
177
 
165
178
  Test-plan line contract:
166
179
  ${TP_CONTRACT}
@@ -178,7 +191,9 @@ Return: { ticketTitle, improvements (array), acceptanceCriteria (array of number
178
191
  const crossCritic = await phase("cross-plan-critic", () =>
179
192
  agent(`Cross-plan conflict audit.
180
193
  All ticket plans + challenged criteria: ${JSON.stringify(challenged)}
194
+ <!-- learning:on -->
181
195
  Decisions context: ${DECISIONS_CONTEXT}
196
+ <!-- learning:end -->
182
197
 
183
198
  Audit:
184
199
  1. API conflicts: two tickets defining the same API differently (different signatures, return types, error codes).
@@ -186,7 +201,11 @@ Audit:
186
201
  3. Undeclared dependencies: ticket A's plan implicitly requires something ticket B provides, but no dependency is declared.
187
202
  4. Scope overlap: two tickets both claim ownership of the same module or behavior.
188
203
 
204
+ <!-- learning:on -->
189
205
  For each conflict found: state which two tickets are involved, describe the conflict precisely, and propose a resolution aligned with the decisions ledger.
206
+ <!-- learning:off -->
207
+ For each conflict found: state which two tickets are involved, describe the conflict precisely, and propose a resolution.
208
+ <!-- learning:end -->
190
209
 
191
210
  Return: { conflicts (array of {tickets, conflictDescription, proposedResolution}), planAmendments (array of {ticketTitle, amendment}) }.`, { agentType: "Design" })
192
211
  );
@@ -199,10 +218,9 @@ const resolved = await phase("preference-resolve", () =>
199
218
  Preference profile: ${PREFERENCE_PROFILE || "(none — no profile found)"}
200
219
  Open decisions from plan-challenge: ${JSON.stringify((challenged || []).flatMap(c => c.openDecisions || []))}
201
220
  Cross-plan conflicts needing resolution: ${JSON.stringify((crossCritic && crossCritic.conflicts) || [])}
202
- Decisions context (ADRs/PFs already settled): ${DECISIONS_CONTEXT}
203
221
 
204
222
  For each open decision:
205
- - If the preference profile or an existing ADR/PF settles it clearly: auto-resolve and note the rationale.
223
+ - If the preference profile settles it clearly: auto-resolve and note the rationale.
206
224
  - If not settled: add to the DECISIONS-NEEDED list for the user.
207
225
 
208
226
  Return: { autoResolved (array of {decision, resolution, source}), decisionsNeeded (array of {decision, context, options}) }.`, { agentType: "Synthesize" })
@@ -224,14 +242,26 @@ For each ticket, write ${OUTDIR}/{ticket-slug}-plan.md containing:
224
242
  - ## Acceptance Criteria (numbered, positive + negative)
225
243
  - ## 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
226
244
  - ## Test Scenarios — one line per TP, in TP order: TP-n: its setup, then its expected outcome, from testScenarios
245
+ <!-- learning:on -->
227
246
  - ## Auto-Resolved Decisions (if any — list each as: decision → resolution → source, naming a recorded decision in words, never by ID)
247
+ <!-- learning:off -->
248
+ - ## Auto-Resolved Decisions (if any — list each as: decision → resolution → source)
249
+ <!-- learning:end -->
228
250
 
229
251
  Then write ${OUTDIR}/DECISIONS-NEEDED.md:
252
+ <!-- learning:on -->
230
253
  - ## 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."
254
+ <!-- learning:off -->
255
+ - ## 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."
256
+ <!-- learning:end -->
231
257
  - ## Decisions Needed
232
258
  - One section per open decision: what the decision is, why it matters, what options exist.
233
259
  - Include cross-plan conflicts that were not auto-resolved.
260
+ <!-- learning:on -->
234
261
  - If no decisions needed, write: "No open decisions — all settled by preference profile or existing ADRs."
262
+ <!-- learning:off -->
263
+ - If no decisions needed, write: "No open decisions — all settled by the preference profile."
264
+ <!-- learning:end -->
235
265
 
236
266
  Return: { planPaths (array), decisionsNeededPath (string), decisionsNeededCount (number) }.`, { agentType: "Synthesize" })
237
267
  );
@@ -3,9 +3,17 @@ description: Decision-preference profile distiller — mine past session transcr
3
3
  argument-hint: "[--dry-run]"
4
4
  output-dir: dist/commands
5
5
  ---
6
- @import { authoring_preamble } from "./_partials/_preamble.mds"
6
+ @import { authoring_preamble, authoring_decisions } from "./_partials/_preamble.mds"
7
+ @import "./_partials/_settings.mds" as settings
7
8
  {{authoring_preamble()}}
8
9
 
10
+ <!-- learning:on -->
11
+ ### Settings line
12
+
13
+ {{settings.settings_resolve()}}
14
+
15
+ <!-- learning:end -->
16
+ {{authoring_decisions()}}
9
17
  ---
10
18
 
11
19
  ## dynamic-profile — distill a decision-preference profile from past sessions
@@ -3,14 +3,22 @@ description: Generalized ticket-factory — turn an initiative or spec into a re
3
3
  argument-hint: "[initiative | spec-doc]"
4
4
  output-dir: dist/commands
5
5
  ---
6
- @import { authoring_preamble } from "./_partials/_preamble.mds"
6
+ @import { authoring_preamble, authoring_decisions } from "./_partials/_preamble.mds"
7
7
  @import { agent_roster, agent_caveats } from "./_partials/_roster.mds"
8
8
  @import { ticket_body_template } from "./_partials/_ticket_template.mds"
9
9
  @import { factory_shape } from "./_partials/_factory.mds"
10
10
  @import { evidence_policy } from "./_partials/_evidence_policy.mds"
11
11
  @import { docs_root } from "./_partials/_docs_root.mds"
12
+ @import "./_partials/_settings.mds" as settings
12
13
  {{authoring_preamble()}}
13
14
 
15
+ <!-- learning:on -->
16
+ ### Settings line
17
+
18
+ {{settings.settings_resolve()}}
19
+
20
+ <!-- learning:end -->
21
+ {{authoring_decisions()}}
14
22
  ---
15
23
 
16
24
  ## dynamic-tickets — author and run a ticket-factory workflow
@@ -53,10 +61,12 @@ Before you write the workflow script:
53
61
 
54
62
  Pass `{worktree}` to the workflow as its `root` argument.
55
63
 
64
+ <!-- learning:on -->
56
65
  **1. Apply decisions context**
57
66
 
58
- Apply the `devflow:apply-decisions` algorithm to the DECISIONS_CONTEXT loaded per the preamble above: scan the index, Read relevant entries, note the verbatim ADR/PF IDs you will inject into Design agent and Review agent prompts.
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.
59
68
 
69
+ <!-- learning:end -->
60
70
  **2. Read and distill the initiative**
61
71
 
62
72
  Read or note the user's input:
@@ -93,7 +103,9 @@ export const meta = {
93
103
  const candidates = args.candidates || []; // [{title, summary, wave, dependsOn}]
94
104
  const initiative = args.initiative || args[0] || "see task description";
95
105
  const constraints = args.constraints || "";
106
+ <!-- learning:on -->
96
107
  const DECISIONS_CONTEXT = args.decisionsContext || ""; // injected before authoring
108
+ <!-- learning:end -->
97
109
  const slug = args.slug || initiative.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 40);
98
110
  const ts = new Date().toISOString().slice(0,16).replace(/[-:T]/g, (c) => c === 'T' ? '_' : c === ':' ? '' : c);
99
111
  const ROOT = args.root; // {worktree} from Pre-authoring setup: the checkout's toplevel, never cwd
@@ -105,7 +117,9 @@ const drafts = await phase("draft", () =>
105
117
  agent(`Draft ticket for initiative: "${initiative}"
106
118
  Ticket: ${JSON.stringify(c)}
107
119
  Constraints: ${constraints}
120
+ <!-- learning:on -->
108
121
  Decisions context (apply devflow:apply-decisions; the ticket is filed to the tracker, so state each decision in words, never by ID): ${DECISIONS_CONTEXT}
122
+ <!-- learning:end -->
109
123
  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).
110
124
  Return a JSON object with: title (string), summary (string), wave (number), dependsOn (array), bodyMarkdown (string), openQuestions (array).`, { agentType: "Design" })
111
125
  ))