cclaw-cli 0.48.31 → 0.48.32

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.
@@ -47,13 +47,18 @@ This is the **recommended way to start** working with cclaw. Use \`/cc-next\` fo
47
47
 
48
48
  Record the chosen class in \`.cclaw/artifacts/00-idea.md\` on the \`Class:\` line. Do NOT silently treat a non-software task as software.
49
49
 
50
- 2. **Phase 1Origin-document discovery.** Before asking the user for context, scan for existing requirements/plan artifacts and merge them into initial context:
50
+ 2. **Phase 0.5Seed shelf recall.** Before routing, scan \`${RUNTIME_ROOT}/seeds/SEED-*.md\` and match each seed's \`trigger_when\` tokens against the prompt text (substring match is enough). If any match:
51
+ - Surface up to 3 matches (file + title + one-line action) as \`Seed recalls\`.
52
+ - Ask whether to apply now, keep as reference, or ignore for this run.
53
+ - If applied/reference, append selected seeds to \`00-idea.md\` under \`Discovered context\` so downstream stages keep the trace.
54
+
55
+ 3. **Phase 1 — Origin-document discovery.** Before asking the user for context, scan for existing requirements/plan artifacts and merge them into initial context:
51
56
  - \`.cclaw/artifacts/00-idea.md\` if it already exists (resumed flow).
52
57
  - Common origin locations: \`docs/prd/**\`, \`docs/rfcs/**\`, \`docs/adr/**\`, \`docs/design/**\`, \`specs/**\`, \`prd/**\`, \`rfc/**\`, \`design/**\`, root-level \`PRD.md\` / \`SPEC.md\` / \`DESIGN.md\` / \`REQUIREMENTS.md\` / \`ROADMAP.md\`.
53
58
  - Summarize each discovered doc in \`00-idea.md\` under a \`Discovered context\` section with path + 1-line summary.
54
59
  - If an origin doc contradicts the prompt, surface the conflict to the user before routing.
55
60
 
56
- 3. **Phase 2 — Tech-stack + version detection.** Sniff the repo for stack + language versions and record under \`Stack:\`:
61
+ 4. **Phase 2 — Tech-stack + version detection.** Sniff the repo for stack + language versions and record under \`Stack:\`:
57
62
  - Node: \`package.json\` \`engines\` / \`volta\` / \`packageManager\` / \`devDependencies\`.
58
63
  - Python: \`pyproject.toml\` / \`requirements*.txt\` / \`.python-version\`.
59
64
  - Go: \`go.mod\` (module + Go version).
@@ -63,9 +68,9 @@ This is the **recommended way to start** working with cclaw. Use \`/cc-next\` fo
63
68
  - CI: \`.github/workflows\`, \`.gitlab-ci.yml\`.
64
69
  Skip detection quietly if no markers are found — do NOT invent a stack.
65
70
 
66
- 4. Read \`${flowPath}\`.
67
- 5. If flow already has completed stages, warn the user that starting a new tracked flow will reset progress. Ask for confirmation before proceeding.
68
- 6. **Track heuristic** — classify the idea text and **recommend** a track (the user can override before any state mutation):
71
+ 5. Read \`${flowPath}\`.
72
+ 6. If flow already has completed stages, warn the user that starting a new tracked flow will reset progress. Ask for confirmation before proceeding.
73
+ 7. **Track heuristic** — classify the idea text and **recommend** a track (the user can override before any state mutation):
69
74
  - First, load \`${RUNTIME_ROOT}/config.yaml\`. If \`trackHeuristics\` is defined, apply those per-track vocabulary hints (\`fallback\`, \`tracks.<id>.{triggers,veto}\`) on top of the built-in defaults. Evaluation order is always \`standard -> medium -> quick\` (narrow-to-broad).
70
75
  - **quick** (\`spec → tdd → review → ship\`) — single-purpose work where the spec is essentially already known.
71
76
  Triggers (case-insensitive substring or close variant): \`bug\`, \`bugfix\`, \`fix\`, \`hotfix\`, \`patch\`, \`typo\`, \`regression\`, \`copy change\`, \`rename\`, \`bump\`, \`upgrade dep\`, \`config tweak\`, \`docs only\`, \`comment\`, \`lint\`, \`format\`, \`small\`, \`tiny\`, \`one-liner\`, \`revert\`.
@@ -74,17 +79,17 @@ This is the **recommended way to start** working with cclaw. Use \`/cc-next\` fo
74
79
  - **standard** (full 8 stages — default fallback) — anything that introduces a new capability with architecture uncertainty, touches many modules, or has unclear scope.
75
80
  Triggers: \`new feature\`, \`refactor\`, \`migration\`, \`platform\`, \`architecture\`, \`schema\`, \`integrate\`, \`workflow\`, \`onboarding\`, or any prompt that does not match quick/medium confidently.
76
81
  - When triggers conflict, prefer **standard** over **medium**, and **medium** over **quick**.
77
- 7. Present the recommendation as a single decision with explicit options:
82
+ 8. Present the recommendation as a single decision with explicit options:
78
83
  > \`Recommended track: <quick|medium|standard>\` because \`<one-line reason citing matched triggers>\`.
79
84
  > Override? (A) keep \`<recommended>\` (B) switch track (C) cancel.
80
85
  If the harness's native ask tool is available (\`AskUserQuestion\` / \`AskQuestion\` / \`question\` / \`request_user_input\`), send exactly ONE question; on schema error, fall back to a plain-text lettered list.
81
- 8. Persist the chosen track to \`${flowPath}\` (\`track\` field). Compute \`skippedStages\` from the track and write that too. Use the **first stage of the chosen track** as \`currentStage\` (quick → \`spec\`, medium/standard → \`brainstorm\`, trivial fast-path → \`design\` or \`spec\` per Phase 0).
82
- 9. Write the prompt to \`.cclaw/artifacts/00-idea.md\` with the following header lines: \`Class:\` (from Phase 0), \`Track:\` (chosen track + matched heuristic), \`Stack:\` (from Phase 2 detection, or \`unknown\`), and a \`Discovered context\` section if Phase 1 found origin docs.
83
- 10. Load the **first-stage skill for the chosen track** and its command file:
86
+ 9. Persist the chosen track to \`${flowPath}\` (\`track\` field). Compute \`skippedStages\` from the track and write that too. Use the **first stage of the chosen track** as \`currentStage\` (quick → \`spec\`, medium/standard → \`brainstorm\`, trivial fast-path → \`design\` or \`spec\` per Phase 0).
87
+ 10. Write the prompt to \`.cclaw/artifacts/00-idea.md\` with the following header lines: \`Class:\` (from Phase 0), \`Track:\` (chosen track + matched heuristic), \`Stack:\` (from Phase 2 detection, or \`unknown\`), and a \`Discovered context\` section if Phase 1/seed recall found references.
88
+ 11. Load the **first-stage skill for the chosen track** and its command file:
84
89
  - quick → \`.cclaw/skills/specification-authoring/SKILL.md\` + \`.cclaw/commands/spec.md\`
85
90
  - medium/standard → \`.cclaw/skills/brainstorming/SKILL.md\` + \`.cclaw/commands/brainstorm.md\`
86
91
  - trivial fast-path → design or spec skill per Phase 0 decision.
87
- 11. Execute that stage with the prompt + Phase 1/Phase 2 context as initial input.
92
+ 12. Execute that stage with the prompt + Phase 1/Phase 2 + seed context as initial input.
88
93
 
89
94
  ### Reclassification on discovery
90
95
 
@@ -152,14 +157,15 @@ Do **not** silently discard an existing flow when the user provides a prompt. If
152
157
  ### Path A: \`/cc <prompt>\`
153
158
 
154
159
  1. **Task classification (Phase 0).** Decide whether the prompt is \`software-standard\`, \`software-trivial\`, \`software-bugfix\`, \`pure-question\`, or \`non-software\`. Non-software and pure-question exit immediately — answer directly, do not open a stage.
155
- 2. **Origin-document discovery (Phase 1).** Scan for \`docs/prd/**\`, \`docs/rfcs/**\`, \`docs/adr/**\`, \`docs/design/**\`, \`specs/**\`, root-level \`PRD.md\` / \`SPEC.md\` / \`DESIGN.md\` / \`REQUIREMENTS.md\`. Summarize any hits in \`00-idea.md\` under \`Discovered context\`. Surface conflicts with the prompt before routing.
156
- 3. **Stack detection (Phase 2).** Inspect \`package.json\` engines, \`pyproject.toml\`, \`go.mod\`, \`Cargo.toml\`, \`pom.xml\`, \`build.gradle*\`, \`Dockerfile\`, \`docker-compose*.yml\`, and CI configs. Record stack + versions on the \`Stack:\` line. Do not invent stack details.
157
- 4. Read \`${flowPath}\`.
158
- 5. If \`completedStages\` is non-empty:
160
+ 2. **Seed shelf recall (Phase 0.5).** Scan \`${RUNTIME_ROOT}/seeds/SEED-*.md\` and match \`trigger_when\` tokens against the prompt text. Surface up to 3 matching seeds with file/title/action and ask whether to apply or ignore. When applied, add them to \`00-idea.md\` under \`Discovered context\`.
161
+ 3. **Origin-document discovery (Phase 1).** Scan for \`docs/prd/**\`, \`docs/rfcs/**\`, \`docs/adr/**\`, \`docs/design/**\`, \`specs/**\`, root-level \`PRD.md\` / \`SPEC.md\` / \`DESIGN.md\` / \`REQUIREMENTS.md\`. Summarize any hits in \`00-idea.md\` under \`Discovered context\`. Surface conflicts with the prompt before routing.
162
+ 4. **Stack detection (Phase 2).** Inspect \`package.json\` engines, \`pyproject.toml\`, \`go.mod\`, \`Cargo.toml\`, \`pom.xml\`, \`build.gradle*\`, \`Dockerfile\`, \`docker-compose*.yml\`, and CI configs. Record stack + versions on the \`Stack:\` line. Do not invent stack details.
163
+ 5. Read \`${flowPath}\`.
164
+ 6. If \`completedStages\` is non-empty:
159
165
  - Inform: "You have an active flow at stage **{currentStage}** with {N} completed stages. Starting a new tracked flow will reset progress."
160
166
  - Ask: "Continue with reset? (A) Yes, start fresh (B) No, resume current flow"
161
167
  - If (B) → switch to Path B behavior.
162
- 6. **Classify the idea** using the heuristic below and present a single track recommendation. Wait for explicit confirmation or override before mutating any state.
168
+ 7. **Classify the idea** using the heuristic below and present a single track recommendation. Wait for explicit confirmation or override before mutating any state.
163
169
  - If \`${RUNTIME_ROOT}/config.yaml\` defines \`trackHeuristics\`, apply those vocabulary hints (\`fallback\`, \`tracks.<id>.{triggers,veto}\`) on top of built-in defaults. Evaluation order is fixed: \`standard -> medium -> quick\`. (Honest note: this is advisory prose; the LLM applies it, not a Node-level router.)
164
170
 
165
171
  **Track heuristic** (lowercase substring match against the user prompt):
@@ -172,9 +178,9 @@ Do **not** silently discard an existing flow when the user provides a prompt. If
172
178
 
173
179
  - On conflict, prefer \`standard\` over \`medium\`, and \`medium\` over \`quick\`.
174
180
  - Always state the recommendation as a one-line reason citing matched triggers.
175
- 7. Persist the chosen track in \`${flowPath}\` (\`track\` + \`skippedStages\`). Set \`currentStage\` to the first stage of the chosen track (\`quick\` → \`spec\`, \`medium\`/ \`standard\` → \`brainstorm\`, trivial fast-path → \`design\` or \`spec\`). Reset gate catalog.
176
- 8. Write \`${RUNTIME_ROOT}/artifacts/00-idea.md\` with the user's prompt plus header lines: \`Class:\`, \`Track:\`, \`Stack:\`, and a \`Discovered context\` section from Phase 1.
177
- 9. Load and execute the **first stage skill of the chosen track** (\`brainstorming\` for medium/standard, \`specification-authoring\` for quick) plus its matching command file.
181
+ 8. Persist the chosen track in \`${flowPath}\` (\`track\` + \`skippedStages\`). Set \`currentStage\` to the first stage of the chosen track (\`quick\` → \`spec\`, \`medium\`/ \`standard\` → \`brainstorm\`, trivial fast-path → \`design\` or \`spec\`). Reset gate catalog.
182
+ 9. Write \`${RUNTIME_ROOT}/artifacts/00-idea.md\` with the user's prompt plus header lines: \`Class:\`, \`Track:\`, \`Stack:\`, and a \`Discovered context\` section from Phase 0.5/1.
183
+ 10. Load and execute the **first stage skill of the chosen track** (\`brainstorming\` for medium/standard, \`specification-authoring\` for quick) plus its matching command file.
178
184
 
179
185
  ### Reclassification on discovery
180
186
 
@@ -28,17 +28,36 @@ inputs_hash: sha256:pending
28
28
  |---|---|---|---|
29
29
  | 1 | | | |
30
30
 
31
+ ## Approach Tier
32
+ - Tier: Lightweight | Standard | Deep
33
+ - Why this tier:
34
+
35
+ ## Short-Circuit Decision
36
+ - Status: bypassed
37
+ - Why:
38
+ - Scope handoff:
39
+
31
40
  ## Approaches
32
- | Approach | Architecture | Trade-offs | Recommendation |
33
- |---|---|---|---|
34
- | A | | | |
35
- | B | | | |
41
+ | Approach | Role | Architecture | Trade-offs | Recommendation |
42
+ |---|---|---|---|---|
43
+ | A | baseline | | | |
44
+ | B | challenger: higher-upside | | | |
45
+
46
+ ## Approach Reaction
47
+ - Closest option:
48
+ - Concerns:
49
+ - What changed after reaction:
36
50
 
37
51
  ## Selected Direction
38
52
  - **Approach:**
39
53
  - **Rationale:**
40
54
  - **Approval:** pending
41
55
 
56
+ ## Seed Shelf Candidates (optional)
57
+ | Seed file | Trigger when | Suggested action | Status (planted/deferred/ignored) |
58
+ |---|---|---|---|
59
+ | .cclaw/seeds/SEED-YYYY-MM-DD-<slug>.md | | | |
60
+
42
61
  ## Design
43
62
  - **Architecture:**
44
63
  - **Key components:**
@@ -62,6 +81,14 @@ inputs_hash: sha256:pending
62
81
 
63
82
  # Scope Artifact
64
83
 
84
+ ## Pre-Scope System Audit
85
+ | Check | Command | Findings |
86
+ |---|---|---|
87
+ | Recent commits | \`git log -30 --oneline\` | |
88
+ | Current diff | \`git diff --stat\` | |
89
+ | Stash state | \`git stash list\` | |
90
+ | Debt markers | \`rg -n "TODO|FIXME|XXX|HACK"\` | |
91
+
65
92
  ## Prime Directives
66
93
  - Zero silent failures:
67
94
  - Every error has a name:
@@ -138,11 +165,28 @@ inputs_hash: sha256:pending
138
165
  |---|---|
139
166
  | | |
140
167
 
168
+ ## Seed Shelf Candidates (optional)
169
+ | Seed file | Trigger when | Suggested action | Status (planted/deferred/ignored) |
170
+ |---|---|---|---|
171
+ | .cclaw/seeds/SEED-YYYY-MM-DD-<slug>.md | | | |
172
+
141
173
  ## Error & Rescue Registry
142
174
  | Capability | Failure mode | Detection | Fallback |
143
175
  |---|---|---|---|
144
176
  | | | | |
145
177
 
178
+ ## Outside Voice Findings
179
+ | ID | Dimension | Finding | Disposition | Rationale |
180
+ |---|---|---|---|---|
181
+ | F-1 | premise_fit | | accept/reject/defer | |
182
+
183
+ ## Spec Review Loop
184
+ | Iteration | Quality Score | Findings | Stop decision |
185
+ |---|---|---|---|
186
+ | 1 | 0.00 | 0 | continue/stop |
187
+ - Stop reason:
188
+ - Unresolved concerns:
189
+
146
190
  ## Completion Dashboard
147
191
  - Checklist findings:
148
192
  - Resolved decisions count:
@@ -236,21 +280,55 @@ inputs_hash: sha256:pending
236
280
 
237
281
  ## Architecture Diagram
238
282
 
283
+ <!-- diagram: architecture -->
284
+
239
285
  \`\`\`
240
286
  (ASCII, Mermaid, or tool-generated diagram showing component boundaries and data flow direction)
241
287
  \`\`\`
242
288
 
243
289
  ## Data-Flow Shadow Paths
290
+ <!-- diagram: data-flow-shadow-paths -->
244
291
  | Path | Trigger | Fallback/Degrade behavior |
245
292
  |---|---|---|
246
293
  | | | |
247
294
 
248
295
  ## Error Flow Diagram
249
296
 
297
+ <!-- diagram: error-flow -->
298
+
250
299
  \`\`\`
251
300
  (failure detection -> rescue action -> user-visible outcome)
252
301
  \`\`\`
253
302
 
303
+ ## State Machine Diagram
304
+
305
+ <!-- diagram: state-machine -->
306
+
307
+ \`\`\`
308
+ (state transitions for the critical flow lifecycle)
309
+ \`\`\`
310
+
311
+ ## Rollback Flowchart
312
+
313
+ <!-- diagram: rollback-flowchart -->
314
+
315
+ \`\`\`
316
+ (trigger -> rollback actions -> verification)
317
+ \`\`\`
318
+
319
+ ## Deployment Sequence Diagram
320
+
321
+ <!-- diagram: deployment-sequence -->
322
+
323
+ \`\`\`
324
+ (rollout order, guard checks, and verification sequence)
325
+ \`\`\`
326
+
327
+ ## Stale Diagram Audit
328
+ | File | Last modified | Diagram marker baseline | Status | Notes |
329
+ |---|---|---|---|---|
330
+ | | | | clear/stale | |
331
+
254
332
  ## What Already Exists
255
333
  | Sub-problem | Existing code/library | Layer | Reuse decision |
256
334
  |---|---|---|---|
@@ -262,6 +340,15 @@ inputs_hash: sha256:pending
262
340
  - Upstream error path:
263
341
  - Timeout/downstream path:
264
342
 
343
+ ### Interaction Edge Case Matrix
344
+ | Edge case | Handled? | Design response | Deferred item (if not handled) |
345
+ |---|---|---|---|
346
+ | double-click | yes/no | | None / D-XX |
347
+ | nav-away-mid-request | yes/no | | None / D-XX |
348
+ | 10K-result dataset | yes/no | | None / D-XX |
349
+ | background-job abandonment | yes/no | | None / D-XX |
350
+ | zombie connection | yes/no | | None / D-XX |
351
+
265
352
  ## Security & Threat Model
266
353
  | Boundary | Threat | Mitigation | Owner |
267
354
  |---|---|---|---|
@@ -292,6 +379,18 @@ inputs_hash: sha256:pending
292
379
  |---|---|---|
293
380
  | | | |
294
381
 
382
+ ## Outside Voice Findings
383
+ | ID | Dimension | Finding | Disposition | Rationale |
384
+ |---|---|---|---|---|
385
+ | F-1 | architecture_fit | | accept/reject/defer | |
386
+
387
+ ## Spec Review Loop
388
+ | Iteration | Quality Score | Findings | Stop decision |
389
+ |---|---|---|---|
390
+ | 1 | 0.00 | 0 | continue/stop |
391
+ - Stop reason:
392
+ - Unresolved concerns:
393
+
295
394
  ## NOT in scope
296
395
  -
297
396
 
@@ -314,6 +413,11 @@ inputs_hash: sha256:pending
314
413
  |---|---|---|---|
315
414
  | | | | |
316
415
 
416
+ ## Seed Shelf Candidates (optional)
417
+ | Seed file | Trigger when | Suggested action | Status (planted/deferred/ignored) |
418
+ |---|---|---|---|
419
+ | .cclaw/seeds/SEED-YYYY-MM-DD-<slug>.md | | | |
420
+
317
421
  ## Completion Dashboard
318
422
  | Review Section | Status | Issues |
319
423
  |---|---|---|
@@ -19,6 +19,11 @@ import { runEnvelopeValidateCommand } from "./envelope-validate.js";
19
19
  import { runKnowledgeDigestCommand } from "./knowledge-digest.js";
20
20
  import { runTddLoopStatusCommand } from "./tdd-loop-status.js";
21
21
  import { runTddRedEvidenceCommand } from "./tdd-red-evidence.js";
22
+ import { extractReviewLoopEnvelopeFromArtifact } from "../content/review-loop.js";
23
+ const AUTO_REVIEW_LOOP_GATE_BY_STAGE = {
24
+ scope: "scope_user_approved",
25
+ design: "design_architecture_locked"
26
+ };
22
27
  function unique(values) {
23
28
  return [...new Set(values)];
24
29
  }
@@ -55,6 +60,111 @@ const SHA_WITH_LABEL_PATTERN = /\b(?:sha|commit)(?:\s*[:=]|\s+)\s*[0-9a-f]{7,40}
55
60
  const PASS_STATUS_PATTERN = /\b(?:pass|passed|green|ok)\b/iu;
56
61
  const SHIP_FINALIZATION_MODE_PATTERN = new RegExp(`\\b(?:${SHIP_FINALIZATION_MODES.join("|")})\\b`, "u");
57
62
  const SHIP_FINALIZATION_MODE_HINT = SHIP_FINALIZATION_MODES.join(", ");
63
+ const REVIEW_LOOP_STOP_REASONS = new Set([
64
+ "quality_threshold_met",
65
+ "max_iterations_reached",
66
+ "user_opt_out"
67
+ ]);
68
+ function asRecord(value) {
69
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
70
+ return null;
71
+ }
72
+ return value;
73
+ }
74
+ function pickReviewLoopEnvelope(value) {
75
+ const direct = asRecord(value);
76
+ if (!direct)
77
+ return null;
78
+ if (direct.type === "review-loop")
79
+ return direct;
80
+ const payload = asRecord(direct.payload);
81
+ if (payload?.type === "review-loop")
82
+ return payload;
83
+ const nested = asRecord(direct.reviewLoop);
84
+ if (nested?.type === "review-loop")
85
+ return nested;
86
+ return null;
87
+ }
88
+ function validateReviewLoopGateEvidence(stage, evidence) {
89
+ let parsed;
90
+ try {
91
+ parsed = JSON.parse(evidence);
92
+ }
93
+ catch {
94
+ return "must be JSON containing a review-loop envelope (`type: \"review-loop\"`) in top-level, `payload`, or `reviewLoop`.";
95
+ }
96
+ const envelope = pickReviewLoopEnvelope(parsed);
97
+ if (!envelope) {
98
+ return "must include a review-loop envelope (`type: \"review-loop\"`) in top-level, `payload`, or `reviewLoop`.";
99
+ }
100
+ if (envelope.stage !== stage) {
101
+ return `review-loop envelope stage must be "${stage}".`;
102
+ }
103
+ const targetScore = envelope.targetScore;
104
+ if (typeof targetScore !== "number" || Number.isNaN(targetScore) || targetScore < 0 || targetScore > 1) {
105
+ return "review-loop targetScore must be a number between 0 and 1.";
106
+ }
107
+ const maxIterations = envelope.maxIterations;
108
+ if (typeof maxIterations !== "number" ||
109
+ Number.isNaN(maxIterations) ||
110
+ !Number.isInteger(maxIterations) ||
111
+ maxIterations < 1) {
112
+ return "review-loop maxIterations must be an integer >= 1.";
113
+ }
114
+ if (typeof envelope.stopReason !== "string" || !REVIEW_LOOP_STOP_REASONS.has(envelope.stopReason)) {
115
+ return "review-loop stopReason must be one of quality_threshold_met, max_iterations_reached, user_opt_out.";
116
+ }
117
+ const rows = envelope.iterations;
118
+ if (!Array.isArray(rows) || rows.length === 0) {
119
+ return "review-loop iterations must be a non-empty array.";
120
+ }
121
+ if (rows.length > maxIterations) {
122
+ return "review-loop iterations count cannot exceed maxIterations.";
123
+ }
124
+ let prevScore = -Infinity;
125
+ let reachedTarget = false;
126
+ for (let index = 0; index < rows.length; index++) {
127
+ const row = asRecord(rows[index]);
128
+ if (!row) {
129
+ return `review-loop iterations[${index}] must be an object.`;
130
+ }
131
+ const iteration = row.iteration;
132
+ const qualityScore = row.qualityScore;
133
+ const findingsCount = row.findingsCount;
134
+ if (typeof iteration !== "number" ||
135
+ Number.isNaN(iteration) ||
136
+ !Number.isInteger(iteration) ||
137
+ iteration < 1) {
138
+ return `review-loop iterations[${index}].iteration must be an integer >= 1.`;
139
+ }
140
+ if (typeof qualityScore !== "number" ||
141
+ Number.isNaN(qualityScore) ||
142
+ qualityScore < 0 ||
143
+ qualityScore > 1) {
144
+ return `review-loop iterations[${index}].qualityScore must be between 0 and 1.`;
145
+ }
146
+ if (typeof findingsCount !== "number" ||
147
+ Number.isNaN(findingsCount) ||
148
+ !Number.isInteger(findingsCount) ||
149
+ findingsCount < 0) {
150
+ return `review-loop iterations[${index}].findingsCount must be an integer >= 0.`;
151
+ }
152
+ if (qualityScore + Number.EPSILON < prevScore) {
153
+ return "review-loop qualityScore must be monotonic non-decreasing across iterations.";
154
+ }
155
+ if (qualityScore >= targetScore) {
156
+ reachedTarget = true;
157
+ }
158
+ prevScore = qualityScore;
159
+ }
160
+ if (envelope.stopReason === "quality_threshold_met" && !reachedTarget) {
161
+ return "review-loop stopReason is quality_threshold_met but no iteration reached targetScore.";
162
+ }
163
+ if (envelope.stopReason === "max_iterations_reached" && rows.length < maxIterations) {
164
+ return "review-loop stopReason is max_iterations_reached but iterations are below maxIterations.";
165
+ }
166
+ return null;
167
+ }
58
168
  // Per-gate validators keyed by `${stage}:${gateId}`. Returning a non-null
59
169
  // string surfaces the reason as an `advance-stage` failure so evidence is
60
170
  // guaranteed to carry the structural breadcrumbs downstream tooling
@@ -77,7 +187,9 @@ const GATE_EVIDENCE_VALIDATORS = {
77
187
  return `must name the finalization mode that ran (for example ${SHIP_FINALIZATION_MODE_HINT}).`;
78
188
  }
79
189
  return null;
80
- }
190
+ },
191
+ "scope:scope_user_approved": (evidence) => validateReviewLoopGateEvidence("scope", evidence),
192
+ "design:design_architecture_locked": (evidence) => validateReviewLoopGateEvidence("design", evidence)
81
193
  };
82
194
  function validateGateEvidenceShape(stage, gateId, evidence) {
83
195
  const validator = GATE_EVIDENCE_VALIDATORS[`${stage}:${gateId}`];
@@ -232,6 +344,35 @@ function parseCsv(raw) {
232
344
  .map((item) => item.trim())
233
345
  .filter((item) => item.length > 0);
234
346
  }
347
+ async function hydrateReviewLoopEvidenceFromArtifact(projectRoot, stage, track, selectedGateIds, evidenceByGate) {
348
+ const gateId = AUTO_REVIEW_LOOP_GATE_BY_STAGE[stage];
349
+ if (!gateId)
350
+ return;
351
+ if (!selectedGateIds.includes(gateId))
352
+ return;
353
+ const existing = evidenceByGate[gateId];
354
+ if (typeof existing === "string" && existing.trim().length > 0)
355
+ return;
356
+ const resolved = await resolveArtifactPath(stage, {
357
+ projectRoot,
358
+ track,
359
+ intent: "read"
360
+ });
361
+ let raw = "";
362
+ try {
363
+ raw = await fs.readFile(resolved.absPath, "utf8");
364
+ }
365
+ catch {
366
+ return;
367
+ }
368
+ const reviewStage = stage === "scope" || stage === "design" ? stage : null;
369
+ if (!reviewStage)
370
+ return;
371
+ const envelope = extractReviewLoopEnvelopeFromArtifact(raw, reviewStage, resolved.relPath);
372
+ if (!envelope)
373
+ return;
374
+ evidenceByGate[gateId] = JSON.stringify(envelope);
375
+ }
235
376
  function parseAdvanceStageArgs(tokens) {
236
377
  const [stageRaw, ...flagTokens] = tokens;
237
378
  if (!isFlowStageValue(stageRaw)) {
@@ -488,6 +629,7 @@ async function runAdvanceStage(projectRoot, args, io) {
488
629
  });
489
630
  }
490
631
  }
632
+ await hydrateReviewLoopEvidenceFromArtifact(projectRoot, args.stage, flowState.track, selectedGateIds, args.evidenceByGate);
491
633
  const catalog = flowState.stageGateCatalog[args.stage];
492
634
  const nextPassed = unique([...catalog.passed, ...selectedGateIds]).filter((gateId) => allowedGateIds.has(gateId));
493
635
  const nextPassedSet = new Set(nextPassed);
@@ -4,10 +4,24 @@ export interface TraceEntry {
4
4
  testSlices: string[];
5
5
  reviewFindings: string[];
6
6
  }
7
+ export interface ReviewLoopTraceEntry {
8
+ stage: "scope" | "design";
9
+ artifactPath: string;
10
+ targetScore: number;
11
+ maxIterations: number;
12
+ stopReason: "quality_threshold_met" | "max_iterations_reached" | "user_opt_out";
13
+ finalScore: number;
14
+ iterations: Array<{
15
+ iteration: number;
16
+ qualityScore: number;
17
+ findingsCount: number;
18
+ }>;
19
+ }
7
20
  export interface TraceMatrix {
8
21
  entries: TraceEntry[];
9
22
  orphanedCriteria: string[];
10
23
  orphanedTasks: string[];
11
24
  orphanedTests: string[];
25
+ reviewLoops: ReviewLoopTraceEntry[];
12
26
  }
13
27
  export declare function buildTraceMatrix(projectRoot: string): Promise<TraceMatrix>;
@@ -1,6 +1,8 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { resolveArtifactPath } from "./artifact-paths.js";
3
4
  import { RUNTIME_ROOT } from "./constants.js";
5
+ import { extractReviewLoopEnvelopeFromArtifact } from "./content/review-loop.js";
4
6
  import { exists } from "./fs-utils.js";
5
7
  function activeArtifactPath(projectRoot, name) {
6
8
  return path.join(projectRoot, RUNTIME_ROOT, "artifacts", name);
@@ -110,6 +112,56 @@ function layer1LinesForCriterion(layer1, criterionId) {
110
112
  }
111
113
  return out;
112
114
  }
115
+ async function readStageArtifact(projectRoot, stage) {
116
+ let resolved = null;
117
+ try {
118
+ resolved = await resolveArtifactPath(stage, {
119
+ projectRoot,
120
+ intent: "read"
121
+ });
122
+ }
123
+ catch {
124
+ resolved = null;
125
+ }
126
+ if (!resolved || !(await exists(resolved.absPath))) {
127
+ return null;
128
+ }
129
+ try {
130
+ const markdown = await fs.readFile(resolved.absPath, "utf8");
131
+ return { markdown, relPath: resolved.relPath };
132
+ }
133
+ catch {
134
+ return null;
135
+ }
136
+ }
137
+ async function collectReviewLoopTraceEntries(projectRoot) {
138
+ const entries = [];
139
+ for (const stage of ["scope", "design"]) {
140
+ const artifact = await readStageArtifact(projectRoot, stage);
141
+ if (!artifact)
142
+ continue;
143
+ const envelope = extractReviewLoopEnvelopeFromArtifact(artifact.markdown, stage, artifact.relPath);
144
+ if (!envelope)
145
+ continue;
146
+ const finalScore = envelope.iterations.length > 0
147
+ ? envelope.iterations[envelope.iterations.length - 1].qualityScore
148
+ : 0;
149
+ entries.push({
150
+ stage,
151
+ artifactPath: artifact.relPath,
152
+ targetScore: envelope.targetScore,
153
+ maxIterations: envelope.maxIterations,
154
+ stopReason: envelope.stopReason,
155
+ finalScore,
156
+ iterations: envelope.iterations.map((row) => ({
157
+ iteration: row.iteration,
158
+ qualityScore: row.qualityScore,
159
+ findingsCount: row.findingsCount
160
+ }))
161
+ });
162
+ }
163
+ return entries;
164
+ }
113
165
  export async function buildTraceMatrix(projectRoot) {
114
166
  const spec = await readArtifact(projectRoot, "04-spec.md");
115
167
  const plan = await readArtifact(projectRoot, "05-plan.md");
@@ -163,10 +215,12 @@ export async function buildTraceMatrix(projectRoot) {
163
215
  return !acs || acs.length === 0;
164
216
  });
165
217
  });
218
+ const reviewLoops = await collectReviewLoopTraceEntries(projectRoot);
166
219
  return {
167
220
  entries,
168
221
  orphanedCriteria,
169
222
  orphanedTasks,
170
- orphanedTests
223
+ orphanedTests,
224
+ reviewLoops
171
225
  };
172
226
  }
package/dist/types.d.ts CHANGED
@@ -118,6 +118,29 @@ export interface IronLawsConfig {
118
118
  */
119
119
  strictLaws?: string[];
120
120
  }
121
+ /**
122
+ * Optional opt-in audit toggles for additional stage lint gates.
123
+ *
124
+ * Disabled by default so existing projects are not forced into stricter
125
+ * checks until they explicitly enable them in config.
126
+ */
127
+ export interface OptInAuditsConfig {
128
+ /** When true, scope lint requires a filled `Pre-Scope System Audit` section. */
129
+ scopePreAudit?: boolean;
130
+ /** When true, design lint runs stale diagram drift checks against blast radius files. */
131
+ staleDiagramAudit?: boolean;
132
+ }
133
+ export interface ReviewLoopExternalSecondOpinionConfig {
134
+ /** Enables a second outside-voice pass for review-loop iterations. */
135
+ enabled?: boolean;
136
+ /** Optional model label for traceability in artifacts/logs. */
137
+ model?: string;
138
+ /** Minimum score delta that should be surfaced as disagreement context. */
139
+ scoreDeltaThreshold?: number;
140
+ }
141
+ export interface ReviewLoopConfig {
142
+ externalSecondOpinion?: ReviewLoopExternalSecondOpinionConfig;
143
+ }
121
144
  export interface CclawConfig {
122
145
  version: string;
123
146
  flowVersion: string;
@@ -170,6 +193,10 @@ export interface CclawConfig {
170
193
  sliceReview?: SliceReviewConfig;
171
194
  /** Optional per-law strictness controls for hook-enforced iron laws. */
172
195
  ironLaws?: IronLawsConfig;
196
+ /** Optional opt-in audit gates for scope/design stages. */
197
+ optInAudits?: OptInAuditsConfig;
198
+ /** Optional runtime knobs for outside-voice review loops. */
199
+ reviewLoop?: ReviewLoopConfig;
173
200
  }
174
201
  /**
175
202
  * @deprecated Use `CclawConfig` instead.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.48.31",
3
+ "version": "0.48.32",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {