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.
Files changed (37) hide show
  1. package/README.md +135 -414
  2. package/dist/artifact-linter.js +10 -6
  3. package/dist/config.d.ts +1 -1
  4. package/dist/config.js +28 -3
  5. package/dist/content/core-agents.d.ts +110 -0
  6. package/dist/content/core-agents.js +235 -3
  7. package/dist/content/examples.js +8 -5
  8. package/dist/content/next-command.js +10 -6
  9. package/dist/content/reference-patterns.d.ts +18 -0
  10. package/dist/content/reference-patterns.js +391 -0
  11. package/dist/content/skills.js +39 -34
  12. package/dist/content/stage-common-guidance.js +19 -3
  13. package/dist/content/stage-schema.d.ts +12 -0
  14. package/dist/content/stage-schema.js +184 -28
  15. package/dist/content/stages/_lint-metadata/index.js +3 -2
  16. package/dist/content/stages/brainstorm.js +7 -3
  17. package/dist/content/stages/design.js +12 -3
  18. package/dist/content/stages/review.js +7 -5
  19. package/dist/content/stages/schema-types.d.ts +9 -2
  20. package/dist/content/stages/scope.js +8 -2
  21. package/dist/content/stages/ship.js +3 -2
  22. package/dist/content/stages/tdd.js +18 -13
  23. package/dist/content/start-command.js +3 -2
  24. package/dist/content/status-command.js +9 -4
  25. package/dist/content/subagents.js +281 -39
  26. package/dist/content/templates.js +64 -3
  27. package/dist/delegation.d.ts +2 -0
  28. package/dist/delegation.js +27 -6
  29. package/dist/doctor.js +47 -5
  30. package/dist/gate-evidence.js +25 -2
  31. package/dist/install.js +2 -9
  32. package/dist/internal/advance-stage.js +179 -26
  33. package/dist/run-persistence.js +21 -3
  34. package/dist/tdd-verification-evidence.d.ts +17 -0
  35. package/dist/tdd-verification-evidence.js +43 -0
  36. package/dist/types.d.ts +10 -0
  37. 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
- | Slice | Source item ID | Spec criterion ID |
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
 
@@ -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
  }>;
@@ -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: if a caller (or a retried hook) tries to append a row
251
- // with a spanId that already exists in the ledger, treat it as a no-op
252
- // instead of growing the log with duplicate entries that subsequent
253
- // delegation checks would mis-count.
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: delegation.satisfied,
1744
- details: delegation.satisfied
1745
- ? `All mandatory delegations satisfied for stage "${flowState.currentStage}" (mode: ${delegation.expectedMode})`
1746
- : `Missing mandatory delegations for stage "${flowState.currentStage}": ${delegation.missing.join(", ")}${missingEvidenceNote}`
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",
@@ -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 readArtifactMarkdown(projectRoot, "03-design.md");
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 `.cclaw/artifacts/03-design.md`, or write `.cclaw/artifacts/02a-research.md` for deep/high-risk research.");
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`));