cclaw-cli 0.51.29 → 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 (151) 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 +2 -18
  25. package/dist/cli.js +8 -246
  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 +13 -10
  34. package/dist/content/core-agents.d.ts +18 -0
  35. package/dist/content/core-agents.js +51 -7
  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 +11 -13
  56. package/dist/content/node-hooks.d.ts +10 -0
  57. package/dist/content/node-hooks.js +45 -11
  58. package/dist/content/opencode-plugin.js +3 -3
  59. package/dist/content/session-hooks.js +1 -1
  60. package/dist/content/skills.js +19 -7
  61. package/dist/content/stage-command.js +1 -1
  62. package/dist/content/stage-schema.js +44 -2
  63. package/dist/content/stages/_lint-metadata/index.js +26 -2
  64. package/dist/content/stages/brainstorm.js +13 -7
  65. package/dist/content/stages/design.js +16 -11
  66. package/dist/content/stages/plan.js +9 -6
  67. package/dist/content/stages/review.js +4 -4
  68. package/dist/content/stages/schema-types.d.ts +1 -1
  69. package/dist/content/stages/scope.js +15 -12
  70. package/dist/content/stages/ship.js +2 -2
  71. package/dist/content/stages/spec.js +9 -3
  72. package/dist/content/stages/tdd.js +14 -4
  73. package/dist/content/start-command.d.ts +2 -2
  74. package/dist/content/start-command.js +24 -21
  75. package/dist/content/status-command.js +8 -8
  76. package/dist/content/subagents.js +61 -7
  77. package/dist/content/templates.d.ts +1 -1
  78. package/dist/content/templates.js +104 -152
  79. package/dist/content/tree-command.js +2 -2
  80. package/dist/content/utility-skills.d.ts +2 -2
  81. package/dist/content/utility-skills.js +2 -2
  82. package/dist/content/view-command.js +4 -2
  83. package/dist/delegation.d.ts +2 -0
  84. package/dist/delegation.js +2 -1
  85. package/dist/early-loop.d.ts +66 -0
  86. package/dist/early-loop.js +275 -0
  87. package/dist/flow-state.d.ts +1 -1
  88. package/dist/flow-state.js +1 -1
  89. package/dist/gate-evidence.d.ts +8 -0
  90. package/dist/gate-evidence.js +141 -5
  91. package/dist/harness-adapters.d.ts +2 -2
  92. package/dist/harness-adapters.js +54 -122
  93. package/dist/harness-selection.d.ts +31 -0
  94. package/dist/harness-selection.js +214 -0
  95. package/dist/install.js +166 -38
  96. package/dist/internal/advance-stage/advance.d.ts +50 -0
  97. package/dist/internal/advance-stage/advance.js +480 -0
  98. package/dist/internal/advance-stage/cancel-run.d.ts +8 -0
  99. package/dist/internal/advance-stage/cancel-run.js +19 -0
  100. package/dist/internal/advance-stage/flow-state-coercion.d.ts +3 -0
  101. package/dist/internal/advance-stage/flow-state-coercion.js +81 -0
  102. package/dist/internal/advance-stage/helpers.d.ts +14 -0
  103. package/dist/internal/advance-stage/helpers.js +145 -0
  104. package/dist/internal/advance-stage/hook.d.ts +8 -0
  105. package/dist/internal/advance-stage/hook.js +40 -0
  106. package/dist/internal/advance-stage/parsers.d.ts +54 -0
  107. package/dist/internal/advance-stage/parsers.js +307 -0
  108. package/dist/internal/advance-stage/review-loop.d.ts +7 -0
  109. package/dist/internal/advance-stage/review-loop.js +170 -0
  110. package/dist/internal/advance-stage/rewind.d.ts +14 -0
  111. package/dist/internal/advance-stage/rewind.js +108 -0
  112. package/dist/internal/advance-stage/start-flow.d.ts +11 -0
  113. package/dist/internal/advance-stage/start-flow.js +136 -0
  114. package/dist/internal/advance-stage/verify.d.ts +29 -0
  115. package/dist/internal/advance-stage/verify.js +225 -0
  116. package/dist/internal/advance-stage.js +21 -1470
  117. package/dist/internal/compound-readiness.d.ts +1 -1
  118. package/dist/internal/compound-readiness.js +2 -2
  119. package/dist/internal/early-loop-status.d.ts +7 -0
  120. package/dist/internal/early-loop-status.js +90 -0
  121. package/dist/internal/runtime-integrity.d.ts +7 -0
  122. package/dist/internal/runtime-integrity.js +288 -0
  123. package/dist/internal/tdd-red-evidence.js +1 -1
  124. package/dist/knowledge-store.d.ts +3 -8
  125. package/dist/knowledge-store.js +16 -29
  126. package/dist/managed-resources.js +24 -2
  127. package/dist/policy.js +5 -7
  128. package/dist/run-archive.d.ts +1 -1
  129. package/dist/run-archive.js +16 -16
  130. package/dist/run-persistence.js +112 -12
  131. package/dist/tdd-cycle.d.ts +3 -3
  132. package/dist/tdd-cycle.js +1 -1
  133. package/dist/types.d.ts +18 -10
  134. package/package.json +1 -1
  135. package/dist/content/finish-command.d.ts +0 -2
  136. package/dist/content/finish-command.js +0 -26
  137. package/dist/content/ideate-command.d.ts +0 -8
  138. package/dist/content/ideate-frames.d.ts +0 -31
  139. package/dist/content/ideate-ranking.d.ts +0 -25
  140. package/dist/content/next-command.d.ts +0 -20
  141. package/dist/content/next-command.js +0 -298
  142. package/dist/content/seed-shelf.d.ts +0 -36
  143. package/dist/content/seed-shelf.js +0 -301
  144. package/dist/content/stage-common-guidance.d.ts +0 -1
  145. package/dist/content/stage-common-guidance.js +0 -106
  146. package/dist/doctor-registry.d.ts +0 -10
  147. package/dist/doctor-registry.js +0 -186
  148. package/dist/doctor.d.ts +0 -17
  149. package/dist/doctor.js +0 -2206
  150. package/dist/internal/hook-manifest.d.ts +0 -16
  151. package/dist/internal/hook-manifest.js +0 -77
package/README.md CHANGED
@@ -33,18 +33,22 @@ npx cclaw-cli
33
33
  Then work from your coding harness:
34
34
 
35
35
  ```text
36
- /cc <idea> start a tracked flow
37
- /cc-next resume or advance the current flow
38
- /cc-view status inspect current state without changing it
36
+ /cc <idea> start, resume, or continue a tracked flow
37
+ /cc-idea generate or refresh an idea backlog
38
+ /cc-cancel end the current run cleanly with a reason
39
39
  ```
40
40
 
41
+ `/cc-idea` is a utility command for backlog discovery. It is distinct from
42
+ `.cclaw/artifacts/00-idea.md`, which is the stage artifact created by
43
+ `/cc <idea>`.
44
+
41
45
  For scripted setup:
42
46
 
43
47
  ```bash
44
48
  npx cclaw-cli init --harnesses=claude,cursor --no-interactive
45
49
  ```
46
50
 
47
- If generated files or hooks look stale, run `cclaw doctor`. If they need to be regenerated, run `npx cclaw-cli sync`.
51
+ If generated files or hooks look stale, run `npx cclaw-cli sync`.
48
52
 
49
53
  ## Why cclaw
50
54
 
@@ -57,34 +61,36 @@ AI coding sessions fail when decisions live only in chat. cclaw puts the operati
57
61
  state/delegation-log.json
58
62
  subagent dispatches, waivers, evidence refs
59
63
  knowledge.jsonl reusable lessons harvested from stage artifacts
60
- runs/ archived run snapshots
64
+ archive/ archived run snapshots (durable closeout proof)
61
65
  ```
62
66
 
67
+ Legacy `.cclaw/runs/` directories are only auto-removed when empty. If the directory still contains data, migrate it manually to `.cclaw/archive/`.
68
+
63
69
  That gives you:
64
70
 
65
71
  - **One path** from idea to ship, with `quick`, `medium`, and `standard` tracks.
66
72
  - **Real gates** for evidence, tests, review, delegation, stale-stage recovery, and closeout.
67
73
  - **Subagents with accountability**: controller owns state, workers do bounded tasks, overseers validate, evidence lands in `delegation-log.json`.
68
- - **Recovery instead of confusion**: `/cc-view status` tells you the blocker and the next command.
74
+ - **Recovery instead of confusion**: `npx cclaw-cli sync` tells you blockers and next fixes.
69
75
  - **Portable harness behavior** across Claude Code, Cursor, OpenCode, and Codex.
70
76
 
71
77
  ## The Daily Loop
72
78
 
73
79
  ```text
74
80
  1. Start or resume
75
- /cc <idea> or /cc-next
81
+ /cc <idea>
76
82
 
77
83
  2. Work the current stage
78
- The agent writes/updates .cclaw/artifacts/<stage>.md
84
+ The agent writes/updates per-stage files like .cclaw/artifacts/00-idea.md, 01-brainstorm-<slug>.md, 02-scope-<slug>.md
79
85
 
80
86
  3. Prove the gate
81
87
  stage-complete records evidence in flow-state.json
82
88
 
83
89
  4. Inspect when stuck
84
- /cc-view status
90
+ npx cclaw-cli sync
85
91
 
86
92
  5. Close out after ship
87
- /cc-next continues retro -> compound -> archive
93
+ /cc continues retro -> compound -> archive
88
94
  ```
89
95
 
90
96
  Tracks keep the flow proportional:
@@ -95,14 +101,14 @@ medium brainstorm -> spec -> plan -> tdd -> review -> ship
95
101
  standard brainstorm -> scope -> design -> spec -> plan -> tdd -> review -> ship
96
102
  ```
97
103
 
98
- Track selection is **model-guided and advisory** during `/cc`. Runtime enforcement begins after state is written: `/cc-next` follows the selected track, required gates, delegation rules, stale-stage markers, and `closeout.shipSubstate`.
104
+ Track selection is **model-guided and advisory** during `/cc`. Runtime enforcement begins after state is written: subsequent `/cc` turns follow the selected track, required gates, delegation rules, stale-stage markers, and `closeout.shipSubstate`.
99
105
 
100
106
  ## When Blocked
101
107
 
102
108
  Start here:
103
109
 
104
110
  ```text
105
- /cc-view status
111
+ npx cclaw-cli sync
106
112
  ```
107
113
 
108
114
  A useful status should read like an operator note, not a raw dump:
@@ -110,7 +116,7 @@ A useful status should read like an operator note, not a raw dump:
110
116
  ```text
111
117
  Current: tdd (standard)
112
118
  Blocked by: NO_SOURCE_CONTEXT
113
- Next: cclaw internal rewind plan "add bootstrap slice", then /cc-next
119
+ Next: cclaw internal rewind plan "add bootstrap slice", then /cc
114
120
  Evidence needed: fresh RED/GREEN/REFACTOR slice and verification output
115
121
  ```
116
122
 
@@ -119,14 +125,14 @@ Common exits:
119
125
 
120
126
  | Situation | Next action |
121
127
  | ------------------------------------------------- | ------------------------------------------------------------------------------------ |
122
- | Missing gates | Run `/cc-next`, finish the stage, then complete with evidence. |
128
+ | Missing gates | Run `/cc`, finish the stage, then complete with evidence. |
123
129
  | Mandatory delegation missing evidence | Dispatch the worker/overseer or waive explicitly with rationale. |
124
130
  | `NO_SOURCE_CONTEXT` or `NO_TEST_SURFACE` | Rewind to `plan`/`spec`, define the source or test surface, then resume TDD. |
125
131
  | `NO_IMPLEMENTABLE_SLICE` or `RED_NOT_EXPRESSIBLE` | Rework `design`/`spec`/`plan` until one vertical slice is testable. |
126
132
  | `NO_VCS_MODE` | Restore git, set `vcs: none` with hash evidence, or configure `tdd.verificationRef`. |
127
133
  | Review blocked | `cclaw internal rewind tdd "review_blocked_by_critical <finding-ids>"`. |
128
134
  | Stale stage after rewind | Redo the marked stage, then `cclaw internal rewind --ack <stage>`. |
129
- | Broken hooks or generated files | `cclaw doctor`, then `npx cclaw-cli sync` if needed. |
135
+ | Broken hooks or generated files | `npx cclaw-cli sync`, then follow the fail-fast error guidance if it still fails. |
130
136
 
131
137
 
132
138
  ## Subagents Without Theater
@@ -182,7 +188,7 @@ npx cclaw-cli # interactive setup or installed status hint
182
188
  npx cclaw-cli init --harnesses=<list> --no-interactive
183
189
  npx cclaw-cli sync # regenerate managed runtime files
184
190
  npx cclaw-cli upgrade # refresh generated files while preserving config
185
- npx cclaw-cli archive # explicit archive/reset; normal closeout uses /cc-next
191
+ npx cclaw-cli archive # explicit archive/reset; normal closeout uses /cc
186
192
  npx cclaw-cli uninstall # remove .cclaw and generated harness shims
187
193
  npx cclaw-cli --version
188
194
  ```
@@ -0,0 +1,2 @@
1
+ import { type StageLintContext } from "./shared.js";
2
+ export declare function lintBrainstormStage(ctx: StageLintContext): Promise<void>;
@@ -0,0 +1,245 @@
1
+ import { sectionBodyByName, validateApproachesTaxonomy, headingLineIndex, meaningfulLineCount, parseShortCircuitStatus, validateCalibratedSelfReview, markdownFieldRegex } from "./shared.js";
2
+ export async function lintBrainstormStage(ctx) {
3
+ const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
4
+ // Brainstorm Iron Law: "NO ARTIFACT IS COMPLETE WITHOUT AN EXPLICITLY
5
+ // APPROVED DIRECTION — SILENCE IS NOT APPROVAL." Previously this was
6
+ // prose-only — nothing failed when the Selected Direction section
7
+ // omitted an approval marker, or when the Approaches table collapsed
8
+ // to a single row (defeating the "2-3 distinct approaches" gate).
9
+ const tierBody = sectionBodyByName(sections, "Approach Tier");
10
+ if (tierBody !== null) {
11
+ // Token vocabulary covers `lite`, `Lightweight`, `Standard`, and
12
+ // `Deep` (case-insensitive). A line that lists ≥2 distinct tokens is
13
+ // the unfilled template placeholder (`Tier: lite | standard | deep`)
14
+ // and must not silently pass; we look for at least one decision line
15
+ // with exactly one token, while ignoring placeholder lines.
16
+ const cleanedLines = tierBody
17
+ .split("\n")
18
+ .map((line) => line.replace(/[*_`]/gu, ""));
19
+ const lineTokenCounts = cleanedLines.map((line) => {
20
+ const tokens = line.match(/\b(?:lite|lightweight|light|standard|deep)\b/giu) ?? [];
21
+ return new Set(tokens.map((token) => token.toLowerCase())).size;
22
+ });
23
+ const hasDecisionLine = lineTokenCounts.some((count) => count === 1);
24
+ const hasPlaceholderLine = lineTokenCounts.some((count) => count >= 2);
25
+ const ok = hasDecisionLine;
26
+ findings.push({
27
+ section: "Approach Tier Classification",
28
+ required: true,
29
+ rule: "Approach Tier must explicitly classify depth as one of `lite` (a.k.a. `Lightweight`), `Standard`, or `Deep`.",
30
+ found: ok,
31
+ details: ok
32
+ ? "Approach Tier includes a single recognized depth token."
33
+ : hasPlaceholderLine
34
+ ? "Approach Tier still lists multiple tier tokens (template placeholder); pick exactly one of `lite`/`Lightweight`, `Standard`, or `Deep`."
35
+ : "Approach Tier is missing a recognized depth token (`lite`/`Lightweight`, `Standard`, or `Deep`)."
36
+ });
37
+ }
38
+ const approachesBody = sectionBodyByName(sections, "Approaches");
39
+ if (approachesBody !== null) {
40
+ const approachesTaxonomy = validateApproachesTaxonomy(approachesBody);
41
+ findings.push({
42
+ section: "Distinct Approaches Enforcement",
43
+ required: true,
44
+ rule: "Approaches section must document at least 2 distinct approaches so the Iron Law comparison is meaningful.",
45
+ found: approachesTaxonomy.rowCount >= 2,
46
+ details: approachesTaxonomy.rowCount >= 2
47
+ ? `Detected ${approachesTaxonomy.rowCount} approach row(s).`
48
+ : `Detected ${approachesTaxonomy.rowCount} approach row(s); at least 2 required.`
49
+ });
50
+ findings.push({
51
+ section: "Approaches Role/Upside Taxonomy",
52
+ required: true,
53
+ rule: "Approaches table must use canonical Role and Upside enum values.",
54
+ found: approachesTaxonomy.roleUpsideOk,
55
+ details: approachesTaxonomy.details
56
+ });
57
+ findings.push({
58
+ section: "Challenger Alternative Enforcement",
59
+ required: true,
60
+ rule: "Approaches must include one challenger option with explicit high/higher upside.",
61
+ found: approachesTaxonomy.challengerOk,
62
+ details: approachesTaxonomy.details
63
+ });
64
+ }
65
+ const reactionIndex = headingLineIndex(raw, "Approach Reaction");
66
+ const directionIndex = headingLineIndex(raw, "Selected Direction");
67
+ if (directionIndex >= 0 && !brainstormShortCircuitActivated) {
68
+ const orderOk = reactionIndex >= 0 && reactionIndex < directionIndex;
69
+ findings.push({
70
+ section: "Approach Reaction Ordering",
71
+ required: true,
72
+ rule: "Approach Reaction must appear before Selected Direction (propose -> react -> recommend).",
73
+ found: orderOk,
74
+ details: orderOk
75
+ ? "Approach Reaction appears before Selected Direction."
76
+ : "Approach Reaction must be present before Selected Direction."
77
+ });
78
+ }
79
+ const directionBody = sectionBodyByName(sections, "Selected Direction");
80
+ if (directionBody !== null) {
81
+ const approvalMarker = /\bapprov(?:ed|al)\b/iu.test(directionBody);
82
+ findings.push({
83
+ section: "Direction Approval Marker",
84
+ required: true,
85
+ rule: "Selected Direction section must state an explicit approval marker (for example `Approval: approved` or `Approved by: user`).",
86
+ found: approvalMarker,
87
+ details: approvalMarker
88
+ ? "Approval marker present in Selected Direction."
89
+ : "No explicit `approved`/`approval` marker found in Selected Direction."
90
+ });
91
+ if (!brainstormShortCircuitActivated) {
92
+ const reactionBody = sectionBodyByName(sections, "Approach Reaction");
93
+ const reactionTrace = /\b(?:reaction|feedback|concern(?:s)?)\b/iu.test(directionBody) ||
94
+ (reactionIndex >= 0 && reactionIndex < directionIndex && meaningfulLineCount(reactionBody ?? "") > 0);
95
+ findings.push({
96
+ section: "Direction Reaction Trace",
97
+ required: true,
98
+ rule: "Selected Direction must be traceable to a prior Approach Reaction section or explicitly reference user reaction/feedback/concerns.",
99
+ found: reactionTrace,
100
+ details: reactionTrace
101
+ ? "Selected Direction is traceable to prior user reaction."
102
+ : "Selected Direction is not traceable to user reaction. Add `## Approach Reaction` before it, or mention the user's reaction/concerns in the rationale."
103
+ });
104
+ // Track-aware handoff: standard track goes to `scope`; medium track
105
+ // goes directly to `spec`; the quick track skips brainstorm entirely.
106
+ // We accept either canonical successor token plus a generic
107
+ // `next-stage` / `handoff` phrase to preserve i18n flexibility.
108
+ const handoffTrace = /(?:`(?:scope|spec)`|\bscope\b|\bspec\b|next[-\s_]stage|next stage|\bhandoff\b|hand[-\s]off)/iu.test(directionBody);
109
+ findings.push({
110
+ section: "Direction Next-Stage Handoff",
111
+ required: true,
112
+ rule: "Selected Direction must record the track-aware next-stage handoff (mention `scope` for standard, `spec` for medium, or include a `Next-stage handoff:` line).",
113
+ found: handoffTrace,
114
+ details: handoffTrace
115
+ ? "Selected Direction names the next-stage handoff."
116
+ : "Selected Direction is missing a next-stage handoff token. Mention `scope` (standard) or `spec` (medium), or add a `Next-stage handoff:` line so downstream stages can trace the contract."
117
+ });
118
+ }
119
+ }
120
+ const shortCircuitBody = brainstormShortCircuitBody;
121
+ if (shortCircuitBody !== null) {
122
+ const statusValue = parseShortCircuitStatus(shortCircuitBody);
123
+ const hasStatus = statusValue.length > 0;
124
+ findings.push({
125
+ section: "Short-Circuit Status",
126
+ required: true,
127
+ rule: "Short-Circuit Decision must include a `Status:` line (`activated` or `bypassed`).",
128
+ found: hasStatus,
129
+ details: hasStatus
130
+ ? `Short-circuit status declared as "${statusValue}".`
131
+ : "Short-Circuit Decision is missing a `Status:` line."
132
+ });
133
+ if (brainstormShortCircuitActivated) {
134
+ const artifactLines = meaningfulLineCount(raw);
135
+ const withinStubLimit = artifactLines <= 30;
136
+ const hasScopeHandoff = /\bscope\b/iu.test(shortCircuitBody);
137
+ findings.push({
138
+ section: "Short-Circuit Stub Size",
139
+ required: true,
140
+ rule: "When short-circuit is activated, brainstorm artifact must remain a <=30 meaningful-line stub.",
141
+ found: withinStubLimit,
142
+ details: withinStubLimit
143
+ ? `Short-circuit stub size within limit (${artifactLines} meaningful lines).`
144
+ : `Short-circuit stub too large (${artifactLines} meaningful lines); expected <= 30.`
145
+ });
146
+ findings.push({
147
+ section: "Short-Circuit Scope Handoff",
148
+ required: true,
149
+ rule: "When short-circuit is activated, the section must explicitly hand off to scope.",
150
+ found: hasScopeHandoff,
151
+ details: hasScopeHandoff
152
+ ? "Short-circuit section includes explicit scope handoff."
153
+ : "Short-circuit section is missing explicit scope handoff guidance."
154
+ });
155
+ }
156
+ }
157
+ const selfReviewBody = sectionBodyByName(sections, "Self-Review Notes");
158
+ if (selfReviewBody !== null) {
159
+ const selfReview = validateCalibratedSelfReview(selfReviewBody);
160
+ findings.push({
161
+ section: "Calibrated Self-Review Format",
162
+ required: true,
163
+ rule: "When Self-Review Notes are present, they must use the calibrated review prompt output shape.",
164
+ found: selfReview.ok,
165
+ details: selfReview.details
166
+ });
167
+ }
168
+ // Universal structural checks (Layer 2.1). Each fires only when the
169
+ // matching section is present so legacy fixtures keep their current
170
+ // shape, while artifacts emitted from the v3 template have to satisfy
171
+ // them. Content is never inspected — only the shape required by the
172
+ // reference patterns (gstack mode, forcing questions, premise list,
173
+ // approach detail cards, anti-sycophancy stamp).
174
+ const modeBody = sectionBodyByName(sections, "Mode Block");
175
+ if (modeBody !== null) {
176
+ const modeTokens = ["STARTUP", "BUILDER", "ENGINEERING", "OPS", "RESEARCH"];
177
+ const modeRegex = markdownFieldRegex("Mode", modeTokens.join("|"), "u");
178
+ const tokenMatches = new Set();
179
+ const lineRegex = new RegExp(modeRegex.source, "gu");
180
+ for (const match of modeBody.matchAll(lineRegex)) {
181
+ const token = (match[0].match(/STARTUP|BUILDER|ENGINEERING|OPS|RESEARCH/u) ?? [""])[0];
182
+ if (token)
183
+ tokenMatches.add(token);
184
+ }
185
+ const placeholderLine = modeBody
186
+ .split("\n")
187
+ .find((line) => /\bMode\b\s*[*_]{0,2}\s*:/iu.test(line) && (line.match(/STARTUP|BUILDER|ENGINEERING|OPS|RESEARCH/giu) ?? []).length >= 2);
188
+ const isPlaceholder = Boolean(placeholderLine);
189
+ const ok = tokenMatches.size === 1 && !isPlaceholder;
190
+ findings.push({
191
+ section: "Mode Block Token",
192
+ required: true,
193
+ rule: "Mode Block must declare exactly one mode token: STARTUP, BUILDER, ENGINEERING, OPS, or RESEARCH.",
194
+ found: ok,
195
+ details: ok
196
+ ? `Recognized mode token detected: ${[...tokenMatches][0] ?? ""}.`
197
+ : isPlaceholder
198
+ ? "Mode Block still lists multiple mode tokens (template placeholder); pick exactly one of STARTUP/BUILDER/ENGINEERING/OPS/RESEARCH."
199
+ : "Mode Block is missing a recognized mode token (STARTUP/BUILDER/ENGINEERING/OPS/RESEARCH)."
200
+ });
201
+ }
202
+ // Approach Detail Cards: structural sub-section under Approaches, one
203
+ // bullet block per approach with the canonical fields.
204
+ const approachCardsRegex = /####\s+APPROACH\s+[A-Z]\b[\s\S]*?(?:^-\s*Summary:[\s\S]*?^-\s*Effort:[\s\S]*?^-\s*Risk:[\s\S]*?^-\s*Pros:[\s\S]*?^-\s*Cons:[\s\S]*?^-\s*Reuses:)/gimu;
205
+ const matches = raw.match(approachCardsRegex);
206
+ const cardCount = matches ? matches.length : 0;
207
+ if (/####\s+APPROACH\s+[A-Z]\b/iu.test(raw) ||
208
+ /^RECOMMENDATION:/imu.test(raw)) {
209
+ findings.push({
210
+ section: "Approach Detail Cards",
211
+ required: true,
212
+ rule: "Approach Detail Cards must include ≥2 `#### APPROACH <letter>` blocks each with Summary/Effort/Risk/Pros/Cons/Reuses.",
213
+ found: cardCount >= 2,
214
+ details: cardCount >= 2
215
+ ? `Detected ${cardCount} valid approach detail card(s).`
216
+ : `Detected ${cardCount} valid approach detail card(s); at least 2 required with all fields present.`
217
+ });
218
+ const recommendationLine = raw.match(/^RECOMMENDATION:\s*(.+)$/imu);
219
+ const hasRecommendation = recommendationLine !== null && recommendationLine[1] !== undefined && recommendationLine[1].trim().length > 0;
220
+ findings.push({
221
+ section: "Approach Recommendation Marker",
222
+ required: true,
223
+ rule: "Approach Detail Cards must conclude with a single `RECOMMENDATION:` line citing the chosen letter and rationale.",
224
+ found: hasRecommendation,
225
+ details: hasRecommendation
226
+ ? "Recommendation marker present."
227
+ : "Missing or empty `RECOMMENDATION:` line after approach detail cards."
228
+ });
229
+ }
230
+ const outsideVoiceBody = sectionBodyByName(sections, "Outside Voice");
231
+ if (outsideVoiceBody !== null) {
232
+ const required = ["source:", "prompt:", "tension:", "resolution:"];
233
+ const missing = required.filter((key) => !new RegExp(`(?:^|\\n)\\s*-?\\s*${key.replace(":", "\\s*:")}`, "iu").test(outsideVoiceBody));
234
+ const optedOut = /\bnot used\b|\bn\/a\b|\bnone\b/iu.test(outsideVoiceBody);
235
+ findings.push({
236
+ section: "Outside Voice Slot Shape",
237
+ required: true,
238
+ rule: "Outside Voice section must either declare opt-out (`not used`/`none`) or include `source:`, `prompt:`, `tension:`, `resolution:`.",
239
+ found: optedOut || missing.length === 0,
240
+ details: optedOut || missing.length === 0
241
+ ? "Outside Voice slot is well-formed."
242
+ : `Outside Voice section is missing field(s): ${missing.join(", ")}.`
243
+ });
244
+ }
245
+ }
@@ -0,0 +1,2 @@
1
+ import { type StageLintContext } from "./shared.js";
2
+ export declare function lintDesignStage(ctx: StageLintContext): Promise<void>;