cclaw-cli 6.14.0 → 6.14.1

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.
@@ -448,36 +448,69 @@ export async function lintTddStage(ctx) {
448
448
  // `integrationCheckRequired()` returns required=false, the gate is
449
449
  // soft (advisory) and an audit-only finding is emitted so the
450
450
  // controller can record the deliberate skip in artifacts.
451
+ //
452
+ // v6.14.1 — also surface the audit row presence. When the controller
453
+ // skips `integration-overseer` dispatch (or the heuristic returns
454
+ // false), the run log MUST contain a
455
+ // `cclaw_integration_overseer_skipped` audit row for traceability.
456
+ // The advisory `tdd_integration_overseer_skipped_audit_missing`
457
+ // surfaces a missing audit row when 2+ closed slices closed without
458
+ // any overseer dispatch AND no audit was recorded.
451
459
  let overseerVerdict = null;
452
460
  let overseerRequired = true;
461
+ const skippedAuditRowCount = await countIntegrationOverseerSkippedAudits(projectRoot, delegationLedger.runId);
462
+ const skippedAuditRowFound = skippedAuditRowCount > 0;
453
463
  if (integrationOverseerMode === "conditional") {
454
464
  const eventsForVerdict = runEvents.length > 0 ? runEvents : [];
455
465
  const auditsForVerdict = fanInAudits.filter((a) => a.runId === delegationLedger.runId);
456
466
  overseerVerdict = integrationCheckRequired(eventsForVerdict, auditsForVerdict);
457
467
  overseerRequired = overseerVerdict.required;
458
468
  if (!overseerVerdict.required) {
469
+ const auditRowSuffix = skippedAuditRowFound
470
+ ? "audit row recorded — skip is fully traceable."
471
+ : "audit row MISSING — controller should append `cclaw_integration_overseer_skipped` for traceability (see `tdd_integration_overseer_skipped_audit_missing`).";
459
472
  findings.push({
460
473
  section: "tdd_integration_overseer_skipped_by_disjoint_paths",
461
474
  required: false,
462
- rule: "v6.14.0 conditional integration-overseer mode: the heuristic returned `required: false` (disjoint claimedPaths, no high-risk slices, no fan-in conflicts). The controller may skip dispatching `integration-overseer` and emit a `cclaw_integration_overseer_skipped` audit row instead.",
475
+ rule: "v6.14.0+ conditional integration-overseer mode: the heuristic returned `required: false` (disjoint claimedPaths, no high-risk slices, no fan-in conflicts). The controller may skip dispatching `integration-overseer` and emit a `cclaw_integration_overseer_skipped` audit row instead.",
463
476
  found: true,
464
- details: `integrationCheckRequired() reasons: ${overseerVerdict.reasons.join(", ")}. Skip is safe — record an audit row via delegation events for traceability.`
477
+ details: `integrationCheckRequired() reasons: ${overseerVerdict.reasons.join(", ")}. Skip is safe — ${auditRowSuffix}`
465
478
  });
466
479
  }
467
480
  }
481
+ // v6.14.1 — `tdd_integration_overseer_skipped_audit_missing` (advisory).
482
+ // Fires when fan-out is detected (2+ completed slice-implementers),
483
+ // no `integration-overseer` was dispatched at all (no scheduled or
484
+ // completed row for the active run), AND no
485
+ // `cclaw_integration_overseer_skipped` audit row exists. This pairs
486
+ // with the controller skill text rule that the wave-closure decision
487
+ // ("dispatch overseer or skip") MUST leave a trail.
488
+ const overseerDispatched = activeRunEntries.some((entry) => entry.agent === "integration-overseer");
489
+ if (!overseerDispatched && !skippedAuditRowFound) {
490
+ findings.push({
491
+ section: "tdd_integration_overseer_skipped_audit_missing",
492
+ required: false,
493
+ rule: "v6.14.1: when a wave with 2+ closed slices closes without any integration-overseer dispatch, the controller should call `integrationCheckRequired()` and emit a `cclaw_integration_overseer_skipped` audit row so the decision is traceable. Advisory — never blocks stage-complete.",
494
+ found: false,
495
+ details: `Fan-out detected (${completedSliceImplementers.length} completed slice-implementer rows) but no integration-overseer dispatch row OR cclaw_integration_overseer_skipped audit row exists for active run. ` +
496
+ "Remediation: emit `node .cclaw/hooks/delegation-record.mjs --audit-kind=cclaw_integration_overseer_skipped --audit-reason=\"<reasons>\" --slice-ids=\"<S-1,S-2,...>\"` after wave closure."
497
+ });
498
+ }
468
499
  findings.push({
469
500
  section: "tdd.integration_overseer_missing",
470
501
  required: overseerRequired,
471
502
  rule: overseerRequired
472
503
  ? "When fan-out is detected, require completed `integration-overseer` evidence with PASS or PASS_WITH_GAPS."
473
- : "v6.14.0 conditional integration-overseer mode: integration-overseer dispatch is advisory because `integrationCheckRequired()` returned required=false. Run it anyway if the run touches new boundaries.",
504
+ : "v6.14.0+ conditional integration-overseer mode: integration-overseer dispatch is advisory because `integrationCheckRequired()` returned required=false. Run it anyway if the run touches new boundaries.",
474
505
  found: integrationOverseerFound,
475
506
  details: integrationOverseerFound
476
507
  ? "integration-overseer completion recorded with PASS/PASS_WITH_GAPS evidence."
477
508
  : completedOverseerRows.length === 0
478
509
  ? overseerRequired
479
510
  ? "Fan-out detected but no completed integration-overseer delegation row exists for active run."
480
- : "Fan-out detected; integration-overseer not dispatched (conditional mode skipped on disjoint paths). Audit-only."
511
+ : skippedAuditRowFound
512
+ ? "Fan-out detected; integration-overseer not dispatched (conditional mode skipped on disjoint paths) and `cclaw_integration_overseer_skipped` audit row recorded. Audit-only."
513
+ : "Fan-out detected; integration-overseer not dispatched (conditional mode skipped on disjoint paths). Audit-only."
481
514
  : "integration-overseer completion exists, but PASS/PASS_WITH_GAPS evidence is missing in delegation evidenceRefs and artifact text."
482
515
  });
483
516
  }
@@ -551,6 +584,46 @@ export async function lintTddStage(ctx) {
551
584
  }
552
585
  }
553
586
  }
587
+ /**
588
+ * v6.14.1 — count `cclaw_integration_overseer_skipped` audit rows in
589
+ * `delegation-events.jsonl` for a given runId. The audit row is not a
590
+ * `DelegationEvent` (no agent/status), so `readDelegationEvents`
591
+ * filters it out; we re-scan the raw file with a narrow JSON match.
592
+ *
593
+ * Best-effort: missing file or parse errors return 0.
594
+ */
595
+ async function countIntegrationOverseerSkippedAudits(projectRoot, runId) {
596
+ const filePath = path.join(projectRoot, ".cclaw/state/delegation-events.jsonl");
597
+ let raw = "";
598
+ try {
599
+ raw = await fs.readFile(filePath, "utf8");
600
+ }
601
+ catch {
602
+ return 0;
603
+ }
604
+ let count = 0;
605
+ for (const line of raw.split(/\r?\n/u)) {
606
+ const trimmed = line.trim();
607
+ if (trimmed.length === 0)
608
+ continue;
609
+ let parsed;
610
+ try {
611
+ parsed = JSON.parse(trimmed);
612
+ }
613
+ catch {
614
+ continue;
615
+ }
616
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
617
+ continue;
618
+ const obj = parsed;
619
+ if (obj.event !== "cclaw_integration_overseer_skipped")
620
+ continue;
621
+ if (typeof obj.runId === "string" && obj.runId !== runId)
622
+ continue;
623
+ count += 1;
624
+ }
625
+ return count;
626
+ }
554
627
  async function listSliceFiles(slicesDir) {
555
628
  let entries = [];
556
629
  try {
@@ -52,6 +52,56 @@ Before doing substantive work, return an ACK object that the parent can record:
52
52
 
53
53
  Finish with the required return schema plus the same \`spanId\` and \`dispatchId\`. The parent must not claim isolated completion unless ACK/result proof matches the ledger/event span.`;
54
54
  }
55
+ /**
56
+ * v6.14.1 — TDD worker self-record contract. The parent records
57
+ * `scheduled` and `launched` rows BEFORE dispatching the Task; the
58
+ * worker is responsible for `acknowledged` (on entry) and `completed`
59
+ * (on exit). This contract restores the v6.13.1 discipline that
60
+ * v6.14.0 dropped — the controller-side fix in v6.14.1's TDD skill
61
+ * text is paired with this worker-side self-record helper template.
62
+ */
63
+ function tddWorkerSelfRecordContract(agentName) {
64
+ const isImplementer = agentName === "slice-implementer";
65
+ const refactorOutcomeFlag = isImplementer
66
+ ? " --refactor-outcome=inline|deferred [--refactor-rationale=\"<why>\"]"
67
+ : "";
68
+ const laneFlags = isImplementer
69
+ ? " [--claim-token=<t>] [--lane-id=<lane>] [--lease-until=<iso>]"
70
+ : "";
71
+ return `## TDD Worker Self-Record Contract (v6.14.1)
72
+
73
+ You are a TDD worker dispatched via \`Task\`. The parent already wrote your \`scheduled\` and \`launched\` ledger rows BEFORE invoking you. **Your responsibility is to self-record \`acknowledged\` on entry and \`completed\` on exit** by invoking \`.cclaw/hooks/delegation-record.mjs\` directly. Do NOT skip these — the controller depends on them, the linter validates them, and back-fill via \`--repair\` is reserved for recovery only.
74
+
75
+ **On entry — record acknowledgement (BEFORE doing work):**
76
+
77
+ \`\`\`bash
78
+ ACK_TS="$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ 2>/dev/null || date -u +%Y-%m-%dT%H:%M:%SZ)"
79
+ node .cclaw/hooks/delegation-record.mjs \\
80
+ --stage=tdd --agent=${agentName} --mode=mandatory \\
81
+ --status=acknowledged \\
82
+ --span-id=<spanId from controller dispatch> \\
83
+ --dispatch-id=<dispatchId from controller dispatch> \\
84
+ --dispatch-surface=<surface from controller dispatch> \\
85
+ --agent-definition-path=.cclaw/agents/${agentName}.md \\
86
+ --ack-ts="$ACK_TS" \\
87
+ --json
88
+ \`\`\`
89
+
90
+ **On exit — record completion (AFTER work + verification):**
91
+
92
+ \`\`\`bash
93
+ COMPLETED_TS="$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ 2>/dev/null || date -u +%Y-%m-%dT%H:%M:%SZ)"
94
+ node .cclaw/hooks/delegation-record.mjs \\
95
+ --stage=tdd --agent=${agentName} --mode=mandatory \\
96
+ --status=completed \\
97
+ --span-id=<same spanId> \\
98
+ --completed-ts="$COMPLETED_TS" \\
99
+ --evidence-ref="<test-path-or-artifact-ref>"${refactorOutcomeFlag}${laneFlags} \\
100
+ --json
101
+ \`\`\`
102
+
103
+ Reuse the same \`<spanId>\` and \`<dispatchId>\` across both rows. \`--ack-ts\` and \`--completed-ts\` must be monotonic on the span (\`startTs ≤ launchedTs ≤ ackTs ≤ completedTs\`); the helper rejects out-of-order writes with \`delegation_timestamp_non_monotonic\`. If the helper rejects with \`dispatch_active_span_collision\` against a stale span, surface the conflicting \`spanId\` to the parent — do NOT silently retry with \`--allow-parallel\`.`;
104
+ }
55
105
  function formatReturnSchema(schema) {
56
106
  const lines = [
57
107
  `- Status field: \`${schema.statusField}\``,
@@ -600,6 +650,18 @@ export const CCLAW_AGENTS = [
600
650
  ].join("\n")
601
651
  }
602
652
  ];
653
+ /**
654
+ * v6.14.1 — agents whose rendered `.cclaw/agents/<name>.md` file gets the
655
+ * TDD worker self-record helper template. These agents are the ones the
656
+ * controller dispatches via `Task` during a TDD wave; they are
657
+ * responsible for `acknowledged` and `completed` ledger writes.
658
+ */
659
+ const TDD_WORKER_SELF_RECORD_AGENTS = new Set([
660
+ "test-author",
661
+ "slice-implementer",
662
+ "slice-documenter",
663
+ "integration-overseer"
664
+ ]);
603
665
  import { stageDelegationSummary } from "./stage-schema.js";
604
666
  /**
605
667
  * Render a complete cclaw agent markdown file (YAML frontmatter + body).
@@ -627,6 +689,9 @@ export function agentMarkdown(agent) {
627
689
  ].join("\n");
628
690
  const relatedStages = agent.relatedStages.length > 0 ? agent.relatedStages.join(", ") : "(none)";
629
691
  const taskDelegation = defaultTaskDelegationSection(agent.name);
692
+ const tddWorkerSelfRecordSection = TDD_WORKER_SELF_RECORD_AGENTS.has(agent.name)
693
+ ? `\n${tddWorkerSelfRecordContract(agent.name)}\n`
694
+ : "";
630
695
  return `${frontmatter}
631
696
 
632
697
  # ${agent.name}
@@ -639,7 +704,7 @@ ${agent.body}
639
704
  - Related stages: ${relatedStages}
640
705
 
641
706
  ${workerAckContract()}
642
-
707
+ ${tddWorkerSelfRecordSection}
643
708
  ## Required Return Schema
644
709
 
645
710
  STRICT_RETURN_SCHEMA: return a structured object matching this contract before any narrative when delegated. Include \`spanId\`, \`dispatchId\` or \`workerRunId\`, \`dispatchSurface\`, \`agentDefinitionPath\`, and lifecycle timestamps when provided by the parent.
@@ -344,6 +344,7 @@ function usage() {
344
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]",
345
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]",
346
346
  " node .cclaw/hooks/delegation-record.mjs --repair --span-id=<id> --repair-reason=\\\"<why>\\\" [--json]",
347
+ " node .cclaw/hooks/delegation-record.mjs --audit-kind=cclaw_integration_overseer_skipped [--audit-reason=\\\"<comma-separated reasons>\\\"] [--slice-ids=\\\"S-1,S-2\\\"] [--json] # v6.14.1: emit non-delegation audit row only",
347
348
  "",
348
349
  "Allowed --dispatch-surface values:",
349
350
  " " + VALID_DISPATCH_SURFACES.join(", "),
@@ -898,6 +899,74 @@ async function findLegacyEntry(root, spanId) {
898
899
  return ledger.entries.find((entry) => entry && entry.spanId === spanId) || null;
899
900
  }
900
901
 
902
+ // v6.14.1 — allow-list of non-delegation audit events the controller
903
+ // can emit via the helper. Keep in sync with NON_DELEGATION_AUDIT_EVENTS
904
+ // in src/delegation.ts.
905
+ const VALID_AUDIT_KINDS = new Set([
906
+ "cclaw_integration_overseer_skipped"
907
+ ]);
908
+
909
+ async function runAuditEmit(args, json) {
910
+ const kind = String(args["audit-kind"]).trim();
911
+ if (!VALID_AUDIT_KINDS.has(kind)) {
912
+ emitProblems([
913
+ "invalid --audit-kind: " + kind +
914
+ " (allowed: " + [...VALID_AUDIT_KINDS].join(", ") + ")"
915
+ ], json, 2);
916
+ return;
917
+ }
918
+ const root = await detectRoot();
919
+ const runId = await readRunId(root);
920
+ const reason = typeof args["audit-reason"] === "string"
921
+ ? args["audit-reason"].trim()
922
+ : "";
923
+ const sliceIdsRaw = typeof args["slice-ids"] === "string"
924
+ ? args["slice-ids"].trim()
925
+ : "";
926
+ const sliceIds = sliceIdsRaw.length > 0
927
+ ? sliceIdsRaw
928
+ .split(",")
929
+ .map((value) => value.trim())
930
+ .filter((value) => value.length > 0)
931
+ : [];
932
+ const ts = new Date().toISOString();
933
+ const payload = {
934
+ event: kind,
935
+ runId,
936
+ ts,
937
+ eventTs: ts,
938
+ ...(reason.length > 0 ? { reasons: reason.split(",").map((r) => r.trim()).filter((r) => r.length > 0) } : {}),
939
+ ...(sliceIds.length > 0 ? { sliceIds } : {})
940
+ };
941
+ const stateDir = path.join(root, RUNTIME_ROOT, "state");
942
+ try {
943
+ await fs.mkdir(stateDir, { recursive: true });
944
+ await fs.appendFile(
945
+ path.join(stateDir, "delegation-events.jsonl"),
946
+ JSON.stringify(payload) + "\\n",
947
+ { encoding: "utf8", mode: 0o600 }
948
+ );
949
+ } catch (error) {
950
+ const message = error && typeof error === "object" && "message" in error
951
+ ? String(error.message)
952
+ : String(error);
953
+ emitErrorJson("audit_emit_failed", { kind, message }, json);
954
+ return;
955
+ }
956
+ if (json) {
957
+ process.stdout.write(JSON.stringify({
958
+ ok: true,
959
+ command: "audit-emit",
960
+ auditKind: kind,
961
+ runId,
962
+ sliceIds,
963
+ ts
964
+ }, null, 2) + "\\n");
965
+ } else {
966
+ process.stdout.write("[cclaw] audit emitted: " + kind + " (run=" + runId + ", ts=" + ts + ")\\n");
967
+ }
968
+ }
969
+
901
970
  async function runRerecord(args, json) {
902
971
  const problems = [];
903
972
  for (const key of ["span-id", "dispatch-id", "dispatch-surface", "agent-definition-path"]) {
@@ -1130,6 +1199,18 @@ async function main() {
1130
1199
  return;
1131
1200
  }
1132
1201
 
1202
+ // v6.14.1 — audit-only emit path. When the controller wants to record
1203
+ // a non-delegation audit row (e.g. \`cclaw_integration_overseer_skipped\`
1204
+ // when the wave heuristic chose to skip the overseer dispatch), pass
1205
+ // --audit-kind=<event-name> [--audit-reason=<text>] [--slice-ids=<csv>]
1206
+ // and the helper appends a single line to delegation-events.jsonl
1207
+ // without touching the lifecycle ledger. The kind must be in the
1208
+ // canonical allow-list so a typo cannot inject an unrecognized event.
1209
+ if (typeof args["audit-kind"] === "string" && args["audit-kind"].trim().length > 0) {
1210
+ await runAuditEmit(args, json);
1211
+ return;
1212
+ }
1213
+
1133
1214
  const problems = [];
1134
1215
  if (!args.stage) problems.push("missing --stage");
1135
1216
  if (!args.agent) problems.push("missing --agent");
@@ -38,6 +38,10 @@ export const TDD = {
38
38
  executionModel: {
39
39
  checklist: [
40
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
+ "**Controller dispatch ordering (v6.14.1 — record BEFORE dispatch).** For every `Task` subagent the controller spawns, record `scheduled` then `launched` ledger events via `node .cclaw/hooks/delegation-record.mjs --status=scheduled ...` and `--status=launched ...` **BEFORE** the `Task(...)` call (one message: ledger writes first, then the matching `Task` calls). Workers self-record `acknowledged` and `completed`; controller back-fill is reserved for `--repair` recovery only. Pass `--span-id`, `--lane-id`, `--claim-token`, `--lease-until` through to the worker so its own helper invocations reuse them.",
42
+ "**Wave closure — integration-overseer decision (v6.14.1).** When every dispatched lane has a `phase=green status=completed` event AND per-lane REFACTOR coverage is satisfied (separate phase event OR `refactorOutcome` folded into GREEN), call `integrationCheckRequired(events, fanInAudits)` from `src/delegation.ts`. (1) `required: true` → dispatch `integration-overseer` as before. (2) `required: false` → emit the audit row via `node .cclaw/hooks/delegation-record.mjs --audit-kind=cclaw_integration_overseer_skipped --audit-reason=\"<reasons>\" --slice-ids=\"S-1,S-2\" --json` and SKIP the dispatch. Linter advisory `tdd_integration_overseer_skipped_audit_missing` flags a wave that closes without either an overseer dispatch or this audit row.",
43
+ "**Inline DOC opt-in (v6.14.1 — single-slice non-deep waves).** Default remains parallel `slice-documenter --phase doc` dispatched alongside `slice-implementer --phase green`. For single-slice waves where `flow-state.json::discoveryMode != \"deep\"`, the controller MAY skip the parallel documenter and instead invoke `slice-implementer --finalize-doc --slice S-<id> --paths <artifacts-dir>/tdd-slices/S-<id>.md` synchronously after GREEN. Multi-slice waves and any `discoveryMode=deep` run keep parallel slice-documenter mandatory.",
44
+ "**Stale active-span recovery (v6.14.1).** If `delegation-record` rejects a new `--status=scheduled` with `dispatch_active_span_collision` or `dispatch_duplicate` and the conflicting span has a `completed` event in `delegation-events.jsonl`, the fold is correct (`computeActiveSubagents` excludes terminal spans) and the rejection is from a different live span on the same `(stage, agent)` pair — pass `--allow-parallel` deliberately, quote the conflicting `spanId` in the turn output, and proceed. If you cannot identify the conflicting active span, STOP and report — do not blanket-add `--allow-parallel` to silence the helper.",
41
45
  "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.",
42
46
  "Map to acceptance criterion — identify the specific spec criterion this test proves.",
43
47
  "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.",
@@ -49,8 +53,8 @@ export const TDD = {
49
53
  "GREEN: Run full suite — execute ALL tests, not just the ones you wrote. The full suite must be GREEN.",
50
54
  "GREEN: Verify no regressions — if any existing test breaks, fix the regression before proceeding.",
51
55
  "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).",
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.",
56
+ "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` `--status=completed` write. Form (3) is the v6.14.0 default for new projects; the linter accepts all three as REFACTOR coverage. Form (1) is the only legal form when BOTH `legacyContinuation: true` AND `flow-state.json::tddCheckpointMode: \"global-red\"` are set (legacy hox-shape projects); other projects may use any form. Set `CCLAW_ACTIVE_AGENT=tdd-refactor` when the harness supports phase labels.",
57
+ "DOC (v6.14.0+ softened, v6.14.1 inline-opt-in): 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`); the controller MAY either keep parallel `slice-documenter` dispatch (default — preserves the documenter's isolated context) OR, **for single-slice non-deep waves**, call `slice-implementer --finalize-doc --slice S-<id> --paths <artifacts-dir>/tdd-slices/S-<id>.md` inline after GREEN completes. Multi-slice waves keep parallel `slice-documenter` regardless of mode. **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
58
  "**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
59
  "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
60
  "**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.",
@@ -58,9 +62,9 @@ export const TDD = {
58
62
  "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
63
  ],
60
64
  interactionProtocol: [
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.",
65
+ "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` (via `delegation-record --audit-kind=cclaw_integration_overseer_skipped --audit-reason=\"<reasons>\"`) 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
66
  "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
- "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.",
67
+ "Controller owns orchestration. **v6.14.1 — record BEFORE dispatch:** every controller `Task` dispatch is preceded by two `delegation-record` writes (`--status=scheduled` then `--status=launched`); workers self-record `--status=acknowledged` on entry and `--status=completed` on exit. Never dispatch first and back-fill — that order breaks the active-span check and forces `--allow-parallel` workarounds. For each slice S-<id>, dispatch in 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 do NOT hand-edit those tables.",
64
68
  "Before writing RED tests, discover relevant existing tests and commands so the new test extends the suite instead of fighting it.",
65
69
  "Before implementation, perform a system-wide impact check across callbacks, state, interfaces, schemas, and external contracts touched by the slice.",
66
70
  "Slice-documenter (mandatory v6.12.0, regardless of `discoveryMode`): in PARALLEL with `slice-implementer --phase green`, dispatch `slice-documenter --slice S-<id> --phase doc` whose only `claimedPaths` is `<artifacts-dir>/tdd-slices/S-<id>.md`. The two dispatches run concurrently because their paths are disjoint. The documenter writes per-slice prose so the main `06-tdd.md` stays thin. Controller MUST NOT author per-slice prose; controller MUST NOT author GREEN production code (use `slice-implementer`).",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "6.14.0",
3
+ "version": "6.14.1",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {