cclaw-cli 0.51.19 → 0.51.22
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 +89 -6
- package/dist/config.d.ts +8 -1
- package/dist/config.js +9 -6
- package/dist/content/examples.js +1 -0
- package/dist/content/hook-events.js +1 -5
- package/dist/content/hook-manifest.d.ts +2 -4
- package/dist/content/hook-manifest.js +4 -3
- package/dist/content/meta-skill.js +7 -9
- package/dist/content/next-command.js +2 -2
- package/dist/content/node-hooks.js +15 -16
- package/dist/content/observe.js +2 -4
- package/dist/content/opencode-plugin.js +5 -6
- package/dist/content/review-loop.js +15 -5
- package/dist/content/review-prompts.js +1 -1
- package/dist/content/skills.js +3 -2
- package/dist/content/stage-schema.d.ts +0 -1
- package/dist/content/stage-schema.js +2 -5
- package/dist/content/stages/brainstorm.js +3 -3
- package/dist/content/stages/design.js +18 -17
- package/dist/content/stages/plan.js +2 -1
- package/dist/content/stages/review.js +10 -10
- package/dist/content/stages/scope.js +13 -13
- package/dist/content/stages/spec.js +7 -5
- package/dist/content/stages/tdd.js +2 -2
- package/dist/content/start-command.d.ts +4 -3
- package/dist/content/start-command.js +21 -17
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +49 -29
- package/dist/content/view-command.js +3 -1
- package/dist/delegation.d.ts +0 -1
- package/dist/delegation.js +29 -11
- package/dist/doctor.js +148 -24
- package/dist/gate-evidence.js +19 -7
- package/dist/harness-adapters.js +1 -5
- package/dist/install.js +111 -24
- package/dist/internal/advance-stage.js +90 -11
- package/dist/knowledge-store.d.ts +4 -1
- package/dist/knowledge-store.js +24 -14
- package/dist/retro-gate.d.ts +1 -0
- package/dist/retro-gate.js +9 -9
- package/dist/run-archive.js +19 -1
- package/dist/run-persistence.js +12 -5
- package/dist/tdd-cycle.js +6 -3
- package/package.json +1 -1
|
@@ -143,6 +143,7 @@ ${SEED_SHELF_SECTION}
|
|
|
143
143
|
| Reversibility cost? | | |
|
|
144
144
|
|
|
145
145
|
## Dream State Mapping
|
|
146
|
+
- Deep/optional only; omit for compact scope.
|
|
146
147
|
- CURRENT STATE:
|
|
147
148
|
- THIS PLAN:
|
|
148
149
|
- 12-MONTH IDEAL:
|
|
@@ -156,6 +157,7 @@ ${SEED_SHELF_SECTION}
|
|
|
156
157
|
| C (optional) | | | | | | |
|
|
157
158
|
|
|
158
159
|
## Temporal Interrogation
|
|
160
|
+
- Deep/optional only; omit for compact scope.
|
|
159
161
|
| Time slice | Likely decision pressure | Lock now or defer? | Reason |
|
|
160
162
|
|---|---|---|---|
|
|
161
163
|
| HOUR 1 (foundations) | | | |
|
|
@@ -170,12 +172,11 @@ ${SEED_SHELF_SECTION}
|
|
|
170
172
|
- [ ] SCOPE REDUCTION — strip to the smallest useful wedge when risk/blast radius is too high.
|
|
171
173
|
|
|
172
174
|
## Mode-Specific Analysis
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
- (SCOPE REDUCTION: ruthless cuts, follow-up split)
|
|
175
|
+
| Selected mode | Rationale | Depth |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| | | default / deep |
|
|
178
|
+
|
|
179
|
+
> Default path: one selected-mode row plus rationale. Deep/high-risk scope may expand below with mode-specific analysis.
|
|
179
180
|
|
|
180
181
|
## Requirements (stable IDs)
|
|
181
182
|
| ID | Requirement (observable outcome) | Priority | Source (origin doc / prompt line) |
|
|
@@ -224,7 +225,7 @@ ${SEED_SHELF_SECTION}
|
|
|
224
225
|
|---|---|---|---|---|
|
|
225
226
|
| F-1 | premise_fit | | accept/reject/defer | |
|
|
226
227
|
|
|
227
|
-
##
|
|
228
|
+
## Scope Outside Voice Loop
|
|
228
229
|
| Iteration | Quality Score | Findings | Stop decision |
|
|
229
230
|
|---|---|---|---|
|
|
230
231
|
| 1 | 0.00 | 0 | continue/stop |
|
|
@@ -288,8 +289,13 @@ ${SEED_SHELF_SECTION}
|
|
|
288
289
|
|
|
289
290
|
# Design Artifact
|
|
290
291
|
|
|
292
|
+
## Compact-First Scaffold
|
|
293
|
+
- Default to the compact design spine unless risk requires Standard/Deep add-ons.
|
|
294
|
+
- Compact required spine: Codebase Investigation, Architecture Boundaries, Architecture Diagram, Data Flow, Failure Mode Table, Test Strategy, and Completion Dashboard.
|
|
295
|
+
- Mark optional Standard/Deep sections as \`Omitted - compact design\` when they do not apply; do not expand the scaffold just to fill empty tables.
|
|
296
|
+
|
|
291
297
|
## Upstream Handoff
|
|
292
|
-
- Source artifacts: \`02-scope-<slug>.md\`, \`02a-research.md\` when present
|
|
298
|
+
- Source artifacts: \`02-scope-<slug>.md\`, \`02a-research.md\` only when present for deep/high-risk research
|
|
293
299
|
- Decisions carried forward:
|
|
294
300
|
- Constraints carried forward:
|
|
295
301
|
- Open questions:
|
|
@@ -308,12 +314,11 @@ ${SEED_SHELF_SECTION}
|
|
|
308
314
|
| Layer 3 | | |
|
|
309
315
|
|
|
310
316
|
## Research Fleet Synthesis
|
|
311
|
-
| Lens | Key findings | Design impact | Evidence |
|
|
317
|
+
| Lens actually run | Key findings | Design impact | Evidence |
|
|
312
318
|
|---|---|---|---|
|
|
313
|
-
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
| pitfalls-researcher | | | |
|
|
319
|
+
| compact inline synthesis | | | |
|
|
320
|
+
|
|
321
|
+
> Default path: compact inline synthesis here. Deep/high-risk work may also write \`.cclaw/artifacts/02a-research.md\`.
|
|
317
322
|
|
|
318
323
|
## Architecture Boundaries
|
|
319
324
|
| Component | Responsibility | Requirement Refs (R#) | Decision Refs (LD#hash) | Owner |
|
|
@@ -329,12 +334,14 @@ ${MARKDOWN_CODE_FENCE}
|
|
|
329
334
|
${MARKDOWN_CODE_FENCE}
|
|
330
335
|
|
|
331
336
|
## Data-Flow Shadow Paths
|
|
337
|
+
- Standard/Deep add-on; omit when compact design does not need a shadow path.
|
|
332
338
|
<!-- diagram: data-flow-shadow-paths -->
|
|
333
339
|
| Path | Trigger | Fallback/Degrade behavior |
|
|
334
340
|
|---|---|---|
|
|
335
341
|
| | | |
|
|
336
342
|
|
|
337
343
|
## Error Flow Diagram
|
|
344
|
+
- Standard/Deep add-on; omit when the Failure Mode Table is sufficient.
|
|
338
345
|
|
|
339
346
|
<!-- diagram: error-flow -->
|
|
340
347
|
|
|
@@ -343,6 +350,7 @@ ${MARKDOWN_CODE_FENCE}
|
|
|
343
350
|
${MARKDOWN_CODE_FENCE}
|
|
344
351
|
|
|
345
352
|
## State Machine Diagram
|
|
353
|
+
- Deep add-on; omit for compact design.
|
|
346
354
|
|
|
347
355
|
<!-- diagram: state-machine -->
|
|
348
356
|
|
|
@@ -351,6 +359,7 @@ ${MARKDOWN_CODE_FENCE}
|
|
|
351
359
|
${MARKDOWN_CODE_FENCE}
|
|
352
360
|
|
|
353
361
|
## Rollback Flowchart
|
|
362
|
+
- Deep add-on; omit for compact design.
|
|
354
363
|
|
|
355
364
|
<!-- diagram: rollback-flowchart -->
|
|
356
365
|
|
|
@@ -359,6 +368,7 @@ ${MARKDOWN_CODE_FENCE}
|
|
|
359
368
|
${MARKDOWN_CODE_FENCE}
|
|
360
369
|
|
|
361
370
|
## Deployment Sequence Diagram
|
|
371
|
+
- Deep add-on; omit for compact design.
|
|
362
372
|
|
|
363
373
|
<!-- diagram: deployment-sequence -->
|
|
364
374
|
|
|
@@ -426,7 +436,7 @@ ${MARKDOWN_CODE_FENCE}
|
|
|
426
436
|
|---|---|---|---|---|
|
|
427
437
|
| F-1 | architecture_fit | | accept/reject/defer | |
|
|
428
438
|
|
|
429
|
-
##
|
|
439
|
+
## Design Outside Voice Loop
|
|
430
440
|
| Iteration | Quality Score | Findings | Stop decision |
|
|
431
441
|
|---|---|---|---|
|
|
432
442
|
| 1 | 0.00 | 0 | continue/stop |
|
|
@@ -439,6 +449,7 @@ ${MARKDOWN_CODE_FENCE}
|
|
|
439
449
|
-
|
|
440
450
|
|
|
441
451
|
## Parallelization Strategy
|
|
452
|
+
- Standard/Deep add-on when multi-module; omit for compact sequential work.
|
|
442
453
|
- Parallel lanes:
|
|
443
454
|
- Conflict risks:
|
|
444
455
|
|
|
@@ -448,11 +459,13 @@ ${MARKDOWN_CODE_FENCE}
|
|
|
448
459
|
| | | |
|
|
449
460
|
|
|
450
461
|
## Interface Contracts
|
|
462
|
+
- Standard/Deep add-on when module boundaries or APIs change; omit for compact local changes.
|
|
451
463
|
| Module | Produces | Consumes |
|
|
452
464
|
|---|---|---|
|
|
453
465
|
| | | |
|
|
454
466
|
|
|
455
467
|
## Unresolved Decisions
|
|
468
|
+
- Standard/Deep add-on; use \`None\` for compact design with no unresolved decisions.
|
|
456
469
|
| Decision | Missing info | Owner | Default |
|
|
457
470
|
|---|---|---|---|
|
|
458
471
|
| | | | |
|
|
@@ -481,7 +494,7 @@ ${SEED_SHELF_SECTION}
|
|
|
481
494
|
# Specification Artifact
|
|
482
495
|
|
|
483
496
|
## Upstream Handoff
|
|
484
|
-
- Source artifacts: \`02-scope-<slug>.md
|
|
497
|
+
- Source artifacts: standard uses \`02-scope-<slug>.md\` + \`03-design-<slug>.md\`; medium uses \`01-brainstorm-<slug>.md\` when present; quick uses \`00-idea.md\` plus reproduction context.
|
|
485
498
|
- Decisions carried forward:
|
|
486
499
|
- Constraints carried forward:
|
|
487
500
|
- Open questions:
|
|
@@ -492,9 +505,14 @@ ${SEED_SHELF_SECTION}
|
|
|
492
505
|
|---|---|---|---|
|
|
493
506
|
| AC-1 | R1 | | |
|
|
494
507
|
|
|
495
|
-
>
|
|
496
|
-
|
|
497
|
-
|
|
508
|
+
> Standard ACs reference at least one \`R#\` from \`02-scope.md\`. Quick-track ACs may instead put \`Quick Reproduction Contract\` / bug-slice refs in the Requirement Ref column and \`N/A\` for Design Decision Ref. ACs are stable (never renumber): dropped ACs stay with Priority \`DROPPED\`; new ones append with the next free \`AC-#\`.
|
|
509
|
+
|
|
510
|
+
## Quick Reproduction Contract
|
|
511
|
+
> Required for quick bug-fix specs; use \`N/A\` for non-bugfix or standard/medium tracks. TDD turns this contract into the RED reproduction test.
|
|
512
|
+
|
|
513
|
+
| Bug slice | Symptom | Repro steps | Expected RED test behavior | Linked acceptance criterion |
|
|
514
|
+
|---|---|---|---|---|
|
|
515
|
+
| QS-1 | | | | AC-1 |
|
|
498
516
|
|
|
499
517
|
## Edge Cases
|
|
500
518
|
| Criterion ID | Boundary case | Error case |
|
|
@@ -510,7 +528,7 @@ ${SEED_SHELF_SECTION}
|
|
|
510
528
|
|---|---|---|---|
|
|
511
529
|
| | | | accepted/rejected/open |
|
|
512
530
|
|
|
513
|
-
##
|
|
531
|
+
## Acceptance Mapping
|
|
514
532
|
| Criterion ID | Verification approach | Command/manual steps |
|
|
515
533
|
|---|---|---|
|
|
516
534
|
| AC-1 | | |
|
|
@@ -627,7 +645,7 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
627
645
|
# TDD Artifact
|
|
628
646
|
|
|
629
647
|
## Upstream Handoff
|
|
630
|
-
- Source artifacts: \`04-spec.md
|
|
648
|
+
- Source artifacts: \`04-spec.md\`; \`05-plan.md\` when present. Quick track uses spec acceptance items / bug reproduction slices instead of nonexistent plan tasks.
|
|
631
649
|
- Decisions carried forward:
|
|
632
650
|
- Constraints carried forward:
|
|
633
651
|
- Open questions:
|
|
@@ -654,9 +672,11 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
654
672
|
| S-1 | | | |
|
|
655
673
|
|
|
656
674
|
## Acceptance Mapping
|
|
657
|
-
| Slice | Plan task ID | Spec criterion ID |
|
|
675
|
+
| Slice | Plan task ID or quick source | Spec criterion ID |
|
|
658
676
|
|---|---|---|
|
|
659
|
-
| S-1 | T-1 | AC-1 |
|
|
677
|
+
| S-1 | T-1 / QS-1 | AC-1 |
|
|
678
|
+
|
|
679
|
+
> On quick track, map to the \`Quick Reproduction Contract\` bug slice or spec acceptance item. Do not invent a plan task just to satisfy this table.
|
|
660
680
|
|
|
661
681
|
## Failure Analysis
|
|
662
682
|
| Slice | Expected missing behavior | Actual failure reason |
|
|
@@ -709,7 +729,7 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
709
729
|
# Review Artifact
|
|
710
730
|
|
|
711
731
|
## Upstream Handoff
|
|
712
|
-
- Source artifacts: \`04-spec.md\`, \`
|
|
732
|
+
- Source artifacts: \`04-spec.md\`, \`06-tdd.md\`; \`05-plan.md\` only when present. Quick track reviews spec acceptance items / bug reproduction slices without requiring plan-task coverage.
|
|
713
733
|
- Decisions carried forward:
|
|
714
734
|
- Constraints carried forward:
|
|
715
735
|
- Open questions:
|
|
@@ -723,7 +743,7 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
723
743
|
## Layer 2 Findings
|
|
724
744
|
| ID | Severity | Category | Description | Status |
|
|
725
745
|
|---|---|---|---|---|
|
|
726
|
-
| R-1 | Critical/Important/Suggestion | correctness/security/performance/architecture | | open/resolved |
|
|
746
|
+
| R-1 | Critical/Important/Suggestion | correctness/security/performance/architecture/external-safety | | open/resolved |
|
|
727
747
|
- NO_CHANGE_ATTESTATION: <required when Category=security has no entries; explain why no security-relevant changes were detected>
|
|
728
748
|
|
|
729
749
|
## Incoming Feedback Queue
|
|
@@ -746,15 +766,15 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
746
766
|
|
|
747
767
|
## Completeness Snapshot
|
|
748
768
|
- AC coverage: <N>/<M> (<percent>%)
|
|
749
|
-
- Task coverage (tasks backed by ≥1 test slice): <N>/<M>
|
|
750
|
-
- Slice coverage (slices linked to ≥1 AC): <N>/<M>
|
|
769
|
+
- Task coverage (tasks backed by ≥1 test slice): <N>/<M> or \`N/A - quick track has no plan artifact\`
|
|
770
|
+
- Slice coverage (slices linked to ≥1 AC or bug reproduction slice): <N>/<M>
|
|
751
771
|
- Adversarial review: not triggered | pass | fail
|
|
752
772
|
- Overall: complete | concerns | blocked
|
|
753
773
|
|
|
754
774
|
## Trace Matrix Check
|
|
755
|
-
- Command: \`cclaw internal trace-matrix\`
|
|
775
|
+
- Command: \`cclaw internal trace-matrix\` when plan artifacts exist or the active track enforces it; quick track may record direct AC/reproduction-slice coverage instead.
|
|
756
776
|
- Orphaned criteria: 0
|
|
757
|
-
- Orphaned tasks: 0
|
|
777
|
+
- Orphaned tasks: 0 or \`N/A - quick track\`
|
|
758
778
|
- Orphaned tests: 0
|
|
759
779
|
- Evidence ref:
|
|
760
780
|
|
|
@@ -955,7 +975,7 @@ Track-specific skips are allowed only when \`flow-state.track\` + \`skippedStage
|
|
|
955
975
|
| Class | Route |
|
|
956
976
|
|---|---|
|
|
957
977
|
| non-trivial software work | \`/cc <idea>\` |
|
|
958
|
-
| trivial software fix | \`/cc <idea>\` (quick
|
|
978
|
+
| trivial software fix | \`/cc <idea>\` (quick track) |
|
|
959
979
|
| bugfix with repro | \`/cc <idea>\` and enforce RED-first in tdd |
|
|
960
980
|
| pure question / non-software | direct answer (no stage flow) |
|
|
961
981
|
|
|
@@ -37,9 +37,11 @@ ${conversationLanguagePolicyMarkdown()}
|
|
|
37
37
|
For machine orchestration, emit one JSON envelope:
|
|
38
38
|
|
|
39
39
|
\`\`\`json
|
|
40
|
-
{"version":"1","kind":"stage-output","stage":"non-flow","payload":{"command":"/cc-view","subcommand":"status","summary":"<short>"},"emittedAt":"<ISO-8601>"}
|
|
40
|
+
{"version":"1","kind":"stage-output","stage":"non-flow","payload":{"command":"/cc-view <status|tree|diff>","subcommand":"<status|tree|diff>","summary":"<short>"},"emittedAt":"<ISO-8601>"}
|
|
41
41
|
\`\`\`
|
|
42
42
|
|
|
43
|
+
Use the parsed/defaulted subcommand in both \`payload.command\` and \`payload.subcommand\`; do not collapse \`tree\` or \`diff\` responses to \`status\`.
|
|
44
|
+
|
|
43
45
|
Validate envelopes with:
|
|
44
46
|
\`cclaw internal envelope-validate --stdin\`
|
|
45
47
|
|
package/dist/delegation.d.ts
CHANGED
|
@@ -93,7 +93,6 @@ export declare function checkMandatoryDelegations(projectRoot: string, stage: Fl
|
|
|
93
93
|
satisfied: boolean;
|
|
94
94
|
missing: string[];
|
|
95
95
|
waived: string[];
|
|
96
|
-
autoWaived: string[];
|
|
97
96
|
staleIgnored: string[];
|
|
98
97
|
/** Delegation rows missing required evidence under a role-switch fallback. */
|
|
99
98
|
missingEvidence: string[];
|
package/dist/delegation.js
CHANGED
|
@@ -103,6 +103,9 @@ async function detectReviewTriggers(projectRoot) {
|
|
|
103
103
|
return empty;
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
+
function hasValidWaiverReason(value) {
|
|
107
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
108
|
+
}
|
|
106
109
|
function isDelegationTokenUsage(value) {
|
|
107
110
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
108
111
|
return false;
|
|
@@ -130,6 +133,7 @@ function isDelegationEntry(value) {
|
|
|
130
133
|
Number.isFinite(o.retryCount) &&
|
|
131
134
|
Number.isInteger(o.retryCount) &&
|
|
132
135
|
o.retryCount >= 0);
|
|
136
|
+
const waiverOk = o.status !== "waived" || hasValidWaiverReason(o.waiverReason);
|
|
133
137
|
return (typeof o.stage === "string" &&
|
|
134
138
|
typeof o.agent === "string" &&
|
|
135
139
|
modeOk &&
|
|
@@ -141,6 +145,7 @@ function isDelegationEntry(value) {
|
|
|
141
145
|
(o.endTs === undefined || typeof o.endTs === "string") &&
|
|
142
146
|
(o.taskId === undefined || typeof o.taskId === "string") &&
|
|
143
147
|
(o.waiverReason === undefined || typeof o.waiverReason === "string") &&
|
|
148
|
+
waiverOk &&
|
|
144
149
|
(o.runId === undefined || typeof o.runId === "string") &&
|
|
145
150
|
(o.fulfillmentMode === undefined ||
|
|
146
151
|
o.fulfillmentMode === "isolated" ||
|
|
@@ -165,8 +170,10 @@ function parseLedger(raw, runId) {
|
|
|
165
170
|
for (const item of entriesRaw) {
|
|
166
171
|
if (isDelegationEntry(item)) {
|
|
167
172
|
const ts = item.startTs ?? item.ts ?? new Date().toISOString();
|
|
168
|
-
const
|
|
169
|
-
|
|
173
|
+
const isLegacyCompletion = item.fulfillmentMode === undefined &&
|
|
174
|
+
item.schemaVersion === undefined &&
|
|
175
|
+
item.status === "completed";
|
|
176
|
+
const inferredFulfillmentMode = item.fulfillmentMode ?? (isLegacyCompletion ? "isolated" : undefined);
|
|
170
177
|
entries.push({
|
|
171
178
|
...item,
|
|
172
179
|
spanId: item.spanId ?? createSpanId(),
|
|
@@ -205,6 +212,9 @@ export async function appendDelegation(projectRoot, entry) {
|
|
|
205
212
|
const filePath = delegationLogPath(projectRoot);
|
|
206
213
|
const prior = await readDelegationLedger(projectRoot);
|
|
207
214
|
const startTs = entry.startTs ?? entry.ts ?? new Date().toISOString();
|
|
215
|
+
if (entry.status === "waived" && !hasValidWaiverReason(entry.waiverReason)) {
|
|
216
|
+
throw new Error("waived delegation entries require a non-empty waiverReason");
|
|
217
|
+
}
|
|
208
218
|
const stamped = { ...entry, runId: entry.runId ?? activeRunId };
|
|
209
219
|
stamped.spanId = entry.spanId ?? createSpanId();
|
|
210
220
|
stamped.startTs = startTs;
|
|
@@ -219,10 +229,19 @@ export async function appendDelegation(projectRoot, entry) {
|
|
|
219
229
|
stamped.evidenceRefs = [];
|
|
220
230
|
}
|
|
221
231
|
if (stamped.status === "completed" && stamped.fulfillmentMode === undefined) {
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
232
|
+
const activeFallback = process.env.CCLAW_ACTIVE_HARNESS
|
|
233
|
+
? HARNESS_ADAPTERS[process.env.CCLAW_ACTIVE_HARNESS]
|
|
234
|
+
?.capabilities.subagentFallback
|
|
235
|
+
: undefined;
|
|
236
|
+
if (activeFallback) {
|
|
237
|
+
stamped.fulfillmentMode = expectedFulfillmentMode([activeFallback]);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
const config = await readConfig(projectRoot).catch(() => null);
|
|
241
|
+
const harnesses = config?.harnesses ?? [];
|
|
242
|
+
const fallbacks = harnesses.map((h) => HARNESS_ADAPTERS[h].capabilities.subagentFallback);
|
|
243
|
+
stamped.fulfillmentMode = expectedFulfillmentMode(fallbacks);
|
|
244
|
+
}
|
|
226
245
|
}
|
|
227
246
|
// Idempotency: if a caller (or a retried hook) tries to append a row
|
|
228
247
|
// with a spanId that already exists in the ledger, treat it as a no-op
|
|
@@ -256,10 +275,11 @@ export function expectedFulfillmentMode(fallbacks) {
|
|
|
256
275
|
return "harness-waiver";
|
|
257
276
|
}
|
|
258
277
|
export async function checkMandatoryDelegations(projectRoot, stage, options = {}) {
|
|
259
|
-
const
|
|
260
|
-
const { activeRunId } = await readFlowState(projectRoot, {
|
|
278
|
+
const flowState = await readFlowState(projectRoot, {
|
|
261
279
|
repairFeatureSystem: options.repairFeatureSystem
|
|
262
280
|
});
|
|
281
|
+
const mandatory = stageSchema(stage, flowState.track).mandatoryDelegations;
|
|
282
|
+
const { activeRunId } = flowState;
|
|
263
283
|
const ledger = await readDelegationLedger(projectRoot);
|
|
264
284
|
const forStage = ledger.entries.filter((e) => e.stage === stage);
|
|
265
285
|
const forRun = forStage.filter((e) => e.runId === activeRunId);
|
|
@@ -268,7 +288,6 @@ export async function checkMandatoryDelegations(projectRoot, stage, options = {}
|
|
|
268
288
|
.map((e) => `${e.agent}(runId=${e.runId ?? "unknown"})`);
|
|
269
289
|
const missing = [];
|
|
270
290
|
const waived = [];
|
|
271
|
-
const autoWaived = [];
|
|
272
291
|
const missingEvidence = [];
|
|
273
292
|
const config = await readConfig(projectRoot).catch(() => null);
|
|
274
293
|
const harnesses = config?.harnesses ?? [];
|
|
@@ -277,7 +296,7 @@ export async function checkMandatoryDelegations(projectRoot, stage, options = {}
|
|
|
277
296
|
for (const agent of mandatory) {
|
|
278
297
|
const rows = forRun.filter((e) => e.agent === agent);
|
|
279
298
|
const completedRows = rows.filter((e) => e.status === "completed");
|
|
280
|
-
const waivedRows = rows.filter((e) => e.status === "waived");
|
|
299
|
+
const waivedRows = rows.filter((e) => e.status === "waived" && e.mode === "mandatory");
|
|
281
300
|
const hasCompleted = completedRows.length >= 1;
|
|
282
301
|
const hasWaived = waivedRows.length > 0;
|
|
283
302
|
const ok = hasWaived || hasCompleted;
|
|
@@ -301,7 +320,6 @@ export async function checkMandatoryDelegations(projectRoot, stage, options = {}
|
|
|
301
320
|
satisfied: missing.length === 0 && missingEvidence.length === 0,
|
|
302
321
|
missing,
|
|
303
322
|
waived,
|
|
304
|
-
autoWaived,
|
|
305
323
|
staleIgnored,
|
|
306
324
|
missingEvidence,
|
|
307
325
|
expectedMode
|
package/dist/doctor.js
CHANGED
|
@@ -25,6 +25,7 @@ import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LEGACY_LANGUAGE_RULE_
|
|
|
25
25
|
import { validateHookDocument } from "./hook-schema.js";
|
|
26
26
|
import { validateKnowledgeEntry } from "./knowledge-store.js";
|
|
27
27
|
import { readSeedShelf } from "./content/seed-shelf.js";
|
|
28
|
+
import { evaluateRetroGate } from "./retro-gate.js";
|
|
28
29
|
const execFileAsync = promisify(execFile);
|
|
29
30
|
async function isGitRepo(projectRoot) {
|
|
30
31
|
try {
|
|
@@ -149,18 +150,30 @@ function knowledgeRoutingSurfaceIsDiscoverable(content) {
|
|
|
149
150
|
return ["trigger", "action", "origin_run"].every((term) => normalized.includes(term));
|
|
150
151
|
}
|
|
151
152
|
async function commandAvailable(command) {
|
|
153
|
+
const version = await commandVersion(command);
|
|
154
|
+
return version.available;
|
|
155
|
+
}
|
|
156
|
+
async function commandVersion(command, args = ["--version"]) {
|
|
152
157
|
try {
|
|
153
158
|
if (process.platform === "win32") {
|
|
154
159
|
await execFileAsync("where", [command]);
|
|
155
|
-
return true;
|
|
156
160
|
}
|
|
157
|
-
await execFileAsync(command,
|
|
158
|
-
return true;
|
|
161
|
+
const { stdout, stderr } = await execFileAsync(command, args);
|
|
162
|
+
return { available: true, output: `${stdout}${stderr}`.trim() };
|
|
159
163
|
}
|
|
160
164
|
catch {
|
|
161
|
-
return false;
|
|
165
|
+
return { available: false, output: "" };
|
|
162
166
|
}
|
|
163
167
|
}
|
|
168
|
+
function parseNodeMajor(versionOutput) {
|
|
169
|
+
const match = /v?(\d+)\./u.exec(versionOutput);
|
|
170
|
+
if (!match)
|
|
171
|
+
return null;
|
|
172
|
+
return Number(match[1]);
|
|
173
|
+
}
|
|
174
|
+
function gitVersionLooksUsable(versionOutput) {
|
|
175
|
+
return /git version \d+\.\d+/iu.test(versionOutput);
|
|
176
|
+
}
|
|
164
177
|
function stripJsonCommentsOutsideStrings(input) {
|
|
165
178
|
let out = "";
|
|
166
179
|
let i = 0;
|
|
@@ -308,6 +321,87 @@ async function opencodeRegistrationCheck(projectRoot) {
|
|
|
308
321
|
}
|
|
309
322
|
return { ok: false, details: `No opencode.json/opencode.jsonc found with plugin ${expected}` };
|
|
310
323
|
}
|
|
324
|
+
async function initRecoveryCheck(projectRoot) {
|
|
325
|
+
const sentinelPath = path.join(projectRoot, RUNTIME_ROOT, "state", ".init-in-progress");
|
|
326
|
+
if (!(await exists(sentinelPath))) {
|
|
327
|
+
return { ok: true, details: "no partial init/sync sentinel found" };
|
|
328
|
+
}
|
|
329
|
+
let summary = `${RUNTIME_ROOT}/state/.init-in-progress sentinel present`;
|
|
330
|
+
try {
|
|
331
|
+
const parsed = JSON.parse(await fs.readFile(sentinelPath, "utf8"));
|
|
332
|
+
const operation = typeof parsed.operation === "string" ? parsed.operation : "unknown";
|
|
333
|
+
const startedAt = typeof parsed.startedAt === "string" ? parsed.startedAt : "unknown";
|
|
334
|
+
summary = `${summary} (operation=${operation}, startedAt=${startedAt})`;
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
summary = `${summary} (unreadable sentinel payload)`;
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
ok: false,
|
|
341
|
+
details: `${summary}. Fix: inspect generated runtime files, then rerun cclaw sync or remove the sentinel only after confirming the runtime is complete.`
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
async function archiveIntegrityCheck(projectRoot) {
|
|
345
|
+
const runsDir = path.join(projectRoot, RUNTIME_ROOT, "runs");
|
|
346
|
+
if (!(await exists(runsDir))) {
|
|
347
|
+
return { ok: true, details: `${RUNTIME_ROOT}/runs is absent; no archives to inspect yet` };
|
|
348
|
+
}
|
|
349
|
+
let entries;
|
|
350
|
+
try {
|
|
351
|
+
entries = await fs.readdir(runsDir, { withFileTypes: true });
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
355
|
+
return { ok: false, details: `unable to inspect ${RUNTIME_ROOT}/runs (${reason})` };
|
|
356
|
+
}
|
|
357
|
+
const problems = [];
|
|
358
|
+
for (const entry of entries) {
|
|
359
|
+
if (!entry.isDirectory())
|
|
360
|
+
continue;
|
|
361
|
+
const runId = entry.name;
|
|
362
|
+
const runPath = path.join(runsDir, runId);
|
|
363
|
+
const relRunPath = `${RUNTIME_ROOT}/runs/${runId}`;
|
|
364
|
+
if (await exists(path.join(runPath, ".archive-in-progress"))) {
|
|
365
|
+
problems.push(`${relRunPath}/.archive-in-progress sentinel present`);
|
|
366
|
+
}
|
|
367
|
+
const manifestPath = path.join(runPath, "archive-manifest.json");
|
|
368
|
+
if (!(await exists(manifestPath))) {
|
|
369
|
+
problems.push(`${relRunPath} missing archive-manifest.json`);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
let manifest;
|
|
373
|
+
try {
|
|
374
|
+
manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
378
|
+
problems.push(`${relRunPath}/archive-manifest.json unreadable (${reason})`);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const stateFiles = Array.isArray(manifest.snapshottedStateFiles)
|
|
382
|
+
? manifest.snapshottedStateFiles.filter((value) => typeof value === "string")
|
|
383
|
+
: [];
|
|
384
|
+
const stateDir = path.join(runPath, "state");
|
|
385
|
+
if (stateFiles.length > 0 && !(await exists(stateDir))) {
|
|
386
|
+
problems.push(`${relRunPath} manifest lists state snapshot files but state/ is missing`);
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
for (const stateFile of stateFiles) {
|
|
390
|
+
if (stateFile.endsWith("/"))
|
|
391
|
+
continue;
|
|
392
|
+
if (!(await exists(path.join(stateDir, stateFile)))) {
|
|
393
|
+
problems.push(`${relRunPath}/state missing ${stateFile} listed in manifest`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (problems.length === 0) {
|
|
398
|
+
return { ok: true, details: "no partial archive sentinels or incomplete archive snapshots found" };
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
ok: false,
|
|
402
|
+
details: `${problems.join("; ")}. Fix: inspect the archive directory, retry archive if the active run was restored, or recover/rollback artifacts and state from the snapshot before removing the sentinel.`
|
|
403
|
+
};
|
|
404
|
+
}
|
|
311
405
|
async function opencodePluginRuntimeShapeCheck(projectRoot) {
|
|
312
406
|
const pluginPath = path.join(projectRoot, ".opencode/plugins/cclaw-plugin.mjs");
|
|
313
407
|
if (!(await exists(pluginPath))) {
|
|
@@ -981,11 +1075,36 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
981
1075
|
details: registration.details
|
|
982
1076
|
});
|
|
983
1077
|
}
|
|
984
|
-
const
|
|
1078
|
+
const nodeVersion = await commandVersion("node");
|
|
1079
|
+
const nodeMajor = parseNodeMajor(nodeVersion.output);
|
|
985
1080
|
checks.push({
|
|
986
1081
|
name: "capability:required:node",
|
|
987
|
-
ok:
|
|
988
|
-
details:
|
|
1082
|
+
ok: nodeVersion.available,
|
|
1083
|
+
details: nodeVersion.available
|
|
1084
|
+
? `node binary available (${nodeVersion.output || "version unknown"})`
|
|
1085
|
+
: "node is required for cclaw runtime scripts and CLI wiring"
|
|
1086
|
+
});
|
|
1087
|
+
checks.push({
|
|
1088
|
+
name: "capability:required:node_version",
|
|
1089
|
+
ok: nodeVersion.available && nodeMajor !== null && nodeMajor >= 20,
|
|
1090
|
+
details: nodeVersion.available
|
|
1091
|
+
? `node >=20 required; detected ${nodeVersion.output || "unknown version"}`
|
|
1092
|
+
: "node version check skipped because node binary is unavailable"
|
|
1093
|
+
});
|
|
1094
|
+
const gitVersion = await commandVersion("git");
|
|
1095
|
+
checks.push({
|
|
1096
|
+
name: "capability:required:git",
|
|
1097
|
+
ok: gitVersion.available,
|
|
1098
|
+
details: gitVersion.available
|
|
1099
|
+
? `git binary available (${gitVersion.output || "version unknown"})`
|
|
1100
|
+
: "git is required for repository detection, hook setup, and doctor checks"
|
|
1101
|
+
});
|
|
1102
|
+
checks.push({
|
|
1103
|
+
name: "capability:required:git_version",
|
|
1104
|
+
ok: gitVersion.available && gitVersionLooksUsable(gitVersion.output),
|
|
1105
|
+
details: gitVersion.available
|
|
1106
|
+
? `git version output: ${gitVersion.output || "unknown version"}`
|
|
1107
|
+
: "git version check skipped because git binary is unavailable"
|
|
989
1108
|
});
|
|
990
1109
|
const windowsHookConfigCandidates = [
|
|
991
1110
|
path.join(projectRoot, ".claude/hooks/hooks.json"),
|
|
@@ -1083,12 +1202,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1083
1202
|
const key = `${trigger} => ${action}`;
|
|
1084
1203
|
triggerActionCounts.set(key, (triggerActionCounts.get(key) ?? 0) + 1);
|
|
1085
1204
|
}
|
|
1086
|
-
const missing = requiredV2Fields.some((field) =>
|
|
1087
|
-
if (field === "origin_run" && Object.prototype.hasOwnProperty.call(parsed, "origin_feature")) {
|
|
1088
|
-
return false;
|
|
1089
|
-
}
|
|
1090
|
-
return !Object.prototype.hasOwnProperty.call(parsed, field);
|
|
1091
|
-
});
|
|
1205
|
+
const missing = requiredV2Fields.some((field) => !Object.prototype.hasOwnProperty.call(parsed, field));
|
|
1092
1206
|
if (missing) {
|
|
1093
1207
|
missingSchemaV2Fields += 1;
|
|
1094
1208
|
}
|
|
@@ -1388,17 +1502,17 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1388
1502
|
? "no stale stages pending acknowledgement"
|
|
1389
1503
|
: `stale stages pending acknowledgement: ${staleStages.join(", ")}`
|
|
1390
1504
|
});
|
|
1391
|
-
const
|
|
1392
|
-
const retroComplete = !retroRequired ||
|
|
1393
|
-
(typeof flowState.retro.completedAt === "string" && flowState.retro.compoundEntries > 0);
|
|
1505
|
+
const retroGateStatus = await evaluateRetroGate(projectRoot, flowState);
|
|
1394
1506
|
checks.push({
|
|
1395
1507
|
name: "state:retro_gate",
|
|
1396
|
-
ok:
|
|
1397
|
-
details:
|
|
1398
|
-
?
|
|
1399
|
-
?
|
|
1508
|
+
ok: retroGateStatus.completed,
|
|
1509
|
+
details: retroGateStatus.completed
|
|
1510
|
+
? retroGateStatus.required
|
|
1511
|
+
? retroGateStatus.skipped
|
|
1512
|
+
? "retro gate complete (retro skipped with recorded closeout decision)"
|
|
1513
|
+
: `retro gate complete (${retroGateStatus.compoundEntries} compound entries)`
|
|
1400
1514
|
: "retro gate not required yet (ship not completed)"
|
|
1401
|
-
: "retro gate incomplete: ship flow requires recorded retrospective evidence."
|
|
1515
|
+
: "retro gate incomplete: ship flow requires recorded retrospective evidence or an explicit retro skip."
|
|
1402
1516
|
});
|
|
1403
1517
|
const tddLogPath = path.join(projectRoot, RUNTIME_ROOT, "state", "tdd-cycle-log.jsonl");
|
|
1404
1518
|
const tddLogExists = await exists(tddLogPath);
|
|
@@ -1444,6 +1558,18 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1444
1558
|
ok: await exists(path.join(projectRoot, RUNTIME_ROOT, "runs")),
|
|
1445
1559
|
details: `${RUNTIME_ROOT}/runs must exist for archived run snapshots`
|
|
1446
1560
|
});
|
|
1561
|
+
const initRecovery = await initRecoveryCheck(projectRoot);
|
|
1562
|
+
checks.push({
|
|
1563
|
+
name: "state:init_recovery",
|
|
1564
|
+
ok: initRecovery.ok,
|
|
1565
|
+
details: initRecovery.details
|
|
1566
|
+
});
|
|
1567
|
+
const archiveIntegrity = await archiveIntegrityCheck(projectRoot);
|
|
1568
|
+
checks.push({
|
|
1569
|
+
name: "runs:archive_integrity",
|
|
1570
|
+
ok: archiveIntegrity.ok,
|
|
1571
|
+
details: archiveIntegrity.details
|
|
1572
|
+
});
|
|
1447
1573
|
const delegation = await checkMandatoryDelegations(projectRoot, flowState.currentStage, {
|
|
1448
1574
|
repairFeatureSystem: false
|
|
1449
1575
|
});
|
|
@@ -1461,9 +1587,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1461
1587
|
name: "warning:delegation:waived",
|
|
1462
1588
|
ok: true,
|
|
1463
1589
|
details: delegation.waived.length > 0
|
|
1464
|
-
? `warning: waived mandatory delegations for stage "${flowState.currentStage}": ${delegation.waived.join(", ")}
|
|
1465
|
-
? ` (auto-waived due to harness limitation: ${delegation.autoWaived.join(", ")})`
|
|
1466
|
-
: ""}`
|
|
1590
|
+
? `warning: waived mandatory delegations for stage "${flowState.currentStage}": ${delegation.waived.join(", ")}`
|
|
1467
1591
|
: "no waived mandatory delegations for current stage"
|
|
1468
1592
|
});
|
|
1469
1593
|
checks.push({
|
package/dist/gate-evidence.js
CHANGED
|
@@ -336,11 +336,27 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
336
336
|
if (stage === "design") {
|
|
337
337
|
const researchGateRequired = schema.requiredGates.some((gate) => gate.id === "design_research_complete" && gate.tier === "required");
|
|
338
338
|
if (researchGateRequired) {
|
|
339
|
+
const designMarkdown = await readArtifactMarkdown(projectRoot, "03-design.md");
|
|
340
|
+
const inlineResearchBody = designMarkdown
|
|
341
|
+
? extractMarkdownSectionBody(designMarkdown, "Research Fleet Synthesis")
|
|
342
|
+
: null;
|
|
343
|
+
const inlineResearchLines = inlineResearchBody
|
|
344
|
+
? inlineResearchBody
|
|
345
|
+
.split(/\r?\n/gu)
|
|
346
|
+
.map((line) => line.trim())
|
|
347
|
+
.filter((line) => line.length > 0)
|
|
348
|
+
.filter((line) => !/^\|?(?:[-:\s|])+$/u.test(line))
|
|
349
|
+
.filter((line) => !/\b(?:TODO|TBD|FIXME|pending)\b/iu.test(line) &&
|
|
350
|
+
!/<fill-in>/iu.test(line) &&
|
|
351
|
+
!/^>\s*Default path:/iu.test(line) &&
|
|
352
|
+
!/^\|\s*compact inline synthesis\s*\|\s*\|\s*\|\s*\|?\s*$/iu.test(line))
|
|
353
|
+
: [];
|
|
354
|
+
const inlineResearchComplete = inlineResearchLines.length > 0;
|
|
339
355
|
const researchMarkdown = await readArtifactMarkdown(projectRoot, "02a-research.md");
|
|
340
|
-
if (!researchMarkdown) {
|
|
341
|
-
issues.push("design research gate blocked (design_research_complete):
|
|
356
|
+
if (!inlineResearchComplete && !researchMarkdown) {
|
|
357
|
+
issues.push("design research gate blocked (design_research_complete): fill `Research Fleet Synthesis` in `.cclaw/artifacts/03-design.md`, or write `.cclaw/artifacts/02a-research.md` for deep/high-risk research.");
|
|
342
358
|
}
|
|
343
|
-
else {
|
|
359
|
+
else if (researchMarkdown) {
|
|
344
360
|
const missingSections = [];
|
|
345
361
|
for (const section of DESIGN_RESEARCH_REQUIRED_SECTIONS) {
|
|
346
362
|
const body = extractMarkdownSectionBody(researchMarkdown, section);
|
|
@@ -353,10 +369,6 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
353
369
|
.map((line) => line.trim())
|
|
354
370
|
.filter((line) => line.length > 0)
|
|
355
371
|
.filter((line) => !/^\|?(?:[-:\s|])+$/u.test(line));
|
|
356
|
-
// `<fill-in>` needs its own check because `\b` does not match
|
|
357
|
-
// around `<`/`>` (non-word characters), so the previous combined
|
|
358
|
-
// pattern `\b(?:...|<fill-in>)\b` silently never matched placeholder
|
|
359
|
-
// templates that used angle-bracket form.
|
|
360
372
|
const nonPlaceholder = meaningfulLines.filter((line) => !/\b(?:TODO|TBD|FIXME|pending)\b/iu.test(line) &&
|
|
361
373
|
!/<fill-in>/iu.test(line));
|
|
362
374
|
if (nonPlaceholder.length === 0) {
|