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
@@ -0,0 +1,323 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { resolveArtifactPath as resolveStageArtifactPath } from "../artifact-paths.js";
4
+ import { exists } from "../fs-utils.js";
5
+ import { CONFIDENCE_FINDING_REGEX_SOURCE } from "../content/skills.js";
6
+ import { extractMarkdownSectionBody, getMarkdownTableRows, meaningfulLineCount, sectionBodyByName, markdownFieldRegex } from "./shared.js";
7
+ const DESIGN_DIAGRAM_REQUIREMENTS = {
8
+ lightweight: [
9
+ {
10
+ section: "Architecture Diagram",
11
+ markers: ["architecture"],
12
+ note: "Architecture diagram is required for all tiers."
13
+ }
14
+ ],
15
+ standard: [
16
+ {
17
+ section: "Architecture Diagram",
18
+ markers: ["architecture"],
19
+ note: "Architecture diagram is required for all tiers."
20
+ },
21
+ {
22
+ section: "Data-Flow Shadow Paths",
23
+ markers: ["data-flow-shadow-paths"],
24
+ note: "Standard+ requires data-flow shadow path coverage."
25
+ },
26
+ {
27
+ section: "Error Flow Diagram",
28
+ markers: ["error-flow"],
29
+ note: "Standard+ requires explicit error-flow rescue mapping."
30
+ }
31
+ ],
32
+ deep: [
33
+ {
34
+ section: "Architecture Diagram",
35
+ markers: ["architecture"],
36
+ note: "Architecture diagram is required for all tiers."
37
+ },
38
+ {
39
+ section: "Data-Flow Shadow Paths",
40
+ markers: ["data-flow-shadow-paths"],
41
+ note: "Standard+ requires data-flow shadow path coverage."
42
+ },
43
+ {
44
+ section: "Error Flow Diagram",
45
+ markers: ["error-flow"],
46
+ note: "Standard+ requires explicit error-flow rescue mapping."
47
+ },
48
+ {
49
+ section: "Deep Diagram Add-on",
50
+ markers: ["state-machine", "rollback-flowchart", "deployment-sequence"],
51
+ note: "Deep tier requires one add-on deep diagram (state machine, rollback flowchart, or deployment sequence)."
52
+ }
53
+ ]
54
+ };
55
+ function normalizeDesignDiagramTier(value) {
56
+ if (!value)
57
+ return null;
58
+ const normalized = value.trim().toLowerCase();
59
+ if (/^(?:lite|light|lightweight)$/u.test(normalized))
60
+ return "lightweight";
61
+ if (/^standard$/u.test(normalized))
62
+ return "standard";
63
+ if (/^deep$/u.test(normalized))
64
+ return "deep";
65
+ return null;
66
+ }
67
+ function parseApproachTierSection(sectionBody) {
68
+ if (!sectionBody)
69
+ return null;
70
+ for (const line of sectionBody.split(/\r?\n/u)) {
71
+ const cleaned = line.replace(/[*_`]/gu, "").trim();
72
+ const directMatch = /(?:^|\b)tier\s*:\s*(lite|lightweight|light|standard|deep)\b/iu.exec(cleaned);
73
+ if (directMatch) {
74
+ const captured = directMatch[1] ?? "";
75
+ const remainder = cleaned.slice(cleaned.toLowerCase().indexOf("tier") + 4);
76
+ const tierTokens = remainder.match(/\b(?:lite|lightweight|light|standard|deep)\b/giu) ?? [];
77
+ const distinct = new Set(tierTokens.map((token) => token.toLowerCase()));
78
+ if (distinct.size >= 2) {
79
+ // Multi-token line is the unfilled template placeholder
80
+ // (`Tier: lite | standard | deep`); treat as no decision.
81
+ continue;
82
+ }
83
+ return normalizeDesignDiagramTier(captured);
84
+ }
85
+ }
86
+ const token = /\b(lite|lightweight|light|standard|deep)\b/iu.exec(sectionBody)?.[1] ?? null;
87
+ return normalizeDesignDiagramTier(token);
88
+ }
89
+ async function resolveDesignDiagramTier(projectRoot, track, designRaw) {
90
+ const fromDesign = parseApproachTierSection(extractMarkdownSectionBody(designRaw, "Approach Tier"));
91
+ if (fromDesign) {
92
+ return { tier: fromDesign, source: "design-artifact:Approach Tier" };
93
+ }
94
+ try {
95
+ const brainstormArtifact = await resolveStageArtifactPath("brainstorm", {
96
+ projectRoot,
97
+ track,
98
+ intent: "read"
99
+ });
100
+ if (await exists(brainstormArtifact.absPath)) {
101
+ const brainstormRaw = await fs.readFile(brainstormArtifact.absPath, "utf8");
102
+ const fromBrainstorm = parseApproachTierSection(extractMarkdownSectionBody(brainstormRaw, "Approach Tier"));
103
+ if (fromBrainstorm) {
104
+ return { tier: fromBrainstorm, source: "brainstorm-artifact:Approach Tier" };
105
+ }
106
+ }
107
+ }
108
+ catch {
109
+ // Ignore read/resolve errors and fall back to default tier.
110
+ }
111
+ return { tier: "standard", source: "default:standard" };
112
+ }
113
+ function normalizeCodebaseInvestigationFileRef(value) {
114
+ const cleaned = value
115
+ .replace(/`/gu, "")
116
+ .replace(/^\s*[-*]\s*/u, "")
117
+ .trim();
118
+ if (!cleaned)
119
+ return null;
120
+ if (/^(?:file|n\/a|none|\(none\)|tbd|\?)$/iu.test(cleaned))
121
+ return null;
122
+ return cleaned;
123
+ }
124
+ function collectCodebaseInvestigationFiles(sectionBody) {
125
+ const refs = [];
126
+ for (const row of getMarkdownTableRows(sectionBody)) {
127
+ const fileCell = normalizeCodebaseInvestigationFileRef(row[0] ?? "");
128
+ if (fileCell)
129
+ refs.push(fileCell);
130
+ }
131
+ return [...new Set(refs)];
132
+ }
133
+ async function runStaleDiagramAudit(projectRoot, artifactPath, artifactRaw, codebaseInvestigationBody) {
134
+ const markerCount = (artifactRaw.match(/<!--\s*diagram:\s*[a-z0-9-]+\s*-->/giu) ?? []).length;
135
+ if (markerCount === 0) {
136
+ return {
137
+ ok: false,
138
+ details: "No diagram markers found in design artifact; stale-diagram baseline cannot be computed."
139
+ };
140
+ }
141
+ let artifactStat;
142
+ try {
143
+ artifactStat = await fs.stat(artifactPath);
144
+ }
145
+ catch {
146
+ return {
147
+ ok: false,
148
+ details: "Cannot stat design artifact to compute diagram marker baseline."
149
+ };
150
+ }
151
+ const refs = collectCodebaseInvestigationFiles(codebaseInvestigationBody);
152
+ if (refs.length === 0) {
153
+ return {
154
+ ok: false,
155
+ details: "Codebase Investigation must list at least one blast-radius file for stale-diagram audit."
156
+ };
157
+ }
158
+ const stale = [];
159
+ const missing = [];
160
+ let scanned = 0;
161
+ for (const ref of refs) {
162
+ const absPath = path.isAbsolute(ref) ? ref : path.join(projectRoot, ref);
163
+ if (!(await exists(absPath))) {
164
+ missing.push(ref);
165
+ continue;
166
+ }
167
+ let fileStat;
168
+ try {
169
+ fileStat = await fs.stat(absPath);
170
+ }
171
+ catch {
172
+ missing.push(ref);
173
+ continue;
174
+ }
175
+ if (!fileStat.isFile())
176
+ continue;
177
+ scanned += 1;
178
+ if (fileStat.mtimeMs > artifactStat.mtimeMs) {
179
+ stale.push(ref);
180
+ }
181
+ }
182
+ if (missing.length > 0) {
183
+ return {
184
+ ok: false,
185
+ details: `Stale Diagram Audit could not read blast-radius file(s): ${missing.join(", ")}.`
186
+ };
187
+ }
188
+ if (scanned === 0) {
189
+ return {
190
+ ok: false,
191
+ details: "Stale Diagram Audit found no readable blast-radius files in Codebase Investigation."
192
+ };
193
+ }
194
+ if (stale.length > 0) {
195
+ return {
196
+ ok: false,
197
+ details: `Stale Diagram Audit flagged stale file(s) newer than diagram baseline: ${stale.join(", ")}.`
198
+ };
199
+ }
200
+ return {
201
+ ok: true,
202
+ details: `Stale Diagram Audit clear: ${scanned} blast-radius file(s) are not newer than diagram baseline.`
203
+ };
204
+ }
205
+ export async function lintDesignStage(ctx) {
206
+ const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
207
+ const tierResolution = await resolveDesignDiagramTier(projectRoot, track, raw);
208
+ const diagramTier = isTrivialOverride
209
+ ? "lightweight"
210
+ : tierResolution.tier;
211
+ const tierSource = isTrivialOverride
212
+ ? `${tierResolution.source}; trivial override forced lightweight`
213
+ : tierResolution.source;
214
+ const hasDiagramMarkers = /<!--\s*diagram:\s*[a-z0-9-]+\s*-->/iu.test(raw);
215
+ const skipDiagramRequirements = isTrivialOverride && !hasDiagramMarkers;
216
+ if (skipDiagramRequirements) {
217
+ findings.push({
218
+ section: "Diagram Requirement: Architecture Diagram",
219
+ required: true,
220
+ rule: "Compact trivial-override slices may omit architecture diagram markers when they intentionally skip diagram work.",
221
+ found: true,
222
+ details: "Diagram requirement skipped: compact trivial-override slice without diagram markers."
223
+ });
224
+ }
225
+ else {
226
+ for (const requirement of DESIGN_DIAGRAM_REQUIREMENTS[diagramTier]) {
227
+ const sectionBody = sectionBodyByName(sections, requirement.section);
228
+ const hasSection = sectionBody !== null;
229
+ const matchedMarker = requirement.markers.find((marker) => {
230
+ const escapedMarker = marker.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
231
+ const markerRegex = new RegExp(`<!--\\s*diagram:\\s*${escapedMarker}\\s*-->`, "iu");
232
+ return sectionBody !== null && markerRegex.test(sectionBody);
233
+ });
234
+ const hasMarker = matchedMarker !== undefined;
235
+ const hasContent = sectionBody !== null && meaningfulLineCount(sectionBody) > 0;
236
+ const found = hasSection && hasMarker && hasContent;
237
+ const markerList = requirement.markers.map((marker) => `<!-- diagram: ${marker} -->`).join(" or ");
238
+ findings.push({
239
+ section: `Diagram Requirement: ${requirement.section}`,
240
+ required: true,
241
+ rule: `Design tier "${diagramTier}" requires "${requirement.section}" with marker ${markerList}. ${requirement.note}`,
242
+ found,
243
+ details: found
244
+ ? `Satisfied (${tierSource}).`
245
+ : !hasSection
246
+ ? `Missing section "${requirement.section}" (${tierSource}).`
247
+ : !hasMarker
248
+ ? `Missing marker (${markerList}) in section "${requirement.section}" (${tierSource}).`
249
+ : `Section "${requirement.section}" has marker but no meaningful content (${tierSource}).`
250
+ });
251
+ }
252
+ }
253
+ if (staleDiagramAuditEnabled) {
254
+ if (isTrivialOverride && !hasDiagramMarkers) {
255
+ findings.push({
256
+ section: "Stale Diagram Drift Check",
257
+ required: true,
258
+ rule: "When stale-diagram audit is enabled, compact trivial-override slices may skip the drift check only if no design diagram markers are present.",
259
+ found: true,
260
+ details: "Stale Diagram Audit skipped: artifact has no diagram markers (compact trivial-override slice)."
261
+ });
262
+ }
263
+ else {
264
+ const codebaseInvestigation = sectionBodyByName(sections, "Codebase Investigation");
265
+ if (codebaseInvestigation === null) {
266
+ findings.push({
267
+ section: "Stale Diagram Drift Check",
268
+ required: true,
269
+ rule: "When stale-diagram audit is enabled, stale diagram audit requires Codebase Investigation blast-radius files.",
270
+ found: false,
271
+ details: "No ## heading matching required section \"Codebase Investigation\"."
272
+ });
273
+ }
274
+ else {
275
+ const staleAudit = await runStaleDiagramAudit(projectRoot, absFile, raw, codebaseInvestigation);
276
+ findings.push({
277
+ section: "Stale Diagram Drift Check",
278
+ required: true,
279
+ rule: "When stale-diagram audit is enabled, blast-radius files must not be newer than current design diagram baseline.",
280
+ found: staleAudit.ok,
281
+ details: staleAudit.details
282
+ });
283
+ }
284
+ }
285
+ }
286
+ // Universal Layer 2.3 structural checks (gstack plan-eng-review). All
287
+ // present-only. Validates regression iron-rule acknowledgment and
288
+ // confidence-calibrated finding format.
289
+ const regressionBody = sectionBodyByName(sections, "Regression Iron Rule");
290
+ if (regressionBody !== null) {
291
+ const ack = markdownFieldRegex("Iron rule acknowledged", "yes|true|y").test(regressionBody);
292
+ findings.push({
293
+ section: "Regression Iron Rule Acknowledgement",
294
+ required: true,
295
+ rule: "Regression Iron Rule section must affirm `Iron rule acknowledged: yes`.",
296
+ found: ack,
297
+ details: ack
298
+ ? "Regression iron rule acknowledged."
299
+ : "Regression Iron Rule is missing explicit `Iron rule acknowledged: yes`."
300
+ });
301
+ }
302
+ const findingsBody = sectionBodyByName(sections, "Calibrated Findings");
303
+ if (findingsBody !== null) {
304
+ const isEmpty = /(^|\n)\s*-\s*None this stage\b/iu.test(findingsBody);
305
+ const findingRegex = new RegExp(CONFIDENCE_FINDING_REGEX_SOURCE, "u");
306
+ const validRows = findingsBody
307
+ .split("\n")
308
+ .filter((line) => /^[-*]\s+\[/u.test(line.trim()))
309
+ .filter((line) => findingRegex.test(line));
310
+ const ok = isEmpty || validRows.length >= 1;
311
+ findings.push({
312
+ section: "Calibrated Finding Format",
313
+ required: true,
314
+ rule: "Calibrated Findings must either declare `None this stage` or contain at least one finding in the form `[P1|P2|P3] (confidence: <n>/10) <path>[:<line>] — <description>`.",
315
+ found: ok,
316
+ details: isEmpty
317
+ ? "No findings recorded for this stage."
318
+ : ok
319
+ ? `Detected ${validRows.length} calibrated finding(s).`
320
+ : "No calibrated findings detected. Use `[P1|P2|P3] (confidence: <n>/10) <repo-path>[:<line>] — <description>`."
321
+ });
322
+ }
323
+ }
@@ -0,0 +1,2 @@
1
+ import { type StageLintContext } from "./shared.js";
2
+ export declare function lintPlanStage(ctx: StageLintContext): Promise<void>;
@@ -0,0 +1,162 @@
1
+ import { headingPresent, sectionBodyByName, collectPatternHits, PLACEHOLDER_PATTERNS, extractDecisionIds, SCOPE_REDUCTION_PATTERNS } from "./shared.js";
2
+ import { resolveArtifactPath as resolveStageArtifactPath } from "../artifact-paths.js";
3
+ import { exists } from "../fs-utils.js";
4
+ import { FORBIDDEN_PLACEHOLDER_TOKENS, CONFIDENCE_FINDING_REGEX_SOURCE } from "../content/skills.js";
5
+ import fs from "node:fs/promises";
6
+ export async function lintPlanStage(ctx) {
7
+ const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
8
+ const strictPlanGuards = parsedFrontmatter.hasFrontmatter ||
9
+ headingPresent(sections, "Plan Quality Scan") ||
10
+ headingPresent(sections, "Locked Decision Coverage");
11
+ const taskListBody = sectionBodyByName(sections, "Task List") ?? raw;
12
+ const placeholderHits = collectPatternHits(taskListBody, PLACEHOLDER_PATTERNS);
13
+ findings.push({
14
+ section: "Plan Quality Scan: Placeholders",
15
+ required: strictPlanGuards,
16
+ rule: "Task List must not contain placeholders (TODO/TBD/FIXME/<fill-in>/<your-*-here>/xxx/ellipsis).",
17
+ found: placeholderHits.length === 0,
18
+ details: placeholderHits.length === 0
19
+ ? "No placeholder tokens detected in Task List."
20
+ : `Detected placeholder token(s) in Task List: ${placeholderHits.join(", ")}.`
21
+ });
22
+ const scopeArtifact = await resolveStageArtifactPath("scope", {
23
+ projectRoot,
24
+ track,
25
+ intent: "read"
26
+ });
27
+ const scopeRaw = (await exists(scopeArtifact.absPath))
28
+ ? await fs.readFile(scopeArtifact.absPath, "utf8")
29
+ : "";
30
+ const scopeDecisionIds = extractDecisionIds(scopeRaw);
31
+ const missingDecisionRefs = scopeDecisionIds.filter((id) => !raw.includes(id));
32
+ findings.push({
33
+ section: "Locked Decision Traceability",
34
+ required: strictPlanGuards && scopeDecisionIds.length > 0,
35
+ rule: "Every locked decision ID (D-XX) in scope must be referenced in plan.",
36
+ found: missingDecisionRefs.length === 0,
37
+ details: scopeDecisionIds.length === 0
38
+ ? "No D-XX IDs found in scope artifact; traceability check skipped."
39
+ : missingDecisionRefs.length === 0
40
+ ? `All ${scopeDecisionIds.length} scope decision IDs are referenced in plan.`
41
+ : `Missing scope decision reference(s) in plan: ${missingDecisionRefs.join(", ")}.`
42
+ });
43
+ const reductionHits = collectPatternHits(taskListBody, SCOPE_REDUCTION_PATTERNS);
44
+ findings.push({
45
+ section: "Plan Quality Scan: Scope Reduction",
46
+ required: strictPlanGuards && scopeDecisionIds.length > 0,
47
+ rule: "Task List must not include scope-reduction language when locked decisions exist.",
48
+ found: reductionHits.length === 0,
49
+ details: scopeDecisionIds.length === 0
50
+ ? "No locked decisions found in scope artifact; scope-reduction scan is advisory."
51
+ : reductionHits.length === 0
52
+ ? "No scope-reduction phrases detected in Task List."
53
+ : `Detected scope-reduction phrase(s) in Task List: ${reductionHits.join(", ")}.`
54
+ });
55
+ // Universal Layer 2.5 structural checks (superpowers writing-plans + ce-plan).
56
+ // Plan-wide placeholder scan (broader than Task List) using the
57
+ // FORBIDDEN_PLACEHOLDER_TOKENS list shared with the cross-cutting block.
58
+ const planHeaderBody = sectionBodyByName(sections, "Plan Header");
59
+ if (planHeaderBody !== null) {
60
+ const required = ["Goal:", "Architecture:", "Tech Stack:"];
61
+ const missing = required.filter((token) => !new RegExp(token.replace(":", "\\s*:"), "iu").test(planHeaderBody));
62
+ findings.push({
63
+ section: "Plan Header Coverage",
64
+ required: true,
65
+ rule: "Plan Header must include Goal, Architecture, and Tech Stack lines.",
66
+ found: missing.length === 0,
67
+ details: missing.length === 0
68
+ ? "Plan Header covers Goal/Architecture/Tech Stack."
69
+ : `Plan Header is missing field(s): ${missing.join(", ")}.`
70
+ });
71
+ }
72
+ const unitBlocks = raw.match(/###\s+Implementation Unit\s+U-\d+/giu) ?? [];
73
+ if (unitBlocks.length > 0) {
74
+ const requiredKeys = ["Goal:", "Files", "Approach:", "Test scenarios:", "Verification:"];
75
+ const blockBodies = raw.split(/(?=###\s+Implementation Unit\s+U-\d+)/iu).slice(1);
76
+ const validBlocks = blockBodies.filter((block) => requiredKeys.every((key) => new RegExp(key.replace(":", "\\s*:"), "iu").test(block)));
77
+ findings.push({
78
+ section: "Implementation Unit Shape",
79
+ required: true,
80
+ rule: "Each `### Implementation Unit U-<n>` must include Goal, Files, Approach, Test scenarios, Verification.",
81
+ found: validBlocks.length === unitBlocks.length,
82
+ details: validBlocks.length === unitBlocks.length
83
+ ? `All ${unitBlocks.length} implementation unit(s) include the required fields.`
84
+ : `${unitBlocks.length - validBlocks.length} implementation unit(s) are missing required fields.`
85
+ });
86
+ }
87
+ const allPlaceholderTokens = FORBIDDEN_PLACEHOLDER_TOKENS.map((token) => token.toLowerCase());
88
+ const lowerRaw = raw.toLowerCase();
89
+ const planWidePlaceholderHits = allPlaceholderTokens.filter((token) => lowerRaw.includes(token));
90
+ // Strip the "## NO PLACEHOLDERS Rule" section (which lists tokens) and
91
+ // any acknowledgement text from the scan to avoid false positives where
92
+ // the plan deliberately references the rule by name.
93
+ const placeholderRuleSection = sectionBodyByName(sections, "NO PLACEHOLDERS Rule");
94
+ const ruleScanBody = (placeholderRuleSection ?? "").toLowerCase();
95
+ const ruleAcceptedHits = ruleScanBody.length > 0
96
+ ? allPlaceholderTokens.filter((token) => ruleScanBody.includes(token))
97
+ : [];
98
+ const filteredPlanHits = planWidePlaceholderHits.filter((token) => {
99
+ // If the only occurrence is in the rule section, ignore it.
100
+ if (!ruleAcceptedHits.includes(token))
101
+ return true;
102
+ const occurrencesElsewhere = lowerRaw.split(token).length - 1
103
+ - (ruleScanBody.split(token).length - 1);
104
+ return occurrencesElsewhere > 0;
105
+ });
106
+ findings.push({
107
+ section: "Plan-wide Placeholder Scan",
108
+ required: false,
109
+ rule: "Plan should not contain forbidden placeholder tokens outside the NO PLACEHOLDERS rule section.",
110
+ found: filteredPlanHits.length === 0,
111
+ details: filteredPlanHits.length === 0
112
+ ? "No forbidden placeholder tokens detected outside the rule section."
113
+ : `Detected forbidden token(s) elsewhere in plan: ${filteredPlanHits.join(", ")}.`
114
+ });
115
+ const handoffBody = sectionBodyByName(sections, "Execution Handoff");
116
+ if (handoffBody !== null) {
117
+ const ok = /(subagent-driven|inline executor)/iu.test(handoffBody);
118
+ findings.push({
119
+ section: "Execution Handoff Posture",
120
+ required: true,
121
+ rule: "Execution Handoff must declare a posture (Subagent-Driven or Inline executor).",
122
+ found: ok,
123
+ details: ok
124
+ ? "Execution Handoff posture declared."
125
+ : "Execution Handoff is missing a posture declaration (Subagent-Driven or Inline executor)."
126
+ });
127
+ }
128
+ const planCalibratedBody = sectionBodyByName(sections, "Calibrated Findings");
129
+ if (planCalibratedBody !== null) {
130
+ const isEmpty = /none this stage|none\b/iu.test(planCalibratedBody);
131
+ const findingRegex = new RegExp(CONFIDENCE_FINDING_REGEX_SOURCE, "iu");
132
+ const validRows = planCalibratedBody
133
+ .split("\n")
134
+ .filter((line) => /^[-*]\s+\[/u.test(line.trim()))
135
+ .filter((line) => findingRegex.test(line));
136
+ const ok = isEmpty || validRows.length >= 1;
137
+ findings.push({
138
+ section: "Plan Calibrated Finding Format",
139
+ required: false,
140
+ rule: "Calibrated Findings should either declare `None this stage` or include at least one line in `[P1|P2|P3] (confidence: <n>/10) <path>[:<line>] — <description>` format.",
141
+ found: ok,
142
+ details: isEmpty
143
+ ? "No calibrated findings recorded for this plan stage."
144
+ : ok
145
+ ? `Detected ${validRows.length} calibrated plan finding(s).`
146
+ : "No calibrated findings detected in canonical format."
147
+ });
148
+ }
149
+ const regressionIronBody = sectionBodyByName(sections, "Regression Iron Rule");
150
+ if (regressionIronBody !== null) {
151
+ const acknowledged = /iron\s+rule\s+acknowledged\s*:\s*yes\b/iu.test(regressionIronBody);
152
+ findings.push({
153
+ section: "Plan Regression Iron Rule Acknowledgement",
154
+ required: false,
155
+ rule: "Regression Iron Rule should include `Iron rule acknowledged: yes`.",
156
+ found: acknowledged,
157
+ details: acknowledged
158
+ ? "Regression Iron Rule is explicitly acknowledged."
159
+ : "Regression Iron Rule section is present but missing `Iron rule acknowledged: yes`."
160
+ });
161
+ }
162
+ }
@@ -0,0 +1,24 @@
1
+ export declare function validateReviewArmy(projectRoot: string): Promise<{
2
+ valid: boolean;
3
+ errors: string[];
4
+ }>;
5
+ export interface ReviewVerdictConsistencyResult {
6
+ ok: boolean;
7
+ errors: string[];
8
+ finalVerdict: "APPROVED" | "APPROVED_WITH_CONCERNS" | "BLOCKED" | "UNKNOWN";
9
+ openCriticalCount: number;
10
+ shipBlockerCount: number;
11
+ }
12
+ export interface ReviewSecurityNoChangeAttestationResult {
13
+ ok: boolean;
14
+ errors: string[];
15
+ hasSecurityFinding: boolean;
16
+ hasNoChangeAttestation: boolean;
17
+ }
18
+ /**
19
+ * Ensure the narrative verdict in 07-review.md is consistent with the
20
+ * structured review-army reconciliation. A review cannot declare
21
+ * APPROVED while open Critical findings or shipBlockers remain.
22
+ */
23
+ export declare function checkReviewVerdictConsistency(projectRoot: string): Promise<ReviewVerdictConsistencyResult>;
24
+ export declare function checkReviewSecurityNoChangeAttestation(projectRoot: string): Promise<ReviewSecurityNoChangeAttestationResult>;