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