cclaw-cli 0.51.30 → 0.55.2

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 (142) hide show
  1. package/README.md +22 -16
  2. package/dist/artifact-linter/brainstorm.d.ts +2 -0
  3. package/dist/artifact-linter/brainstorm.js +245 -0
  4. package/dist/artifact-linter/design.d.ts +2 -0
  5. package/dist/artifact-linter/design.js +323 -0
  6. package/dist/artifact-linter/plan.d.ts +2 -0
  7. package/dist/artifact-linter/plan.js +162 -0
  8. package/dist/artifact-linter/review-army.d.ts +24 -0
  9. package/dist/artifact-linter/review-army.js +365 -0
  10. package/dist/artifact-linter/review.d.ts +2 -0
  11. package/dist/artifact-linter/review.js +65 -0
  12. package/dist/artifact-linter/scope.d.ts +2 -0
  13. package/dist/artifact-linter/scope.js +115 -0
  14. package/dist/artifact-linter/shared.d.ts +246 -0
  15. package/dist/artifact-linter/shared.js +1488 -0
  16. package/dist/artifact-linter/ship.d.ts +2 -0
  17. package/dist/artifact-linter/ship.js +46 -0
  18. package/dist/artifact-linter/spec.d.ts +2 -0
  19. package/dist/artifact-linter/spec.js +108 -0
  20. package/dist/artifact-linter/tdd.d.ts +2 -0
  21. package/dist/artifact-linter/tdd.js +124 -0
  22. package/dist/artifact-linter.d.ts +4 -76
  23. package/dist/artifact-linter.js +56 -2949
  24. package/dist/cli.d.ts +1 -6
  25. package/dist/cli.js +4 -159
  26. package/dist/codex-feature-flag.d.ts +1 -1
  27. package/dist/codex-feature-flag.js +1 -1
  28. package/dist/config.d.ts +3 -2
  29. package/dist/config.js +67 -3
  30. package/dist/constants.d.ts +1 -7
  31. package/dist/constants.js +9 -15
  32. package/dist/content/cancel-command.js +2 -2
  33. package/dist/content/closeout-guidance.js +10 -7
  34. package/dist/content/core-agents.d.ts +18 -0
  35. package/dist/content/core-agents.js +46 -2
  36. package/dist/content/decision-protocol.d.ts +1 -1
  37. package/dist/content/decision-protocol.js +1 -1
  38. package/dist/content/examples.js +6 -6
  39. package/dist/content/harness-doc.js +20 -2
  40. package/dist/content/hook-inline-snippets.d.ts +17 -4
  41. package/dist/content/hook-inline-snippets.js +218 -5
  42. package/dist/content/hook-manifest.d.ts +2 -2
  43. package/dist/content/hook-manifest.js +2 -2
  44. package/dist/content/hooks.d.ts +1 -0
  45. package/dist/content/hooks.js +32 -137
  46. package/dist/content/idea-command.d.ts +8 -0
  47. package/dist/content/{ideate-command.js → idea-command.js} +57 -50
  48. package/dist/content/idea-frames.d.ts +31 -0
  49. package/dist/content/{ideate-frames.js → idea-frames.js} +9 -9
  50. package/dist/content/idea-ranking.d.ts +25 -0
  51. package/dist/content/{ideate-ranking.js → idea-ranking.js} +5 -5
  52. package/dist/content/iron-laws.d.ts +0 -1
  53. package/dist/content/iron-laws.js +31 -16
  54. package/dist/content/learnings.js +1 -1
  55. package/dist/content/meta-skill.js +7 -7
  56. package/dist/content/node-hooks.d.ts +10 -0
  57. package/dist/content/node-hooks.js +43 -9
  58. package/dist/content/opencode-plugin.js +3 -3
  59. package/dist/content/skills.js +19 -7
  60. package/dist/content/stage-schema.js +44 -2
  61. package/dist/content/stages/_lint-metadata/index.js +26 -2
  62. package/dist/content/stages/brainstorm.js +13 -7
  63. package/dist/content/stages/design.js +16 -11
  64. package/dist/content/stages/plan.js +7 -4
  65. package/dist/content/stages/review.js +4 -4
  66. package/dist/content/stages/schema-types.d.ts +1 -1
  67. package/dist/content/stages/scope.js +15 -12
  68. package/dist/content/stages/ship.js +2 -2
  69. package/dist/content/stages/spec.js +9 -3
  70. package/dist/content/stages/tdd.js +14 -4
  71. package/dist/content/start-command.js +11 -10
  72. package/dist/content/status-command.js +3 -3
  73. package/dist/content/subagents.js +60 -6
  74. package/dist/content/templates.d.ts +1 -1
  75. package/dist/content/templates.js +102 -150
  76. package/dist/content/tree-command.js +2 -2
  77. package/dist/content/utility-skills.d.ts +2 -2
  78. package/dist/content/utility-skills.js +2 -2
  79. package/dist/content/view-command.js +4 -2
  80. package/dist/delegation.d.ts +2 -0
  81. package/dist/delegation.js +2 -1
  82. package/dist/early-loop.d.ts +66 -0
  83. package/dist/early-loop.js +275 -0
  84. package/dist/gate-evidence.d.ts +8 -0
  85. package/dist/gate-evidence.js +141 -5
  86. package/dist/harness-adapters.d.ts +2 -2
  87. package/dist/harness-adapters.js +47 -18
  88. package/dist/install.js +153 -29
  89. package/dist/internal/advance-stage/advance.d.ts +50 -0
  90. package/dist/internal/advance-stage/advance.js +480 -0
  91. package/dist/internal/advance-stage/cancel-run.d.ts +8 -0
  92. package/dist/internal/advance-stage/cancel-run.js +19 -0
  93. package/dist/internal/advance-stage/flow-state-coercion.d.ts +3 -0
  94. package/dist/internal/advance-stage/flow-state-coercion.js +81 -0
  95. package/dist/internal/advance-stage/helpers.d.ts +14 -0
  96. package/dist/internal/advance-stage/helpers.js +145 -0
  97. package/dist/internal/advance-stage/hook.d.ts +8 -0
  98. package/dist/internal/advance-stage/hook.js +40 -0
  99. package/dist/internal/advance-stage/parsers.d.ts +54 -0
  100. package/dist/internal/advance-stage/parsers.js +307 -0
  101. package/dist/internal/advance-stage/review-loop.d.ts +7 -0
  102. package/dist/internal/advance-stage/review-loop.js +170 -0
  103. package/dist/internal/advance-stage/rewind.d.ts +14 -0
  104. package/dist/internal/advance-stage/rewind.js +108 -0
  105. package/dist/internal/advance-stage/start-flow.d.ts +11 -0
  106. package/dist/internal/advance-stage/start-flow.js +136 -0
  107. package/dist/internal/advance-stage/verify.d.ts +29 -0
  108. package/dist/internal/advance-stage/verify.js +225 -0
  109. package/dist/internal/advance-stage.js +21 -1470
  110. package/dist/internal/compound-readiness.d.ts +1 -1
  111. package/dist/internal/compound-readiness.js +2 -2
  112. package/dist/internal/early-loop-status.d.ts +7 -0
  113. package/dist/internal/early-loop-status.js +90 -0
  114. package/dist/internal/runtime-integrity.d.ts +7 -0
  115. package/dist/internal/runtime-integrity.js +288 -0
  116. package/dist/internal/tdd-red-evidence.js +1 -1
  117. package/dist/knowledge-store.d.ts +3 -8
  118. package/dist/knowledge-store.js +16 -29
  119. package/dist/managed-resources.js +24 -2
  120. package/dist/policy.js +4 -6
  121. package/dist/run-archive.d.ts +1 -1
  122. package/dist/run-archive.js +12 -12
  123. package/dist/run-persistence.js +111 -11
  124. package/dist/tdd-cycle.d.ts +3 -3
  125. package/dist/tdd-cycle.js +1 -1
  126. package/dist/types.d.ts +18 -10
  127. package/package.json +1 -1
  128. package/dist/content/ideate-command.d.ts +0 -8
  129. package/dist/content/ideate-frames.d.ts +0 -31
  130. package/dist/content/ideate-ranking.d.ts +0 -25
  131. package/dist/content/next-command.d.ts +0 -20
  132. package/dist/content/next-command.js +0 -298
  133. package/dist/content/seed-shelf.d.ts +0 -36
  134. package/dist/content/seed-shelf.js +0 -301
  135. package/dist/content/stage-common-guidance.d.ts +0 -1
  136. package/dist/content/stage-common-guidance.js +0 -106
  137. package/dist/doctor-registry.d.ts +0 -10
  138. package/dist/doctor-registry.js +0 -186
  139. package/dist/doctor.d.ts +0 -17
  140. package/dist/doctor.js +0 -2201
  141. package/dist/internal/hook-manifest.d.ts +0 -16
  142. package/dist/internal/hook-manifest.js +0 -77
@@ -1,298 +0,0 @@
1
- import { RUNTIME_ROOT } from "../constants.js";
2
- import { nextStage as nextStageForTrack } from "../flow-state.js";
3
- import { conversationLanguagePolicyMarkdown } from "./language-policy.js";
4
- import { stageSchema } from "./stage-schema.js";
5
- import { closeoutChainInline, closeoutNextCommandGuidance, closeoutSubstateInline, closeoutSubstateProtocolBullets } from "./closeout-guidance.js";
6
- import { stageSkillFolder } from "./skills.js";
7
- const NEXT_SKILL_FOLDER = "flow-next-step";
8
- const NEXT_SKILL_NAME = "flow-next-step";
9
- function flowStatePath() {
10
- return `${RUNTIME_ROOT}/state/flow-state.json`;
11
- }
12
- function delegationLogPathLine() {
13
- return `${RUNTIME_ROOT}/state/delegation-log.json`;
14
- }
15
- function reconciliationNoticesPathLine() {
16
- return `${RUNTIME_ROOT}/state/reconciliation-notices.json`;
17
- }
18
- /**
19
- * Single source of truth for how /cc-next should treat Ralph Loop status.
20
- *
21
- * IMPORTANT: Ralph Loop is a **progress indicator + soft pre-advance nudge**,
22
- * not a hard gate. Hard enforcement always flows through flow-state.json
23
- * gates via `stage-complete.mjs`. Both the command contract and the skill
24
- * document render this same paragraph to prevent drift — see
25
- * `tests/e2e/next-command-ralph-loop-contract.test.ts`.
26
- */
27
- export const RALPH_LOOP_CONTRACT_MARKER = "ralph-loop-contract:v1";
28
- export function ralphLoopContractSnippet() {
29
- return `**Ralph Loop (tdd only).** When \`currentStage === "tdd"\`, read
30
- \`${RUNTIME_ROOT}/state/ralph-loop.json\` (refreshed on every session-start
31
- while the flow is in tdd) as a **progress indicator**:
32
-
33
- - \`loopIteration\` — running count of RED → GREEN cycles already landed.
34
- - \`acClosed\` — distinct acceptance-criterion IDs closed by GREEN rows
35
- (populated from \`acIds\` in \`tdd-cycle-log.jsonl\`).
36
- - \`redOpenSlices\` — slices with an unsatisfied RED.
37
-
38
- Ralph Loop is a **soft pre-advance nudge**, not a gate: do not advance
39
- toward review while \`redOpenSlices\` is non-empty unless the user
40
- explicitly defers a slice. Hard gate enforcement always flows through
41
- \`flow-state.json\` gates via \`node .cclaw/hooks/stage-complete.mjs <stage>\`;
42
- Ralph Loop fields never gate-check on their own.
43
-
44
- <!-- ${RALPH_LOOP_CONTRACT_MARKER} -->`;
45
- }
46
- /**
47
- * Internal compatibility contract for /cc-next.
48
- * /cc is the promoted public progression command; this remains as an internal/legacy alias.
49
- */
50
- export function nextCommandContract() {
51
- const flowPath = flowStatePath();
52
- const skillRel = `${RUNTIME_ROOT}/skills/${NEXT_SKILL_FOLDER}/SKILL.md`;
53
- const delegationPath = delegationLogPathLine();
54
- const reconciliationNoticesPath = reconciliationNoticesPathLine();
55
- return `# /cc-next
56
-
57
- ## Purpose
58
-
59
- **Internal compatibility progression command.** \`/cc\` is the promoted public start/resume/continue command. This contract remains for legacy shims and internal references; read flow state, determine what to do:
60
-
61
- - **Current stage not started / in progress** → load its skill and execute it.
62
- - **Current stage complete (all gates passed)** → advance \`currentStage\` and load the next skill.
63
- - **Ship complete** → continue the resumable ${closeoutChainInline()} closeout. Public habit: use \`/cc\`.
64
- - **Flow complete** → report done after closeout has archived the run.
65
-
66
- The public habit is \`/cc\`; this command is a compatibility/internal route for the same progression behavior. Stage command contracts are internal implementation details.
67
-
68
- ## HARD-GATE
69
-
70
- ${conversationLanguagePolicyMarkdown()}
71
- - **Do not** invent gate completion: use only \`${flowPath}\` plus observable evidence in repo artifacts.
72
- - **Do not** skip stages: advance only from \`currentStage\` to its configured successor.
73
- - ${closeoutNextCommandGuidance()}
74
-
75
- ## Algorithm (mandatory)
76
-
77
- 1. Read **\`${flowPath}\`**. If missing → **BLOCKED** (state missing). Next action: run \`cclaw sync\` to safely regenerate generated runtime files, then \`cclaw doctor --explain\`; do not hand-edit state unless doctor says user repair is required.
78
- 2. Parse JSON. Capture \`currentStage\` and \`stageGateCatalog[currentStage]\`.
79
- 3. If \`staleStages[currentStage]\` exists, do not advance automatically. Report \`Blocked by: stale stage\`, the marker reason/rewindId, the stage artifact work to re-run, and clear only the current stage marker with \`cclaw internal rewind --ack <currentStage>\` after rework.
80
- 4. Read **\`${reconciliationNoticesPath}\`** when present. If it contains entries for \`activeRunId + currentStage\` and the listed gate is still blocked in \`stageGateCatalog[currentStage].blocked\`, emit \`Blocked by: reconciliation notice\` with gate id, reason, and next action \`cclaw doctor --reconcile-gates --explain\`. Clarify that reconciliation refreshes derived gate status only; it does not repair missing artifacts or tests.
81
- 5. Let \`G\` = \`requiredGates\` for **\`currentStage\`** from the stage schema.
82
- 6. Let \`catalog\` = \`stageGateCatalog[currentStage]\` from flow state.
83
- 7. **Satisfied** for gate id \`g\`: \`g\` in \`catalog.passed\` and \`g\` not in \`catalog.blocked\`.
84
- 8. Let \`M\` = \`mandatoryDelegations\` for \`currentStage\`.
85
- 9. If \`M\` is non-empty, inspect **\`${delegationPath}\`**. Treat as satisfied only if each mandatory agent is **completed** or **waived**.
86
- 10. For each satisfied mandatory delegation row, verify \`evidenceRefs\` is a non-empty array (unless status is \`waived\` with rationale). Missing evidenceRefs means delegation is unresolved.
87
- 11. If any mandatory delegation is missing and no waiver exists: **STOP** and ask the user whether to dispatch \`<agent>\` now or waive with rationale. State who must run, why the role is mandatory, whether the gap is ledger status, event-log dispatch proof, or artifact \`evidenceRefs\`, and do not mark gates passed while delegation is unresolved.
88
- 12. If \`currentStage === "review"\` and \`catalog.blocked\` includes \`review_criticals_resolved\`, treat this as a hard remediation branch: recommend the managed command \`cclaw internal rewind tdd "review_blocked_by_critical <finding-ids>"\`, and do not attempt to advance toward ship. After TDD rework, require \`cclaw internal rewind --ack tdd\` before continuing.
89
-
90
- ### Path A: Current stage is NOT complete (any gate unmet or delegation missing)
91
-
92
- → Load **\`${RUNTIME_ROOT}/skills/<skillFolder>/SKILL.md\`** for the current stage.
93
- → Execute that stage's protocol. The stage skill handles the full interaction including STOP points and gate tracking.
94
- → Stage completion must use \`node .cclaw/hooks/stage-complete.mjs <currentStage>\` (canonical), which validates delegations + gate evidence before mutating \`flow-state.json\`.
95
-
96
- ${ralphLoopContractSnippet()}
97
-
98
- ### Path B: Current stage IS complete (all gates passed, all delegations satisfied)
99
-
100
- → If current stage's \`next\` is **\`done\`**:
101
-
102
- ${closeoutSubstateProtocolBullets()}
103
-
104
- Otherwise report **"Flow complete. All stages finished."** and stop.
105
-
106
- → Otherwise: load **\`${RUNTIME_ROOT}/skills/<skillFolder>/SKILL.md\`** for the successor stage. Execute that stage's protocol.
107
-
108
- ### Track-aware successor resolution
109
-
110
- \`flow-state.json\` carries a \`track\` field (\`"quick"\`, \`"medium"\`, or \`"standard"\`) and a \`skippedStages\` array.
111
-
112
- - If \`track === "quick"\`, the critical path is **spec → tdd → review → ship**. Quick skips ceremony, not safety: spec approval, RED/GREEN/REFACTOR evidence, review, and ship gates still apply. When advancing, skip any stage listed in \`skippedStages\` — i.e. after the current stage completes, pick the next stage that is NOT in \`skippedStages\`.
113
- - If \`track === "medium"\`, the critical path is **brainstorm → spec → plan → tdd → review → ship**. Scope and design are intentionally skipped unless the run is reclassified to standard.
114
- - If \`track === "standard"\`, advance through all 8 stages in their natural order.
115
- - Never manually reintroduce a skipped stage mid-run. If evidence shows the track was wrong, stop and use the managed start-flow helper with \`--reclassify\`; only that managed reclassification may add upstream stages back into the active track.
116
-
117
- ## Resume Semantics
118
-
119
- \`/cc\` in a **new session** resumes from where you left off; \`/cc-next\` is a compatibility alias:
120
- - Flow-state records \`currentStage\` and which gates have passed.
121
- - The stage skill reads upstream artifacts and picks up context.
122
- - ${closeoutSubstateInline()} carries the post-ship substate, so a crashed
123
- session during retro/compound/archive resumes at the exact step without
124
- regenerating the retro draft.
125
- - No special resume command needed — \`/cc\` is the public resume/progression command.
126
-
127
- ## Headless mode
128
-
129
- When orchestrated by another skill/subagent, emit exactly one JSON envelope and
130
- no narrative text:
131
-
132
- \`\`\`json
133
- {"version":"1","kind":"gate-result","stage":"<currentStage>","payload":{"command":"/cc-next","decision":"resume_or_advance","nextStage":"<nextStage>"},"emittedAt":"<ISO-8601>"}
134
- \`\`\`
135
-
136
- Validate envelopes with:
137
- \`cclaw internal envelope-validate --stdin\`
138
-
139
- ## Primary skill
140
-
141
- **${skillRel}** — full protocol and stage table.
142
-
143
- ## Surface reference
144
-
145
- Use the flow-start skill plus \`.cclaw/state/flow-state.json\` for orientation when needed.
146
- `;
147
- }
148
- /**
149
- * Skill body for /cc-next — the primary flow progression command.
150
- */
151
- export function nextCommandSkillMarkdown() {
152
- const flowPath = flowStatePath();
153
- const delegationPath = delegationLogPathLine();
154
- const reconciliationNoticesPath = reconciliationNoticesPathLine();
155
- const stageRows = ["brainstorm", "scope", "design", "spec", "plan", "tdd", "review", "ship"]
156
- .map((stage) => {
157
- const skillMd = `${RUNTIME_ROOT}/skills/${stageSkillFolder(stage)}/SKILL.md`;
158
- const standardNext = nextStageForTrack(stage, "standard") ?? "(terminal)";
159
- const mediumNext = nextStageForTrack(stage, "medium") ?? "not in track";
160
- const quickNext = nextStageForTrack(stage, "quick") ?? "not in track";
161
- return `| \`${stage}\` | \`${standardNext}\` | \`${mediumNext}\` | \`${quickNext}\` | \`${skillMd}\` |`;
162
- })
163
- .join("\n");
164
- const naturalStageRows = ["brainstorm", "scope", "design", "spec", "plan", "tdd", "review", "ship"]
165
- .map((stage) => {
166
- const schema = stageSchema(stage);
167
- const next = schema.next === "done" ? "(terminal)" : schema.next;
168
- const skillMd = `${RUNTIME_ROOT}/skills/${stageSkillFolder(stage)}/SKILL.md`;
169
- return `| \`${stage}\` | \`${next}\` | \`${skillMd}\` |`;
170
- })
171
- .join("\n");
172
- return `---
173
- name: ${NEXT_SKILL_NAME}
174
- description: "Internal compatibility progression command. Prefer /cc for start, resume, continue, and closeout."
175
- ---
176
-
177
- # /cc-next — Flow Progression Compatibility
178
-
179
- ## Overview
180
-
181
- \`/cc\` is the public command to drive the cclaw flow. \`/cc-next\` remains as an internal/compatibility alias for progression.
182
-
183
- ## Operator Output Contract
184
-
185
- ${conversationLanguagePolicyMarkdown()}
186
- Default output should be compact, like OMC/OMX operator surfaces:
187
-
188
- \`\`\`
189
- Current: <currentStage or closeout.shipSubstate> (<track>)
190
- Stage: <currentStage>
191
- Gates: <passed>/<required> passed, <blocked> blocked
192
- Delegations: <done>/<mandatory> done
193
- Blocked by: <none | gate/delegation/reconciliation/stale/TDD/review/closeout ids>
194
- Blocker category: <sync-recovery | user-decision | stage-work | delegation-proof | review-rework | closeout>
195
- Next: <exact next action, usually /cc or one named remediation>
196
- Evidence needed: <artifact/test/review/delegation evidence required to unblock>
197
- \`\`\`
198
-
199
- Only expand beyond this when blocked, when asking a structured question, or when
200
- the user explicitly requests detail. When blocked, name the blocker category,
201
- why it blocks progression, the one next command/action, and the exact proof that
202
- would unblock it. Do not dump full artifacts in progression output.
203
-
204
- **How it works:**
205
- 1. Reads \`flow-state.json\` to find \`currentStage\`
206
- 2. Checks if all gates for that stage are satisfied
207
- 3. If **not** → loads the stage skill and starts/resumes execution
208
- 4. If **yes** → advances to the next stage and loads its skill
209
-
210
- **Resume:** \`/cc\` in a new session picks up from where \`flow-state.json\` says you are.
211
-
212
- ## HARD-GATE
213
-
214
- Do **not** mark gates satisfied from memory alone. Cite **artifact evidence** (paths, excerpts). If evidence is missing, list the gate as **unmet**. Do **not** skip stages.
215
-
216
- ## Algorithm
217
-
218
- ### Step 1: Read state
219
-
220
- 1. Open **\`${flowPath}\`**.
221
- 2. Record \`currentStage\` and \`stageGateCatalog[currentStage]\`.
222
- 3. If \`staleStages[currentStage]\` exists, show \`Blocked by: stale stage\`, the marker reason/rewindId, re-run the stage, and clear only the current marker via \`cclaw internal rewind --ack <currentStage>\` before advancing.
223
- 4. If the file is missing or invalid JSON → **BLOCKED** (state missing/corrupt). Next action: run \`cclaw sync\` for safe regeneration of generated runtime files, then \`cclaw doctor --explain\`; do not hand-edit flow state unless doctor says user repair is required.
224
- 5. Read \`${reconciliationNoticesPath}\` when present. For entries matching \`activeRunId + currentStage\` whose gate is still in \`stageGateCatalog[currentStage].blocked\`, show \`Blocked by: reconciliation notice\` with gate id + reason before proceeding; \`cclaw doctor --reconcile-gates --explain\` refreshes derived gate-state only; it does not repair missing artifacts or tests.
225
-
226
- ### Step 2: Evaluate gates
227
-
228
- For each gate id in \`requiredGates\` for \`currentStage\`:
229
- - **Met** if in \`catalog.passed\` and not in \`catalog.blocked\`.
230
- - **Unmet** otherwise.
231
-
232
- Check \`mandatoryDelegations\` via **\`${delegationPath}\`** — satisfied only if **completed** or **waived**.
233
- Also verify each completed mandatory delegation row has non-empty \`evidenceRefs\` (waived rows must include rationale).
234
- If a mandatory delegation is missing and no waiver exists, **STOP** and ask:
235
- (A) dispatch \`<agent>\` now, (B) waive with rationale, (C) cancel stage advance.
236
- Explain who must run, why that role is mandatory for this stage, and whether the missing proof is ledger status, event-log dispatch proof, or artifact \`evidenceRefs\`. Waivers must include a user-visible safety reason.
237
-
238
- If reconciliation warnings were emitted in Step 1, treat them as a pre-advance stop point: require explicit acknowledgement before continuing Path A or Path B.
239
-
240
- ### Step 3: Act
241
-
242
- **Path A — stage NOT complete (any gate unmet):**
243
-
244
- Load the current stage skill:
245
- - \`${RUNTIME_ROOT}/skills/<skillFolder>/SKILL.md\`
246
-
247
- Execute the stage protocol. The stage skill handles interaction, STOP points, gate tracking, and stage completion via \`node .cclaw/hooks/stage-complete.mjs <stage>\` (canonical flow-state mutation path).
248
-
249
- ${ralphLoopContractSnippet()}
250
-
251
- Special-case for review: if \`review_criticals_resolved\` is in \`blocked\`, route to rework instead of looping review forever - recommend \`cclaw internal rewind tdd "review_blocked_by_critical <finding-ids>"\`, then \`cclaw internal rewind --ack tdd\` after TDD rework.
252
-
253
- Special-case for TDD blockers: when \`06-tdd.md\` records \`NO_SOURCE_CONTEXT\`, \`NO_TEST_SURFACE\`, \`NO_IMPLEMENTABLE_SLICE\`, \`RED_NOT_EXPRESSIBLE\`, or \`NO_VCS_MODE\`, keep status BLOCKED and print \`Current\`, \`Blocked by\`, \`Next\`, \`Repair path\`, and \`Evidence needed\` instead of retrying speculative RED/GREEN work. RED blockers need a runnable failing test surface, GREEN blockers need passing full-suite evidence, REFACTOR blockers need behavior-preservation evidence.
254
-
255
- **Path B — stage IS complete (all gates met, all delegations done):**
256
-
257
- If \`next\` is \`done\`:
258
-
259
- When \`currentStage\` is \`ship\`, automatically drive the **closeout chain**
260
- by inspecting ${closeoutSubstateInline()}:
261
-
262
- | shipSubstate | Action |
263
- |-----------------------|-----------------------------------------------------|
264
- | \`idle\` / missing | Flip to \`retro_review\` and start retro protocol |
265
- | \`retro_review\` | Draft/update \`09-retro.md\`, ask accept/edit/skip |
266
- | \`compound_review\` | Compound closeout: overlap scan, refresh/supersede, ask approve/skip |
267
- | \`ready_to_archive\` | Run \`npx cclaw-cli archive\`; reset flow-state on success |
268
- | \`archived\` | Report "run archived"; stop |
269
-
270
- Each step owns its own state transition. \`/cc\` keeps retro and compound
271
- in-session, then uses the archive runtime only at \`ready_to_archive\`.
272
-
273
- Otherwise report **"Flow complete. All stages finished."** and stop.
274
-
275
- Otherwise (non-terminal \`next\`): load the next stage skill and begin execution.
276
-
277
- ## Stage order
278
-
279
- This table is the track-aware critical path. It must match \`flow-state.json.track\`; do not follow the natural schema edge when the active track skips a stage. Quick skips ceremony, not safety: spec approval, RED/GREEN/REFACTOR evidence, review, and ship gates still apply. After \`ship\`, \`/cc\` continues closeout via ${closeoutSubstateInline()}: ${closeoutChainInline()}.
280
-
281
- | Stage | Standard next | Medium next | Quick next | Skill path |
282
- |---|---|---|---|---|
283
- ${stageRows}
284
-
285
- Natural schema edge reference for diagnostics only:
286
-
287
- | Stage | Natural next | Skill path |
288
- |---|---|---|
289
- ${naturalStageRows}
290
-
291
- ## Anti-patterns
292
-
293
- - Advancing when \`blocked\` is non-empty for the current stage.
294
- - Treating \`passed\` as trusted when artifact evidence contradicts it.
295
- - Skipping **review** or **ship** because "the code looks fine".
296
- - Loading a stage skill directly instead of using \`/cc\` for progression.
297
- `;
298
- }
@@ -1,36 +0,0 @@
1
- export interface SeedShelfEntry {
2
- fileName: string;
3
- absPath: string;
4
- relPath: string;
5
- createdOn: string;
6
- title: string;
7
- triggerWhen: string[];
8
- sourceStage: string | null;
9
- sourceArtifact: string | null;
10
- hypothesis: string | null;
11
- action: string | null;
12
- summary: string;
13
- raw: string;
14
- }
15
- export interface SeedTemplateInput {
16
- title: string;
17
- triggerWhen: readonly string[];
18
- hypothesis: string;
19
- action: string;
20
- sourceStage?: string;
21
- sourceArtifact?: string;
22
- createdAt?: Date;
23
- }
24
- export interface ResolvedSeedPath {
25
- fileName: string;
26
- absPath: string;
27
- relPath: string;
28
- }
29
- export declare function seedShelfDir(projectRoot: string): string;
30
- export declare function seedSlug(title: string): string;
31
- export declare function seedFileName(title: string, createdAt?: Date): string;
32
- export declare function resolveSeedPathForWrite(projectRoot: string, title: string, createdAt?: Date): Promise<ResolvedSeedPath>;
33
- export declare function readSeedShelf(projectRoot: string): Promise<SeedShelfEntry[]>;
34
- export declare function seedMatchesPrompt(seed: SeedShelfEntry, prompt: string): boolean;
35
- export declare function findMatchingSeeds(projectRoot: string, prompt: string, maxMatches?: number): Promise<SeedShelfEntry[]>;
36
- export declare function renderSeedTemplate(input: SeedTemplateInput): string;
@@ -1,301 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { parse } from "yaml";
4
- import { RUNTIME_ROOT } from "../constants.js";
5
- const SEED_FILE_NAME_PATTERN = /^SEED-(\d{4}-\d{2}-\d{2})-([a-z0-9]+(?:-[a-z0-9]+)*)(?:-(\d+))?\.md$/u;
6
- const DEFAULT_MAX_MATCHES = 3;
7
- const MAX_SEED_MATCHES = 10;
8
- const MIN_TOKEN_OVERLAP = 2;
9
- function isRecord(value) {
10
- return typeof value === "object" && value !== null && !Array.isArray(value);
11
- }
12
- function normalizeTriggerList(value) {
13
- if (Array.isArray(value)) {
14
- return value
15
- .map((item) => typeof item === "string" || typeof item === "number" ? String(item).trim() : "")
16
- .filter((item) => item.length > 0);
17
- }
18
- if (typeof value === "string") {
19
- return value
20
- .split(/,\s*/u)
21
- .map((item) => item.trim())
22
- .filter((item) => item.length > 0);
23
- }
24
- return [];
25
- }
26
- function parseSeedFrontmatter(raw) {
27
- if (!raw.startsWith("---")) {
28
- return { values: {}, body: raw };
29
- }
30
- const lines = raw.split(/\r?\n/u);
31
- let closingIndex = -1;
32
- for (let index = 1; index < lines.length; index += 1) {
33
- if (lines[index]?.trim() === "---") {
34
- closingIndex = index;
35
- break;
36
- }
37
- }
38
- if (closingIndex < 0) {
39
- return { values: {}, body: raw };
40
- }
41
- const frontmatterRaw = lines.slice(1, closingIndex).join("\n");
42
- const body = lines.slice(closingIndex + 1).join("\n");
43
- try {
44
- const parsed = parse(frontmatterRaw);
45
- if (isRecord(parsed)) {
46
- return { values: parsed, body };
47
- }
48
- return { values: {}, body };
49
- }
50
- catch {
51
- return { values: {}, body };
52
- }
53
- }
54
- function firstHeading(body) {
55
- const match = /^#\s+(.+)$/mu.exec(body);
56
- if (!match)
57
- return null;
58
- const title = match[1]?.trim() ?? "";
59
- return title.length > 0 ? title : null;
60
- }
61
- function firstNonEmptyParagraph(body) {
62
- const lines = body.split(/\r?\n/u);
63
- for (const line of lines) {
64
- const trimmed = line.trim();
65
- if (trimmed.length === 0)
66
- continue;
67
- if (/^#\s+/u.test(trimmed))
68
- continue;
69
- if (/^[-*]\s+/u.test(trimmed))
70
- continue;
71
- return trimmed;
72
- }
73
- return "";
74
- }
75
- function fromFileNameFallbackTitle(fileName) {
76
- const stem = fileName.replace(/\.md$/u, "");
77
- const withoutPrefix = stem.replace(/^SEED-\d{4}-\d{2}-\d{2}-/u, "");
78
- return withoutPrefix
79
- .split("-")
80
- .filter((part) => part.length > 0 && !/^\d+$/u.test(part))
81
- .map((part) => part[0]?.toUpperCase() + part.slice(1))
82
- .join(" ")
83
- .trim();
84
- }
85
- export function seedShelfDir(projectRoot) {
86
- return path.join(projectRoot, RUNTIME_ROOT, "seeds");
87
- }
88
- export function seedSlug(title) {
89
- const normalized = title
90
- .toLowerCase()
91
- .trim()
92
- .replace(/[`"'“”‘’()[\]{}<>]/gu, " ")
93
- .replace(/[^a-z0-9]+/gu, "-")
94
- .replace(/^-+/u, "")
95
- .replace(/-+$/u, "");
96
- if (normalized.length === 0) {
97
- return "seed";
98
- }
99
- return normalized.slice(0, 48);
100
- }
101
- function isoDate(value) {
102
- return value.toISOString().slice(0, 10);
103
- }
104
- export function seedFileName(title, createdAt = new Date()) {
105
- return `SEED-${isoDate(createdAt)}-${seedSlug(title)}.md`;
106
- }
107
- async function pathExists(absPath) {
108
- try {
109
- await fs.stat(absPath);
110
- return true;
111
- }
112
- catch {
113
- return false;
114
- }
115
- }
116
- export async function resolveSeedPathForWrite(projectRoot, title, createdAt = new Date()) {
117
- const seedsDir = seedShelfDir(projectRoot);
118
- await fs.mkdir(seedsDir, { recursive: true });
119
- const baseFile = seedFileName(title, createdAt);
120
- const baseStem = baseFile.replace(/\.md$/u, "");
121
- let candidate = baseFile;
122
- let index = 2;
123
- while (await pathExists(path.join(seedsDir, candidate))) {
124
- candidate = `${baseStem}-${index}.md`;
125
- index += 1;
126
- }
127
- return {
128
- fileName: candidate,
129
- absPath: path.join(seedsDir, candidate),
130
- relPath: path.join(RUNTIME_ROOT, "seeds", candidate)
131
- };
132
- }
133
- export async function readSeedShelf(projectRoot) {
134
- const dir = seedShelfDir(projectRoot);
135
- let names = [];
136
- try {
137
- names = await fs.readdir(dir);
138
- }
139
- catch {
140
- return [];
141
- }
142
- const entries = [];
143
- for (const fileName of names) {
144
- if (!SEED_FILE_NAME_PATTERN.test(fileName))
145
- continue;
146
- const absPath = path.join(dir, fileName);
147
- let raw = "";
148
- try {
149
- raw = await fs.readFile(absPath, "utf8");
150
- }
151
- catch {
152
- continue;
153
- }
154
- const frontmatter = parseSeedFrontmatter(raw);
155
- const title = (typeof frontmatter.values.title === "string" && frontmatter.values.title.trim().length > 0
156
- ? frontmatter.values.title.trim()
157
- : null) ??
158
- firstHeading(frontmatter.body) ??
159
- fromFileNameFallbackTitle(fileName) ??
160
- "Untitled seed";
161
- const triggerWhen = normalizeTriggerList(frontmatter.values.trigger_when ?? frontmatter.values.triggerWhen);
162
- const sourceStage = typeof frontmatter.values.source_stage === "string"
163
- ? frontmatter.values.source_stage
164
- : typeof frontmatter.values.sourceStage === "string"
165
- ? frontmatter.values.sourceStage
166
- : null;
167
- const sourceArtifact = typeof frontmatter.values.source_artifact === "string"
168
- ? frontmatter.values.source_artifact
169
- : typeof frontmatter.values.sourceArtifact === "string"
170
- ? frontmatter.values.sourceArtifact
171
- : null;
172
- const hypothesis = typeof frontmatter.values.hypothesis === "string" ? frontmatter.values.hypothesis : null;
173
- const action = typeof frontmatter.values.action === "string" ? frontmatter.values.action : null;
174
- const createdOn = SEED_FILE_NAME_PATTERN.exec(fileName)?.[1] ?? "1970-01-01";
175
- const summary = firstNonEmptyParagraph(frontmatter.body);
176
- entries.push({
177
- fileName,
178
- absPath,
179
- relPath: path.join(RUNTIME_ROOT, "seeds", fileName),
180
- createdOn,
181
- title,
182
- triggerWhen,
183
- sourceStage,
184
- sourceArtifact,
185
- hypothesis,
186
- action,
187
- summary,
188
- raw
189
- });
190
- }
191
- entries.sort((a, b) => b.fileName.localeCompare(a.fileName));
192
- return entries;
193
- }
194
- function normalizeMatchText(value) {
195
- return value.toLowerCase().trim().replace(/\s+/gu, " ");
196
- }
197
- function tokenizeSeedText(value) {
198
- if (!value)
199
- return [];
200
- return value
201
- .toLowerCase()
202
- .split(/[^a-z0-9]+/u)
203
- .map((token) => token.trim())
204
- .filter((token) => token.length >= 3);
205
- }
206
- function uniqueTokens(values) {
207
- return new Set(values);
208
- }
209
- function exactTriggerMatch(seed, normalizedPrompt) {
210
- if (normalizedPrompt.length === 0 || seed.triggerWhen.length === 0)
211
- return false;
212
- return seed.triggerWhen.some((trigger) => {
213
- const normalizedTrigger = normalizeMatchText(trigger);
214
- return normalizedTrigger.length > 0 && normalizedPrompt.includes(normalizedTrigger);
215
- });
216
- }
217
- function seedContentTokens(seed) {
218
- return uniqueTokens([
219
- ...tokenizeSeedText(seed.title),
220
- ...tokenizeSeedText(seed.summary),
221
- ...tokenizeSeedText(seed.hypothesis),
222
- ...tokenizeSeedText(seed.action)
223
- ]);
224
- }
225
- function tokenOverlap(seed, promptTokens) {
226
- if (promptTokens.size === 0)
227
- return 0;
228
- const contentTokens = seedContentTokens(seed);
229
- let overlap = 0;
230
- for (const token of promptTokens) {
231
- if (contentTokens.has(token))
232
- overlap += 1;
233
- }
234
- return overlap;
235
- }
236
- export function seedMatchesPrompt(seed, prompt) {
237
- const normalizedPrompt = normalizeMatchText(prompt);
238
- if (exactTriggerMatch(seed, normalizedPrompt))
239
- return true;
240
- return tokenOverlap(seed, uniqueTokens(tokenizeSeedText(prompt))) >= MIN_TOKEN_OVERLAP;
241
- }
242
- export async function findMatchingSeeds(projectRoot, prompt, maxMatches = DEFAULT_MAX_MATCHES) {
243
- const seeds = await readSeedShelf(projectRoot);
244
- const normalizedPrompt = normalizeMatchText(prompt);
245
- if (normalizedPrompt.length === 0)
246
- return [];
247
- const promptTokens = uniqueTokens(tokenizeSeedText(prompt));
248
- const cappedMax = typeof maxMatches === "number" && Number.isFinite(maxMatches) && maxMatches > 0
249
- ? Math.min(MAX_SEED_MATCHES, Math.max(1, Math.floor(maxMatches)))
250
- : DEFAULT_MAX_MATCHES;
251
- const ranked = seeds
252
- .map((seed, index) => {
253
- const exact = exactTriggerMatch(seed, normalizedPrompt);
254
- const overlap = tokenOverlap(seed, promptTokens);
255
- return { seed, index, exact, overlap };
256
- })
257
- .filter((row) => row.exact || row.overlap >= MIN_TOKEN_OVERLAP);
258
- ranked.sort((a, b) => {
259
- if (a.exact !== b.exact)
260
- return a.exact ? -1 : 1;
261
- if (b.overlap !== a.overlap)
262
- return b.overlap - a.overlap;
263
- const recency = b.seed.createdOn.localeCompare(a.seed.createdOn);
264
- if (recency !== 0)
265
- return recency;
266
- return a.index - b.index;
267
- });
268
- return ranked.slice(0, cappedMax).map((row) => row.seed);
269
- }
270
- export function renderSeedTemplate(input) {
271
- const triggerWhen = [...input.triggerWhen].map((item) => item.trim()).filter((item) => item.length > 0);
272
- const createdAt = input.createdAt ?? new Date();
273
- const sourceStage = input.sourceStage?.trim() || "unknown";
274
- const sourceArtifact = input.sourceArtifact?.trim() || "unknown";
275
- return `---
276
- title: ${input.title.trim()}
277
- created_at: ${createdAt.toISOString()}
278
- source_stage: ${sourceStage}
279
- source_artifact: ${sourceArtifact}
280
- trigger_when:
281
- ${triggerWhen.length > 0 ? triggerWhen.map((trigger) => ` - ${trigger}`).join("\n") : " - <trigger token>"}
282
- hypothesis: ${input.hypothesis.trim()}
283
- action: ${input.action.trim()}
284
- ---
285
-
286
- # ${input.title.trim()}
287
-
288
- ## Why capture this seed
289
- ${input.hypothesis.trim()}
290
-
291
- ## Trigger when
292
- ${triggerWhen.length > 0 ? triggerWhen.map((trigger) => `- ${trigger}`).join("\n") : "- <trigger token>"}
293
-
294
- ## Suggested action
295
- ${input.action.trim()}
296
-
297
- ## Notes
298
- - Expected payoff:
299
- - Risks:
300
- `;
301
- }
@@ -1 +0,0 @@
1
- export declare function stageCommonGuidanceMarkdown(): string;