cclaw-cli 6.13.0 → 6.14.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.
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { resolveArtifactPath as resolveStageArtifactPath } from "./artifact-paths.js";
4
- import { effectiveWorktreeExecutionMode } from "./flow-state.js";
4
+ import { effectiveIntegrationOverseerMode, effectiveTddCheckpointMode, effectiveWorktreeExecutionMode } from "./flow-state.js";
5
5
  import { exists } from "./fs-utils.js";
6
6
  import { stageSchema } from "./content/stage-schema.js";
7
7
  import { readFlowState } from "./run-persistence.js";
@@ -124,6 +124,8 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
124
124
  let completedStageMetaForAudit;
125
125
  let legacyContinuation = false;
126
126
  let worktreeExecutionMode = "single-tree";
127
+ let tddCheckpointMode = "per-slice";
128
+ let integrationOverseerMode = "always";
127
129
  try {
128
130
  const flowState = await readFlowState(projectRoot);
129
131
  const hint = flowState.interactionHints?.[stage];
@@ -136,6 +138,8 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
136
138
  completedStageMetaForAudit = flowState.completedStageMeta;
137
139
  legacyContinuation = flowState.legacyContinuation === true;
138
140
  worktreeExecutionMode = effectiveWorktreeExecutionMode(flowState);
141
+ tddCheckpointMode = effectiveTddCheckpointMode(flowState);
142
+ integrationOverseerMode = effectiveIntegrationOverseerMode(flowState);
139
143
  }
140
144
  catch {
141
145
  activeStageFlags = [];
@@ -146,6 +150,8 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
146
150
  completedStageMetaForAudit = undefined;
147
151
  legacyContinuation = false;
148
152
  worktreeExecutionMode = "single-tree";
153
+ tddCheckpointMode = "per-slice";
154
+ integrationOverseerMode = "always";
149
155
  }
150
156
  for (const extra of options.extraStageFlags ?? []) {
151
157
  if (typeof extra === "string" && extra.length > 0 && !activeStageFlags.includes(extra)) {
@@ -283,7 +289,9 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
283
289
  activeStageFlags,
284
290
  taskClass,
285
291
  legacyContinuation,
286
- worktreeExecutionMode
292
+ worktreeExecutionMode,
293
+ tddCheckpointMode,
294
+ integrationOverseerMode
287
295
  };
288
296
  switch (stage) {
289
297
  case "brainstorm":
@@ -294,6 +294,19 @@ async function readRunId(root) {
294
294
  }
295
295
  }
296
296
 
297
+ async function readWorktreeExecutionModeInline(root) {
298
+ try {
299
+ const raw = await fs.readFile(path.join(root, RUNTIME_ROOT, "state", "flow-state.json"), "utf8");
300
+ const parsed = JSON.parse(raw);
301
+ if (parsed && parsed.worktreeExecutionMode === "worktree-first") {
302
+ return "worktree-first";
303
+ }
304
+ return "single-tree";
305
+ } catch {
306
+ return "single-tree";
307
+ }
308
+ }
309
+
297
310
  async function readDelegationEvents(root) {
298
311
  try {
299
312
  const raw = await fs.readFile(path.join(root, RUNTIME_ROOT, "state", "delegation-events.jsonl"), "utf8");
@@ -330,7 +343,7 @@ function usage() {
330
343
  "Usage:",
331
344
  " node .cclaw/hooks/delegation-record.mjs --stage=<stage> --agent=<agent> --mode=<mandatory|proactive> --status=<scheduled|launched|acknowledged|completed|failed|waived|stale> --span-id=<id> [--dispatch-id=<id>] [--worker-run-id=<id>] [--dispatch-surface=<surface>] [--agent-definition-path=<path>] [--ack-ts=<iso>] [--launched-ts=<iso>] [--completed-ts=<iso>] [--evidence-ref=<ref>] [--waiver-reason=<text>] [--supersede=<prevSpanId>] [--allow-parallel] [--paths=<comma-separated>] [--override-cap=<int>] [--json]",
332
345
  " node .cclaw/hooks/delegation-record.mjs --rerecord --span-id=<id> --dispatch-id=<id> --dispatch-surface=<surface> --agent-definition-path=<path> [--ack-ts=<iso>] [--completed-ts=<iso>] [--evidence-ref=<ref>] [--json]",
333
- " node .cclaw/hooks/delegation-record.mjs --repair --span-id=<id> --repair-reason=\"<why>\" [--json]",
346
+ " node .cclaw/hooks/delegation-record.mjs --repair --span-id=<id> --repair-reason=\\\"<why>\\\" [--json]",
334
347
  "",
335
348
  "Allowed --dispatch-surface values:",
336
349
  " " + VALID_DISPATCH_SURFACES.join(", "),
@@ -349,12 +362,14 @@ function usage() {
349
362
  "TDD slice phase tagging (v6.11.0):",
350
363
  " --slice=<id> TDD slice identifier (e.g. S-1) used by the linter to auto-derive the Watched-RED + Vertical Slice Cycle tables.",
351
364
  " --phase=<phase> one of " + VALID_DELEGATION_PHASES.join(", ") + ". Pair with --slice to record a TDD slice phase event.",
352
- " --refactor-rationale=<t> required when --phase=refactor-deferred unless --evidence-ref carries the rationale text.",
365
+ " --refactor-rationale=<t> required when --phase=refactor-deferred unless --evidence-ref carries the rationale text. v6.14.0: also paired with --refactor-outcome on phase=green.",
353
366
  " --claim-token=<opaque> v6.13 — required for worktree-first slice-implementer schedules with --slice (echo on all terminal rows for the span).",
354
367
  " --lane-id=<id> v6.13 — worktree lane id (ownerLaneId metadata).",
355
368
  " --lease-until=<iso> v6.13 — ISO8601 lease expiry for reclaim tooling.",
356
369
  " --depends-on=<a,b> v6.13 — comma-separated plan unit ids for scheduler diagnostics.",
357
370
  " --integration-state=<s> v6.13 — one of pending|applied|conflict|resolved|abandoned.",
371
+ " --refactor-outcome=<m> v6.14.0 — one of inline|deferred. Folds REFACTOR into the phase=green event so a single row can close RED→GREEN→REFACTOR. Pair --refactor-outcome=deferred with --refactor-rationale.",
372
+ " --risk-tier=<t> v6.14.0 — one of low|medium|high. high triggers integration-overseer in conditional mode.",
358
373
  ""
359
374
  ].join("\\n") + "\\n");
360
375
  }
@@ -525,6 +540,40 @@ function buildRow(args, status, runId, now, options) {
525
540
  : undefined;
526
541
  const leaseState =
527
542
  leasedUntil && status === "scheduled" ? "claimed" : undefined;
543
+ // v6.14.0: refactorOutcome folds REFACTOR into a phase=green event. We
544
+ // also accept it on phase=refactor / phase=refactor-deferred for forward
545
+ // compatibility with controllers that emit it on the legacy lifecycle.
546
+ // When mode=deferred and a --refactor-rationale is supplied we also
547
+ // mirror the rationale into evidenceRefs[0] so legacy linters keep
548
+ // reading evidence (matches the v6.11.0 refactor-deferred behavior).
549
+ const refactorOutcomeMode =
550
+ typeof args["refactor-outcome"] === "string"
551
+ ? args["refactor-outcome"].trim()
552
+ : "";
553
+ let refactorOutcome;
554
+ if (refactorOutcomeMode === "inline" || refactorOutcomeMode === "deferred") {
555
+ const rationaleRaw =
556
+ typeof args["refactor-rationale"] === "string"
557
+ ? args["refactor-rationale"].trim()
558
+ : "";
559
+ refactorOutcome = {
560
+ mode: refactorOutcomeMode,
561
+ ...(rationaleRaw.length > 0 ? { rationale: rationaleRaw } : {})
562
+ };
563
+ if (
564
+ refactorOutcomeMode === "deferred" &&
565
+ rationaleRaw.length > 0 &&
566
+ !resolvedEvidenceRefs.includes(rationaleRaw)
567
+ ) {
568
+ resolvedEvidenceRefs = [rationaleRaw, ...resolvedEvidenceRefs];
569
+ }
570
+ }
571
+ const riskTierRaw =
572
+ typeof args["risk-tier"] === "string" ? args["risk-tier"].trim() : "";
573
+ const riskTier =
574
+ riskTierRaw === "low" || riskTierRaw === "medium" || riskTierRaw === "high"
575
+ ? riskTierRaw
576
+ : undefined;
528
577
  return {
529
578
  stage: args.stage,
530
579
  agent: args.agent,
@@ -555,7 +604,9 @@ function buildRow(args, status, runId, now, options) {
555
604
  leasedUntil,
556
605
  leaseState,
557
606
  dependsOn,
558
- integrationState
607
+ integrationState,
608
+ refactorOutcome,
609
+ riskTier
559
610
  };
560
611
  }
561
612
 
@@ -1126,6 +1177,44 @@ async function main() {
1126
1177
  }
1127
1178
  }
1128
1179
 
1180
+ // v6.14.0 — --refactor-outcome must be one of inline|deferred. When
1181
+ // mode=deferred a rationale is required (either --refactor-rationale or
1182
+ // --evidence-ref carrying the rationale text). --risk-tier must be one of
1183
+ // low|medium|high if provided.
1184
+ if (
1185
+ args["refactor-outcome"] !== undefined &&
1186
+ args["refactor-outcome"] !== "inline" &&
1187
+ args["refactor-outcome"] !== "deferred"
1188
+ ) {
1189
+ problems.push("invalid --refactor-outcome (allowed: inline, deferred)");
1190
+ emitProblems(problems, json, 2);
1191
+ return;
1192
+ }
1193
+ if (args["refactor-outcome"] === "deferred") {
1194
+ const rationaleProvided =
1195
+ typeof args["refactor-rationale"] === "string" && args["refactor-rationale"].trim().length > 0;
1196
+ const evidenceProvided =
1197
+ (typeof args["evidence-ref"] === "string" && args["evidence-ref"].trim().length > 0) ||
1198
+ (Array.isArray(args["evidence-refs"]) && args["evidence-refs"].some(
1199
+ (ref) => typeof ref === "string" && ref.trim().length > 0
1200
+ ));
1201
+ if (!rationaleProvided && !evidenceProvided) {
1202
+ problems.push("--refactor-outcome=deferred requires --refactor-rationale=<text> or --evidence-ref=<text>");
1203
+ emitProblems(problems, json, 2);
1204
+ return;
1205
+ }
1206
+ }
1207
+ if (
1208
+ args["risk-tier"] !== undefined &&
1209
+ args["risk-tier"] !== "low" &&
1210
+ args["risk-tier"] !== "medium" &&
1211
+ args["risk-tier"] !== "high"
1212
+ ) {
1213
+ problems.push("invalid --risk-tier (allowed: low, medium, high)");
1214
+ emitProblems(problems, json, 2);
1215
+ return;
1216
+ }
1217
+
1129
1218
  if (args.status === "completed" && args["dispatch-surface"] !== "role-switch") {
1130
1219
  for (const key of ["dispatch-id", "dispatch-surface", "agent-definition-path"]) {
1131
1220
  if (!args[key]) problems.push("completed isolated/generic status requires --" + key);
@@ -1265,6 +1354,33 @@ async function main() {
1265
1354
  }
1266
1355
  }
1267
1356
 
1357
+ if (
1358
+ clean.stage === "tdd" &&
1359
+ clean.agent === "slice-implementer" &&
1360
+ clean.phase === "green" &&
1361
+ (await readWorktreeExecutionModeInline(root)) === "worktree-first"
1362
+ ) {
1363
+ const tok = typeof clean.claimToken === "string" ? clean.claimToken.trim() : "";
1364
+ const lane = typeof clean.ownerLaneId === "string" ? clean.ownerLaneId.trim() : "";
1365
+ const lease = typeof clean.leasedUntil === "string" ? clean.leasedUntil.trim() : "";
1366
+ if (tok.length === 0 || lane.length === 0 || lease.length === 0) {
1367
+ const missing = [];
1368
+ if (tok.length === 0) missing.push("--claim-token");
1369
+ if (lane.length === 0) missing.push("--lane-id");
1370
+ if (lease.length === 0) missing.push("--lease-until");
1371
+ emitErrorJson(
1372
+ "dispatch_lane_metadata_missing",
1373
+ {
1374
+ missing,
1375
+ remediation:
1376
+ "worktree-first mode requires --claim-token, --lane-id, and --lease-until on every slice-implementer --phase green delegation-record write (from scheduled through completed)."
1377
+ },
1378
+ json
1379
+ );
1380
+ return;
1381
+ }
1382
+ }
1383
+
1268
1384
  await persistEntry(root, runId, clean, event);
1269
1385
  process.stdout.write(JSON.stringify({ ok: true, event }, null, 2) + "\\n");
1270
1386
  }
@@ -4,6 +4,7 @@ import { FLOW_STAGES } from "../types.js";
4
4
  import { behaviorAnchorFor, stageExamples } from "./examples.js";
5
5
  import { INVESTIGATION_DISCIPLINE_BLOCK } from "./templates.js";
6
6
  import { reviewStackAwareRoutes, reviewStackAwareRoutingSummary, stageAutoSubagentDispatch, stageSchema, stageTrackRenderContext } from "./stage-schema.js";
7
+ import { renderTrackTerminology } from "./track-render-context.js";
7
8
  import { referencePatternsForStage } from "./reference-patterns.js";
8
9
  import { harnessDelegationRecipes } from "../harness-adapters.js";
9
10
  const VERIFICATION_STAGES = ["tdd", "review", "ship"];
@@ -196,16 +197,20 @@ ONE slice = THREE dispatches, in this order. Do not skip, do not collapse.
196
197
  The file-overlap scheduler auto-allows parallel dispatch because \`claimedPaths\` are disjoint. Fire BOTH calls in the same message — never serialize independent work.
197
198
  4. **REFACTOR** — \`Task("slice-implementer --slice S-<id> --phase refactor")\` OR \`--phase refactor-deferred --refactor-rationale '<why>'\`.
198
199
 
200
+ **Rule 1 (v6.13.1):** Before any slice-routing question, read \`<artifacts-dir>/05-plan.md\` (managed \`## Parallel Execution Plan\`) **and** list \`<artifacts-dir>/wave-plans/wave-NN.md\`. Merge mentally: Parallel Execution Plan first, wave files second; duplicate slices with conflicting wave membership are invalid. If the merged plan shows a wave with **two or more** scheduler-ready slices, issue **exactly one** \`AskQuestion\`: \`Launch wave W-NN with N parallel lanes (S-a, S-b, ...)?\` with default option **launch wave** and alternate **single-slice instead**. Do not ask "which slice next?" when that question is redundant (single ready slice or no wave). After **launch wave** confirmation, execute RED checkpoint → parallel GREEN+DOC → per-lane REFACTOR without further routing asks. After **single-slice instead**, fall back to the legacy single-slice ritual. **Wave dispatch resume:** if part of the wave is already done, parallelize only the remaining members.
201
+
199
202
  **FORBIDDEN:**
200
203
  - Controller writing GREEN production code. ALL GREEN goes through \`slice-implementer\` — linter rule \`tdd_slice_implementer_missing\` blocks the gate.
201
204
  - Controller writing per-slice prose into legacy \`06-tdd.md\` sections (Test Discovery / RED Evidence / GREEN Evidence / Watched-RED Proof / Vertical Slice Cycle / Per-Slice Review / Failure Analysis / Acceptance Mapping). \`slice-documenter\` owns \`tdd-slices/S-<id>.md\` — \`tdd_slice_documenter_missing\` blocks the gate.
202
205
  - Hand-editing auto-render blocks between \`auto-start: tdd-slice-summary\` / \`auto-start: slices-index\` markers — overwritten every lint.
203
206
 
204
- Delegation-record signature: \`node .cclaw/hooks/delegation-record.mjs --stage=tdd --agent=<agent> --mode=mandatory --status=<...> --span-id=<id> --dispatch-id=<id> --dispatch-surface=<surface> --agent-definition-path=<path> --slice=S-<id> --phase=<red|green|refactor|refactor-deferred|doc> [--paths=<csv>] [--refactor-rationale=<why>] [--ack-ts=<iso>] [--evidence-ref=<ref>] --json\`.
207
+ Delegation-record signature (extend with lane metadata for every GREEN row in \`worktree-first\`):
208
+
209
+ \`node .cclaw/hooks/delegation-record.mjs --stage=tdd --agent=slice-implementer --mode=mandatory --status=scheduled --span-id=<id> --dispatch-id=<id> --dispatch-surface=<surface> --agent-definition-path=<path> --slice=S-1 --phase=green --paths=src/a.ts --claim-token=<opaque> --lane-id=<lane> --lease-until=<iso8601> --json\`
205
210
 
206
- ## Wave Batch Mode (v6.12.0+)
211
+ ## Wave Batch Mode (v6.13.1+)
207
212
 
208
- Trigger: any \`<artifacts-dir>/wave-plans/wave-NN.md\` exists, OR 2+ slices have disjoint \`claimedPaths\`. Cap = 5 \`slice-implementer\` lanes (10 subagents incl. paired documenters) via \`MAX_PARALLEL_SLICE_IMPLEMENTERS\`.
213
+ **Triggers:** managed \`## Parallel Execution Plan\` in \`05-plan.md\` **or** any \`<artifacts-dir>/wave-plans/wave-NN.md\`, OR 2+ slices with disjoint \`claimedPaths\`. Cap = 5 \`slice-implementer\` lanes (10 subagents incl. paired documenters) via \`MAX_PARALLEL_SLICE_IMPLEMENTERS\`. **Preconditions:** Load both sources before routing. Worktree-first: every GREEN delegation-record MUST include \`--claim-token\`, \`--lane-id\`, \`--lease-until\` (hook exits \`2\`, \`dispatch_lane_metadata_missing\` otherwise).
209
214
 
210
215
  **Phase A — RED checkpoint** — ONE message, all test-authors:
211
216
  \`\`\`
@@ -215,20 +220,18 @@ Task("test-author --slice S-3 --phase red")
215
220
  \`\`\`
216
221
  Wait for ALL Phase A REDs to land with non-empty \`evidenceRefs\` before Phase B. Linter \`tdd_red_checkpoint_violation\` (required: true) blocks any wave where a \`phase=green\` \`completedTs\` precedes the wave's last \`phase=red\` \`completedTs\`.
217
222
 
218
- **Phase B — GREEN+DOC fan-out** — ONE message, paired implementer+documenter Tasks per slice:
223
+ **Phase B — GREEN+DOC fan-out** — ONE message; pair per slice (repeat for each lane, flags unique per lane):
224
+
219
225
  \`\`\`
220
- Task("slice-implementer --slice S-1 --phase green --paths <S-1 prod>")
226
+ Task("slice-implementer --slice S-1 --phase green --paths <prod> --claim-token=<t> --lane-id=<lane-1> --lease-until=<iso>")
221
227
  Task("slice-documenter --slice S-1 --phase doc --paths <artifacts-dir>/tdd-slices/S-1.md")
222
- Task("slice-implementer --slice S-2 --phase green --paths <S-2 prod>")
223
- Task("slice-documenter --slice S-2 --phase doc --paths <artifacts-dir>/tdd-slices/S-2.md")
224
228
  \`\`\`
225
- Launch ALL Phase B pairs in ONE message. **Never serialize independent work.**
226
229
 
227
- **Phase C — REFACTOR per slice** after GREEN+DOC evidence is recorded, dispatch \`slice-implementer --phase refactor\` or \`--phase refactor-deferred\` per slice (may be parallelized when lanes stay disjoint).
230
+ Launch every slice's pair in that same message. **Never serialize independent work.**
228
231
 
229
- **Fan-in (v6.13.0+, worktree-first default)** — each \`slice-implementer\` GREEN row should record \`--claim-token\`, \`--lane-id\`, and \`--lease-until\` per the delegation hook. On successful TDD stage-complete, the runtime performs deterministic \`git apply --3way\` fan-in from each lane worktree onto the current integration branch (no \`-X ours/theirs\`). Conflicts emit \`cclaw_fanin_conflict\` audit rows; resolve with \`slice-implementer --phase resolve-conflict\` then re-run stage-complete. When 2+ parallel lanes finish a wave, still dispatch \`integration-overseer\` so cohesion-contract evidence exists before review.
232
+ **Phase C REFACTOR per slice** — after GREEN+DOC lands, dispatch refactor/refactor-deferred per slice. **Fan-in (worktree-first):** echo claim/lane/lease on completed GREEN rows; stage-complete runs deterministic \`git apply --3way\` (no \`-X ours/theirs\`). Conflicts: \`slice-implementer --phase resolve-conflict\`. With 2+ lanes, still dispatch \`integration-overseer\` before review.
230
233
 
231
- **slice-documenter** may mark prose \`provisional\` until GREEN is proven; finalize \`tdd-slices/S-<id>.md\` after GREEN evidence is recorded.
234
+ **slice-documenter:** record the \`phase=doc\` row in the same message as GREEN; write a **provisional** row in \`tdd-slices/S-<id>.md\` immediately at dispatch, then **finalize** that file after the matching \`slice-implementer\` \`phase=green\` event lands (evidence-backed prose, not guesswork before GREEN exists).
232
235
 
233
236
  `;
234
237
  }
@@ -655,7 +658,7 @@ If you are about to violate the Iron Law, STOP. No amount of urgency, partial pr
655
658
 
656
659
  </EXTREMELY-IMPORTANT>
657
660
 
658
- ${tddTopOfSkillBlock(stage)}${quickStartBlock(stage, track)}
661
+ ${renderTrackTerminology(tddTopOfSkillBlock(stage), trackContext)}${quickStartBlock(stage, track)}
659
662
 
660
663
  ${STAGE_LANGUAGE_POLICY_POINTER}
661
664
  ## Philosophy
@@ -37,29 +37,29 @@ export const TDD = {
37
37
  },
38
38
  executionModel: {
39
39
  checklist: [
40
- "Select vertical slice pick one source item from the active track (plan task on standard/medium, spec AC or bug reproduction slice on quick). Do not batch multiple tasks. Before starting, read `.cclaw/state/ralph-loop.json` (`loopIteration`, `acClosed[]`, `redOpenSlices[]`) so you skip cycles already closed. If `redOpenSlices[]` is non-empty, repair or explicitly park those slices before opening a new RED.",
40
+ "**Stream-style wave dispatch (v6.14.0):** Before routing, read the Parallel Execution Plan (managed block in the track planning artifact) and `<artifacts-dir>/wave-plans/`. Per-lane stream: each lane runs RED→GREEN→REFACTOR independently as soon as its `dependsOn` closes — no global RED checkpoint between Phase A and Phase B. The linter enforces RED-before-GREEN per slice via `tdd_slice_red_completed_before_green`; cross-lane interleaving is allowed. **Legacy `global-red` mode** is preserved for projects with `legacyContinuation: true` and any project that explicitly sets `flow-state.json::tddCheckpointMode: \"global-red\"` (rule `tdd_red_checkpoint_violation` still fires there). Multi-ready waves still get one AskQuestion (launch wave vs single-slice); then per-lane GREEN+DOC dispatch with worktree-first flags. Integration-overseer fires only on cross-slice trigger (see `integrationCheckRequired()` heuristic). Resume partial waves by parallelizing remaining members only (see top-of-skill `## Wave Batch Mode`).",
41
+ "Select vertical slice — the active wave plan (or single ready slice) defines work. Do not ask \"which slice next?\" when the plan already resolves it. Before starting, read `.cclaw/state/ralph-loop.json` (`loopIteration`, `acClosed[]`, `redOpenSlices[]`) so you skip cycles already closed. If `redOpenSlices[]` is non-empty, repair or explicitly park those slices before opening a new RED.",
41
42
  "Map to acceptance criterion — identify the specific spec criterion this test proves.",
42
43
  "Discover the test surface — inspect existing tests, fixtures, helpers, test commands, and nearby assertions before authoring RED. Reuse the local test style unless the slice genuinely needs a new pattern.",
43
44
  "Run a system-wide impact check — name callbacks, state transitions, interfaces, schemas, CLI/config/API contracts, persistence, or event boundaries that this slice can affect. Add RED coverage for each affected public contract or record why it is out of scope.",
44
45
  "Source/test preflight — before production edits, classify planned paths using test-path patterns; verify the RED touches a test path and the GREEN touches only source paths needed for the failing behavior.",
45
46
  "Use the mandatory `test-author` delegation for RED — after discovery and impact check, dispatch with `--slice S-<id> --phase red`. Produce failing behavior tests only (no production edits) and let the harness record the dispatch via the generated `delegation-record` hook. Set `CCLAW_ACTIVE_AGENT=tdd-red` when the harness supports phase labels.",
46
47
  "RED: do NOT hand-edit `## Watched-RED Proof`, `## Vertical Slice Cycle`, or `## RED Evidence` markdown tables. The linter auto-renders them from `delegation-events.jsonl` slice phase rows; manual edits inside the auto-render markers are overwritten on the next lint.",
47
- "Dispatch the `slice-implementer` for GREEN with `--slice S-<id> --phase green` and explicit `--paths` so the file-overlap scheduler can auto-allow parallel slices. Attach an evidence ref (test path, span ref, or pasted-output pointer) so the Vertical Slice Cycle row is well-formed. Set `CCLAW_ACTIVE_AGENT=tdd-green` when the harness supports phase labels.",
48
+ "Dispatch the `slice-implementer` for GREEN with `--slice S-<id> --phase green` and explicit `--paths` so the file-overlap scheduler can auto-allow parallel slices. When `flow-state.json::worktreeExecutionMode` is `worktree-first`, **mandatory** flags on every GREEN delegation-record row: `--claim-token=<opaque> --lane-id=<lane> --lease-until=<iso8601>`. Attach an evidence ref so the Vertical Slice Cycle row is well-formed. Set `CCLAW_ACTIVE_AGENT=tdd-green` when the harness supports phase labels.",
48
49
  "GREEN: Run full suite — execute ALL tests, not just the ones you wrote. The full suite must be GREEN.",
49
50
  "GREEN: Verify no regressions — if any existing test breaks, fix the regression before proceeding.",
50
51
  "Run verification-before-completion discipline for the slice — capture a fresh test command, explicit PASS/FAIL status, and a config-aware ref (commit SHA when VCS is present/required, or no-vcs attestation when allowed).",
51
- "REFACTOR: re-dispatch the `slice-implementer` (or `test-author`) with `--phase refactor` once GREEN holds, OR `--phase refactor-deferred --refactor-rationale \"<why>\"` to close the slice without a refactor pass. Both options are recorded as a delegation event; the linter accepts either as REFACTOR coverage. Set `CCLAW_ACTIVE_AGENT=tdd-refactor` when the harness supports phase labels.",
52
- "DOC (parallel, mandatory v6.12.0): dispatch `slice-documenter --slice S-<id> --phase doc --paths <artifacts-dir>/tdd-slices/S-<id>.md` IN PARALLEL with `slice-implementer --phase green` for the same slice ONE message with TWO concurrent Task calls. The documenter only writes `tdd-slices/S-<id>.md`, so its `--paths` are disjoint from the implementer's production paths and the file-overlap scheduler auto-allows the parallel dispatch. Linter rule `tdd_slice_documenter_missing` blocks the gate when the `phase=doc` event is absent (regardless of `discoveryMode`). v6.13.0: documenter output may remain `provisional` until GREEN evidence exists; finalize the slice file after GREEN is recorded.",
53
- "## Wave Batch Mode (v6.13+) — (A) RED checkpoint across all wave members before any GREEN in that wave. (B) Parallel implementer+documenter fan-out across multiple slices when paths are disjoint and claims/leases are valid (`--claim-token`, `--lane-id`, `--lease-until`). (C) Per-lane REFACTOR or refactor-deferred. (D) Deterministic git fan-in at TDD stage-complete merges lane diffs with `git apply --3way`; unresolved conflicts block advance until `--phase resolve-conflict` succeeds.",
52
+ "REFACTOR (v6.14.0 — three forms): (1) re-dispatch `slice-implementer` with `--phase refactor` after GREEN; (2) re-dispatch with `--phase refactor-deferred --refactor-rationale \"<why>\"` to close without a separate pass; (3) **fold REFACTOR into GREEN** by adding `--refactor-outcome=inline|deferred [--refactor-rationale=\"<why>\"]` on the same `slice-implementer --phase green` dispatch. Form (3) is the v6.14.0 default; the linter accepts all three as REFACTOR coverage. Set `CCLAW_ACTIVE_AGENT=tdd-refactor` when the harness supports phase labels.",
53
+ "DOC (v6.14.0 — softened): in `discoveryMode=deep` runs DOC remains mandatory — dispatch `slice-documenter --slice S-<id> --phase doc --paths <artifacts-dir>/tdd-slices/S-<id>.md` IN PARALLEL with `slice-implementer --phase green` for the same slice (ONE message with TWO concurrent Task calls). The documenter only writes `tdd-slices/S-<id>.md`, so its `--paths` are disjoint from the implementer's production paths and the file-overlap scheduler auto-allows parallel dispatch. **In `lean` and `guided` modes DOC is advisory** (linter `tdd_slice_documenter_missing` becomes `required: false`); controllers may either keep parallel `slice-documenter` dispatch or call `slice-implementer --finalize-doc` inline at GREEN-completion. **Provisional-then-finalize still applies for parallel dispatch:** append a provisional row/section in `tdd-slices/S-<id>.md` at dispatch time, then finalize after the matching `phase=green` event records evidence.",
54
54
  "**slice-documenter writes per-slice prose** (test discovery, system-wide impact check, RED/GREEN/REFACTOR notes, acceptance mapping, failure analysis) into `tdd-slices/S-<id>.md`. Controller does NOT touch this content. When logging a `green` row, attach the closed acceptance-criterion IDs in `acIds` so Ralph Loop status counts them.",
55
55
  "Annotate traceability — link to the active track's source: plan task ID + spec criterion on standard/medium, or spec acceptance item / bug reproduction slice on quick.",
56
56
  "**Boundary with review (do NOT escalate single-slice findings to whole-diff review).** `tdd.Per-Slice Review` OWNS severity-classified findings WITHIN one slice (correctness, edge cases, regression). `review` OWNS whole-diff Layer 1 (spec compliance) plus Layer 2 (cross-slice integration, security sweep, dependency/version audit, observability). When a single-slice finding genuinely needs whole-diff escalation, surface it in `06-tdd.md > Per-Slice Review` first; review will cite it (not re-classify) and the cross-artifact-duplication linter requires matching severity/disposition.",
57
57
  "Per-Slice Review (conditional) — if the slice meets any trigger (touchCount >= filesChangedThreshold, touchPaths match touchTriggers, or highRisk=true), append a `## Per-Slice Review` entry for this slice before moving on (see the dedicated section below).",
58
- "Repeat for each slice — return to step 1 for the next plan slice."
58
+ "Repeat for each slice — when not in multi-slice wave mode, return to wave-plan discovery; otherwise continue the active wave until members close.",
59
59
  ],
60
60
  interactionProtocol: [
61
- "Pick one vertical slice at a time: source item, RED test, GREEN implementation, REFACTOR, and verification evidence move together.",
62
- "Slice implementers are sequential by default. Parallel implementers are allowed only when (a) lanes touch non-overlapping files (the file-overlap scheduler auto-allows parallel when `--paths` are disjoint), and (b) an `integration-overseer` is dispatched after the parallel lanes and writes cohesion-evidence into the artifact before the gate is marked passed.",
61
+ "Pick one vertical slice at a time **only when** the merged wave plan leaves a single scheduler-ready slice or the operator chose single-slice mode. Parallel implementers are allowed when lanes touch non-overlapping files (the file-overlap scheduler auto-allows parallel when `--paths` are disjoint). **Integration-overseer is conditional in v6.14.0** (see `flow-state.json::integrationOverseerMode`): with the default `\"conditional\"` it dispatches only when `integrationCheckRequired()` returns `required: true` (shared import boundaries between closed slices, any slice with `riskTier=high`, or a recorded `cclaw_fanin_conflict`). When the heuristic returns `required: false`, record an audit `cclaw_integration_overseer_skipped` and let the linter emit advisory `tdd_integration_overseer_skipped_by_disjoint_paths`. Projects with `legacyContinuation: true` or explicit `\"always\"` keep the v6.13.x mandatory dispatch.",
62
+ "Slice implementers are sequential only when the plan serializes work; prefer wave-parallel GREEN+DOC when the Parallel Execution Plan marks multiple ready members.",
63
63
  "Controller owns orchestration. For each slice S-<id>, dispatch in this order: (1) `test-author --slice S-<id> --phase red` (RED-only, no production edits), (2) `slice-implementer --slice S-<id> --phase green --paths <comma-separated>` for GREEN, (3) re-dispatch `--phase refactor` or `--phase refactor-deferred --refactor-rationale \"<why>\"` to close REFACTOR. Each dispatch records a row in `delegation-events.jsonl` and the linter auto-derives the Watched-RED + Vertical Slice Cycle tables from those rows. Do NOT hand-edit those tables.",
64
64
  "Before writing RED tests, discover relevant existing tests and commands so the new test extends the suite instead of fighting it.",
65
65
  "Before implementation, perform a system-wide impact check across callbacks, state, interfaces, schemas, and external contracts touched by the slice.",
@@ -115,6 +115,7 @@ If during any stage the agent discovers evidence that contradicts the initial Ph
115
115
  2. If flow state is missing → guide the user to run \`npx cclaw-cli init\` and stop.
116
116
  3. If flow state is only a fresh init placeholder (\`completedStages: []\`, all \`passed\` arrays empty, and no \`00-idea.md\`) → stop and ask for \`/cc <prompt>\` to start a tracked run. Do not create a brainstorm state implicitly.
117
117
  4. Otherwise check current stage gates, resume if incomplete, and advance if complete.
118
+ 5. **TDD wave dispatch (v6.14.0 — stream-style):** When \`currentStage\` is \`tdd\`, read \`${RUNTIME_ROOT}/artifacts/05-plan.md\` Parallel Execution Plan block and \`${RUNTIME_ROOT}/artifacts/wave-plans/\` **before** any slice-routing question. If an open wave still has multiple ready slices, resume per-lane stream dispatch for the **remaining** members only (do not restart completed slices). **No global RED checkpoint barrier** in default \`tddCheckpointMode: "per-slice"\` — each lane advances RED→GREEN→REFACTOR as soon as its \`dependsOn\` closes; the linter enforces RED-before-GREEN per slice. Projects with \`legacyContinuation: true\` or explicit \`tddCheckpointMode: "global-red"\` retain the v6.13.x global barrier.
118
119
 
119
120
  ## Headless mode (CI/automation only)
120
121
 
@@ -207,9 +208,11 @@ Progress the tracked flow only when one exists:
207
208
  2. If missing, guide the user to run \`npx cclaw-cli init\` and stop.
208
209
  3. If it is only a fresh init placeholder (\`completedStages: []\`, no passed gates, and no \`${RUNTIME_ROOT}/artifacts/00-idea.md\`), stop and ask for \`/cc <prompt>\` to start a tracked run. Do not silently create a brainstorm run.
209
210
  4. Check gates for \`currentStage\`.
210
- 5. If incomplete load current stage skill and execute.
211
- 6. If complete advance to next stage and execute.
212
- 7. If flow is done report completion.
211
+ 5. **TDD (v6.14.0 stream-style):** When \`currentStage\` is \`tdd\`, read \`${RUNTIME_ROOT}/artifacts/05-plan.md\` (managed \`## Parallel Execution Plan\` between \`parallel-exec-managed\` markers) and scan \`${RUNTIME_ROOT}/artifacts/wave-plans/wave-NN.md\` **before** asking which slice runs next. Merge sources in controller memory: Parallel Execution Plan first, wave files second; the same slice must not disagree across sources.
212
+ 6. **Wave dispatch resume (per-lane stream):** If a wave is partially closed (some members already past GREEN/REFACTOR), continue with the remaining members in parallel as soon as their \`dependsOn\` closes — do **not** wait for a global RED-completion barrier in default \`tddCheckpointMode: "per-slice"\`. Never redo finished lanes. Integration-overseer fires only on cross-slice trigger (default \`integrationOverseerMode: "conditional"\`); skipped dispatches must record audit \`cclaw_integration_overseer_skipped\`.
213
+ 7. If incomplete load current stage skill and execute.
214
+ 8. If complete → advance to next stage and execute.
215
+ 9. If flow is done → report completion.
213
216
 
214
217
  ## Public flow habit
215
218
 
@@ -2,6 +2,7 @@ import { type SubagentFallback } from "./harness-adapters.js";
2
2
  import { type MandatoryDelegationTaskClass } from "./content/stage-schema.js";
3
3
  import type { FlowStage } from "./types.js";
4
4
  import { type FlowState } from "./flow-state.js";
5
+ import { type ParseImplementationUnitParallelOptions, type ParsedParallelWave } from "./internal/plan-split-waves.js";
5
6
  export type DelegationMode = "mandatory" | "proactive";
6
7
  export type DelegationStatus = "scheduled" | "launched" | "acknowledged" | "completed" | "failed" | "waived" | "stale";
7
8
  export declare const DELEGATION_DISPATCH_SURFACES: readonly ["claude-task", "cursor-task", "opencode-agent", "codex-agent", "generic-task", "role-switch", "manual"];
@@ -182,6 +183,35 @@ export type DelegationEntry = {
182
183
  * v6.13.0 — integration branch merge status after deterministic fan-in.
183
184
  */
184
185
  integrationState?: "pending" | "applied" | "conflict" | "resolved" | "abandoned";
186
+ /**
187
+ * v6.14.0 — refactor outcome folded into `phase=green` events so a single
188
+ * row can close RED→GREEN→REFACTOR for the slice without a separate
189
+ * `phase=refactor` / `phase=refactor-deferred` lifecycle pass.
190
+ *
191
+ * - `mode: "inline"` — refactor pass ran inline as part of the GREEN
192
+ * delegation (rationale optional but recommended for traceability).
193
+ * - `mode: "deferred"` — refactor was intentionally deferred; rationale
194
+ * is required (carried in `rationale` and mirrored into
195
+ * `evidenceRefs[0]` so legacy linters that read evidence still work).
196
+ *
197
+ * `phase=refactor` and `phase=refactor-deferred` events remain valid
198
+ * for backward compatibility; the linter accepts either form for
199
+ * REFACTOR coverage.
200
+ *
201
+ * keep in sync with the inline copy in
202
+ * `src/content/hooks.ts::delegationRecordScript`.
203
+ */
204
+ refactorOutcome?: {
205
+ mode: "inline" | "deferred";
206
+ rationale?: string;
207
+ };
208
+ /**
209
+ * v6.14.0 — risk tier hint copied from the plan slice. Used by
210
+ * `integrationCheckRequired()` to decide whether the
211
+ * integration-overseer must run. `low` and `medium` are advisory;
212
+ * `high` always triggers the overseer. Optional on every row.
213
+ */
214
+ riskTier?: "low" | "medium" | "high";
185
215
  };
186
216
  export declare const DELEGATION_PHASES: readonly ["red", "green", "refactor", "refactor-deferred", "doc", "resolve-conflict"];
187
217
  export type DelegationPhase = (typeof DELEGATION_PHASES)[number];
@@ -390,6 +420,64 @@ export interface SelectReadySlicesOptions {
390
420
  * `claimedPaths` intersections with already-selected units and active holders.
391
421
  */
392
422
  export declare function selectReadySlices(units: ReadySliceUnit[], opts: SelectReadySlicesOptions): ReadySliceUnit[];
423
+ /**
424
+ * v6.13.1 — build scheduler rows from merged parallel wave definitions + plan units.
425
+ */
426
+ export declare function readySliceUnitsFromMergedWaves(mergedWaves: ParsedParallelWave[], planMarkdown: string, options?: ParseImplementationUnitParallelOptions): ReadySliceUnit[];
427
+ /**
428
+ * v6.14.0 — verdict from `integrationCheckRequired()`.
429
+ *
430
+ * `required: true` means the controller MUST dispatch
431
+ * `integration-overseer` before stage-complete; `reasons[]` lists the
432
+ * triggers that fired so the controller can quote them in artifacts.
433
+ *
434
+ * `required: false` means the integration check can be safely skipped
435
+ * (disjoint paths, no high-risk slices, no fan-in conflicts). Callers
436
+ * that skip dispatch should append a `cclaw_integration_overseer_skipped`
437
+ * audit row to `delegation-events.jsonl` so the run log stays honest
438
+ * about the decision.
439
+ */
440
+ export interface IntegrationCheckVerdict {
441
+ required: boolean;
442
+ reasons: string[];
443
+ }
444
+ /**
445
+ * v6.14.0 — heuristic helper deciding whether a multi-slice wave needs
446
+ * the `integration-overseer` dispatch.
447
+ *
448
+ * Triggers (any one):
449
+ * - **two or more closed slices share import boundaries** (heuristic:
450
+ * two slices declare a `claimedPaths` whose first 2 path segments
451
+ * match — same package/module directory);
452
+ * - any slice has `riskTier === "high"`;
453
+ * - any `cclaw_fanin_conflict` audit row exists in the supplied
454
+ * events list (regardless of slice).
455
+ *
456
+ * When none fire, the verdict is `{ required: false, reasons: ["disjoint-paths"] }`
457
+ * and the caller should record a `cclaw_integration_overseer_skipped`
458
+ * audit before bypassing the dispatch.
459
+ *
460
+ * Note on inputs: this function reads from the supplied delegation
461
+ * events list directly so callers can inject synthetic data in tests.
462
+ * Use `readDelegationEvents(projectRoot)` in production paths.
463
+ */
464
+ export declare function integrationCheckRequired(events: DelegationEvent[]): IntegrationCheckVerdict;
465
+ export declare function integrationCheckRequired(events: DelegationEvent[], fanInAudits: FanInAuditRecord[]): IntegrationCheckVerdict;
466
+ /**
467
+ * v6.14.0 — append a non-delegation audit event recording that the
468
+ * integration-overseer dispatch was skipped because
469
+ * `integrationCheckRequired()` returned `required: false`. Best-effort;
470
+ * never throws.
471
+ */
472
+ export declare function recordIntegrationOverseerSkipped(projectRoot: string, params: {
473
+ runId: string;
474
+ reasons: string[];
475
+ sliceIds: string[];
476
+ }): Promise<void>;
477
+ /**
478
+ * v6.13.1 — load merged wave plan (Parallel Execution Plan block + wave-plans/) and map to `ReadySliceUnit[]`.
479
+ */
480
+ export declare function loadTddReadySlicePool(planMarkdown: string, artifactsDir: string, options?: ParseImplementationUnitParallelOptions): Promise<ReadySliceUnit[]>;
393
481
  /**
394
482
  * v6.10.0 (P1) — when scheduling a `slice-implementer` on a TDD stage,
395
483
  * compare `claimedPaths` against every currently active span on the