cclaw-cli 7.5.0 → 7.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +2 -1
  2. package/dist/artifact-linter/plan.js +238 -26
  3. package/dist/artifact-linter/tdd.js +4 -3
  4. package/dist/config.d.ts +18 -1
  5. package/dist/config.js +176 -5
  6. package/dist/content/core-agents.d.ts +1 -1
  7. package/dist/content/core-agents.js +17 -2
  8. package/dist/content/hooks.js +37 -0
  9. package/dist/content/meta-skill.js +4 -4
  10. package/dist/content/skills.js +12 -8
  11. package/dist/content/stage-schema.js +3 -2
  12. package/dist/content/stages/plan.js +18 -17
  13. package/dist/content/stages/tdd.js +13 -10
  14. package/dist/content/start-command.js +3 -3
  15. package/dist/content/subagent-context-skills.js +2 -2
  16. package/dist/content/subagents.js +6 -6
  17. package/dist/content/templates.js +12 -7
  18. package/dist/delegation.d.ts +43 -3
  19. package/dist/delegation.js +80 -9
  20. package/dist/execution-topology.d.ts +36 -0
  21. package/dist/execution-topology.js +73 -0
  22. package/dist/gate-evidence.js +10 -12
  23. package/dist/internal/advance-stage/start-flow.js +13 -4
  24. package/dist/internal/cohesion-contract-stub.js +2 -14
  25. package/dist/internal/plan-split-waves.d.ts +5 -2
  26. package/dist/internal/plan-split-waves.js +27 -16
  27. package/dist/internal/slice-commit.js +161 -7
  28. package/dist/internal/wave-status.d.ts +4 -0
  29. package/dist/internal/wave-status.js +50 -9
  30. package/dist/stack-detection.d.ts +94 -0
  31. package/dist/stack-detection.js +431 -0
  32. package/dist/tdd-cycle.js +7 -5
  33. package/dist/types.d.ts +67 -0
  34. package/dist/util/slice-id.d.ts +58 -0
  35. package/dist/util/slice-id.js +89 -0
  36. package/package.json +1 -1
package/dist/config.js CHANGED
@@ -6,7 +6,14 @@ import { exists, writeFileSafe } from "./fs-utils.js";
6
6
  import { HARNESS_IDS } from "./types.js";
7
7
  const CONFIG_PATH = `${RUNTIME_ROOT}/config.yaml`;
8
8
  const HARNESS_ID_SET = new Set(HARNESS_IDS);
9
- const ALLOWED_CONFIG_KEYS = new Set(["version", "flowVersion", "harnesses", "tdd"]);
9
+ const ALLOWED_CONFIG_KEYS = new Set([
10
+ "version",
11
+ "flowVersion",
12
+ "harnesses",
13
+ "tdd",
14
+ "execution",
15
+ "plan"
16
+ ]);
10
17
  const SUPPORTED_HARNESSES_TEXT = HARNESS_IDS.join(", ");
11
18
  export const TDD_COMMIT_MODES = [
12
19
  "managed-per-slice",
@@ -20,6 +27,28 @@ export const TDD_ISOLATION_MODES = ["worktree", "in-place", "auto"];
20
27
  const TDD_ISOLATION_MODE_SET = new Set(TDD_ISOLATION_MODES);
21
28
  export const DEFAULT_TDD_ISOLATION_MODE = "worktree";
22
29
  export const DEFAULT_TDD_WORKTREE_ROOT = `${RUNTIME_ROOT}/worktrees`;
30
+ export const LOCKFILE_TWIN_POLICIES = ["auto-include", "auto-revert", "strict-fence"];
31
+ const LOCKFILE_TWIN_POLICY_SET = new Set(LOCKFILE_TWIN_POLICIES);
32
+ export const DEFAULT_LOCKFILE_TWIN_POLICY = "auto-include";
33
+ export const EXECUTION_TOPOLOGIES = [
34
+ "auto",
35
+ "inline",
36
+ "single-builder",
37
+ "parallel-builders",
38
+ "strict-micro"
39
+ ];
40
+ const EXECUTION_TOPOLOGY_SET = new Set(EXECUTION_TOPOLOGIES);
41
+ export const DEFAULT_EXECUTION_TOPOLOGY = "auto";
42
+ export const EXECUTION_STRICTNESS_PROFILES = ["fast", "balanced", "strict"];
43
+ const EXECUTION_STRICTNESS_PROFILE_SET = new Set(EXECUTION_STRICTNESS_PROFILES);
44
+ export const DEFAULT_EXECUTION_STRICTNESS = "balanced";
45
+ export const DEFAULT_MAX_BUILDERS = 5;
46
+ export const PLAN_SLICE_GRANULARITIES = ["feature-atomic", "strict-micro"];
47
+ const PLAN_SLICE_GRANULARITY_SET = new Set(PLAN_SLICE_GRANULARITIES);
48
+ export const DEFAULT_PLAN_SLICE_GRANULARITY = "feature-atomic";
49
+ export const PLAN_MICRO_TASK_POLICIES = ["advisory", "strict"];
50
+ const PLAN_MICRO_TASK_POLICY_SET = new Set(PLAN_MICRO_TASK_POLICIES);
51
+ export const DEFAULT_PLAN_MICRO_TASK_POLICY = "advisory";
23
52
  // Kept for runtime modules that use these defaults directly.
24
53
  export const DEFAULT_TDD_TEST_PATH_PATTERNS = [
25
54
  "**/*.test.*",
@@ -46,7 +75,14 @@ function configFixExample() {
46
75
  tdd:
47
76
  commitMode: managed-per-slice
48
77
  isolationMode: worktree
49
- worktreeRoot: .cclaw/worktrees`;
78
+ worktreeRoot: .cclaw/worktrees
79
+ execution:
80
+ topology: auto
81
+ strictness: balanced
82
+ maxBuilders: 5
83
+ plan:
84
+ sliceGranularity: feature-atomic
85
+ microTaskPolicy: advisory`;
50
86
  }
51
87
  function configValidationError(configFilePath, reason) {
52
88
  return new InvalidConfigError(`Invalid cclaw config at ${configFilePath}: ${reason}\n` +
@@ -68,7 +104,17 @@ export function createDefaultConfig(harnesses = DEFAULT_HARNESSES, _defaultTrack
68
104
  tdd: {
69
105
  commitMode: DEFAULT_TDD_COMMIT_MODE,
70
106
  isolationMode: DEFAULT_TDD_ISOLATION_MODE,
71
- worktreeRoot: DEFAULT_TDD_WORKTREE_ROOT
107
+ worktreeRoot: DEFAULT_TDD_WORKTREE_ROOT,
108
+ lockfileTwinPolicy: DEFAULT_LOCKFILE_TWIN_POLICY
109
+ },
110
+ execution: {
111
+ topology: DEFAULT_EXECUTION_TOPOLOGY,
112
+ strictness: DEFAULT_EXECUTION_STRICTNESS,
113
+ maxBuilders: DEFAULT_MAX_BUILDERS
114
+ },
115
+ plan: {
116
+ sliceGranularity: DEFAULT_PLAN_SLICE_GRANULARITY,
117
+ microTaskPolicy: DEFAULT_PLAN_MICRO_TASK_POLICY
72
118
  }
73
119
  };
74
120
  }
@@ -93,6 +139,48 @@ export function resolveTddWorktreeRoot(config) {
93
139
  }
94
140
  return DEFAULT_TDD_WORKTREE_ROOT;
95
141
  }
142
+ export function resolveLockfileTwinPolicy(config) {
143
+ const raw = config?.tdd?.lockfileTwinPolicy;
144
+ if (typeof raw === "string" && LOCKFILE_TWIN_POLICY_SET.has(raw)) {
145
+ return raw;
146
+ }
147
+ return DEFAULT_LOCKFILE_TWIN_POLICY;
148
+ }
149
+ export function resolveExecutionTopology(config) {
150
+ const raw = config?.execution?.topology;
151
+ if (typeof raw === "string" && EXECUTION_TOPOLOGY_SET.has(raw)) {
152
+ return raw;
153
+ }
154
+ return DEFAULT_EXECUTION_TOPOLOGY;
155
+ }
156
+ export function resolveExecutionStrictness(config) {
157
+ const raw = config?.execution?.strictness;
158
+ if (typeof raw === "string" && EXECUTION_STRICTNESS_PROFILE_SET.has(raw)) {
159
+ return raw;
160
+ }
161
+ return DEFAULT_EXECUTION_STRICTNESS;
162
+ }
163
+ export function resolveMaxBuilders(config) {
164
+ const raw = config?.execution?.maxBuilders;
165
+ if (typeof raw === "number" && Number.isInteger(raw) && raw >= 1) {
166
+ return raw;
167
+ }
168
+ return DEFAULT_MAX_BUILDERS;
169
+ }
170
+ export function resolvePlanSliceGranularity(config) {
171
+ const raw = config?.plan?.sliceGranularity;
172
+ if (typeof raw === "string" && PLAN_SLICE_GRANULARITY_SET.has(raw)) {
173
+ return raw;
174
+ }
175
+ return DEFAULT_PLAN_SLICE_GRANULARITY;
176
+ }
177
+ export function resolvePlanMicroTaskPolicy(config) {
178
+ const raw = config?.plan?.microTaskPolicy;
179
+ if (typeof raw === "string" && PLAN_MICRO_TASK_POLICY_SET.has(raw)) {
180
+ return raw;
181
+ }
182
+ return DEFAULT_PLAN_MICRO_TASK_POLICY;
183
+ }
96
184
  function assertOnlySupportedKeys(parsed, fullPath) {
97
185
  const unknownKeys = Object.keys(parsed).filter((key) => !ALLOWED_CONFIG_KEYS.has(key));
98
186
  if (unknownKeys.length === 0)
@@ -130,6 +218,14 @@ export async function readConfig(projectRoot, _options = {}) {
130
218
  !isRecord(parsed.tdd)) {
131
219
  throw configValidationError(fullPath, `"tdd" must be an object when provided`);
132
220
  }
221
+ if (Object.prototype.hasOwnProperty.call(parsed, "execution") &&
222
+ !isRecord(parsed.execution)) {
223
+ throw configValidationError(fullPath, `"execution" must be an object when provided`);
224
+ }
225
+ if (Object.prototype.hasOwnProperty.call(parsed, "plan") &&
226
+ !isRecord(parsed.plan)) {
227
+ throw configValidationError(fullPath, `"plan" must be an object when provided`);
228
+ }
133
229
  const rawHarnesses = Array.isArray(parsed.harnesses) ? parsed.harnesses : DEFAULT_HARNESSES;
134
230
  const normalizedHarnesses = [];
135
231
  for (const harness of rawHarnesses) {
@@ -153,6 +249,14 @@ export async function readConfig(projectRoot, _options = {}) {
153
249
  const rawCommitMode = parsedTdd.commitMode;
154
250
  const rawIsolationMode = parsedTdd.isolationMode;
155
251
  const rawWorktreeRoot = parsedTdd.worktreeRoot;
252
+ const rawLockfileTwinPolicy = parsedTdd.lockfileTwinPolicy;
253
+ const parsedExecution = isRecord(parsed.execution) ? parsed.execution : {};
254
+ const rawExecutionTopology = parsedExecution.topology;
255
+ const rawExecutionStrictness = parsedExecution.strictness;
256
+ const rawMaxBuilders = parsedExecution.maxBuilders;
257
+ const parsedPlan = isRecord(parsed.plan) ? parsed.plan : {};
258
+ const rawPlanSliceGranularity = parsedPlan.sliceGranularity;
259
+ const rawPlanMicroTaskPolicy = parsedPlan.microTaskPolicy;
156
260
  if (rawCommitMode !== undefined &&
157
261
  (typeof rawCommitMode !== "string" || !TDD_COMMIT_MODE_SET.has(rawCommitMode))) {
158
262
  throw configValidationError(fullPath, `"tdd.commitMode" must be one of: ${TDD_COMMIT_MODES.join(", ")}`);
@@ -165,6 +269,33 @@ export async function readConfig(projectRoot, _options = {}) {
165
269
  (typeof rawWorktreeRoot !== "string" || rawWorktreeRoot.trim().length === 0)) {
166
270
  throw configValidationError(fullPath, `"tdd.worktreeRoot" must be a non-empty string when provided`);
167
271
  }
272
+ if (rawLockfileTwinPolicy !== undefined &&
273
+ (typeof rawLockfileTwinPolicy !== "string" || !LOCKFILE_TWIN_POLICY_SET.has(rawLockfileTwinPolicy))) {
274
+ throw configValidationError(fullPath, `"tdd.lockfileTwinPolicy" must be one of: ${LOCKFILE_TWIN_POLICIES.join(", ")}`);
275
+ }
276
+ if (rawExecutionTopology !== undefined &&
277
+ (typeof rawExecutionTopology !== "string" || !EXECUTION_TOPOLOGY_SET.has(rawExecutionTopology))) {
278
+ throw configValidationError(fullPath, `"execution.topology" must be one of: ${EXECUTION_TOPOLOGIES.join(", ")}`);
279
+ }
280
+ if (rawExecutionStrictness !== undefined &&
281
+ (typeof rawExecutionStrictness !== "string" ||
282
+ !EXECUTION_STRICTNESS_PROFILE_SET.has(rawExecutionStrictness))) {
283
+ throw configValidationError(fullPath, `"execution.strictness" must be one of: ${EXECUTION_STRICTNESS_PROFILES.join(", ")}`);
284
+ }
285
+ if (rawMaxBuilders !== undefined &&
286
+ (!Number.isInteger(rawMaxBuilders) || rawMaxBuilders < 1)) {
287
+ throw configValidationError(fullPath, `"execution.maxBuilders" must be an integer >= 1 when provided`);
288
+ }
289
+ if (rawPlanSliceGranularity !== undefined &&
290
+ (typeof rawPlanSliceGranularity !== "string" ||
291
+ !PLAN_SLICE_GRANULARITY_SET.has(rawPlanSliceGranularity))) {
292
+ throw configValidationError(fullPath, `"plan.sliceGranularity" must be one of: ${PLAN_SLICE_GRANULARITIES.join(", ")}`);
293
+ }
294
+ if (rawPlanMicroTaskPolicy !== undefined &&
295
+ (typeof rawPlanMicroTaskPolicy !== "string" ||
296
+ !PLAN_MICRO_TASK_POLICY_SET.has(rawPlanMicroTaskPolicy))) {
297
+ throw configValidationError(fullPath, `"plan.microTaskPolicy" must be one of: ${PLAN_MICRO_TASK_POLICIES.join(", ")}`);
298
+ }
168
299
  const commitMode = typeof rawCommitMode === "string"
169
300
  ? rawCommitMode
170
301
  : DEFAULT_TDD_COMMIT_MODE;
@@ -174,6 +305,26 @@ export async function readConfig(projectRoot, _options = {}) {
174
305
  const worktreeRoot = typeof rawWorktreeRoot === "string" && rawWorktreeRoot.trim().length > 0
175
306
  ? rawWorktreeRoot.trim()
176
307
  : DEFAULT_TDD_WORKTREE_ROOT;
308
+ const lockfileTwinPolicy = typeof rawLockfileTwinPolicy === "string"
309
+ ? rawLockfileTwinPolicy
310
+ : DEFAULT_LOCKFILE_TWIN_POLICY;
311
+ const executionTopology = typeof rawExecutionTopology === "string"
312
+ ? rawExecutionTopology
313
+ : DEFAULT_EXECUTION_TOPOLOGY;
314
+ const executionStrictness = typeof rawExecutionStrictness === "string"
315
+ ? rawExecutionStrictness
316
+ : DEFAULT_EXECUTION_STRICTNESS;
317
+ const maxBuilders = typeof rawMaxBuilders === "number" &&
318
+ Number.isInteger(rawMaxBuilders) &&
319
+ rawMaxBuilders >= 1
320
+ ? rawMaxBuilders
321
+ : DEFAULT_MAX_BUILDERS;
322
+ const planSliceGranularity = typeof rawPlanSliceGranularity === "string"
323
+ ? rawPlanSliceGranularity
324
+ : DEFAULT_PLAN_SLICE_GRANULARITY;
325
+ const planMicroTaskPolicy = typeof rawPlanMicroTaskPolicy === "string"
326
+ ? rawPlanMicroTaskPolicy
327
+ : DEFAULT_PLAN_MICRO_TASK_POLICY;
177
328
  return {
178
329
  version,
179
330
  flowVersion,
@@ -181,7 +332,17 @@ export async function readConfig(projectRoot, _options = {}) {
181
332
  tdd: {
182
333
  commitMode,
183
334
  isolationMode,
184
- worktreeRoot
335
+ worktreeRoot,
336
+ lockfileTwinPolicy
337
+ },
338
+ execution: {
339
+ topology: executionTopology,
340
+ strictness: executionStrictness,
341
+ maxBuilders
342
+ },
343
+ plan: {
344
+ sliceGranularity: planSliceGranularity,
345
+ microTaskPolicy: planMicroTaskPolicy
185
346
  }
186
347
  };
187
348
  }
@@ -193,7 +354,17 @@ export async function writeConfig(projectRoot, config, _options = {}) {
193
354
  tdd: {
194
355
  commitMode: resolveTddCommitMode(config),
195
356
  isolationMode: resolveTddIsolationMode(config),
196
- worktreeRoot: resolveTddWorktreeRoot(config)
357
+ worktreeRoot: resolveTddWorktreeRoot(config),
358
+ lockfileTwinPolicy: resolveLockfileTwinPolicy(config)
359
+ },
360
+ execution: {
361
+ topology: resolveExecutionTopology(config),
362
+ strictness: resolveExecutionStrictness(config),
363
+ maxBuilders: resolveMaxBuilders(config)
364
+ },
365
+ plan: {
366
+ sliceGranularity: resolvePlanSliceGranularity(config),
367
+ microTaskPolicy: resolvePlanMicroTaskPolicy(config)
197
368
  }
198
369
  };
199
370
  await writeFileSafe(configPath(projectRoot), stringify(serialisable));
@@ -196,7 +196,7 @@ export declare const CCLAW_AGENTS: readonly [{
196
196
  readonly body: string;
197
197
  }, {
198
198
  readonly name: "slice-builder";
199
- readonly description: "MANDATORY for every TDD slice. Owns RED → GREEN → REFACTOR → per-slice DOC for one bounded vertical slice in a single delegated span. Multiple slice-builder spans run in parallel inside one wave when their `claimedPaths` are disjoint.";
199
+ readonly description: "MANDATORY for delegated TDD slices. Owns RED → GREEN → REFACTOR → per-slice DOC for one feature-atomic implementation unit/slice in a single delegated span. Multiple slice-builder spans run in parallel only under `parallel-builders` topology with disjoint `claimedPaths`; `strict-micro` keeps tiny tasks separate.";
200
200
  readonly tools: ["Read", "Write", "Edit", "Grep", "Glob", "Bash"];
201
201
  readonly model: "balanced";
202
202
  readonly activation: "mandatory";
@@ -151,7 +151,7 @@ export function sliceBuilderProtocol() {
151
151
  return [
152
152
  "## slice-builder protocol",
153
153
  "",
154
- "**slice-builder** is the canonical worker for **one bounded vertical slice** end-to-end: **RED → GREEN → REFACTOR → inline DOC** in **one** delegated span. Multiple slice-builder spans run in parallel under a single wave when the wave plan declares disjoint `claimedPaths`.",
154
+ "**slice-builder** is the canonical worker for **one feature-atomic implementation unit/slice** end-to-end: **RED → GREEN → REFACTOR → inline DOC** in **one** delegated span. The unit may contain internal 2-5 minute TDD steps; do not split those into separate agents unless the parent selected `strict-micro`. Multiple slice-builder spans run in parallel only when topology is `parallel-builders` and the wave plan declares disjoint `claimedPaths`.",
155
155
  "",
156
156
  "### Invariants",
157
157
  "- Produce failing RED evidence (or cite the delegated RED artifact) **before** production edits.",
@@ -165,6 +165,21 @@ export function sliceBuilderProtocol() {
165
165
  "- Honor every `delegation-record`/`delegation-record.mjs` row shape the controller requests so artifact linters keep passing.",
166
166
  "- The umbrella `slice-completed` row ties RED/GREEN/REFACTOR/DOC timestamps to your builder span.",
167
167
  "",
168
+ "### Event → status flag table (7.6.0 — phase-event status validation)",
169
+ "",
170
+ "Phase-level granularity is only meaningful on terminal outcomes. The dispatch-level ack (no `--phase`) is the controller saying \"I see the dispatch surface back\" — it stays on `--status=acknowledged`. Phase events MUST use `--status=completed` or `--status=failed`. The hook rejects mismatches with `phase_event_requires_completed_or_failed_status` (exit 2) and prints a corrected-command hint.",
171
+ "",
172
+ "| event | --phase | --status |",
173
+ "|---|---|---|",
174
+ "| dispatch ack (controller-side) | (none) | `acknowledged` |",
175
+ "| RED watched-fail captured | `red` | `completed` |",
176
+ "| GREEN test passes | `green` | `completed` (with `--refactor-outcome=inline\\|deferred\\|...`) |",
177
+ "| REFACTOR landed | `refactor` | `completed` |",
178
+ "| DOC card landed (triggers slice-commit) | `doc` | `completed` |",
179
+ "| BLOCKED / unrecoverable | (any phase reached) | `failed` |",
180
+ "",
181
+ "Common slip: recording every phase event with `--status=acknowledged` (e.g. `--phase=doc --status=acknowledged`). The event row is silently dropped from terminal-phase aggregations, `slice-commit.mjs` never fires (it only triggers on `phase=doc status=completed`), and `wave-status` reports the slice as phantom-open. Recovery requires raw backfill commands. The 7.6.0 hook validator forbids this configuration up front.",
182
+ "",
168
183
  "### Streaming output contract",
169
184
  "- Emit one JSON line to stdout per completed phase: `{\"event\":\"phase-completed\",\"stage\":\"tdd\",\"sliceId\":\"S-<n>\",\"phase\":\"<red|green|refactor|refactor-deferred|doc>\",\"spanId\":\"<span>\",\"runId\":\"<run>\",\"ts\":\"<iso>\"}`.",
170
185
  "- For `phase=green` with inline/deferred refactor folding, include `refactorOutcome.mode` in the same JSON line so live controllers can close the slice without waiting for file sync.",
@@ -574,7 +589,7 @@ export const CCLAW_AGENTS = [
574
589
  },
575
590
  {
576
591
  name: "slice-builder",
577
- description: "MANDATORY for every TDD slice. Owns RED → GREEN → REFACTOR → per-slice DOC for one bounded vertical slice in a single delegated span. Multiple slice-builder spans run in parallel inside one wave when their `claimedPaths` are disjoint.",
592
+ description: "MANDATORY for delegated TDD slices. Owns RED → GREEN → REFACTOR → per-slice DOC for one feature-atomic implementation unit/slice in a single delegated span. Multiple slice-builder spans run in parallel only under `parallel-builders` topology with disjoint `claimedPaths`; `strict-micro` keeps tiny tasks separate.",
578
593
  tools: ["Read", "Write", "Edit", "Grep", "Glob", "Bash"],
579
594
  model: "balanced",
580
595
  activation: "mandatory",
@@ -1489,6 +1489,43 @@ async function main() {
1489
1489
  emitProblems(problems, json, 2);
1490
1490
  return;
1491
1491
  }
1492
+ // 7.6.0 — phase-event status validation.
1493
+ // \`--phase=<phase>\` carries phase-level granularity (RED/GREEN/REFACTOR/DOC
1494
+ // outcomes). It is only meaningful on terminal statuses
1495
+ // (\`completed\` or \`failed\`). The dispatch-level ack (no phase) keeps
1496
+ // \`--status=acknowledged\`. Refuse acknowledged/launched/scheduled/waived/stale
1497
+ // rows that carry a phase so phantom-open slices cannot be recorded.
1498
+ if (
1499
+ typeof args.phase === "string" &&
1500
+ args.phase.length > 0 &&
1501
+ args.status !== "completed" &&
1502
+ args.status !== "failed"
1503
+ ) {
1504
+ const sliceFlag = typeof args.slice === "string" && args.slice.length > 0
1505
+ ? "--slice=" + args.slice + " "
1506
+ : "";
1507
+ const spanFlag = typeof args["span-id"] === "string" && args["span-id"].length > 0
1508
+ ? "--span-id=" + args["span-id"] + " "
1509
+ : "";
1510
+ const correctedCommandHint =
1511
+ "node .cclaw/hooks/delegation-record.mjs --stage=" + (args.stage || "<stage>") +
1512
+ " --agent=" + (args.agent || "<agent>") +
1513
+ " --mode=" + (args.mode || "mandatory") +
1514
+ " --status=completed --phase=" + args.phase +
1515
+ " " + sliceFlag + spanFlag +
1516
+ '--evidence-ref="<phase outcome>"';
1517
+ emitErrorJson(
1518
+ "phase_event_requires_completed_or_failed_status",
1519
+ {
1520
+ phase: args.phase,
1521
+ status: args.status,
1522
+ spanId: args["span-id"] || "unknown",
1523
+ correctedCommandHint
1524
+ },
1525
+ json
1526
+ );
1527
+ return;
1528
+ }
1492
1529
  if (args.phase === "refactor-deferred") {
1493
1530
  const rationaleQuality = validateDeferredRationaleInline(args["refactor-rationale"], args);
1494
1531
  if (rationaleQuality !== "ok") {
@@ -57,14 +57,14 @@ If you think any of these, stop and follow the routing flow:
57
57
  - "I can answer from memory without loading the active stage skill." -> No. Load the skill first.
58
58
  - "Hook guard warned, but I can ignore it." -> No. Resolve the warning before continuing.
59
59
  - "I'll edit \`.cclaw/state\` directly to move faster." -> No. Use managed commands only.
60
- - "I'll just do the worker's job inline so we move faster." -> No. See the Controller dispatch discipline below.
60
+ - "I'll just do the worker's job inline so we move faster." -> Only if the active TDD topology is explicitly \`inline\`; otherwise see the Controller dispatch discipline below.
61
61
 
62
62
  ## Controller dispatch discipline (applies to every stage)
63
63
 
64
- cclaw stages have **mandatory delegations** (TDD: \`slice-builder\`; review: \`reviewer\` + \`security-reviewer\`; design: \`architect\`; scope: \`planner\`; etc.). The controller is the **orchestrator**, not the worker. When a stage declares a mandatory delegation:
64
+ cclaw stages have **mandatory delegations** (TDD normally routes through \`slice-builder\`; review: \`reviewer\` + \`security-reviewer\`; design: \`architect\`; scope: \`planner\`; etc.). The controller is the **orchestrator**, not the worker, except when TDD topology is explicitly \`inline\` for a low-risk unit. When a stage declares a mandatory delegation:
65
65
 
66
- - **Dispatch via the harness Task tool.** Do NOT write the worker's output (slice code, review findings, architect notes) into the artifact yourself as a substitute for delegating. Editing \`06-tdd.md\` slice cards, \`07-review.md\` findings, or any other "result of mandatory worker" content inline in the controller chat is a protocol violation.
67
- - **Parallel by default when paths/lenses are independent.** TDD wave-fanout (disjoint \`claimedPaths\`) and review-army (independent reviewer lenses) MUST emit all parallel \`Task\` calls in a SINGLE controller message — not sequentially over multiple turns. The controller waits for all spans to return before reconciling.
66
+ - **Dispatch via the harness Task tool unless topology says inline.** Do NOT write the worker's output (slice code, review findings, architect notes) into the artifact yourself as a substitute for delegating. For TDD \`inline\`, record the routing reason and satisfy the same RED/GREEN/REFACTOR, AC traceability, path containment, managed commit/worktree, lockfile twin, and orphan-change gates.
67
+ - **Parallel only when topology says parallel-builders.** TDD fan-out requires genuinely independent substantial units with disjoint \`claimedPaths\`; review-army (independent reviewer lenses) MUST emit all parallel \`Task\` calls in a SINGLE controller message — not sequentially over multiple turns. The controller waits for all spans to return before reconciling.
68
68
  - **Record lifecycle on the same span** via \`delegation-record --status=scheduled|launched|acknowledged|completed\`; the worker emits its own \`--phase=…\` and evidence rows. A \`completed\` row without a matching ACK or dispatch surface is a forgery.
69
69
  - **Auto-advance when stage-complete returns ok.** When the helper reports a new \`currentStage\`, immediately load the next stage skill and continue. Announce \`Stage <prev> complete → entering <next>. Continuing.\` Do NOT pause for the user to retype \`/cc\` or say \"продолжай\" — that pause is the failure mode 7.0.2 explicitly removed. The only legitimate stop is a real blocker (missing user input, ambiguous decision, hook fail).
70
70
 
@@ -183,22 +183,24 @@ export function tddTopOfSkillBlock(stage) {
183
183
  return "";
184
184
  return `## TDD orchestration primer
185
185
 
186
- **MANDATE — controller never implements.** In TDD the controller plans, dispatches, and reconciles. **NEVER edit production code, tests, or run cargo/npm/pytest yourself in the controller chat.** Every slice's RED GREEN REFACTOR DOC cycle MUST happen inside an isolated \`slice-builder\` span dispatched via the harness Task tool. Inline code edits in the controller chat are a protocol violation that defeats parallelism, evidence isolation, and the audit ledger.
186
+ **MANDATE — controller preserves TDD evidence.** In TDD the controller routes, dispatches when needed, and reconciles. Default topology is \`auto\` + \`balanced\`: feature-atomic units contain internal 2-5 minute RED/GREEN/REFACTOR steps. Inline execution is allowed only when \`wave-status\`/the plan selects \`inline\`; it still must satisfy RED-before-GREEN, AC traceability, path containment, verification, managed commit/worktree, lockfile twin, and orphan-change gates. \`single-builder\` and \`parallel-builders\` use \`slice-builder\`; \`strict-micro\` preserves one tiny task per schedulable slice for high-risk work.
187
187
 
188
188
  **Step 1 — Wave status (always first):**
189
189
  \`node .cclaw/cli.mjs internal wave-status --json\`
190
190
 
191
- The output names: \`waves[]\` (closed/open), \`nextDispatch.waveId\`, \`nextDispatch.mode\` (\`wave-fanout\`, \`single-slice\`, or \`blocked\`), \`nextDispatch.readyToDispatch\` (slice ids), and \`nextDispatch.pathConflicts\` (overlapping \`claimedPaths\` between members).
191
+ The output names: \`waves[]\` (closed/open), \`nextDispatch.waveId\`, \`nextDispatch.mode\` (\`wave-fanout\`, \`single-slice\`, or \`blocked\`), \`nextDispatch.topology\` (\`inline\`, \`single-builder\`, \`parallel-builders\`, or \`strict-micro\`), \`nextDispatch.readyToDispatch\` (slice ids), and \`nextDispatch.pathConflicts\` (overlapping \`claimedPaths\` between members).
192
192
 
193
193
  **Step 2 — Decide automatically (no user question when paths disjoint):**
194
194
 
195
- | \`mode\` | \`pathConflicts\` | Action |
196
- |------------------|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
197
- | \`wave-fanout\` | \`[]\` | **Fan out the entire wave in one tool batch.** Emit one \`Task\` per ready slice in a single controller message. Do NOT ask the user. |
198
- | \`blocked\` | non-empty | Issue exactly one AskQuestion (resolve overlap, split/serialize, or adjust claimedPaths), then re-run \`wave-status\`. |
199
- | \`single-slice\` | | One \`Task\` for the next ready slice. |
195
+ | \`topology\` | \`pathConflicts\` | Action |
196
+ |---------------------------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
197
+ | \`parallel-builders\` | \`[]\` | **Fan out independent substantial units in one tool batch.** Emit one \`Task\` per routed ready builder in a single controller message. Do NOT ask. |
198
+ | \`single-builder\` | any/none | Dispatch one \`slice-builder\` for the next feature-atomic unit; serialize remaining ready units. |
199
+ | \`inline\` | \`[]\` | Execute inline only for low-risk inline-safe units; record equivalent RED/GREEN/REFACTOR evidence before completion. |
200
+ | \`strict-micro\` | any/none | Preserve micro-slice sequencing: one tiny task/slice at a time unless the strict plan explicitly proves safe fan-out. |
201
+ | \`blocked\`/mode blocked | non-empty | Issue exactly one AskQuestion (resolve overlap, split/serialize, or adjust claimedPaths), then re-run \`wave-status\`. |
200
202
 
201
- **Step 3 — Dispatch protocol per slice:** in the SAME controller message that issues the \`Task\` call:
203
+ **Step 3 — Dispatch protocol per delegated slice:** for \`single-builder\`, \`parallel-builders\`, and delegated \`strict-micro\`, in the SAME controller message that issues the \`Task\` call:
202
204
 
203
205
  1. Append \`delegation-record --status=scheduled\` for the \`slice-builder\` span (one row per slice; reuse the same \`spanId\` across the entire RED → GREEN → REFACTOR → DOC lifecycle).
204
206
  2. Append \`delegation-record --status=launched\` immediately after.
@@ -206,6 +208,8 @@ The output names: \`waves[]\` (closed/open), \`nextDispatch.waveId\`, \`nextDisp
206
208
  4. The slice-builder span ACKs locally (\`delegation-record --status=acknowledged\`) and runs the **complete** RED → GREEN → REFACTOR → DOC cycle inside the span — including writing \`tdd-slices/S-<id>.md\` and emitting \`--phase=red\`, \`--phase=green\`, \`--phase=refactor\` (or \`--phase=refactor-deferred\` with rationale), and \`--phase=doc\` rows on its own.
207
209
  5. The controller waits for ALL parallel spans to terminate before reconciling. Do not page back into the controller chat between spans.
208
210
 
211
+ For \`inline\`, skip the Task call but do not skip evidence: write the same per-slice card/evidence refs, run RED before GREEN, verify before completion, and keep all path/commit/worktree gates satisfied.
212
+
209
213
  **Step 4 — Wave closeout:** after all in-flight slices report \`completed\`:
210
214
 
211
215
  1. Re-run \`wave-status --json\`. Confirm the wave is \`closed\` and the next dispatch is the following wave (or \`closeout\`).
@@ -272,6 +272,7 @@ const REQUIRED_GATE_IDS = {
272
272
  "plan_execution_posture_recorded",
273
273
  "plan_parallel_exec_full_coverage",
274
274
  "plan_wave_paths_disjoint",
275
+ "plan_module_introducing_slice_wires_root",
275
276
  "plan_wait_for_confirm"
276
277
  ],
277
278
  tdd: (track) => [
@@ -677,8 +678,8 @@ const STAGE_AUTO_SUBAGENT_DISPATCH = {
677
678
  agent: "slice-builder",
678
679
  mode: "mandatory",
679
680
  requiredAtTier: "lightweight",
680
- when: "Always during the TDD cycle. Controller MUST NOT write tests or production code itself.",
681
- purpose: "Own one bounded vertical slice end-to-end: RED → GREEN → REFACTOR → per-slice DOC in a single delegated span. Multiple slice-builder spans run in parallel inside one wave when their `claimedPaths` are disjoint. Linter rules `tdd_slice_builder_missing` and `tdd_slice_doc_missing` block unauthorized GREEN authors and missing per-slice prose.",
681
+ when: "During delegated TDD topologies (`single-builder`, `parallel-builders`, `strict-micro`). Inline topology may skip the dispatch only when the same RED/GREEN/REFACTOR evidence gates are preserved.",
682
+ purpose: "Own one feature-atomic implementation unit/slice end-to-end: RED → GREEN → REFACTOR → per-slice DOC in a single delegated span. Multiple slice-builder spans run in parallel only under `parallel-builders` topology with disjoint `claimedPaths`. Linter rules `tdd_slice_builder_missing` and `tdd_slice_doc_missing` block unauthorized GREEN authors and missing per-slice prose unless inline topology is explicitly recorded.",
682
683
  requiresUserGate: false,
683
684
  skill: "tdd-cycle-evidence"
684
685
  },
@@ -10,8 +10,8 @@ export const PLAN = {
10
10
  skillDescription: "Execution planning stage with strict confirmation gate before implementation.",
11
11
  philosophy: {
12
12
  hardGate: "Do NOT write code or tests. Planning only. This stage produces a task graph and execution order. WAIT_FOR_CONFIRM before any handoff to implementation.",
13
- ironLaw: "EVERY TASK IS 2–5 MINUTES, FULLY SPELLED OUT, AND CARRIES A STABLE ID NO PLACEHOLDERS, NO ‘ETC.’.",
14
- purpose: "Create small executable tasks with dependencies and pause for explicit user confirmation.",
13
+ ironLaw: "EVERY IMPLEMENTATION UNIT IS FEATURE-ATOMIC, WITH INTERNAL 2–5 MINUTE TDD STEPS STRICT MICRO-SLICES ARE RESERVED FOR HIGH-RISK WORK.",
14
+ purpose: "Create feature-atomic implementation units with dependencies, internal TDD steps, and explicit confirmation before execution.",
15
15
  whenToUse: [
16
16
  "After spec approval",
17
17
  "Before writing tests or implementation",
@@ -43,21 +43,21 @@ export const PLAN = {
43
43
  "Read upstream — load spec, design, and scope artifacts. Cross-reference acceptance criteria.",
44
44
  "Build dependency graph — identify task ordering, parallel opportunities, and blocking dependencies.",
45
45
  "Group tasks into dependency batches — batch N+1 cannot start until batch N has verification evidence.",
46
- "Slice into vertical tasks — each task targets 2-5 minutes, produces one testable outcome, and touches one coherent area.",
47
- "Task Contract — every task has one coherent outcome, AC mapping, exact verification command/manual step, and expected evidence snippet or pass condition. Avoid vague `run tests` wording.",
46
+ "Slice into implementation units — each unit is feature-atomic and testable end-to-end, with internal 2-5 minute RED/GREEN/REFACTOR steps. Use `strict-micro` only for high-risk or explicitly requested micro-slice execution.",
47
+ "Task Contract — every unit has one coherent outcome, AC mapping, exact verification command/manual step, and expected evidence snippet or pass condition. Internal steps may be `T-NNN` rows, but they are not automatically separate schedulable agents in balanced/fast mode.",
48
48
  "Annotate slice-review metadata — task rows may carry `touchCount` (rough number of files expected to change), `touchPaths` (glob hints, e.g. `migrations/**`, `src/auth/**`), and optional `highRisk: true` to force a review pass. These fields feed the TDD stage's Per-Slice Review point.",
49
49
  "For every `### Implementation Unit U-<n>`, declare parallel metadata bullets: `id`, `dependsOn` (unit ids or `none`), `claimedPaths` (repo-relative), `parallelizable` (true|false), `riskTier` (low|standard|high), optional `lane` — used for conflict-aware wave plans and schedulers.",
50
50
  "Map scope Locked Decisions — every D-XX ID from scope is referenced by at least one plan task (or explicitly marked deferred with reason).",
51
51
  "Run anti-placeholder + anti-scope-reduction scans — block `TODO/TBD/...` and phrasing like `v1`, `for now`, `later` for locked boundaries.",
52
52
  "Define validation points — mark where progress must be checked before continuing, with concrete command and expected evidence.",
53
- "Define execution posture — record whether execution should be sequential, dependency-batched, parallel-safe, or blocked; include risk triggers and RED/GREEN/REFACTOR checkpoint/commit expectations when the repo workflow supports them. This fulfills the `plan_execution_posture_recorded` gate.",
54
- "**Author the FULL Parallel Execution Plan.** Inside the `<!-- parallel-exec-managed-start -->` block, enumerate ALL waves W-02..W-N covering EVERY T-NNN task in `## Task List` no `we'll author waves later`, `next batch only`, or open-ended Backlog handwave is acceptable. Each task gets a slice with `sliceId | taskId | dependsOn | claimedPaths | parallelizable | riskTier | lane`. Spike rows (`S-N`) and tasks marked `deferred` in an explicit `Deferred:` column may be omitted, but every other T-NNN must be claimed. This fulfills the `plan_parallel_exec_full_coverage` gate. The TDD stage downstream is a pure consumer of these waves — if the plan does not author them, TDD cannot fan out that work.",
55
- "After authoring/refreshing the managed parallel-exec block, render a Mermaid `flowchart` or `gantt` covering waves (`W-*`) and slice dependencies (`S-*`) so parallelism and fan-in boundaries are visually auditable.",
53
+ "Define execution topology — record `execution.topology` (`auto|inline|single-builder|parallel-builders|strict-micro`), `execution.strictness` (`fast|balanced|strict`), `execution.maxBuilders`, `plan.sliceGranularity`, `plan.microTaskPolicy`, stop conditions, and RED/GREEN/REFACTOR checkpoint/commit expectations. Default posture is `auto` + `balanced`: choose the cheapest safe route, not the most parallel one.",
54
+ "**Author the FULL Parallel Execution Plan.** Inside the `<!-- parallel-exec-managed-start -->` block, enumerate all waves W-01..W-N covering every feature-atomic `U-*` implementation unit/slice. Legacy strict-micro plans may cover each non-deferred `T-NNN` task instead. Each row carries `unit|sliceId | dependsOn | claimedPaths | parallelizable | riskTier | lane`. Do not leave `we'll author waves later`, `next batch only`, or open-ended Backlog handwaves. This fulfills the `plan_parallel_exec_full_coverage` gate. The TDD stage downstream is a pure consumer of these waves — if the plan does not author them, TDD cannot fan out that work.",
55
+ "After authoring/refreshing the managed parallel-exec block, render a Mermaid `flowchart` or `gantt` covering waves (`W-*`) and unit/slice dependencies (`U-*`/`S-*`) so topology and fan-in boundaries are visually auditable.",
56
56
  "WAIT_FOR_CONFIRM — write plan artifact and explicitly pause. **STOP.** Do NOT proceed until user confirms. Then close the stage with `node .cclaw/hooks/stage-complete.mjs plan` and tell user to run `/cc`."
57
57
  ],
58
58
  interactionProtocol: [
59
59
  "Plan in read-only mode relative to implementation.",
60
- "Split work into small vertical slices (target 2-5 minute tasks).",
60
+ "Split work into feature-atomic vertical units; place 2-5 minute TDD steps inside each unit.",
61
61
  "Publish explicit dependency batches with entry and exit checks for each batch.",
62
62
  "Expose execution posture: sequential vs batch/parallel, stop conditions, and checkpoint cadence for the TDD handoff.",
63
63
  "Keep same-wave `claimedPaths` disjoint; if overlap exists, split waves or serialize explicitly before handoff.",
@@ -79,17 +79,18 @@ export const PLAN = {
79
79
  "Write plan artifact and pause at WAIT_FOR_CONFIRM."
80
80
  ],
81
81
  requiredGates: [
82
- { id: "plan_tasks_sliced_2_5_min", description: "Tasks are small, executable slices." },
82
+ { id: "plan_tasks_sliced_2_5_min", description: "Implementation units are feature-atomic and contain internal 2-5 minute TDD steps; strict micro-slices are explicit." },
83
83
  { id: "plan_dependency_batches_defined", description: "Tasks are grouped into executable batches with gate checks and execution posture." },
84
84
  { id: "plan_acceptance_mapped", description: "Each task maps to a spec acceptance criterion." },
85
- { id: "plan_execution_posture_recorded", description: "Execution posture is recorded before implementation handoff." },
86
- { id: "plan_parallel_exec_full_coverage", description: "Every T-NNN task in `## Task List` (other than spikes/explicitly-deferred) is assigned to at least one slice inside the `<!-- parallel-exec-managed-start -->` block; TDD cannot fan out work that the plan never authored as waves." },
85
+ { id: "plan_execution_posture_recorded", description: "Execution topology/posture is recorded before implementation handoff." },
86
+ { id: "plan_parallel_exec_full_coverage", description: "Every U-* implementation unit/slice (or every T-NNN task in explicit strict-micro plans) is assigned inside the `<!-- parallel-exec-managed-start -->` block; TDD cannot fan out work that the plan never authored as waves." },
87
87
  { id: "plan_wave_paths_disjoint", description: "Within each authored wave, slice `claimedPaths` remain disjoint so `wave-fanout` can dispatch safely without overlap conflicts." },
88
+ { id: "plan_module_introducing_slice_wires_root", description: "When a slice introduces a new module file, the stack-adapter's wiring aggregator (Rust `lib.rs`, Python `__init__.py`, Node-TS barrel when present) must appear in the same slice's claim or a transitive predecessor's claim so RED can be expressed." },
88
89
  { id: "plan_wait_for_confirm", description: "Execution blocked until explicit user confirmation." }
89
90
  ],
90
91
  requiredEvidence: [
91
92
  "Artifact written to `.cclaw/artifacts/05-plan.md`.",
92
- "Task list includes acceptance mapping, exact verification command/manual step, and expected evidence/pass condition.",
93
+ "Implementation units include acceptance mapping, internal 2-5 minute TDD steps, exact verification command/manual step, and expected evidence/pass condition.",
93
94
  "Locked decision coverage table present with D-XX trace links.",
94
95
  "Dependency graph documented.",
95
96
  "Dependency batches documented with batch-by-batch verification gates.",
@@ -135,13 +136,13 @@ export const PLAN = {
135
136
  { section: "Upstream Handoff", required: false, validationRule: "Summarizes spec/design/scope decisions, constraints, open questions, and explicit drift before task breakdown." },
136
137
  { section: "Dependency Graph", required: false, validationRule: "Ordering and parallel opportunities explicit. No circular dependencies." },
137
138
  { section: "Dependency Batches", required: true, validationRule: "Every task belongs to a batch. Each batch has an exit gate and dependency statement." },
138
- { section: "Task List", required: true, validationRule: "Each task row includes ID, description, acceptance criterion, exact verification command/manual step, expected evidence/pass condition, and effort estimate (S/M/L). Every task must also carry a minutes estimate within the 2-5 minute budget. When present, touchCount/touchPaths/highRisk metadata drives Per-Slice Review escalation in TDD." },
139
+ { section: "Task List", required: true, validationRule: "Task rows may describe internal 2-5 minute TDD steps. In balanced/fast mode, the schedulable unit is the feature-atomic Implementation Unit, not every T-NNN row. Strict-micro plans may still use one T-NNN per slice." },
139
140
  { section: "Acceptance Mapping", required: true, validationRule: "Every spec criterion is covered by at least one task." },
140
- { section: "Execution Posture", required: true, validationRule: "States sequential/batch/parallel posture, stop conditions, risk triggers, and RED/GREEN/REFACTOR checkpoint or commit expectations for TDD when consistent with the repo workflow." },
141
+ { section: "Execution Posture", required: true, validationRule: "States `execution.topology` (`auto|inline|single-builder|parallel-builders|strict-micro`), `execution.strictness`, `execution.maxBuilders`, `plan.sliceGranularity`, `plan.microTaskPolicy`, stop conditions, risk triggers, and RED/GREEN/REFACTOR checkpoint or commit expectations for TDD when consistent with the repo workflow." },
141
142
  { section: "Locked Decision Coverage", required: false, validationRule: "Every locked decision ID (D-XX) from scope is listed with linked task IDs or explicit defer rationale." },
142
143
  { section: "Risk Assessment", required: false, validationRule: "If present: per-task or per-batch risk identification with likelihood, impact, and mitigation strategy." },
143
144
  { section: "Boundary Map", required: false, validationRule: "If present: per-batch or per-task interface contracts listing what each task produces (exports) and consumes (imports) from other tasks." },
144
- { section: "Implementation Units", required: false, validationRule: "If present: each `### Implementation Unit U-<n>` includes Goal, Files, Approach, Test scenarios, Verification fields, plus bullets (`id`, `dependsOn`, `claimedPaths`, `parallelizable`, `riskTier`, optional `lane`)." },
145
+ { section: "Implementation Units", required: false, validationRule: "Each `### Implementation Unit U-<n>` includes Goal, Files, Approach, Test scenarios, Verification, internal 2-5 minute TDD steps, plus bullets (`id`, `dependsOn`, `claimedPaths`, `parallelizable`, `riskTier`, optional `lane`)." },
145
146
  { section: "Calibrated Findings", required: false, validationRule: "If present: either `None this stage` or one or more lines in `[P1|P2|P3] (confidence: <n>/10) <path>[:<line>] — <description>` format." },
146
147
  { section: "Regression Iron Rule", required: false, validationRule: "If present: includes `Iron rule acknowledged: yes`." },
147
148
  { section: "WAIT_FOR_CONFIRM", required: true, validationRule: "Explicit marker present. Status: pending until user approves." },
@@ -155,7 +156,7 @@ export const PLAN = {
155
156
  title: "Task Decomposition Audit",
156
157
  evaluationPoints: [
157
158
  "Does every task target a single coherent area (vertical slice)?",
158
- "Can each task be completed in 2-5 minutes?",
159
+ "Is each implementation unit feature-atomic, with internal steps that fit 2-5 minutes each?",
159
160
  "Does every task have an acceptance criterion link, exact verification command/manual step, and expected evidence/pass condition?",
160
161
  "Are there tasks that touch multiple unrelated areas?",
161
162
  "Would a new engineer understand and start each task within two minutes?"
@@ -177,7 +178,7 @@ export const PLAN = {
177
178
  {
178
179
  title: "Five-Minute Budget + No-Placeholders Audit",
179
180
  evaluationPoints: [
180
- "Does every task carry an explicit minutes estimate (e.g. `[~3m]`) and does every estimate fit the 2-to-5-minute budget? Estimates >5 minutes must be split.",
181
+ "Does every internal TDD step carry a bounded 2-to-5-minute estimate or checkbox-level step? Whole units may be larger when feature-atomic.",
181
182
  "Are all file paths, test commands, verification commands, and expected evidence copy-pasteable/specific as written — no `TODO`, `TBD`, `FIXME`, `<fill-in>`, `<your-*-here>`, `xxx`, bare `run tests`, or ellipsis standing in for omitted args?",
182
183
  "Does every acceptance-criterion reference resolve to a real R# / AC-### in the spec (not a blank link)?",
183
184
  "If an estimate is genuinely uncertain (first-time integration, unfamiliar library), is the uncertainty named explicitly and scheduled as a spike task in batch 0, rather than hidden behind a large estimate?"