cclaw-cli 6.10.0 → 6.12.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.
@@ -5,7 +5,6 @@ import { exists } from "./fs-utils.js";
5
5
  import { stageSchema } from "./content/stage-schema.js";
6
6
  import { readFlowState } from "./run-persistence.js";
7
7
  import { duplicateH2Headings, extractEvidencePointers, extractH2Sections, extractRequirementIdsFromMarkdown, isShortCircuitActivated, normalizeHeadingTitle, parseFrontmatter, parseLearningsSection, sectionBodyByAnyName, sectionBodyByHeadingPrefix, sectionBodyByName, validateSectionBody, formatLearningsErrorsBullets } from "./artifact-linter/shared.js";
8
- import { foldTddSliceLedger, readTddSliceLedger } from "./tdd-slices.js";
9
8
  import { shouldDemoteArtifactValidationByTrack } from "./content/stage-schema.js";
10
9
  import { readDelegationLedger, recordArtifactValidationDemotedByTrack } from "./delegation.js";
11
10
  import { classifyAndPersistFindings } from "./artifact-linter/findings-dedup.js";
@@ -147,11 +146,12 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
147
146
  }
148
147
  }
149
148
  const liteTierForValidators = shouldDemoteArtifactValidationByTrack(track, taskClass);
150
- // v6.10.0 (T3) — pre-resolve RED/GREEN Evidence pointers and sidecar
151
- // auto-satisfy state once for the whole TDD loop, then thread the
152
- // booleans through `validateSectionBody`. We do the async resolution
153
- // here (path existence + delegation spanId match) so the validators
154
- // themselves stay sync.
149
+ // v6.11.0 (D5) — pre-resolve RED/GREEN Evidence pointers AND
150
+ // delegation phase events so `validateSectionBody` (sync) can
151
+ // short-circuit. The Evidence: pointer mode (v6.10.0 T3) stays as a
152
+ // fallback alongside legacy markdown content; phase events with a
153
+ // `phase=red`/`phase=green` row plus non-empty evidenceRefs auto-pass
154
+ // the corresponding markdown validator.
155
155
  const tddEvidenceContext = stage === "tdd"
156
156
  ? await resolveTddEvidencePointerContext({
157
157
  projectRoot,
@@ -435,14 +435,21 @@ const ARTIFACT_VALIDATION_LITE_DEMOTE_SECTIONS = new Set([
435
435
  "Product Discovery Delegation (Strategist Mode)"
436
436
  ]);
437
437
  /**
438
- * v6.10.0 (T3) — pre-resolve `Evidence:` pointers and sidecar
439
- * auto-satisfy state for the TDD stage's RED/GREEN Evidence rows so
440
- * `validateSectionBody` (sync) can short-circuit. A pointer of the form
441
- * `<path>` is satisfied when the path exists on disk relative to the
442
- * project root; `spanId:<id>` is satisfied when any delegation ledger
443
- * row carries that span id. Sidecar auto-satisfy fires when
444
- * `06-tdd-slices.jsonl` carries at least one slice with a non-empty
445
- * `redOutputRef` / `greenOutputRef`.
438
+ * v6.11.0 (D5) — pre-resolve `Evidence:` pointers and delegation
439
+ * phase-event auto-satisfy state for the TDD stage's RED/GREEN
440
+ * Evidence rows so `validateSectionBody` (sync) can short-circuit.
441
+ *
442
+ * - `<path>` pointer is satisfied when the path exists on disk relative
443
+ * to the project root.
444
+ * - `spanId:<id>` pointer is satisfied when any delegation ledger row
445
+ * carries that span id.
446
+ * - Phase-event auto-satisfy fires when `delegation-events.jsonl`
447
+ * carries at least one slice-tagged event for the active run with
448
+ * `phase=red`/`phase=green` and non-empty `evidenceRefs`. This is the
449
+ * v6.11.0 replacement for the v6.10.0 sidecar auto-satisfy hook —
450
+ * slice events are now the source of truth, the RED/GREEN markdown
451
+ * tables are auto-rendered from them, and the validators MUST NOT
452
+ * demand pasted stdout when the events already prove RED/GREEN.
446
453
  */
447
454
  async function resolveTddEvidencePointerContext(input) {
448
455
  const { projectRoot, sections } = input;
@@ -451,16 +458,29 @@ async function resolveTddEvidencePointerContext(input) {
451
458
  const redPointers = extractEvidencePointers(redSection);
452
459
  const greenPointers = extractEvidencePointers(greenSection);
453
460
  let knownSpanIds = new Set();
454
- if (redPointers.length > 0 || greenPointers.length > 0) {
455
- try {
456
- const ledger = await readDelegationLedger(projectRoot);
457
- knownSpanIds = new Set(ledger.entries
458
- .map((entry) => entry.spanId)
459
- .filter((id) => typeof id === "string" && id.length > 0));
460
- }
461
- catch {
462
- knownSpanIds = new Set();
463
- }
461
+ let phaseEventsAutoSatisfy = { red: false, green: false };
462
+ try {
463
+ const ledger = await readDelegationLedger(projectRoot);
464
+ knownSpanIds = new Set(ledger.entries
465
+ .map((entry) => entry.spanId)
466
+ .filter((id) => typeof id === "string" && id.length > 0));
467
+ const runId = ledger.runId;
468
+ const slicePhaseRows = ledger.entries.filter((entry) => entry.runId === runId &&
469
+ entry.stage === "tdd" &&
470
+ typeof entry.sliceId === "string" &&
471
+ entry.sliceId.length > 0 &&
472
+ typeof entry.phase === "string");
473
+ const redOk = slicePhaseRows.some((entry) => entry.phase === "red" &&
474
+ Array.isArray(entry.evidenceRefs) &&
475
+ entry.evidenceRefs.some((ref) => typeof ref === "string" && ref.trim().length > 0));
476
+ const greenOk = slicePhaseRows.some((entry) => entry.phase === "green" &&
477
+ Array.isArray(entry.evidenceRefs) &&
478
+ entry.evidenceRefs.some((ref) => typeof ref === "string" && ref.trim().length > 0));
479
+ phaseEventsAutoSatisfy = { red: redOk, green: greenOk };
480
+ }
481
+ catch {
482
+ knownSpanIds = new Set();
483
+ phaseEventsAutoSatisfy = { red: false, green: false };
464
484
  }
465
485
  async function pointerResolves(value) {
466
486
  const trimmed = value.replace(/[`*_]/gu, "").trim();
@@ -480,28 +500,14 @@ async function resolveTddEvidencePointerContext(input) {
480
500
  }
481
501
  return false;
482
502
  }
483
- let redSidecarAutoSatisfy = false;
484
- let greenSidecarAutoSatisfy = false;
485
- try {
486
- const sidecar = await readTddSliceLedger(projectRoot);
487
- if (sidecar.entries.length > 0) {
488
- const folded = foldTddSliceLedger(sidecar.entries);
489
- redSidecarAutoSatisfy = folded.some((entry) => typeof entry.redOutputRef === "string" && entry.redOutputRef.length > 0);
490
- greenSidecarAutoSatisfy = folded.some((entry) => typeof entry.greenOutputRef === "string" && entry.greenOutputRef.length > 0);
491
- }
492
- }
493
- catch {
494
- redSidecarAutoSatisfy = false;
495
- greenSidecarAutoSatisfy = false;
496
- }
497
503
  return {
498
504
  red: {
499
505
  pointerSatisfied: await anyResolved(redPointers),
500
- sidecarAutoSatisfy: redSidecarAutoSatisfy
506
+ phaseEventsSatisfied: phaseEventsAutoSatisfy.red
501
507
  },
502
508
  green: {
503
509
  pointerSatisfied: await anyResolved(greenPointers),
504
- sidecarAutoSatisfy: greenSidecarAutoSatisfy
510
+ phaseEventsSatisfied: phaseEventsAutoSatisfy.green
505
511
  }
506
512
  };
507
513
  }
@@ -207,6 +207,20 @@ export declare const CCLAW_AGENTS: readonly [{
207
207
  readonly relatedStages: ["tdd"];
208
208
  readonly returnSchema: AgentReturnSchema;
209
209
  readonly body: string;
210
+ }, {
211
+ readonly name: "slice-documenter";
212
+ readonly description: "MANDATORY in PARALLEL with slice-implementer for every TDD slice (regardless of discoveryMode, v6.12.0 Phase R). Writes per-slice prose summary to `<artifacts-dir>/tdd-slices/S-<id>.md`. Does NOT implement, does NOT write tests. Linter rule `tdd_slice_documenter_missing` blocks the gate when a `phase=doc` event is missing for a green slice.";
213
+ readonly tools: ["Read", "Write", "Edit", "Grep", "Glob"];
214
+ readonly model: "fast";
215
+ readonly activation: "mandatory";
216
+ readonly relatedStages: ["tdd"];
217
+ readonly returnSchema: {
218
+ readonly statusField: "status";
219
+ readonly allowedStatuses: ["DONE", "DONE_WITH_CONCERNS", "NEEDS_CONTEXT", "BLOCKED"];
220
+ readonly requiredFields: ["status", "summaryMd", "learnings", "evidenceRefs", "blockers"];
221
+ readonly evidenceFields: ["summaryMd", "evidenceRefs"];
222
+ };
223
+ readonly body: string;
210
224
  }, {
211
225
  readonly name: "fixer";
212
226
  readonly description: "ON-DEMAND fresh worker after review FAIL/PARTIAL evidence. Must fix only the cited criterion within explicit allowed files.";
@@ -544,6 +544,41 @@ export const CCLAW_AGENTS = [
544
544
  "**Role boundary:** do not broaden scope, do not review your own work as final approval, and do not spawn subagents."
545
545
  ].join("\n")
546
546
  },
547
+ {
548
+ name: "slice-documenter",
549
+ description: "MANDATORY in PARALLEL with slice-implementer for every TDD slice (regardless of discoveryMode, v6.12.0 Phase R). Writes per-slice prose summary to `<artifacts-dir>/tdd-slices/S-<id>.md`. Does NOT implement, does NOT write tests. Linter rule `tdd_slice_documenter_missing` blocks the gate when a `phase=doc` event is missing for a green slice.",
550
+ tools: ["Read", "Write", "Edit", "Grep", "Glob"],
551
+ model: "fast",
552
+ activation: "mandatory",
553
+ relatedStages: ["tdd"],
554
+ returnSchema: {
555
+ statusField: "status",
556
+ allowedStatuses: ["DONE", "DONE_WITH_CONCERNS", "NEEDS_CONTEXT", "BLOCKED"],
557
+ requiredFields: ["status", "summaryMd", "learnings", "evidenceRefs", "blockers"],
558
+ evidenceFields: ["summaryMd", "evidenceRefs"]
559
+ },
560
+ body: [
561
+ "You are a **slice-documenter** dispatched in PARALLEL with `slice-implementer` for the same slice.",
562
+ "",
563
+ "**Mission:** capture per-slice prose summary while production code is being written.",
564
+ "Because your only `claimedPath` is `<artifacts-dir>/tdd-slices/S-<id>.md` and the implementer's `claimedPaths` are production code, the file-overlap scheduler auto-allows the parallel dispatch.",
565
+ "",
566
+ "When invoked:",
567
+ "1. Read the active plan unit, acceptance criterion, and the failing RED test for this slice.",
568
+ "2. Write a thin per-slice file at `<artifacts-dir>/tdd-slices/S-<id>.md` with the headings:",
569
+ " - `# Slice S-<id>`",
570
+ " - `## Plan unit` (T-... pointer)",
571
+ " - `## Acceptance criteria` (AC-... ids)",
572
+ " - `## Why this slice`",
573
+ " - `## What was tested`",
574
+ " - `## What was implemented`",
575
+ " - `## REFACTOR notes`",
576
+ " - `## Learnings`",
577
+ "3. Return JSON: `{ status, summaryMd, learnings: string[], evidenceRefs: [\"<artifacts-dir>/tdd-slices/S-<id>.md\"], blockers: [] }`.",
578
+ "",
579
+ "**Forbidden:** edit `06-tdd.md`, test files, or production code. Edit ONLY your slice file."
580
+ ].join("\n")
581
+ },
547
582
  {
548
583
  name: "fixer",
549
584
  description: "ON-DEMAND fresh worker after review FAIL/PARTIAL evidence. Must fix only the cited criterion within explicit allowed files.",
@@ -694,7 +729,7 @@ ${(() => {
694
729
  const mode = activationModeSummary();
695
730
  return `- **Mandatory:** ${mode.mandatory}.
696
731
  - **Proactive:** ${mode.proactive}.
697
- - **On-demand:** slice-implementer, fixer. Research playbooks are in-thread procedures.`;
732
+ - **On-demand:** fixer. Research playbooks are in-thread procedures.`;
698
733
  })()}
699
734
 
700
735
  ### Cost-aware routing
@@ -36,10 +36,10 @@ export const BEHAVIOR_ANCHORS = [
36
36
  },
37
37
  {
38
38
  stage: "tdd",
39
- section: "Watched-RED Proof",
40
- bad: "Hand-edit `S-1 | 2026-04-15T10:00 | observed RED` into the markdown table; nothing lands in the JSONL sidecar, so retries silently overwrite the row.",
41
- good: "Run `cclaw-cli internal tdd-slice-record --slice S-1 --status red --test-file tests/feed.spec.ts --command \"npm test\" --paths src/api/feed.ts --ac AC-3`; the linter reads the sidecar.",
42
- ruleHint: "RED/GREEN/REFACTOR transitions are recorded by `cclaw-cli internal tdd-slice-record`; the markdown tables are an auto-derived view from v6.10.0 onward."
39
+ section: "Vertical Slice Cycle",
40
+ bad: "Controller writes the failing test, the GREEN fix, AND per-slice prose into `06-tdd.md` itself, then hand-edits Watched-RED / Vertical Slice Cycle tables. `phase=red`/`green`/`doc` events missing; `tdd_slice_implementer_missing` and `tdd_slice_documenter_missing` block the gate.",
41
+ good: "Per slice: (1) `Task(\"test-author --slice S-1 --phase red\")`. Verify the event. (2) ONE message, TWO Tasks — `slice-implementer --phase green` AND `slice-documenter --phase doc`. (3) `slice-implementer --phase refactor` (or `refactor-deferred`). Linter auto-renders Vertical Slice Cycle from events.",
42
+ ruleHint: "Per-Slice Ritual (v6.12.0+): RED verify GREEN+DOC fan-out (one message, two Tasks) REFACTOR. Controller never writes GREEN code or per-slice prose. Mandatory regardless of `discoveryMode`."
43
43
  },
44
44
  {
45
45
  stage: "review",
@@ -252,7 +252,7 @@ Plan is ready to execute after user confirmation.
252
252
  | Slice | Source ID | AC ID | Expected behavior | RED-link |
253
253
  | --- | --- | --- | --- | --- |
254
254
  | S-1 | SRC-1 | AC-1 | feed window honors 30d cap | spanId:tdd-feed-window-red |
255
- | S-2 | SRC-2 | AC-3 | degraded banner appears on disconnect | .cclaw/artifacts/06-tdd-slices.jsonl |
255
+ | S-2 | SRC-2 | AC-3 | degraded banner appears on disconnect | spanId:tdd-banner-red (auto-derived from delegation-events.jsonl) |
256
256
 
257
257
  ## GREEN
258
258
 
@@ -2,7 +2,7 @@ import { existsSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { RUNTIME_ROOT } from "../constants.js";
5
- import { DELEGATION_DISPATCH_SURFACES, DELEGATION_DISPATCH_SURFACE_PATH_PREFIXES } from "../delegation.js";
5
+ import { DELEGATION_DISPATCH_SURFACES, DELEGATION_DISPATCH_SURFACE_PATH_PREFIXES, DELEGATION_PHASES } from "../delegation.js";
6
6
  function resolveCliRuntimeForGeneratedHook() {
7
7
  const here = fileURLToPath(import.meta.url);
8
8
  // Vitest runs init/sync from src/ and expects helpers to execute the same
@@ -210,6 +210,8 @@ const TERMINAL = new Set(["completed", "failed", "waived", "stale"]);
210
210
  const VALID_DISPATCH_SURFACES = ${JSON.stringify([...DELEGATION_DISPATCH_SURFACES])};
211
211
  const VALID_DISPATCH_SURFACES_SET = new Set(VALID_DISPATCH_SURFACES);
212
212
  const SURFACE_PATH_PREFIXES = ${JSON.stringify(DELEGATION_DISPATCH_SURFACE_PATH_PREFIXES)};
213
+ const VALID_DELEGATION_PHASES = ${JSON.stringify([...DELEGATION_PHASES])};
214
+ const VALID_DELEGATION_PHASES_SET = new Set(VALID_DELEGATION_PHASES);
213
215
  const LEDGER_SCHEMA_VERSION = 3;
214
216
  const FLOW_STATE_GUARD_REL_PATH = RUNTIME_ROOT + "/.flow-state.guard.json";
215
217
 
@@ -343,6 +345,11 @@ function usage() {
343
345
  "TDD parallel scheduler (v6.10.0):",
344
346
  " --paths=<a,b,c> repo-relative paths the slice-implementer will edit; disjoint sets auto-promote to allowParallel, overlap throws DispatchOverlapError",
345
347
  " --override-cap=<int> raise the slice-implementer fan-out cap once for this dispatch (default cap " + String(5) + ", env CCLAW_MAX_PARALLEL_SLICE_IMPLEMENTERS overrides globally)",
348
+ "",
349
+ "TDD slice phase tagging (v6.11.0):",
350
+ " --slice=<id> TDD slice identifier (e.g. S-1) used by the linter to auto-derive the Watched-RED + Vertical Slice Cycle tables.",
351
+ " --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.",
346
353
  ""
347
354
  ].join("\\n") + "\\n");
348
355
  }
@@ -451,6 +458,32 @@ function buildRow(args, status, runId, now, options) {
451
458
  .split(",")
452
459
  .map((value) => value.trim())
453
460
  .filter((value) => value.length > 0);
461
+ // v6.11.0 (D1+D2): TDD slice tagging via --slice / --phase. Phase
462
+ // must be one of the canonical enum values; the inline validator
463
+ // rejects unknown phases before the row hits the ledger.
464
+ const sliceId =
465
+ typeof args.slice === "string" && args.slice.trim().length > 0
466
+ ? args.slice.trim()
467
+ : undefined;
468
+ const phase =
469
+ typeof args.phase === "string" && args.phase.trim().length > 0
470
+ ? args.phase.trim()
471
+ : undefined;
472
+ // v6.11.0 (D2): when --refactor-rationale is supplied it is folded
473
+ // into evidenceRefs[0] so the linter (which reads evidenceRefs only)
474
+ // can surface the rationale without touching new fields. The user
475
+ // may also pass --evidence-ref containing the rationale text.
476
+ let resolvedEvidenceRefs = normalizeEvidenceRefs(args);
477
+ if (
478
+ phase === "refactor-deferred" &&
479
+ typeof args["refactor-rationale"] === "string" &&
480
+ args["refactor-rationale"].trim().length > 0
481
+ ) {
482
+ const rationale = args["refactor-rationale"].trim();
483
+ if (!resolvedEvidenceRefs.includes(rationale)) {
484
+ resolvedEvidenceRefs = [rationale, ...resolvedEvidenceRefs];
485
+ }
486
+ }
454
487
  return {
455
488
  stage: args.stage,
456
489
  agent: args.agent,
@@ -463,7 +496,7 @@ function buildRow(args, status, runId, now, options) {
463
496
  agentDefinitionPath: args["agent-definition-path"],
464
497
  fulfillmentMode,
465
498
  waiverReason: args["waiver-reason"],
466
- evidenceRefs: normalizeEvidenceRefs(args),
499
+ evidenceRefs: resolvedEvidenceRefs,
467
500
  runId,
468
501
  startTs,
469
502
  ts: now,
@@ -473,7 +506,9 @@ function buildRow(args, status, runId, now, options) {
473
506
  endTs: TERMINAL.has(status) ? now : undefined,
474
507
  schemaVersion: LEDGER_SCHEMA_VERSION,
475
508
  allowParallel: args["allow-parallel"] === true ? true : undefined,
476
- claimedPaths: claimedPaths.length > 0 ? claimedPaths : undefined
509
+ claimedPaths: claimedPaths.length > 0 ? claimedPaths : undefined,
510
+ sliceId,
511
+ phase
477
512
  };
478
513
  }
479
514
 
@@ -1014,6 +1049,36 @@ async function main() {
1014
1049
  return;
1015
1050
  }
1016
1051
 
1052
+ // v6.11.0 (D2) — TDD slice phase tagging validation. --phase is
1053
+ // strictly enum-bound; --slice must be a non-empty string when
1054
+ // provided; --phase=refactor-deferred requires either an explicit
1055
+ // --refactor-rationale or an --evidence-ref with rationale text so
1056
+ // the linter has something to render.
1057
+ if (args.phase !== undefined && !VALID_DELEGATION_PHASES_SET.has(args.phase)) {
1058
+ problems.push("invalid --phase (allowed: " + VALID_DELEGATION_PHASES.join(", ") + ")");
1059
+ emitProblems(problems, json, 2);
1060
+ return;
1061
+ }
1062
+ if (args.slice !== undefined && (typeof args.slice !== "string" || args.slice.trim().length === 0)) {
1063
+ problems.push("--slice requires a non-empty value");
1064
+ emitProblems(problems, json, 2);
1065
+ return;
1066
+ }
1067
+ if (args.phase === "refactor-deferred") {
1068
+ const rationaleProvided =
1069
+ typeof args["refactor-rationale"] === "string" && args["refactor-rationale"].trim().length > 0;
1070
+ const evidenceProvided =
1071
+ (typeof args["evidence-ref"] === "string" && args["evidence-ref"].trim().length > 0) ||
1072
+ (Array.isArray(args["evidence-refs"]) && args["evidence-refs"].some(
1073
+ (ref) => typeof ref === "string" && ref.trim().length > 0
1074
+ ));
1075
+ if (!rationaleProvided && !evidenceProvided) {
1076
+ problems.push("--phase=refactor-deferred requires --refactor-rationale=<text> or --evidence-ref=<text>");
1077
+ emitProblems(problems, json, 2);
1078
+ return;
1079
+ }
1080
+ }
1081
+
1017
1082
  if (args.status === "completed" && args["dispatch-surface"] !== "role-switch") {
1018
1083
  for (const key of ["dispatch-id", "dispatch-surface", "agent-definition-path"]) {
1019
1084
  if (!args[key]) problems.push("completed isolated/generic status requires --" + key);
@@ -18,6 +18,16 @@ export declare function watchedFailProofBlock(): string;
18
18
  export declare const INVESTIGATION_DISCIPLINE_STAGES: ReadonlySet<FlowStage>;
19
19
  export declare function investigationDisciplineBlock(): string;
20
20
  export declare function behaviorAnchorBlock(stage: FlowStage): string;
21
+ /**
22
+ * v6.12.0 Phase Ritual + Phase W — TDD-only top-of-skill sections that
23
+ * sit immediately after the `<EXTREMELY-IMPORTANT>` Iron Law block and
24
+ * before `## Quick Start`. They establish the per-slice three-dispatch
25
+ * ritual + wave batch mode in imperative voice with literal commands so
26
+ * pattern-matching on read works in our favor.
27
+ *
28
+ * Empty for non-TDD stages.
29
+ */
30
+ export declare function tddTopOfSkillBlock(stage: FlowStage): string;
21
31
  export declare function stageSkillFolder(stage: FlowStage): string;
22
32
  export declare function stageSkillMarkdown(stage: FlowStage, track?: FlowTrack): string;
23
33
  export declare function executingWavesSkillMarkdown(): string;
@@ -102,7 +102,7 @@ Any "the failure is real" claim (failing test, broken build, regression catch, d
102
102
 
103
103
  \`proof: <iso-ts> | <observed snippet — first 200 chars> | source: <command or log path>\`
104
104
 
105
- For TDD specifically, this is the watched-RED proof and is required per new test before \`stage-complete\` accepts the stage. From v6.10.0 onward, record TDD slice transitions through the sidecar CLI \`cclaw-cli internal tdd-slice-record --slice <id> --status red|green|refactor-done|refactor-deferred ...\` rather than hand-editing the \`Watched-RED Proof\` or \`Vertical Slice Cycle\` markdown tables; the linter reads \`.cclaw/artifacts/06-tdd-slices.jsonl\` when present and treats the markdown as an auto-derived view.
105
+ For TDD specifically, this is the watched-RED proof and is required per new test before \`stage-complete\` accepts the stage. From v6.12.0 onward, every slice on every TDD run dispatches three roles in this exact order: (1) \`test-author --slice S-<id> --phase red\`, (2) ONE message with TWO concurrent Task calls — \`slice-implementer --slice S-<id> --phase green --paths <production paths>\` AND \`slice-documenter --slice S-<id> --phase doc --paths <artifacts-dir>/tdd-slices/S-<id>.md\`, (3) \`slice-implementer --phase refactor\` or \`--phase refactor-deferred --refactor-rationale "<why>"\`. The linter auto-derives the \`Watched-RED Proof\` and \`Vertical Slice Cycle\` tables in \`06-tdd.md\` from \`.cclaw/state/delegation-events.jsonl\`. Do NOT hand-edit those tables. \`slice-implementer\` and \`slice-documenter\` are mandatory regardless of \`discoveryMode\` (v6.12.0 Phase R/M); the controller MUST NOT write GREEN production code or per-slice prose itself. v6.10.0 sidecar (\`06-tdd-slices.jsonl\`) is removed; \`cclaw-cli sync\` cleans the file from existing installs.
106
106
  `;
107
107
  }
108
108
  /**
@@ -168,6 +168,64 @@ function whenNotToUseBlock(items) {
168
168
  return `## When Not to Use
169
169
  ${items.map((item) => `- ${item}`).join("\n")}
170
170
 
171
+ `;
172
+ }
173
+ /**
174
+ * v6.12.0 Phase Ritual + Phase W — TDD-only top-of-skill sections that
175
+ * sit immediately after the `<EXTREMELY-IMPORTANT>` Iron Law block and
176
+ * before `## Quick Start`. They establish the per-slice three-dispatch
177
+ * ritual + wave batch mode in imperative voice with literal commands so
178
+ * pattern-matching on read works in our favor.
179
+ *
180
+ * Empty for non-TDD stages.
181
+ */
182
+ export function tddTopOfSkillBlock(stage) {
183
+ if (stage !== "tdd")
184
+ return "";
185
+ return `## Per-Slice Ritual (v6.12.0+)
186
+
187
+ ONE slice = THREE dispatches, in this order. Do not skip, do not collapse.
188
+
189
+ 1. **RED** — \`Task("test-author --slice S-<id> --phase red")\`.
190
+ 2. **Verify RED** — wait for the \`phase=red\` event in \`.cclaw/state/delegation-events.jsonl\` with non-empty \`evidenceRefs\`. No production edits.
191
+ 3. **GREEN+DOC fan-out** — ONE message, TWO concurrent Tasks:
192
+ \`\`\`
193
+ Task("slice-implementer --slice S-<id> --phase green --paths <prod paths>")
194
+ Task("slice-documenter --slice S-<id> --phase doc --paths <artifacts-dir>/tdd-slices/S-<id>.md")
195
+ \`\`\`
196
+ The file-overlap scheduler auto-allows parallel dispatch because \`claimedPaths\` are disjoint. Fire BOTH calls in the same message — never serialize independent work.
197
+ 4. **REFACTOR** — \`Task("slice-implementer --slice S-<id> --phase refactor")\` OR \`--phase refactor-deferred --refactor-rationale '<why>'\`.
198
+
199
+ **FORBIDDEN:**
200
+ - Controller writing GREEN production code. ALL GREEN goes through \`slice-implementer\` — linter rule \`tdd_slice_implementer_missing\` blocks the gate.
201
+ - 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
+ - Hand-editing auto-render blocks between \`auto-start: tdd-slice-summary\` / \`auto-start: slices-index\` markers — overwritten every lint.
203
+
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\`.
205
+
206
+ ## Wave Batch Mode (v6.12.0+)
207
+
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\`.
209
+
210
+ **Phase A — RED checkpoint** — ONE message, all test-authors:
211
+ \`\`\`
212
+ Task("test-author --slice S-1 --phase red")
213
+ Task("test-author --slice S-2 --phase red")
214
+ Task("test-author --slice S-3 --phase red")
215
+ \`\`\`
216
+ 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
+
218
+ **Phase B — GREEN+DOC fan-out** — ONE message, paired implementer+documenter Tasks per slice:
219
+ \`\`\`
220
+ Task("slice-implementer --slice S-1 --phase green --paths <S-1 prod>")
221
+ 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
+ \`\`\`
225
+ Launch ALL Phase B pairs in ONE message. **Never serialize independent work.**
226
+
227
+ **Fan-in** — when 2+ \`slice-implementer\` rows complete in a wave, dispatch \`integration-overseer\` to verify cohesion contract (shared types, touchpoints, invariants, integration tests).
228
+
171
229
  `;
172
230
  }
173
231
  function artifactTemplatePathForStage(stage) {
@@ -593,7 +651,7 @@ If you are about to violate the Iron Law, STOP. No amount of urgency, partial pr
593
651
 
594
652
  </EXTREMELY-IMPORTANT>
595
653
 
596
- ${quickStartBlock(stage, track)}
654
+ ${tddTopOfSkillBlock(stage)}${quickStartBlock(stage, track)}
597
655
 
598
656
  ${STAGE_LANGUAGE_POLICY_POINTER}
599
657
  ## Philosophy
@@ -117,6 +117,7 @@ function defaultReturnSchemaForAgent(agent) {
117
117
  case "feasibility-reviewer":
118
118
  return "review-return";
119
119
  case "slice-implementer":
120
+ case "slice-documenter":
120
121
  return "worker-return";
121
122
  case "release-reviewer":
122
123
  return "release-return";
@@ -143,7 +144,7 @@ function defaultReturnSchemaForAgent(agent) {
143
144
  function dispatchClassForRow(row) {
144
145
  if (row.dispatchClass)
145
146
  return row.dispatchClass;
146
- if (row.agent === "fixer" || row.agent === "slice-implementer")
147
+ if (row.agent === "fixer" || row.agent === "slice-implementer" || row.agent === "slice-documenter")
147
148
  return "worker";
148
149
  return row.skill?.includes("review") || row.agent === "reviewer" || row.agent === "security-reviewer" || row.agent.endsWith("-reviewer")
149
150
  ? "review-lens"
@@ -357,15 +358,12 @@ const REQUIRED_ARTIFACT_SECTIONS = {
357
358
  ],
358
359
  plan: ["Task List", "Dependency Batches", "Acceptance Mapping", "Execution Posture", "WAIT_FOR_CONFIRM"],
359
360
  tdd: [
360
- "Test Discovery",
361
361
  "System-Wide Impact Check",
362
362
  "RED Evidence",
363
363
  "GREEN Evidence",
364
364
  "REFACTOR Notes",
365
365
  "Traceability",
366
366
  "Iron Law Acknowledgement",
367
- "Watched-RED Proof",
368
- "Vertical Slice Cycle",
369
367
  "Verification Ladder"
370
368
  ],
371
369
  review: ["Review Evidence Scope", "Changed-File Coverage", "Layer 1 Verdict", "Review Findings Contract", "Severity Summary", "Final Verdict"],
@@ -724,10 +722,18 @@ const STAGE_AUTO_SUBAGENT_DISPATCH = {
724
722
  },
725
723
  {
726
724
  agent: "slice-implementer",
727
- mode: "proactive",
725
+ mode: "mandatory",
726
+ requiredAtTier: "lightweight",
727
+ when: "Always for GREEN and REFACTOR phases. Controller MUST NOT write production code itself.",
728
+ purpose: "Implement the minimal passing slice inside explicit file boundaries and return strict worker evidence. v6.12.0 Phase M makes this dispatch mandatory; the linter rule `tdd_slice_implementer_missing` blocks the gate when GREEN was authored by anyone other than `slice-implementer`.",
729
+ requiresUserGate: false
730
+ },
731
+ {
732
+ agent: "slice-documenter",
733
+ mode: "mandatory",
728
734
  requiredAtTier: "lightweight",
729
- when: "When a bounded GREEN/REFACTOR slice has non-overlapping file ownership and a clear RED failure.",
730
- purpose: "Implement the minimal passing slice inside explicit file boundaries and return strict worker evidence.",
735
+ when: "Always in PARALLEL with `slice-implementer --phase green` for the same slice.",
736
+ purpose: "Write per-slice prose into `<artifacts-dir>/tdd-slices/S-<id>.md` while production code is being implemented. v6.12.0 Phase R makes this mandatory regardless of `discoveryMode`; the linter rule `tdd_slice_documenter_missing` blocks the gate when a `phase=doc` event is missing.",
731
737
  requiresUserGate: false
732
738
  },
733
739
  {
@@ -20,7 +20,7 @@ export interface ArtifactValidation {
20
20
  tier?: "required" | "recommended";
21
21
  validationRule: string;
22
22
  }
23
- export type StageSubagentName = "researcher" | "architect" | "spec-validator" | "spec-document-reviewer" | "coherence-reviewer" | "scope-guardian-reviewer" | "feasibility-reviewer" | "slice-implementer" | "release-reviewer" | "planner" | "product-discovery" | "divergent-thinker" | "critic" | "reviewer" | "security-reviewer" | "integration-overseer" | "test-author" | "doc-updater" | "fixer";
23
+ export type StageSubagentName = "researcher" | "architect" | "spec-validator" | "spec-document-reviewer" | "coherence-reviewer" | "scope-guardian-reviewer" | "feasibility-reviewer" | "slice-implementer" | "slice-documenter" | "release-reviewer" | "planner" | "product-discovery" | "divergent-thinker" | "critic" | "reviewer" | "security-reviewer" | "integration-overseer" | "test-author" | "doc-updater" | "fixer";
24
24
  export type StageSubagentDispatchClass = "stage-specialist" | "worker" | "review-lens";
25
25
  export type StageSubagentReturnSchema = "planning-return" | "product-return" | "critic-return" | "review-return" | "security-return" | "tdd-return" | "docs-return" | "worker-return" | "fixer-return" | "research-return" | "architecture-return" | "spec-validation-return" | "release-return";
26
26
  export interface StageAutoSubagentDispatch {