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.
- package/dist/artifact-linter.js +609 -10
- package/dist/config.d.ts +1 -1
- package/dist/config.js +82 -4
- package/dist/content/examples.js +23 -6
- package/dist/content/ideate-command.d.ts +6 -2
- package/dist/content/ideate-command.js +43 -16
- package/dist/content/ideate-frames.d.ts +31 -0
- package/dist/content/ideate-frames.js +140 -0
- package/dist/content/ideate-ranking.d.ts +25 -0
- package/dist/content/ideate-ranking.js +65 -0
- package/dist/content/review-loop.d.ts +192 -0
- package/dist/content/review-loop.js +689 -0
- package/dist/content/seed-shelf.d.ts +36 -0
- package/dist/content/seed-shelf.js +236 -0
- package/dist/content/skills.js +14 -1
- package/dist/content/stage-schema.d.ts +1 -1
- package/dist/content/stage-schema.js +14 -2
- package/dist/content/stages/brainstorm.js +15 -4
- package/dist/content/stages/design.js +31 -8
- package/dist/content/stages/schema-types.d.ts +10 -0
- package/dist/content/stages/scope.js +17 -6
- package/dist/content/start-command.js +24 -18
- package/dist/content/templates.js +108 -4
- package/dist/internal/advance-stage.js +143 -1
- package/dist/trace-matrix.d.ts +14 -0
- package/dist/trace-matrix.js +55 -1
- package/dist/types.d.ts +27 -0
- package/package.json +1 -1
|
@@ -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
|
|
50
|
+
2. **Phase 0.5 — Seed 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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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. **
|
|
156
|
-
3. **
|
|
157
|
-
4.
|
|
158
|
-
5.
|
|
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
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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);
|
package/dist/trace-matrix.d.ts
CHANGED
|
@@ -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>;
|
package/dist/trace-matrix.js
CHANGED
|
@@ -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.
|