cclaw-cli 0.51.24 → 0.51.25
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/README.md +135 -414
- package/dist/artifact-linter.js +10 -6
- package/dist/config.d.ts +1 -1
- package/dist/config.js +28 -3
- package/dist/content/core-agents.d.ts +110 -0
- package/dist/content/core-agents.js +235 -3
- package/dist/content/examples.js +8 -5
- package/dist/content/next-command.js +10 -6
- package/dist/content/reference-patterns.d.ts +18 -0
- package/dist/content/reference-patterns.js +391 -0
- package/dist/content/skills.js +39 -34
- package/dist/content/stage-common-guidance.js +19 -3
- package/dist/content/stage-schema.d.ts +12 -0
- package/dist/content/stage-schema.js +184 -28
- package/dist/content/stages/_lint-metadata/index.js +3 -2
- package/dist/content/stages/brainstorm.js +7 -3
- package/dist/content/stages/design.js +12 -3
- package/dist/content/stages/review.js +7 -5
- package/dist/content/stages/schema-types.d.ts +9 -2
- package/dist/content/stages/scope.js +8 -2
- package/dist/content/stages/ship.js +3 -2
- package/dist/content/stages/tdd.js +18 -13
- package/dist/content/start-command.js +3 -2
- package/dist/content/status-command.js +9 -4
- package/dist/content/subagents.js +281 -39
- package/dist/content/templates.js +64 -3
- package/dist/delegation.d.ts +2 -0
- package/dist/delegation.js +27 -6
- package/dist/doctor.js +47 -5
- package/dist/gate-evidence.js +25 -2
- package/dist/install.js +2 -9
- package/dist/internal/advance-stage.js +179 -26
- package/dist/run-persistence.js +21 -3
- package/dist/tdd-verification-evidence.d.ts +17 -0
- package/dist/tdd-verification-evidence.js +43 -0
- package/dist/types.d.ts +10 -0
- package/package.json +1 -1
|
@@ -29,6 +29,11 @@ export const ARTIFACT_TEMPLATES = {
|
|
|
29
29
|
### Discovered context
|
|
30
30
|
- (paths, prior artifacts, seeds, prompt fragments — referenced by downstream stages, or \`- None.\`)
|
|
31
31
|
|
|
32
|
+
## Reference Pattern Candidates
|
|
33
|
+
| Pattern / source | Reusable invariant | Disposition (accept/reject/defer) | Why |
|
|
34
|
+
|---|---|---|---|
|
|
35
|
+
| | | | |
|
|
36
|
+
|
|
32
37
|
## Problem Decision Record
|
|
33
38
|
- **Depth:** lite | standard | deep
|
|
34
39
|
- **Frame type:** product | technical-maintenance
|
|
@@ -60,6 +65,12 @@ export const ARTIFACT_TEMPLATES = {
|
|
|
60
65
|
## How Might We
|
|
61
66
|
- *How might we …?* — one line naming the user, the desired outcome, and the binding constraint.
|
|
62
67
|
|
|
68
|
+
## Clarity Gate
|
|
69
|
+
- Ambiguity score (0.00-1.00):
|
|
70
|
+
- Decision boundaries (what this stage will decide):
|
|
71
|
+
- Reaffirmed non-goals:
|
|
72
|
+
- Residual-risk handoff to scope:
|
|
73
|
+
|
|
63
74
|
## Sharpening Questions
|
|
64
75
|
> Ask one decision-changing question at a time. For concrete early exits, record \`None - early exit\` with rationale.
|
|
65
76
|
| # | Question | Answer / Assumption | Decision impact |
|
|
@@ -81,7 +92,7 @@ export const ARTIFACT_TEMPLATES = {
|
|
|
81
92
|
- Scope handoff:
|
|
82
93
|
|
|
83
94
|
## Approaches
|
|
84
|
-
| Approach | Role | Upside | Architecture | Trade-offs | Reuses | Recommendation |
|
|
95
|
+
| Approach | Role | Upside | Architecture | Trade-offs | Reuses / reference pattern | Recommendation |
|
|
85
96
|
|---|---|---|---|---|---|---|
|
|
86
97
|
| A | baseline | modest | | | | |
|
|
87
98
|
| B | challenger | high | | | | |
|
|
@@ -195,6 +206,20 @@ ${SEED_SHELF_SECTION}
|
|
|
195
206
|
- **Success definition:**
|
|
196
207
|
- **Design handoff:**
|
|
197
208
|
|
|
209
|
+
## Decision Drivers
|
|
210
|
+
| Driver | Weight (1-5) | Option A | Option B | Option C | Notes |
|
|
211
|
+
|---|---|---|---|---|---|
|
|
212
|
+
| Value impact | | | | | |
|
|
213
|
+
| Risk reduction | | | | | |
|
|
214
|
+
| Reversibility | | | | | |
|
|
215
|
+
| Delivery effort | | | | | |
|
|
216
|
+
| Timeline fit | | | | | |
|
|
217
|
+
|
|
218
|
+
## Scope Completeness Score
|
|
219
|
+
- Score (0.00-1.00):
|
|
220
|
+
- What is still uncertain:
|
|
221
|
+
- Blockers requiring escalation:
|
|
222
|
+
|
|
198
223
|
## Scope Mode
|
|
199
224
|
- [ ] SCOPE EXPANSION — explore ambitious alternatives; user explicitly opts into the larger product slice.
|
|
200
225
|
- [ ] SELECTIVE EXPANSION — hold baseline scope and cherry-pick one high-leverage addition.
|
|
@@ -214,6 +239,11 @@ ${SEED_SHELF_SECTION}
|
|
|
214
239
|
## Taste Calibration
|
|
215
240
|
- Optional quality-bar references from in-repo modules/files.
|
|
216
241
|
|
|
242
|
+
## Reference Pattern Registry
|
|
243
|
+
| Pattern / source | Invariant to preserve | Disposition (accepted/rejected/deferred) | Scope boundary impact |
|
|
244
|
+
|---|---|---|---|
|
|
245
|
+
| | | | |
|
|
246
|
+
|
|
217
247
|
## Reference Pull
|
|
218
248
|
- Optional evidence from \`/Users/zuevrs/Downloads/references\`; list accepted/rejected ideas or \`Not needed - compact scope\`.
|
|
219
249
|
|
|
@@ -359,6 +389,11 @@ ${SEED_SHELF_SECTION}
|
|
|
359
389
|
|---|---|---|---|---|---|---|
|
|
360
390
|
| | | | | | | |
|
|
361
391
|
|
|
392
|
+
## Architecture Decision Record (ADR)
|
|
393
|
+
| ADR ID | Context | Decision | Alternatives considered | Consequences | Reversal trigger |
|
|
394
|
+
|---|---|---|---|---|---|
|
|
395
|
+
| ADR-1 | | | | | |
|
|
396
|
+
|
|
362
397
|
## Search Before Building
|
|
363
398
|
| Layer | Label | What to reuse first |
|
|
364
399
|
|---|---|---|
|
|
@@ -466,11 +501,21 @@ ${MARKDOWN_CODE_FENCE}
|
|
|
466
501
|
|---|---|---|---|
|
|
467
502
|
| | | | |
|
|
468
503
|
|
|
504
|
+
## Pre-mortem
|
|
505
|
+
| Scenario | Earliest warning signal | Mitigation owner | Containment action |
|
|
506
|
+
|---|---|---|---|
|
|
507
|
+
| | | | |
|
|
508
|
+
|
|
469
509
|
## Test Strategy
|
|
470
510
|
- Unit:
|
|
471
511
|
- Integration:
|
|
472
512
|
- E2E:
|
|
473
513
|
|
|
514
|
+
## Test-Diagram Mapping
|
|
515
|
+
| Critical flow | Test coverage (ID/command) | Diagram anchor | Gap status |
|
|
516
|
+
|---|---|---|---|
|
|
517
|
+
| | | | covered/gap |
|
|
518
|
+
|
|
474
519
|
## Performance Budget
|
|
475
520
|
| Critical path | Metric | Target | Measurement method |
|
|
476
521
|
|---|---|---|---|
|
|
@@ -530,6 +575,11 @@ ${MARKDOWN_CODE_FENCE}
|
|
|
530
575
|
|---|---|---|
|
|
531
576
|
| | | |
|
|
532
577
|
|
|
578
|
+
## Reference-Grade Contracts
|
|
579
|
+
| Pattern / source | Reusable invariant | Local adaptation | Rejection boundary | Verification signal |
|
|
580
|
+
|---|---|---|---|---|
|
|
581
|
+
| | | | | |
|
|
582
|
+
|
|
533
583
|
## Interface Contracts
|
|
534
584
|
- Standard/Deep add-on when module boundaries or APIs change; omit for compact local changes.
|
|
535
585
|
| Module | Produces | Consumes |
|
|
@@ -558,6 +608,9 @@ ${SEED_SHELF_SECTION}
|
|
|
558
608
|
|
|
559
609
|
**Decisions made:** 0 | **Unresolved:** 0
|
|
560
610
|
|
|
611
|
+
## Learning Capture Hint
|
|
612
|
+
For meaningful design work, replace the Learnings sentinel with 1-3 JSON learning bullets, for example: \`- {"type":"lesson","trigger":"when design chooses a risky fallback path","action":"record the switch trigger and rollback signal in Spec Handoff","confidence":"medium","domain":"architecture","stage":"design"}\`
|
|
613
|
+
|
|
561
614
|
## Learnings
|
|
562
615
|
- None this stage.
|
|
563
616
|
`,
|
|
@@ -735,7 +788,7 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
735
788
|
|
|
736
789
|
## Execution Posture
|
|
737
790
|
- Posture: sequential | dependency-batched | blocked
|
|
738
|
-
- RED/GREEN/REFACTOR checkpoint plan:
|
|
791
|
+
- Vertical-slice RED/GREEN/REFACTOR checkpoint plan:
|
|
739
792
|
- Incremental commits: yes/no/deferred because
|
|
740
793
|
|
|
741
794
|
## RED Evidence
|
|
@@ -744,7 +797,7 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
744
797
|
| S-1 | | | |
|
|
745
798
|
|
|
746
799
|
## Acceptance Mapping
|
|
747
|
-
|
|
|
800
|
+
| Vertical slice | Source item ID | Spec criterion ID |
|
|
748
801
|
|---|---|---|
|
|
749
802
|
| S-1 | SRC-1 | AC-1 |
|
|
750
803
|
|
|
@@ -793,6 +846,9 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
793
846
|
|---|---|---|---|---|
|
|
794
847
|
| S-1 | | | | |
|
|
795
848
|
|
|
849
|
+
## Learning Capture Hint
|
|
850
|
+
For meaningful TDD work, replace the Learnings sentinel with 1-3 JSON learning bullets, for example: \`- {"type":"pattern","trigger":"when a regression only fails after state rewind","action":"keep the RED fixture and add a cycle-log assertion before GREEN","confidence":"medium","domain":"testing","stage":"tdd"}\`
|
|
851
|
+
|
|
796
852
|
## Learnings
|
|
797
853
|
- None this stage.
|
|
798
854
|
`,
|
|
@@ -853,6 +909,7 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
853
909
|
|
|
854
910
|
## Review Readiness Snapshot
|
|
855
911
|
|
|
912
|
+
- Victory Detector: pass | fail (Layer 1, Layer 2, security sweep, structured findings, trace evidence, unresolved-critical status)
|
|
856
913
|
- Completed checks: Layer 1, Layer 2 tags, security sweep, schema validation
|
|
857
914
|
- Delegation log: \`.cclaw/state/delegation-log.json\` required/completed/waived/pending
|
|
858
915
|
- Staleness signal: commit at last review pass vs current commit
|
|
@@ -893,6 +950,9 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
893
950
|
## Final Verdict
|
|
894
951
|
- APPROVED | APPROVED_WITH_CONCERNS | BLOCKED
|
|
895
952
|
|
|
953
|
+
## Learning Capture Hint
|
|
954
|
+
For meaningful review work, replace the Learnings sentinel with 1-3 JSON learning bullets, for example: \`- {"type":"lesson","trigger":"when security sweep finds no issues but touches trust boundaries","action":"record NO_SECURITY_IMPACT with inspected surfaces and rationale","confidence":"medium","domain":"security","stage":"review"}\`
|
|
955
|
+
|
|
896
956
|
## Learnings
|
|
897
957
|
- None this stage.
|
|
898
958
|
`,
|
|
@@ -961,6 +1021,7 @@ ${SHIP_FINALIZATION_ENUM_LINES}
|
|
|
961
1021
|
- NO_VCS handoff target + artifact path (if FINALIZE_NO_VCS):
|
|
962
1022
|
|
|
963
1023
|
## Completion Status
|
|
1024
|
+
- Victory Detector: pass | fail (review verdict valid, preflight fresh, rollback ready, one finalization enum selected, execution result present)
|
|
964
1025
|
- SHIPPED | SHIPPED_WITH_EXCEPTIONS | BLOCKED
|
|
965
1026
|
- Exceptions (if any):
|
|
966
1027
|
|
package/dist/delegation.d.ts
CHANGED
|
@@ -96,6 +96,8 @@ export declare function checkMandatoryDelegations(projectRoot: string, stage: Fl
|
|
|
96
96
|
staleIgnored: string[];
|
|
97
97
|
/** Delegation rows missing required evidence under a role-switch fallback. */
|
|
98
98
|
missingEvidence: string[];
|
|
99
|
+
/** Current-run scheduled rows with no terminal row sharing the same spanId. */
|
|
100
|
+
staleWorkers: string[];
|
|
99
101
|
/** Expected fulfillment mode for the active harness set. */
|
|
100
102
|
expectedMode: DelegationFulfillmentMode;
|
|
101
103
|
}>;
|
package/dist/delegation.js
CHANGED
|
@@ -9,6 +9,7 @@ import { HARNESS_ADAPTERS } from "./harness-adapters.js";
|
|
|
9
9
|
import { readFlowState } from "./runs.js";
|
|
10
10
|
import { stageSchema } from "./content/stage-schema.js";
|
|
11
11
|
const execFileAsync = promisify(execFile);
|
|
12
|
+
const TERMINAL_DELEGATION_STATUSES = new Set(["completed", "failed", "waived"]);
|
|
12
13
|
function delegationLogPath(projectRoot) {
|
|
13
14
|
return path.join(projectRoot, RUNTIME_ROOT, "state", "delegation-log.json");
|
|
14
15
|
}
|
|
@@ -135,6 +136,11 @@ function isDelegationEntry(value) {
|
|
|
135
136
|
o.status === "waived";
|
|
136
137
|
const timestampOk = typeof o.ts === "string" ||
|
|
137
138
|
typeof o.startTs === "string";
|
|
139
|
+
const terminalStatus = o.status === "completed" || o.status === "failed" || o.status === "waived";
|
|
140
|
+
const lifecycleOk = o.status !== "scheduled" || o.endTs === undefined;
|
|
141
|
+
const terminalLifecycleOk = !terminalStatus ||
|
|
142
|
+
o.endTs === undefined ||
|
|
143
|
+
typeof o.endTs === "string";
|
|
138
144
|
const retryOk = o.retryCount === undefined ||
|
|
139
145
|
(typeof o.retryCount === "number" &&
|
|
140
146
|
Number.isFinite(o.retryCount) &&
|
|
@@ -146,6 +152,8 @@ function isDelegationEntry(value) {
|
|
|
146
152
|
modeOk &&
|
|
147
153
|
statusOk &&
|
|
148
154
|
timestampOk &&
|
|
155
|
+
lifecycleOk &&
|
|
156
|
+
terminalLifecycleOk &&
|
|
149
157
|
(o.spanId === undefined || typeof o.spanId === "string") &&
|
|
150
158
|
(o.parentSpanId === undefined || typeof o.parentSpanId === "string") &&
|
|
151
159
|
(o.startTs === undefined || typeof o.startTs === "string") &&
|
|
@@ -185,6 +193,7 @@ function parseLedger(raw, runId) {
|
|
|
185
193
|
...item,
|
|
186
194
|
spanId: item.spanId ?? createSpanId(),
|
|
187
195
|
startTs: ts,
|
|
196
|
+
endTs: TERMINAL_DELEGATION_STATUSES.has(item.status) ? (item.endTs ?? ts) : undefined,
|
|
188
197
|
ts,
|
|
189
198
|
retryCount: typeof item.retryCount === "number" && Number.isInteger(item.retryCount) && item.retryCount >= 0
|
|
190
199
|
? item.retryCount
|
|
@@ -226,6 +235,12 @@ export async function appendDelegation(projectRoot, entry) {
|
|
|
226
235
|
stamped.spanId = entry.spanId ?? createSpanId();
|
|
227
236
|
stamped.startTs = startTs;
|
|
228
237
|
stamped.ts = startTs;
|
|
238
|
+
if (TERMINAL_DELEGATION_STATUSES.has(stamped.status) && !stamped.endTs) {
|
|
239
|
+
stamped.endTs = new Date().toISOString();
|
|
240
|
+
}
|
|
241
|
+
if (stamped.status === "scheduled") {
|
|
242
|
+
delete stamped.endTs;
|
|
243
|
+
}
|
|
229
244
|
stamped.schemaVersion = 1;
|
|
230
245
|
if (stamped.retryCount === undefined ||
|
|
231
246
|
!Number.isInteger(stamped.retryCount) ||
|
|
@@ -247,11 +262,10 @@ export async function appendDelegation(projectRoot, entry) {
|
|
|
247
262
|
stamped.fulfillmentMode = expectedFulfillmentMode(fallbacks);
|
|
248
263
|
}
|
|
249
264
|
}
|
|
250
|
-
// Idempotency:
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
if (prior.entries.some((existing) => existing.spanId === stamped.spanId)) {
|
|
265
|
+
// Idempotency: a retried hook may replay the same lifecycle row. Allow a
|
|
266
|
+
// terminal row to close an existing scheduled span, but drop exact same
|
|
267
|
+
// span/status duplicates so checks do not mis-count repeated writes.
|
|
268
|
+
if (prior.entries.some((existing) => existing.spanId === stamped.spanId && existing.status === stamped.status)) {
|
|
255
269
|
return;
|
|
256
270
|
}
|
|
257
271
|
const ledger = {
|
|
@@ -293,6 +307,12 @@ export async function checkMandatoryDelegations(projectRoot, stage, options = {}
|
|
|
293
307
|
const missing = [];
|
|
294
308
|
const waived = [];
|
|
295
309
|
const missingEvidence = [];
|
|
310
|
+
const terminalSpanIds = new Set(forRun
|
|
311
|
+
.filter((entry) => TERMINAL_DELEGATION_STATUSES.has(entry.status) && entry.spanId)
|
|
312
|
+
.map((entry) => entry.spanId));
|
|
313
|
+
const staleWorkers = forRun
|
|
314
|
+
.filter((entry) => entry.status === "scheduled" && entry.spanId && !terminalSpanIds.has(entry.spanId))
|
|
315
|
+
.map((entry) => `${entry.agent}(spanId=${entry.spanId})`);
|
|
296
316
|
const config = await readConfig(projectRoot).catch(() => null);
|
|
297
317
|
const harnesses = config?.harnesses ?? [];
|
|
298
318
|
const configuredFallbacks = harnesses.map((h) => HARNESS_ADAPTERS[h].capabilities.subagentFallback);
|
|
@@ -324,11 +344,12 @@ export async function checkMandatoryDelegations(projectRoot, stage, options = {}
|
|
|
324
344
|
}
|
|
325
345
|
}
|
|
326
346
|
return {
|
|
327
|
-
satisfied: missing.length === 0 && missingEvidence.length === 0,
|
|
347
|
+
satisfied: missing.length === 0 && missingEvidence.length === 0 && staleWorkers.length === 0,
|
|
328
348
|
missing,
|
|
329
349
|
waived,
|
|
330
350
|
staleIgnored,
|
|
331
351
|
missingEvidence,
|
|
352
|
+
staleWorkers,
|
|
332
353
|
expectedMode
|
|
333
354
|
};
|
|
334
355
|
}
|
package/dist/doctor.js
CHANGED
|
@@ -131,6 +131,20 @@ async function generatedCliEntrypointsOk(projectRoot) {
|
|
|
131
131
|
: "local CLI entrypoint check skipped because generated hook scripts are absent"
|
|
132
132
|
};
|
|
133
133
|
}
|
|
134
|
+
function expectedArtifactPrefix(stage) {
|
|
135
|
+
const index = FLOW_STAGES.indexOf(stage);
|
|
136
|
+
return `${String(index + 1).padStart(2, "0")}-`;
|
|
137
|
+
}
|
|
138
|
+
function artifactStageFromFileName(fileName) {
|
|
139
|
+
if (!fileName.endsWith(".md"))
|
|
140
|
+
return null;
|
|
141
|
+
for (const stage of FLOW_STAGES) {
|
|
142
|
+
if (fileName.startsWith(expectedArtifactPrefix(stage))) {
|
|
143
|
+
return stage;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
134
148
|
function extractUserPromptFromIdeaArtifact(markdown) {
|
|
135
149
|
const normalized = markdown.replace(/\r\n?/gu, "\n");
|
|
136
150
|
const heading = /^##\s+User prompt\s*$/imu.exec(normalized);
|
|
@@ -1630,13 +1644,21 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1630
1644
|
});
|
|
1631
1645
|
const artifactsRoot = path.join(projectRoot, RUNTIME_ROOT, "artifacts");
|
|
1632
1646
|
let artifactPlaceholderHits = [];
|
|
1647
|
+
let duplicateArtifactGroups = [];
|
|
1633
1648
|
if (await exists(artifactsRoot)) {
|
|
1634
1649
|
try {
|
|
1635
1650
|
const entries = await fs.readdir(artifactsRoot, { withFileTypes: true });
|
|
1636
1651
|
const placeholderPattern = /\b(?:TODO|TBD|FIXME)\b|<fill-in>|<your-.*-here>/giu;
|
|
1652
|
+
const stageArtifactFiles = new Map();
|
|
1637
1653
|
for (const entry of entries) {
|
|
1638
1654
|
if (!entry.isFile() || !entry.name.endsWith(".md"))
|
|
1639
1655
|
continue;
|
|
1656
|
+
const stageForArtifact = artifactStageFromFileName(entry.name);
|
|
1657
|
+
if (stageForArtifact) {
|
|
1658
|
+
const files = stageArtifactFiles.get(stageForArtifact) ?? [];
|
|
1659
|
+
files.push(entry.name);
|
|
1660
|
+
stageArtifactFiles.set(stageForArtifact, files);
|
|
1661
|
+
}
|
|
1640
1662
|
const filePath = path.join(artifactsRoot, entry.name);
|
|
1641
1663
|
const content = await fs.readFile(filePath, "utf8");
|
|
1642
1664
|
const matchCount = (content.match(placeholderPattern) ?? []).length;
|
|
@@ -1644,9 +1666,13 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1644
1666
|
artifactPlaceholderHits.push(`${entry.name}:${matchCount}`);
|
|
1645
1667
|
}
|
|
1646
1668
|
}
|
|
1669
|
+
duplicateArtifactGroups = [...stageArtifactFiles.entries()]
|
|
1670
|
+
.filter(([, files]) => files.length > 1)
|
|
1671
|
+
.map(([stageName, files]) => `${stageName}: ${files.sort().join(", ")}`);
|
|
1647
1672
|
}
|
|
1648
1673
|
catch {
|
|
1649
1674
|
artifactPlaceholderHits = [];
|
|
1675
|
+
duplicateArtifactGroups = [];
|
|
1650
1676
|
}
|
|
1651
1677
|
}
|
|
1652
1678
|
checks.push({
|
|
@@ -1656,13 +1682,20 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1656
1682
|
? "no TODO/TBD/FIXME placeholder markers found in active artifacts"
|
|
1657
1683
|
: `warning: placeholder markers detected in active artifacts (${artifactPlaceholderHits.join(", ")}). Clear before marking completion.`
|
|
1658
1684
|
});
|
|
1685
|
+
checks.push({
|
|
1686
|
+
name: "warning:artifacts:duplicate_stage_artifacts",
|
|
1687
|
+
ok: duplicateArtifactGroups.length === 0,
|
|
1688
|
+
details: duplicateArtifactGroups.length === 0
|
|
1689
|
+
? "no duplicate stage artifacts detected in active artifacts"
|
|
1690
|
+
: `warning: duplicate stage artifacts detected (${duplicateArtifactGroups.join("; ")}). The resolver uses the newest matching file; archive or rename stale copies to avoid ambiguous operator handoff.`
|
|
1691
|
+
});
|
|
1659
1692
|
const staleStages = Object.keys(flowState.staleStages).filter((value) => FLOW_STAGES.includes(value));
|
|
1660
1693
|
checks.push({
|
|
1661
1694
|
name: "state:stale_stages_resolved",
|
|
1662
1695
|
ok: staleStages.length === 0,
|
|
1663
1696
|
details: staleStages.length === 0
|
|
1664
1697
|
? "no stale stages pending acknowledgement"
|
|
1665
|
-
: `stale stages pending acknowledgement: ${staleStages.join(", ")}
|
|
1698
|
+
: `stale stages pending acknowledgement: ${staleStages.join(", ")}. Re-run the current stale stage, then clear it with cclaw internal rewind --ack ${flowState.currentStage}.`
|
|
1666
1699
|
});
|
|
1667
1700
|
const retroGateStatus = await evaluateRetroGate(projectRoot, flowState);
|
|
1668
1701
|
checks.push({
|
|
@@ -1732,18 +1765,27 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1732
1765
|
ok: archiveIntegrity.ok,
|
|
1733
1766
|
details: archiveIntegrity.details
|
|
1734
1767
|
});
|
|
1768
|
+
const currentGateState = flowState.stageGateCatalog[flowState.currentStage];
|
|
1769
|
+
const currentStageUntouched = flowState.completedStages.length === 0 &&
|
|
1770
|
+
flowState.rewinds.length === 0 &&
|
|
1771
|
+
Object.keys(flowState.guardEvidence).length === 0 &&
|
|
1772
|
+
(currentGateState?.passed.length ?? 0) === 0 &&
|
|
1773
|
+
(currentGateState?.blocked.length ?? 0) === 0;
|
|
1735
1774
|
const delegation = await checkMandatoryDelegations(projectRoot, flowState.currentStage, {
|
|
1736
1775
|
repairFeatureSystem: false
|
|
1737
1776
|
});
|
|
1777
|
+
const delegationSatisfiedForDoctor = currentStageUntouched || delegation.satisfied;
|
|
1738
1778
|
const missingEvidenceNote = delegation.missingEvidence && delegation.missingEvidence.length > 0
|
|
1739
1779
|
? ` (role-switch rows without evidenceRefs: ${delegation.missingEvidence.join(", ")})`
|
|
1740
1780
|
: "";
|
|
1741
1781
|
checks.push({
|
|
1742
1782
|
name: "delegation:mandatory:current_stage",
|
|
1743
|
-
ok:
|
|
1744
|
-
details:
|
|
1745
|
-
? `
|
|
1746
|
-
:
|
|
1783
|
+
ok: delegationSatisfiedForDoctor,
|
|
1784
|
+
details: currentStageUntouched
|
|
1785
|
+
? `mandatory delegation check deferred for untouched stage "${flowState.currentStage}"; stage-complete enforces it when work begins`
|
|
1786
|
+
: delegation.satisfied
|
|
1787
|
+
? `All mandatory delegations satisfied for stage "${flowState.currentStage}" (mode: ${delegation.expectedMode})`
|
|
1788
|
+
: `Missing mandatory delegations for stage "${flowState.currentStage}": ${delegation.missing.join(", ")}${missingEvidenceNote}`
|
|
1747
1789
|
});
|
|
1748
1790
|
checks.push({
|
|
1749
1791
|
name: "warning:delegation:waived",
|
package/dist/gate-evidence.js
CHANGED
|
@@ -9,6 +9,7 @@ import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
|
|
|
9
9
|
import { detectPublicApiChanges } from "./internal/detect-public-api-changes.js";
|
|
10
10
|
import { readFlowState, writeFlowState } from "./runs.js";
|
|
11
11
|
import { parseTddCycleLog, validateTddCycleOrder } from "./tdd-cycle.js";
|
|
12
|
+
import { validateTddVerificationEvidence } from "./tdd-verification-evidence.js";
|
|
12
13
|
import { buildTraceMatrix } from "./trace-matrix.js";
|
|
13
14
|
import { FLOW_STAGES } from "./types.js";
|
|
14
15
|
async function currentStageArtifactExists(projectRoot, stage, track) {
|
|
@@ -36,6 +37,22 @@ async function readArtifactMarkdown(projectRoot, artifactFile) {
|
|
|
36
37
|
}
|
|
37
38
|
return null;
|
|
38
39
|
}
|
|
40
|
+
async function readStageArtifactMarkdown(projectRoot, stage, track) {
|
|
41
|
+
const resolved = await resolveArtifactPath(stage, {
|
|
42
|
+
projectRoot,
|
|
43
|
+
track,
|
|
44
|
+
intent: "read"
|
|
45
|
+
});
|
|
46
|
+
if (!(await exists(resolved.absPath))) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return await fs.readFile(resolved.absPath, "utf8");
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
39
56
|
function unique(values) {
|
|
40
57
|
return [...new Set(values)];
|
|
41
58
|
}
|
|
@@ -263,6 +280,12 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
263
280
|
issues.push(`passed gate "${gateId}" is missing guardEvidence entry.`);
|
|
264
281
|
continue;
|
|
265
282
|
}
|
|
283
|
+
if (stage === "tdd" && gateId === "tdd_verified_before_complete") {
|
|
284
|
+
const verification = await validateTddVerificationEvidence(projectRoot, evidence);
|
|
285
|
+
if (!verification.ok) {
|
|
286
|
+
issues.push(`tdd verification gate blocked (${gateId}): ${verification.issues.join(" ")}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
266
289
|
const discoveredCommandIssue = await verifyDiscoveredCommandEvidence(projectRoot, stage, gateId, flowState);
|
|
267
290
|
if (discoveredCommandIssue) {
|
|
268
291
|
issues.push(discoveredCommandIssue);
|
|
@@ -336,7 +359,7 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
336
359
|
if (stage === "design") {
|
|
337
360
|
const researchGateRequired = schema.requiredGates.some((gate) => gate.id === "design_research_complete" && gate.tier === "required");
|
|
338
361
|
if (researchGateRequired) {
|
|
339
|
-
const designMarkdown = await
|
|
362
|
+
const designMarkdown = await readStageArtifactMarkdown(projectRoot, "design", flowState.track);
|
|
340
363
|
const inlineResearchBody = designMarkdown
|
|
341
364
|
? extractMarkdownSectionBody(designMarkdown, "Research Fleet Synthesis")
|
|
342
365
|
: null;
|
|
@@ -354,7 +377,7 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
354
377
|
const inlineResearchComplete = inlineResearchLines.length > 0;
|
|
355
378
|
const researchMarkdown = await readArtifactMarkdown(projectRoot, "02a-research.md");
|
|
356
379
|
if (!inlineResearchComplete && !researchMarkdown) {
|
|
357
|
-
issues.push("design research gate blocked (design_research_complete): fill `Research Fleet Synthesis` in
|
|
380
|
+
issues.push("design research gate blocked (design_research_complete): fill `Research Fleet Synthesis` in the active design artifact, or write `.cclaw/artifacts/02a-research.md` for deep/high-risk research.");
|
|
358
381
|
}
|
|
359
382
|
else if (researchMarkdown) {
|
|
360
383
|
const missingSections = [];
|
package/dist/install.js
CHANGED
|
@@ -23,6 +23,7 @@ import { stageSkillFolder, stageSkillMarkdown } from "./content/skills.js";
|
|
|
23
23
|
import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LANGUAGE_RULE_PACK_GENERATORS, LEGACY_LANGUAGE_RULE_PACK_FOLDERS } from "./content/utility-skills.js";
|
|
24
24
|
import { RESEARCH_PLAYBOOKS } from "./content/research-playbooks.js";
|
|
25
25
|
import { SUBAGENT_CONTEXT_SKILLS } from "./content/subagent-context-skills.js";
|
|
26
|
+
import { CCLAW_AGENTS } from "./content/core-agents.js";
|
|
26
27
|
import { createInitialFlowState } from "./flow-state.js";
|
|
27
28
|
import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
|
|
28
29
|
import { ensureGitignore, removeGitignorePatterns } from "./gitignore.js";
|
|
@@ -1339,15 +1340,7 @@ export async function uninstallCclaw(projectRoot) {
|
|
|
1339
1340
|
}
|
|
1340
1341
|
await removeIfEmpty(codexSkillsRoot);
|
|
1341
1342
|
await removeIfEmpty(path.join(projectRoot, ".agents"));
|
|
1342
|
-
const managedAgentNames =
|
|
1343
|
-
"planner",
|
|
1344
|
-
"product-manager",
|
|
1345
|
-
"critic",
|
|
1346
|
-
"reviewer",
|
|
1347
|
-
"security-reviewer",
|
|
1348
|
-
"test-author",
|
|
1349
|
-
"doc-updater"
|
|
1350
|
-
];
|
|
1343
|
+
const managedAgentNames = CCLAW_AGENTS.map((agent) => agent.name);
|
|
1351
1344
|
for (const agentName of managedAgentNames) {
|
|
1352
1345
|
await removeBestEffort(path.join(projectRoot, ".opencode/agents", `${agentName}.md`));
|
|
1353
1346
|
await removeBestEffort(path.join(projectRoot, ".codex/agents", `${agentName}.toml`));
|