cclaw-cli 6.14.3 → 7.0.0

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 (86) hide show
  1. package/README.md +0 -2
  2. package/dist/artifact-linter/brainstorm.js +1 -1
  3. package/dist/artifact-linter/design.js +2 -2
  4. package/dist/artifact-linter/findings-dedup.js +1 -1
  5. package/dist/artifact-linter/plan.js +6 -6
  6. package/dist/artifact-linter/review-army.d.ts +1 -1
  7. package/dist/artifact-linter/review-army.js +1 -1
  8. package/dist/artifact-linter/scope.js +6 -6
  9. package/dist/artifact-linter/shared.d.ts +37 -73
  10. package/dist/artifact-linter/shared.js +30 -37
  11. package/dist/artifact-linter/spec.js +1 -1
  12. package/dist/artifact-linter/tdd.d.ts +20 -33
  13. package/dist/artifact-linter/tdd.js +89 -617
  14. package/dist/artifact-linter.js +11 -32
  15. package/dist/cli.js +1 -1
  16. package/dist/config.js +1 -1
  17. package/dist/constants.js +1 -1
  18. package/dist/content/core-agents.d.ts +8 -26
  19. package/dist/content/core-agents.js +48 -94
  20. package/dist/content/examples.d.ts +1 -1
  21. package/dist/content/examples.js +4 -4
  22. package/dist/content/hooks.js +62 -149
  23. package/dist/content/idea.js +2 -2
  24. package/dist/content/iron-laws.js +1 -1
  25. package/dist/content/node-hooks.js +2 -2
  26. package/dist/content/skills-elicitation.js +2 -2
  27. package/dist/content/skills.d.ts +4 -6
  28. package/dist/content/skills.js +14 -53
  29. package/dist/content/stage-schema.d.ts +3 -3
  30. package/dist/content/stage-schema.js +8 -46
  31. package/dist/content/stages/brainstorm.js +5 -5
  32. package/dist/content/stages/plan.js +2 -2
  33. package/dist/content/stages/review.js +1 -1
  34. package/dist/content/stages/schema-types.d.ts +1 -1
  35. package/dist/content/stages/scope.js +1 -1
  36. package/dist/content/stages/spec.js +2 -2
  37. package/dist/content/stages/tdd.js +43 -108
  38. package/dist/content/start-command.js +3 -3
  39. package/dist/content/subagent-context-skills.js +5 -3
  40. package/dist/content/subagents.js +13 -74
  41. package/dist/content/templates.d.ts +6 -6
  42. package/dist/content/templates.js +23 -24
  43. package/dist/content/utility-skills.d.ts +1 -1
  44. package/dist/content/utility-skills.js +1 -1
  45. package/dist/delegation.d.ts +79 -139
  46. package/dist/delegation.js +83 -215
  47. package/dist/early-loop.js +1 -1
  48. package/dist/flow-state.d.ts +24 -129
  49. package/dist/flow-state.js +5 -30
  50. package/dist/gate-evidence.d.ts +2 -7
  51. package/dist/gate-evidence.js +2 -59
  52. package/dist/harness-adapters.d.ts +1 -1
  53. package/dist/harness-adapters.js +11 -10
  54. package/dist/install.js +24 -459
  55. package/dist/internal/advance-stage/advance.d.ts +5 -5
  56. package/dist/internal/advance-stage/advance.js +9 -24
  57. package/dist/internal/advance-stage/parsers.d.ts +1 -1
  58. package/dist/internal/advance-stage/review-loop.d.ts +1 -1
  59. package/dist/internal/advance-stage/review-loop.js +3 -3
  60. package/dist/internal/advance-stage/start-flow.js +1 -3
  61. package/dist/internal/advance-stage.js +4 -23
  62. package/dist/internal/cohesion-contract-stub.d.ts +8 -13
  63. package/dist/internal/cohesion-contract-stub.js +18 -24
  64. package/dist/internal/flow-state-repair.d.ts +1 -1
  65. package/dist/internal/plan-split-waves.d.ts +44 -7
  66. package/dist/internal/plan-split-waves.js +113 -12
  67. package/dist/internal/wave-status.d.ts +3 -6
  68. package/dist/internal/wave-status.js +5 -27
  69. package/dist/policy.js +1 -1
  70. package/dist/run-persistence.js +10 -44
  71. package/dist/runtime/run-hook.mjs +3 -3
  72. package/dist/track-heuristics.js +1 -1
  73. package/dist/types.d.ts +2 -2
  74. package/package.json +1 -1
  75. package/dist/integration-fanin.d.ts +0 -44
  76. package/dist/integration-fanin.js +0 -180
  77. package/dist/internal/set-checkpoint-mode.d.ts +0 -16
  78. package/dist/internal/set-checkpoint-mode.js +0 -72
  79. package/dist/internal/set-integration-overseer-mode.d.ts +0 -14
  80. package/dist/internal/set-integration-overseer-mode.js +0 -69
  81. package/dist/internal/set-worktree-mode.d.ts +0 -10
  82. package/dist/internal/set-worktree-mode.js +0 -28
  83. package/dist/worktree-manager.d.ts +0 -50
  84. package/dist/worktree-manager.js +0 -136
  85. package/dist/worktree-types.d.ts +0 -36
  86. package/dist/worktree-types.js +0 -6
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { resolveArtifactPath } from "../../artifact-paths.js";
4
4
  import { appendDelegation, checkMandatoryDelegations, readDelegationEvents, readDelegationLedger } from "../../delegation.js";
5
- import { verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence, verifyTddWorktreeFanInClosure } from "../../gate-evidence.js";
5
+ import { verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence } from "../../gate-evidence.js";
6
6
  import { extractMarkdownSectionBody, learningsParseFailureHumanSummary, parseLearningsSection } from "../../artifact-linter.js";
7
7
  import { getAvailableTransitions, getTransitionGuards } from "../../flow-state.js";
8
8
  import { appendKnowledge } from "../../knowledge-store.js";
@@ -13,7 +13,6 @@ import { unique } from "./helpers.js";
13
13
  import { AUTO_REVIEW_LOOP_GATE_BY_STAGE, reviewLoopArtifactFixHint, reviewLoopEnvelopeExample, validateGateEvidenceShape } from "./review-loop.js";
14
14
  import { ensureProactiveDelegationTrace } from "./proactive-delegation-trace.js";
15
15
  import { consumeWaiverToken } from "../waiver-grant.js";
16
- import { runTddDeterministicFanInBeforeAdvance } from "../../integration-fanin.js";
17
16
  function resolveSuccessorTransition(stage, track, transitionTargets, satisfiedGuards, selectedTransitionGuards) {
18
17
  const natural = transitionTargets[0] ?? null;
19
18
  const specialTargets = transitionTargets.filter((target) => target !== natural);
@@ -60,16 +59,16 @@ function nextInteractionHints(flowState, args, successor) {
60
59
  return hints;
61
60
  }
62
61
  /**
63
- * Wave 24 entry point — auto-hydrate evidence for an auto-hydratable
62
+ * entry point — auto-hydrate evidence for an auto-hydratable
64
63
  * gate that the agent already included in --passed but for which they
65
64
  * forgot to provide --evidence-json. Returns silently when no
66
65
  * hydration is possible (no auto-hydratable gate, no artifact, no
67
66
  * envelope, etc.).
68
67
  *
69
- * Wave 25 (v6.1.0) layered `tryAutoHydrateAndSelectReviewLoopGate` on
68
+ * layered `tryAutoHydrateAndSelectReviewLoopGate` on
70
69
  * top of this so the gate is also auto-included in selectedGateIds
71
70
  * when the artifact yields a valid envelope. Together the two helpers
72
- * remove the contradiction the user reported in Wave 24:
71
+ * remove the contradiction:
73
72
  * - "omit this gate from --evidence-json so stage-complete can
74
73
  * auto-hydrate it" → "missing --evidence-json entries for passed
75
74
  * gates: design_diagram_freshness".
@@ -105,7 +104,7 @@ export async function hydrateReviewLoopEvidenceFromArtifact(projectRoot, stage,
105
104
  evidenceByGate[gateId] = JSON.stringify(envelope);
106
105
  }
107
106
  /**
108
- * Wave 25 (v6.1.0) — auto-include an auto-hydratable review-loop gate
107
+ * auto-include an auto-hydratable review-loop gate
109
108
  * in `selectedGateIds` when:
110
109
  * - The stage has an auto-hydratable gate registered via
111
110
  * `AUTO_REVIEW_LOOP_GATE_BY_STAGE` (currently `design`).
@@ -162,7 +161,7 @@ export async function tryAutoHydrateAndSelectReviewLoopGate(projectRoot, stage,
162
161
  return [...selectedGateIds, gateId];
163
162
  }
164
163
  export async function buildValidationReport(projectRoot, flowState, options = {}) {
165
- // Wave 24 follow-up (v6.1.1): forward `flowState.taskClass` so the
164
+ // forward `flowState.taskClass` so the
166
165
  // bugfix-skip lights up via the `cclaw advance-stage` path. The
167
166
  // delegation helper now has its own fallback (it reads `flowState`
168
167
  // internally), but threading the value here keeps the call site
@@ -314,11 +313,11 @@ export async function runAdvanceStage(projectRoot, args, io) {
314
313
  let selectedGateIds = args.passedGateIds.length > 0
315
314
  ? args.passedGateIds.filter((gateId) => selectableGateIds.has(gateId))
316
315
  : requiredGateIds;
317
- // Wave 25 (v6.1.0): if the active stage has an auto-hydratable
316
+ // if the active stage has an auto-hydratable
318
317
  // review-loop gate (currently `design.design_architecture_locked`)
319
318
  // and the artifact already contains a valid review-loop envelope,
320
319
  // include the gate in selectedGateIds and hydrate evidence in one
321
- // step. This removes the Wave 24 contradiction between "omit from
320
+ // step. This removes the contradiction between "omit from
322
321
  // --evidence-json so we can auto-hydrate" and "missing
323
322
  // --evidence-json entries for passed gates".
324
323
  selectedGateIds = await tryAutoHydrateAndSelectReviewLoopGate(projectRoot, args.stage, flowState.track, requiredGateIds, selectedGateIds, args.evidenceByGate);
@@ -363,7 +362,7 @@ export async function runAdvanceStage(projectRoot, args, io) {
363
362
  });
364
363
  }
365
364
  }
366
- // Wave 25 (v6.1.0): hydration + auto-select happens earlier via
365
+ // hydration + auto-select happens earlier via
367
366
  // `tryAutoHydrateAndSelectReviewLoopGate`. The previous explicit
368
367
  // call here was redundant (helper already covered both the
369
368
  // already-selected and not-yet-selected paths).
@@ -603,20 +602,6 @@ export async function runAdvanceStage(projectRoot, args, io) {
603
602
  }
604
603
  const satisfiedGuards = new Set([...nextPassed, ...selectedTransitionGuards]);
605
604
  const successor = resolveSuccessorTransition(args.stage, flowState.track, transitionTargets, satisfiedGuards, new Set(selectedTransitionGuards));
606
- if (args.stage === "tdd" && successor !== null && successor !== "tdd") {
607
- const fanIn = await runTddDeterministicFanInBeforeAdvance(projectRoot, flowState);
608
- if (!fanIn.ok) {
609
- io.stderr.write(`cclaw internal advance-stage: deterministic worktree fan-in failed:\n${fanIn.issues
610
- .map((line) => ` - ${line}`)
611
- .join("\n")}\n`);
612
- return 1;
613
- }
614
- const closure = await verifyTddWorktreeFanInClosure(projectRoot, flowState);
615
- if (closure.length > 0) {
616
- io.stderr.write(`cclaw internal advance-stage: ${closure.join(" | ")}\n`);
617
- return 1;
618
- }
619
- }
620
605
  const completedStages = blockedReviewRoute
621
606
  ? flowState.completedStages.filter((finished) => finished !== args.stage)
622
607
  : flowState.completedStages.includes(args.stage)
@@ -49,7 +49,7 @@ export interface StartFlowArgs {
49
49
  reclassify: boolean;
50
50
  quiet: boolean;
51
51
  /**
52
- * Wave 23 (v5.0.0) — `/cc-ideate` handoff carry-forward.
52
+ * `/cc-ideate` handoff carry-forward.
53
53
  * Workspace-relative POSIX path to `.cclaw/ideas/idea-YYYY-MM-DD-<slug>.md`
54
54
  * (or wherever `/cc-ideate` wrote its artifact).
55
55
  */
@@ -1,7 +1,7 @@
1
1
  import type { FlowStage } from "../../types.js";
2
2
  export declare const AUTO_REVIEW_LOOP_GATE_BY_STAGE: Partial<Record<FlowStage, string>>;
3
3
  /**
4
- * Wave 25 (v6.1.0) — exact JSON shape that gate-evidence validators
4
+ * exact JSON shape that gate-evidence validators
5
5
  * accept for a review-loop envelope. The error messages emitted by
6
6
  * `validateReviewLoopGateEvidence` always include this example so the
7
7
  * agent never has to guess where `stage` lives (top-level of the
@@ -12,7 +12,7 @@ const REVIEW_LOOP_STOP_REASONS = new Set([
12
12
  "user_opt_out"
13
13
  ]);
14
14
  /**
15
- * Wave 25 (v6.1.0) — exact JSON shape that gate-evidence validators
15
+ * exact JSON shape that gate-evidence validators
16
16
  * accept for a review-loop envelope. The error messages emitted by
17
17
  * `validateReviewLoopGateEvidence` always include this example so the
18
18
  * agent never has to guess where `stage` lives (top-level of the
@@ -183,9 +183,9 @@ export async function validateGateEvidenceShape(projectRoot, stage, gateId, evid
183
183
  export function reviewLoopArtifactFixHint(stage, gateId) {
184
184
  if (AUTO_REVIEW_LOOP_GATE_BY_STAGE[stage] !== gateId)
185
185
  return "";
186
- // Wave 25 (v6.1.0): the consistent flow is "include the gate in
186
+ // the consistent flow is "include the gate in
187
187
  // --passed AND let stage-complete auto-hydrate evidence from the
188
- // artifact". Wave 24's hint told agents to omit the gate from
188
+ // artifact". An older hint told agents to omit the gate from
189
189
  // --evidence-json, but they then hit
190
190
  // `missing --evidence-json entries for passed gates: <gateId>`
191
191
  // because hydration only runs when --evidence-json is also present
@@ -175,8 +175,7 @@ export async function runStartFlow(projectRoot, args, io) {
175
175
  guardEvidence,
176
176
  stageGateCatalog,
177
177
  rewinds: current.rewinds,
178
- staleStages: current.staleStages,
179
- worktreeExecutionMode: current.worktreeExecutionMode ?? "worktree-first"
178
+ staleStages: current.staleStages
180
179
  };
181
180
  const validation = await buildValidationReport(projectRoot, nextState);
182
181
  const evidenceIssues = completedStageClosureEvidenceIssues(nextState);
@@ -194,7 +193,6 @@ export async function runStartFlow(projectRoot, args, io) {
194
193
  if (nextTaskClass !== undefined) {
195
194
  nextState = { ...nextState, taskClass: nextTaskClass };
196
195
  }
197
- nextState = { ...nextState, worktreeExecutionMode: "worktree-first" };
198
196
  }
199
197
  if (args.fromIdeaArtifact) {
200
198
  const existingHints = nextState.interactionHints ?? {};
@@ -14,11 +14,8 @@ import { parseAdvanceStageArgs, parseCancelRunArgs, parseHookArgs, parseRewindAr
14
14
  import { parseFlowStateRepairArgs, runFlowStateRepair } from "./flow-state-repair.js";
15
15
  import { parseWaiverGrantArgs, runWaiverGrant } from "./waiver-grant.js";
16
16
  import { FlowStateGuardMismatchError, verifyFlowStateGuard } from "../run-persistence.js";
17
- import { DelegationTimestampError, DispatchCapError, DispatchClaimInvalidError, DispatchDuplicateError, DispatchOverlapError } from "../delegation.js";
17
+ import { DelegationTimestampError, DispatchCapError, DispatchDuplicateError, DispatchOverlapError } from "../delegation.js";
18
18
  import { parsePlanSplitWavesArgs, runPlanSplitWaves } from "./plan-split-waves.js";
19
- import { runSetWorktreeMode } from "./set-worktree-mode.js";
20
- import { runSetCheckpointMode } from "./set-checkpoint-mode.js";
21
- import { runSetIntegrationOverseerMode } from "./set-integration-overseer-mode.js";
22
19
  import { runWaveStatusCommand } from "./wave-status.js";
23
20
  import { runCohesionContractCommand } from "./cohesion-contract-stub.js";
24
21
  /**
@@ -33,15 +30,12 @@ const GUARD_ENFORCED_SUBCOMMANDS = new Set([
33
30
  "cancel-run",
34
31
  "rewind",
35
32
  "verify-flow-state-diff",
36
- "verify-current-state",
37
- "set-worktree-mode",
38
- "set-checkpoint-mode",
39
- "set-integration-overseer-mode"
33
+ "verify-current-state"
40
34
  ]);
41
35
  export async function runInternalCommand(projectRoot, argv, io) {
42
36
  const [subcommand, ...tokens] = argv;
43
37
  if (!subcommand) {
44
- io.stderr.write("cclaw internal requires a subcommand: advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves | set-worktree-mode | set-checkpoint-mode | set-integration-overseer-mode | wave-status | cohesion-contract\n");
38
+ io.stderr.write("cclaw internal requires a subcommand: advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves | wave-status | cohesion-contract\n");
45
39
  return 1;
46
40
  }
47
41
  try {
@@ -96,22 +90,13 @@ export async function runInternalCommand(projectRoot, argv, io) {
96
90
  if (subcommand === "plan-split-waves") {
97
91
  return await runPlanSplitWaves(projectRoot, parsePlanSplitWavesArgs(tokens), io);
98
92
  }
99
- if (subcommand === "set-worktree-mode") {
100
- return await runSetWorktreeMode(projectRoot, tokens, io);
101
- }
102
- if (subcommand === "set-checkpoint-mode") {
103
- return await runSetCheckpointMode(projectRoot, tokens, io);
104
- }
105
- if (subcommand === "set-integration-overseer-mode") {
106
- return await runSetIntegrationOverseerMode(projectRoot, tokens, io);
107
- }
108
93
  if (subcommand === "wave-status") {
109
94
  return await runWaveStatusCommand(projectRoot, tokens, io);
110
95
  }
111
96
  if (subcommand === "cohesion-contract") {
112
97
  return await runCohesionContractCommand(projectRoot, tokens, io);
113
98
  }
114
- io.stderr.write(`Unknown internal subcommand: ${subcommand}. Expected advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves | set-worktree-mode | set-checkpoint-mode | set-integration-overseer-mode | wave-status | cohesion-contract\n`);
99
+ io.stderr.write(`Unknown internal subcommand: ${subcommand}. Expected advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves | wave-status | cohesion-contract\n`);
115
100
  return 1;
116
101
  }
117
102
  catch (err) {
@@ -135,10 +120,6 @@ export async function runInternalCommand(projectRoot, argv, io) {
135
120
  io.stderr.write(`error: dispatch_cap — ${err.message}\n`);
136
121
  return 2;
137
122
  }
138
- if (err instanceof DispatchClaimInvalidError) {
139
- io.stderr.write(`error: dispatch_claim_invalid — ${err.message}\n`);
140
- return 2;
141
- }
142
123
  io.stderr.write(`cclaw internal ${subcommand} failed: ${err instanceof Error ? err.message : String(err)}\n`);
143
124
  return 1;
144
125
  }
@@ -10,20 +10,15 @@ export interface CohesionContractArgs {
10
10
  }
11
11
  export declare function parseCohesionContractArgs(tokens: string[]): CohesionContractArgs | null;
12
12
  /**
13
- * v6.14.2 — emit a minimal advisory cohesion contract that satisfies
14
- * the linter shape check (`cohesion-contract.{md,json}`) so projects
15
- * with `legacyContinuation: true` and 2 completed slice-implementer
16
- * rows can clear the soft `tdd.cohesion_contract_missing` finding
17
- * without hand-authoring the document.
13
+ * Scaffold a minimal cohesion-contract pair (`cohesion-contract.{md,json}`)
14
+ * so authors have a starting point when a multi-slice wave needs cross-slice
15
+ * cohesion documentation. The stub seeds `sharedTypes`, `touchpoints`, and
16
+ * `slices` from the active run delegation ledger and carries
17
+ * `status.verdict: "scaffold"` so reviewers know to fill in the real content
18
+ * before treating it as authoritative.
18
19
  *
19
- * The stub is intentionally bare `sharedTypes`, `touchpoints`, and
20
- * `slices` are populated from the active run delegation ledger so
21
- * downstream tooling can see which slices the contract acknowledges,
22
- * but the contract carries `status.verdict: "advisory_legacy"` so
23
- * reviewers know not to treat it as authoritative.
24
- *
25
- * Refuses to overwrite an existing contract unless `--force` is
26
- * passed; the existing file is treated as authored work.
20
+ * Refuses to overwrite an existing contract unless `--force` is passed; the
21
+ * existing file is treated as authored work.
27
22
  */
28
23
  export declare function runCohesionContractCommand(projectRoot: string, tokens: string[], io: InternalIo): Promise<number>;
29
24
  export {};
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { RUNTIME_ROOT } from "../constants.js";
4
4
  import { writeFileSafe } from "../fs-utils.js";
5
- import { readDelegationLedger } from "../delegation.js";
5
+ import { readDelegationLedger, isParallelTddSliceWorker } from "../delegation.js";
6
6
  export function parseCohesionContractArgs(tokens) {
7
7
  const args = { stub: false, force: false, reason: null };
8
8
  for (const token of tokens) {
@@ -27,20 +27,15 @@ export function parseCohesionContractArgs(tokens) {
27
27
  return args;
28
28
  }
29
29
  /**
30
- * v6.14.2 — emit a minimal advisory cohesion contract that satisfies
31
- * the linter shape check (`cohesion-contract.{md,json}`) so projects
32
- * with `legacyContinuation: true` and 2 completed slice-implementer
33
- * rows can clear the soft `tdd.cohesion_contract_missing` finding
34
- * without hand-authoring the document.
30
+ * Scaffold a minimal cohesion-contract pair (`cohesion-contract.{md,json}`)
31
+ * so authors have a starting point when a multi-slice wave needs cross-slice
32
+ * cohesion documentation. The stub seeds `sharedTypes`, `touchpoints`, and
33
+ * `slices` from the active run delegation ledger and carries
34
+ * `status.verdict: "scaffold"` so reviewers know to fill in the real content
35
+ * before treating it as authoritative.
35
36
  *
36
- * The stub is intentionally bare `sharedTypes`, `touchpoints`, and
37
- * `slices` are populated from the active run delegation ledger so
38
- * downstream tooling can see which slices the contract acknowledges,
39
- * but the contract carries `status.verdict: "advisory_legacy"` so
40
- * reviewers know not to treat it as authoritative.
41
- *
42
- * Refuses to overwrite an existing contract unless `--force` is
43
- * passed; the existing file is treated as authored work.
37
+ * Refuses to overwrite an existing contract unless `--force` is passed; the
38
+ * existing file is treated as authored work.
44
39
  */
45
40
  export async function runCohesionContractCommand(projectRoot, tokens, io) {
46
41
  const parsed = parseCohesionContractArgs(tokens);
@@ -63,13 +58,12 @@ export async function runCohesionContractCommand(projectRoot, tokens, io) {
63
58
  const sliceIds = collectSliceIds(ledger?.entries ?? []);
64
59
  const reasonNote = parsed.reason
65
60
  ? `Reason: ${parsed.reason}`
66
- : "Reason: legacyContinuation auto-stub.";
61
+ : "Reason: scaffold for a multi-slice wave that needs cohesion documentation.";
67
62
  const md = [
68
63
  "# Cohesion Contract",
69
64
  "",
70
- "_Advisory stub generated by `cclaw-cli internal cohesion-contract --stub`._",
71
- "_Status: `advisory_legacy` — populated for legacyContinuation projects so the_",
72
- "_TDD linter does not block stage-complete on `tdd.cohesion_contract_missing`._",
65
+ "_Scaffold generated by `cclaw-cli internal cohesion-contract --stub`._",
66
+ "_Status: `scaffold` — replace placeholders with real cross-slice data before review._",
73
67
  "",
74
68
  `${reasonNote}`,
75
69
  "",
@@ -110,21 +104,21 @@ export async function runCohesionContractCommand(projectRoot, tokens, io) {
110
104
  touchpoints: [],
111
105
  slices: sliceIds.map((sid) => ({
112
106
  sliceId: sid,
113
- description: `Stub entry for ${sid}; advisory under legacyContinuation.`,
107
+ description: `Scaffold entry for ${sid}; replace with real behavior summary.`,
114
108
  implemented: true,
115
109
  testsPass: true,
116
110
  cohesionVerified: false
117
111
  })),
118
112
  status: {
119
- verdict: "advisory_legacy",
113
+ verdict: "scaffold",
120
114
  generatedBy: "cclaw-cli internal cohesion-contract --stub",
121
- reason: parsed.reason ?? "legacyContinuation auto-stub"
115
+ reason: parsed.reason ?? "scaffold for a multi-slice wave"
122
116
  }
123
117
  };
124
118
  await writeFileSafe(mdPath, md);
125
119
  await writeFileSafe(jsonPath, `${JSON.stringify(jsonStub, null, 2)}\n`);
126
- io.stdout.write(`cclaw: cohesion-contract stub written (${sliceIds.length} slice(s) referenced). ` +
127
- "Status: advisory_legacyreview and replace once cross-slice cohesion data is real.\n");
120
+ io.stdout.write(`cclaw: cohesion-contract scaffold written (${sliceIds.length} slice(s) referenced). ` +
121
+ "Status: scaffoldreplace placeholders with real cross-slice data.\n");
128
122
  return 0;
129
123
  }
130
124
  async function fileExists(filePath) {
@@ -139,7 +133,7 @@ async function fileExists(filePath) {
139
133
  function collectSliceIds(entries) {
140
134
  const set = new Set();
141
135
  for (const entry of entries) {
142
- if (entry.agent !== "slice-implementer")
136
+ if (!isParallelTddSliceWorker(entry.agent ?? ""))
143
137
  continue;
144
138
  if (entry.status !== "completed")
145
139
  continue;
@@ -8,7 +8,7 @@ export interface FlowStateRepairArgs {
8
8
  json: boolean;
9
9
  quiet: boolean;
10
10
  /**
11
- * v6.9.0 — when true, normalize `state/early-loop.json` to the canonical
11
+ * when true, normalize `state/early-loop.json` to the canonical
12
12
  * shape derived from `early-loop-log.jsonl`. Lets operators recover from
13
13
  * legacy hand-written `early-loop.json` files that drifted from the
14
14
  * source-of-truth log.
@@ -4,7 +4,7 @@ interface InternalIo {
4
4
  stderr: Writable;
5
5
  }
6
6
  /**
7
- * v6.10.0 (P3) — split a large `05-plan.md` Implementation Units section
7
+ * split a large `05-plan.md` Implementation Units section
8
8
  * into wave-NN.md sub-files so an executor can carry one wave at a time
9
9
  * without re-reading the whole plan.
10
10
  *
@@ -31,7 +31,7 @@ export interface PlanSplitWavesArgs {
31
31
  }
32
32
  export declare const PLAN_SPLIT_DEFAULT_WAVE_SIZE = 5;
33
33
  export declare const PLAN_SPLIT_SMALL_PLAN_THRESHOLD = 50;
34
- /** v6.13.1 — member line in Parallel Execution Plan or wave-NN.md */
34
+ /** Member line in Parallel Execution Plan or wave-NN.md */
35
35
  export interface ParsedParallelWaveMember {
36
36
  sliceId: string;
37
37
  unitId: string;
@@ -57,8 +57,45 @@ export declare function extractParallelExecutionManagedBody(planMarkdown: string
57
57
  */
58
58
  export declare function extractMembersListFromLine(trimmedLine: string): string | null;
59
59
  /**
60
- * Parse `## Parallel Execution Plan` managed block for wave headings and Members lines.
61
- * Malformed member tokens are skipped. Duplicate slice ids in one plan source throw.
60
+ * Extract a `(sliceId, unitId)` pair from a markdown table data row
61
+ * whose first column is an `S-NN` token. Used by the wave parser to
62
+ * recognize the table-format Parallel Execution Plan alongside (or
63
+ * instead of) the `**Members:**` bullet line.
64
+ *
65
+ * Rules:
66
+ * - The line must start with `|` (after trimming).
67
+ * - Column 1 (after stripping markdown noise) must match `^S-(\d+)$` —
68
+ * header rows (`| sliceId | …`) and separator rows (`|---|---|…`) are
69
+ * silently skipped.
70
+ * - Column 2, when present and non-empty, becomes the `unitId`
71
+ * verbatim (after stripping whitespace + backticks/quotes/brackets).
72
+ * This lets authors record task ids (`T-010`, `T-008a`, …) in the
73
+ * `unit` column without forcing a `U-NN` derivation.
74
+ * - When column 2 is absent or empty, fall back to the
75
+ * `S-NN → U-NN` derivation so the `**Members:**` parser path stays
76
+ * bit-identical for non-table plans.
77
+ */
78
+ export declare function parseTableRowMember(trimmedLine: string): ParsedParallelWaveMember | null;
79
+ /**
80
+ * Parse `## Parallel Execution Plan` managed block for wave headings and
81
+ * member declarations. Recognizes BOTH the `**Members:**` / `Members:`
82
+ * line shape AND the markdown-table shape
83
+ * (`| sliceId | unit | dependsOn | …`).
84
+ *
85
+ * Wave headings accepted (case-insensitive, trailing text allowed):
86
+ * - `### Wave 04`
87
+ * - `### Wave W-04`
88
+ * - `### Wave W-04 — after fan-in W-03 (5 lanes …)`
89
+ *
90
+ * Within a single wave the parser dedupes by `sliceId`: if the same
91
+ * slice appears in both `**Members:**` and a table row, the first
92
+ * occurrence wins (line-order). Cross-wave duplicates still throw
93
+ * `WavePlanDuplicateSliceError`.
94
+ *
95
+ * Malformed member tokens are skipped. Empty waves (heading present
96
+ * but neither a Members line nor any matching `| S-NN |` row found
97
+ * before the next heading) are RETURNED with `members: []` so callers
98
+ * can surface the boundary; classification is up to the caller.
62
99
  */
63
100
  export declare function parseParallelExecutionPlanWaves(planMarkdown: string): ParsedParallelWave[];
64
101
  /**
@@ -98,20 +135,20 @@ export interface ImplementationUnitParallelFields {
98
135
  }
99
136
  export interface ParseImplementationUnitParallelOptions {
100
137
  /**
101
- * Legacy continuation (v6.13.0): when the plan predates explicit parallel
138
+ * Continuation: when the plan predates explicit parallel
102
139
  * bullets, units without a `parallelizable:` line default to serial eligibility
103
140
  * in the scheduler (`parallelizable: false`).
104
141
  */
105
142
  legacyParallelDefaultSerial?: boolean;
106
143
  }
107
144
  /**
108
- * Parse v6.13 parallel-metadata bullets from an implementation unit body.
145
+ * Parse parallel-metadata bullets from an implementation unit body.
109
146
  * Missing keys use conservative defaults (`dependsOn: []`, `parallelizable: true`
110
147
  * unless `legacyParallelDefaultSerial` is set).
111
148
  */
112
149
  export declare function parseImplementationUnitParallelFields(unit: ParsedImplementationUnit, options?: ParseImplementationUnitParallelOptions): ImplementationUnitParallelFields;
113
150
  /**
114
- * True when the plan has implementation units but any unit is missing v6.13.0
151
+ * True when the plan has implementation units but any unit is missing
115
152
  * `dependsOn` / `claimedPaths` / `parallelizable` / `riskTier` bullets.
116
153
  */
117
154
  export declare function planArtifactLacksV613ParallelMetadata(planMarkdown: string): boolean;
@@ -61,8 +61,70 @@ export function extractMembersListFromLine(trimmedLine) {
61
61
  return null;
62
62
  }
63
63
  /**
64
- * Parse `## Parallel Execution Plan` managed block for wave headings and Members lines.
65
- * Malformed member tokens are skipped. Duplicate slice ids in one plan source throw.
64
+ * Extract a `(sliceId, unitId)` pair from a markdown table data row
65
+ * whose first column is an `S-NN` token. Used by the wave parser to
66
+ * recognize the table-format Parallel Execution Plan alongside (or
67
+ * instead of) the `**Members:**` bullet line.
68
+ *
69
+ * Rules:
70
+ * - The line must start with `|` (after trimming).
71
+ * - Column 1 (after stripping markdown noise) must match `^S-(\d+)$` —
72
+ * header rows (`| sliceId | …`) and separator rows (`|---|---|…`) are
73
+ * silently skipped.
74
+ * - Column 2, when present and non-empty, becomes the `unitId`
75
+ * verbatim (after stripping whitespace + backticks/quotes/brackets).
76
+ * This lets authors record task ids (`T-010`, `T-008a`, …) in the
77
+ * `unit` column without forcing a `U-NN` derivation.
78
+ * - When column 2 is absent or empty, fall back to the
79
+ * `S-NN → U-NN` derivation so the `**Members:**` parser path stays
80
+ * bit-identical for non-table plans.
81
+ */
82
+ export function parseTableRowMember(trimmedLine) {
83
+ if (!trimmedLine.startsWith("|"))
84
+ return null;
85
+ const inner = trimmedLine.replace(/^\|/u, "").replace(/\|\s*$/u, "");
86
+ if (inner.length === 0)
87
+ return null;
88
+ const cells = inner.split("|").map((cell) => cell.trim());
89
+ if (cells.length === 0)
90
+ return null;
91
+ const stripDecorations = (raw) => raw.replace(/^[`"'[\]()]+|[`"'[\]()]+$/gu, "").trim();
92
+ const col1 = stripDecorations(cells[0]);
93
+ const sliceMatch = /^S-(\d+)$/u.exec(col1);
94
+ if (!sliceMatch)
95
+ return null;
96
+ const sliceNum = sliceMatch[1];
97
+ const sliceId = `S-${sliceNum}`;
98
+ let unitId = `U-${sliceNum}`;
99
+ if (cells.length >= 2) {
100
+ const col2 = stripDecorations(cells[1]);
101
+ if (col2.length > 0) {
102
+ const normalized = tokenToSliceAndUnit(col2);
103
+ unitId = normalized ? normalized.unitId : col2;
104
+ }
105
+ }
106
+ return { sliceId, unitId };
107
+ }
108
+ /**
109
+ * Parse `## Parallel Execution Plan` managed block for wave headings and
110
+ * member declarations. Recognizes BOTH the `**Members:**` / `Members:`
111
+ * line shape AND the markdown-table shape
112
+ * (`| sliceId | unit | dependsOn | …`).
113
+ *
114
+ * Wave headings accepted (case-insensitive, trailing text allowed):
115
+ * - `### Wave 04`
116
+ * - `### Wave W-04`
117
+ * - `### Wave W-04 — after fan-in W-03 (5 lanes …)`
118
+ *
119
+ * Within a single wave the parser dedupes by `sliceId`: if the same
120
+ * slice appears in both `**Members:**` and a table row, the first
121
+ * occurrence wins (line-order). Cross-wave duplicates still throw
122
+ * `WavePlanDuplicateSliceError`.
123
+ *
124
+ * Malformed member tokens are skipped. Empty waves (heading present
125
+ * but neither a Members line nor any matching `| S-NN |` row found
126
+ * before the next heading) are RETURNED with `members: []` so callers
127
+ * can surface the boundary; classification is up to the caller.
66
128
  */
67
129
  export function parseParallelExecutionPlanWaves(planMarkdown) {
68
130
  const body = extractParallelExecutionManagedBody(planMarkdown);
@@ -72,22 +134,60 @@ export function parseParallelExecutionPlanWaves(planMarkdown) {
72
134
  const waves = [];
73
135
  let current = null;
74
136
  const seenSlices = new Set();
137
+ let inWaveSlicesSeen = new Set();
75
138
  const flushCurrent = () => {
76
- if (current && current.members.length > 0) {
139
+ if (current) {
77
140
  waves.push(current);
78
141
  }
79
142
  };
143
+ /**
144
+ * Strict add: throw on duplicates within the same wave OR across waves.
145
+ * Used for the `**Members:**` path so the duplicate-detection
146
+ * contract is preserved bit-identically.
147
+ */
148
+ const addMemberStrict = (member) => {
149
+ if (!current)
150
+ return;
151
+ if (inWaveSlicesSeen.has(member.sliceId) ||
152
+ seenSlices.has(member.sliceId)) {
153
+ throw new WavePlanDuplicateSliceError(`duplicate slice ${member.sliceId} in Parallel Execution Plan managed block`);
154
+ }
155
+ seenSlices.add(member.sliceId);
156
+ inWaveSlicesSeen.add(member.sliceId);
157
+ current.members.push(member);
158
+ };
159
+ /**
160
+ * Lenient add: silently dedupe duplicates within the same wave (so the
161
+ * documented "Members + table both present" case keeps the Members
162
+ * declaration as authoritative); still throw on cross-wave duplicates
163
+ * to surface real plan-authoring bugs.
164
+ */
165
+ const addMemberDedupInWave = (member) => {
166
+ if (!current)
167
+ return;
168
+ if (inWaveSlicesSeen.has(member.sliceId))
169
+ return;
170
+ if (seenSlices.has(member.sliceId)) {
171
+ throw new WavePlanDuplicateSliceError(`duplicate slice ${member.sliceId} in Parallel Execution Plan managed block`);
172
+ }
173
+ seenSlices.add(member.sliceId);
174
+ inWaveSlicesSeen.add(member.sliceId);
175
+ current.members.push(member);
176
+ };
80
177
  for (const rawLine of lines) {
81
178
  const trimmed = rawLine.trim();
82
- const waveMatch = /^###\s+Wave\s+(\d+)\s*$/iu.exec(trimmed);
179
+ const waveMatch = /^###\s+Wave\s+(?:W-)?(\d+)\b/iu.exec(trimmed);
83
180
  if (waveMatch) {
84
181
  flushCurrent();
85
182
  const n = waveMatch[1];
86
183
  current = { waveId: `W-${n.padStart(2, "0")}`, members: [] };
184
+ inWaveSlicesSeen = new Set();
87
185
  continue;
88
186
  }
187
+ if (!current)
188
+ continue;
89
189
  const membersCsv = extractMembersListFromLine(trimmed);
90
- if (membersCsv !== null && current) {
190
+ if (membersCsv !== null) {
91
191
  const parts = membersCsv
92
192
  .split(/,/u)
93
193
  .map((p) => p.trim())
@@ -96,12 +196,13 @@ export function parseParallelExecutionPlanWaves(planMarkdown) {
96
196
  const ids = tokenToSliceAndUnit(part);
97
197
  if (!ids)
98
198
  continue;
99
- if (seenSlices.has(ids.sliceId)) {
100
- throw new WavePlanDuplicateSliceError(`duplicate slice ${ids.sliceId} in Parallel Execution Plan managed block`);
101
- }
102
- seenSlices.add(ids.sliceId);
103
- current.members.push(ids);
199
+ addMemberStrict(ids);
104
200
  }
201
+ continue;
202
+ }
203
+ const tableMember = parseTableRowMember(trimmed);
204
+ if (tableMember) {
205
+ addMemberDedupInWave(tableMember);
105
206
  }
106
207
  }
107
208
  flushCurrent();
@@ -220,7 +321,7 @@ export function formatNextParallelWaveSyncHint(merged) {
220
321
  return `Parallel Execution Plan: ${candidate.waveId} has ${candidate.members.length} parallel members (${ids}).`;
221
322
  }
222
323
  /**
223
- * Parse v6.13 parallel-metadata bullets from an implementation unit body.
324
+ * Parse parallel-metadata bullets from an implementation unit body.
224
325
  * Missing keys use conservative defaults (`dependsOn: []`, `parallelizable: true`
225
326
  * unless `legacyParallelDefaultSerial` is set).
226
327
  */
@@ -276,7 +377,7 @@ function unitBodyHasV613ParallelBullet(body, label) {
276
377
  });
277
378
  }
278
379
  /**
279
- * True when the plan has implementation units but any unit is missing v6.13.0
380
+ * True when the plan has implementation units but any unit is missing
280
381
  * `dependsOn` / `claimedPaths` / `parallelizable` / `riskTier` bullets.
281
382
  */
282
383
  export function planArtifactLacksV613ParallelMetadata(planMarkdown) {
@@ -21,9 +21,6 @@ export interface WaveStatusNextDispatch {
21
21
  export interface WaveStatusReport {
22
22
  activeRunId: string;
23
23
  currentStage: string;
24
- tddCutoverSliceId: string | null;
25
- tddWorktreeCutoverSliceId: string | null;
26
- legacyContinuation: boolean;
27
24
  waves: WaveStatusWaveSummary[];
28
25
  nextDispatch: WaveStatusNextDispatch;
29
26
  warnings: string[];
@@ -37,11 +34,11 @@ export interface RunWaveStatusOptions {
37
34
  artifactsDir?: string;
38
35
  }
39
36
  /**
40
- * v6.14.2 — deterministic helper for the TDD controller. Reads the
41
- * managed `<!-- parallel-exec-managed-start -->` block from
37
+ * Deterministic helper for the TDD controller. Reads the managed
38
+ * `<!-- parallel-exec-managed-start -->` block from
42
39
  * `<artifacts-dir>/05-plan.md` plus the `wave-plans/` directory and
43
40
  * reports waves + the next dispatchable members so the controller does
44
- * NOT have to page through a 1400-line plan to find the active wave.
41
+ * NOT have to page through a long plan to find the active wave.
45
42
  *
46
43
  * Always exits 0 unless the plan is malformed (no managed block AND no
47
44
  * wave-plans directory), in which case exit 2 with a structured error.