cclaw-cli 6.13.1 → 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.
- package/dist/artifact-linter/shared.d.ts +15 -0
- package/dist/artifact-linter/tdd.d.ts +40 -10
- package/dist/artifact-linter/tdd.js +256 -44
- package/dist/artifact-linter.js +10 -2
- package/dist/content/core-agents.js +66 -1
- package/dist/content/hooks.js +160 -3
- package/dist/content/stages/tdd.js +9 -5
- package/dist/content/start-command.js +3 -3
- package/dist/delegation.d.ts +79 -0
- package/dist/delegation.js +122 -2
- package/dist/flow-state.d.ts +45 -0
- package/dist/flow-state.js +18 -0
- package/dist/install.js +88 -0
- package/dist/run-persistence.js +14 -0
- package/package.json +1 -1
package/dist/content/hooks.js
CHANGED
|
@@ -343,7 +343,8 @@ function usage() {
|
|
|
343
343
|
"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
|
-
" 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]",
|
|
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(", "),
|
|
@@ -362,12 +363,14 @@ function usage() {
|
|
|
362
363
|
"TDD slice phase tagging (v6.11.0):",
|
|
363
364
|
" --slice=<id> TDD slice identifier (e.g. S-1) used by the linter to auto-derive the Watched-RED + Vertical Slice Cycle tables.",
|
|
364
365
|
" --phase=<phase> one of " + VALID_DELEGATION_PHASES.join(", ") + ". Pair with --slice to record a TDD slice phase event.",
|
|
365
|
-
" --refactor-rationale=<t> required when --phase=refactor-deferred unless --evidence-ref carries the rationale text.",
|
|
366
|
+
" --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.",
|
|
366
367
|
" --claim-token=<opaque> v6.13 — required for worktree-first slice-implementer schedules with --slice (echo on all terminal rows for the span).",
|
|
367
368
|
" --lane-id=<id> v6.13 — worktree lane id (ownerLaneId metadata).",
|
|
368
369
|
" --lease-until=<iso> v6.13 — ISO8601 lease expiry for reclaim tooling.",
|
|
369
370
|
" --depends-on=<a,b> v6.13 — comma-separated plan unit ids for scheduler diagnostics.",
|
|
370
371
|
" --integration-state=<s> v6.13 — one of pending|applied|conflict|resolved|abandoned.",
|
|
372
|
+
" --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.",
|
|
373
|
+
" --risk-tier=<t> v6.14.0 — one of low|medium|high. high triggers integration-overseer in conditional mode.",
|
|
371
374
|
""
|
|
372
375
|
].join("\\n") + "\\n");
|
|
373
376
|
}
|
|
@@ -538,6 +541,40 @@ function buildRow(args, status, runId, now, options) {
|
|
|
538
541
|
: undefined;
|
|
539
542
|
const leaseState =
|
|
540
543
|
leasedUntil && status === "scheduled" ? "claimed" : undefined;
|
|
544
|
+
// v6.14.0: refactorOutcome folds REFACTOR into a phase=green event. We
|
|
545
|
+
// also accept it on phase=refactor / phase=refactor-deferred for forward
|
|
546
|
+
// compatibility with controllers that emit it on the legacy lifecycle.
|
|
547
|
+
// When mode=deferred and a --refactor-rationale is supplied we also
|
|
548
|
+
// mirror the rationale into evidenceRefs[0] so legacy linters keep
|
|
549
|
+
// reading evidence (matches the v6.11.0 refactor-deferred behavior).
|
|
550
|
+
const refactorOutcomeMode =
|
|
551
|
+
typeof args["refactor-outcome"] === "string"
|
|
552
|
+
? args["refactor-outcome"].trim()
|
|
553
|
+
: "";
|
|
554
|
+
let refactorOutcome;
|
|
555
|
+
if (refactorOutcomeMode === "inline" || refactorOutcomeMode === "deferred") {
|
|
556
|
+
const rationaleRaw =
|
|
557
|
+
typeof args["refactor-rationale"] === "string"
|
|
558
|
+
? args["refactor-rationale"].trim()
|
|
559
|
+
: "";
|
|
560
|
+
refactorOutcome = {
|
|
561
|
+
mode: refactorOutcomeMode,
|
|
562
|
+
...(rationaleRaw.length > 0 ? { rationale: rationaleRaw } : {})
|
|
563
|
+
};
|
|
564
|
+
if (
|
|
565
|
+
refactorOutcomeMode === "deferred" &&
|
|
566
|
+
rationaleRaw.length > 0 &&
|
|
567
|
+
!resolvedEvidenceRefs.includes(rationaleRaw)
|
|
568
|
+
) {
|
|
569
|
+
resolvedEvidenceRefs = [rationaleRaw, ...resolvedEvidenceRefs];
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
const riskTierRaw =
|
|
573
|
+
typeof args["risk-tier"] === "string" ? args["risk-tier"].trim() : "";
|
|
574
|
+
const riskTier =
|
|
575
|
+
riskTierRaw === "low" || riskTierRaw === "medium" || riskTierRaw === "high"
|
|
576
|
+
? riskTierRaw
|
|
577
|
+
: undefined;
|
|
541
578
|
return {
|
|
542
579
|
stage: args.stage,
|
|
543
580
|
agent: args.agent,
|
|
@@ -568,7 +605,9 @@ function buildRow(args, status, runId, now, options) {
|
|
|
568
605
|
leasedUntil,
|
|
569
606
|
leaseState,
|
|
570
607
|
dependsOn,
|
|
571
|
-
integrationState
|
|
608
|
+
integrationState,
|
|
609
|
+
refactorOutcome,
|
|
610
|
+
riskTier
|
|
572
611
|
};
|
|
573
612
|
}
|
|
574
613
|
|
|
@@ -860,6 +899,74 @@ async function findLegacyEntry(root, spanId) {
|
|
|
860
899
|
return ledger.entries.find((entry) => entry && entry.spanId === spanId) || null;
|
|
861
900
|
}
|
|
862
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
|
+
|
|
863
970
|
async function runRerecord(args, json) {
|
|
864
971
|
const problems = [];
|
|
865
972
|
for (const key of ["span-id", "dispatch-id", "dispatch-surface", "agent-definition-path"]) {
|
|
@@ -1092,6 +1199,18 @@ async function main() {
|
|
|
1092
1199
|
return;
|
|
1093
1200
|
}
|
|
1094
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
|
+
|
|
1095
1214
|
const problems = [];
|
|
1096
1215
|
if (!args.stage) problems.push("missing --stage");
|
|
1097
1216
|
if (!args.agent) problems.push("missing --agent");
|
|
@@ -1139,6 +1258,44 @@ async function main() {
|
|
|
1139
1258
|
}
|
|
1140
1259
|
}
|
|
1141
1260
|
|
|
1261
|
+
// v6.14.0 — --refactor-outcome must be one of inline|deferred. When
|
|
1262
|
+
// mode=deferred a rationale is required (either --refactor-rationale or
|
|
1263
|
+
// --evidence-ref carrying the rationale text). --risk-tier must be one of
|
|
1264
|
+
// low|medium|high if provided.
|
|
1265
|
+
if (
|
|
1266
|
+
args["refactor-outcome"] !== undefined &&
|
|
1267
|
+
args["refactor-outcome"] !== "inline" &&
|
|
1268
|
+
args["refactor-outcome"] !== "deferred"
|
|
1269
|
+
) {
|
|
1270
|
+
problems.push("invalid --refactor-outcome (allowed: inline, deferred)");
|
|
1271
|
+
emitProblems(problems, json, 2);
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
if (args["refactor-outcome"] === "deferred") {
|
|
1275
|
+
const rationaleProvided =
|
|
1276
|
+
typeof args["refactor-rationale"] === "string" && args["refactor-rationale"].trim().length > 0;
|
|
1277
|
+
const evidenceProvided =
|
|
1278
|
+
(typeof args["evidence-ref"] === "string" && args["evidence-ref"].trim().length > 0) ||
|
|
1279
|
+
(Array.isArray(args["evidence-refs"]) && args["evidence-refs"].some(
|
|
1280
|
+
(ref) => typeof ref === "string" && ref.trim().length > 0
|
|
1281
|
+
));
|
|
1282
|
+
if (!rationaleProvided && !evidenceProvided) {
|
|
1283
|
+
problems.push("--refactor-outcome=deferred requires --refactor-rationale=<text> or --evidence-ref=<text>");
|
|
1284
|
+
emitProblems(problems, json, 2);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
if (
|
|
1289
|
+
args["risk-tier"] !== undefined &&
|
|
1290
|
+
args["risk-tier"] !== "low" &&
|
|
1291
|
+
args["risk-tier"] !== "medium" &&
|
|
1292
|
+
args["risk-tier"] !== "high"
|
|
1293
|
+
) {
|
|
1294
|
+
problems.push("invalid --risk-tier (allowed: low, medium, high)");
|
|
1295
|
+
emitProblems(problems, json, 2);
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1142
1299
|
if (args.status === "completed" && args["dispatch-surface"] !== "role-switch") {
|
|
1143
1300
|
for (const key of ["dispatch-id", "dispatch-surface", "agent-definition-path"]) {
|
|
1144
1301
|
if (!args[key]) problems.push("completed isolated/generic status requires --" + key);
|
|
@@ -37,7 +37,11 @@ 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
|
+
"**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: re-dispatch
|
|
53
|
-
"DOC (
|
|
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
|
|
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
|
|
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`).",
|
|
@@ -115,7 +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.
|
|
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.
|
|
119
119
|
|
|
120
120
|
## Headless mode (CI/automation only)
|
|
121
121
|
|
|
@@ -208,8 +208,8 @@ Progress the tracked flow only when one exists:
|
|
|
208
208
|
2. If missing, guide the user to run \`npx cclaw-cli init\` and stop.
|
|
209
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.
|
|
210
210
|
4. Check gates for \`currentStage\`.
|
|
211
|
-
5. **TDD (v6.
|
|
212
|
-
6. **Wave dispatch resume:** If a wave is partially closed (some members already past GREEN/REFACTOR), continue with the remaining members in parallel
|
|
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
213
|
7. If incomplete → load current stage skill and execute.
|
|
214
214
|
8. If complete → advance to next stage and execute.
|
|
215
215
|
9. If flow is done → report completion.
|
package/dist/delegation.d.ts
CHANGED
|
@@ -183,6 +183,35 @@ export type DelegationEntry = {
|
|
|
183
183
|
* v6.13.0 — integration branch merge status after deterministic fan-in.
|
|
184
184
|
*/
|
|
185
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";
|
|
186
215
|
};
|
|
187
216
|
export declare const DELEGATION_PHASES: readonly ["red", "green", "refactor", "refactor-deferred", "doc", "resolve-conflict"];
|
|
188
217
|
export type DelegationPhase = (typeof DELEGATION_PHASES)[number];
|
|
@@ -395,6 +424,56 @@ export declare function selectReadySlices(units: ReadySliceUnit[], opts: SelectR
|
|
|
395
424
|
* v6.13.1 — build scheduler rows from merged parallel wave definitions + plan units.
|
|
396
425
|
*/
|
|
397
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>;
|
|
398
477
|
/**
|
|
399
478
|
* v6.13.1 — load merged wave plan (Parallel Execution Plan block + wave-plans/) and map to `ReadySliceUnit[]`.
|
|
400
479
|
*/
|
package/dist/delegation.js
CHANGED
|
@@ -257,7 +257,22 @@ function isDelegationEntry(value) {
|
|
|
257
257
|
o.integrationState === "applied" ||
|
|
258
258
|
o.integrationState === "conflict" ||
|
|
259
259
|
o.integrationState === "resolved" ||
|
|
260
|
-
o.integrationState === "abandoned")
|
|
260
|
+
o.integrationState === "abandoned") &&
|
|
261
|
+
(o.refactorOutcome === undefined || isRefactorOutcomeShape(o.refactorOutcome)) &&
|
|
262
|
+
(o.riskTier === undefined ||
|
|
263
|
+
o.riskTier === "low" ||
|
|
264
|
+
o.riskTier === "medium" ||
|
|
265
|
+
o.riskTier === "high"));
|
|
266
|
+
}
|
|
267
|
+
function isRefactorOutcomeShape(value) {
|
|
268
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
269
|
+
return false;
|
|
270
|
+
const o = value;
|
|
271
|
+
if (o.mode !== "inline" && o.mode !== "deferred")
|
|
272
|
+
return false;
|
|
273
|
+
if (o.rationale !== undefined && typeof o.rationale !== "string")
|
|
274
|
+
return false;
|
|
275
|
+
return true;
|
|
261
276
|
}
|
|
262
277
|
function isDelegationDispatchSurface(value) {
|
|
263
278
|
return typeof value === "string" && DELEGATION_DISPATCH_SURFACES.includes(value);
|
|
@@ -372,7 +387,8 @@ const NON_DELEGATION_AUDIT_EVENTS = new Set([
|
|
|
372
387
|
"cclaw_fanin_applied",
|
|
373
388
|
"cclaw_fanin_conflict",
|
|
374
389
|
"cclaw_fanin_resolved",
|
|
375
|
-
"cclaw_fanin_abandoned"
|
|
390
|
+
"cclaw_fanin_abandoned",
|
|
391
|
+
"cclaw_integration_overseer_skipped"
|
|
376
392
|
]);
|
|
377
393
|
function isAuditEventLine(parsed) {
|
|
378
394
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
@@ -771,6 +787,110 @@ export function readySliceUnitsFromMergedWaves(mergedWaves, planMarkdown, option
|
|
|
771
787
|
}
|
|
772
788
|
return out;
|
|
773
789
|
}
|
|
790
|
+
export function integrationCheckRequired(events, fanInAudits) {
|
|
791
|
+
const reasons = [];
|
|
792
|
+
// Closed slices = ones whose phase=green or phase=refactor row is
|
|
793
|
+
// completed. We collect each unique sliceId's representative paths
|
|
794
|
+
// and risk tier so the heuristic looks at terminal state only.
|
|
795
|
+
const sliceState = new Map();
|
|
796
|
+
for (const evt of events) {
|
|
797
|
+
if (evt.stage !== "tdd")
|
|
798
|
+
continue;
|
|
799
|
+
if (typeof evt.sliceId !== "string" || evt.sliceId.length === 0)
|
|
800
|
+
continue;
|
|
801
|
+
if (evt.status !== "completed")
|
|
802
|
+
continue;
|
|
803
|
+
if (evt.phase !== "green" && evt.phase !== "refactor" && evt.phase !== "refactor-deferred") {
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
const existing = sliceState.get(evt.sliceId) ?? { sliceId: evt.sliceId };
|
|
807
|
+
if (Array.isArray(evt.claimedPaths) && evt.claimedPaths.length > 0) {
|
|
808
|
+
const merged = new Set(existing.claimedPaths ?? []);
|
|
809
|
+
for (const p of evt.claimedPaths)
|
|
810
|
+
merged.add(p);
|
|
811
|
+
existing.claimedPaths = [...merged];
|
|
812
|
+
}
|
|
813
|
+
if (evt.riskTier === "low" || evt.riskTier === "medium" || evt.riskTier === "high") {
|
|
814
|
+
// Highest-wins so the verdict is conservative.
|
|
815
|
+
const order = { low: 0, medium: 1, high: 2 };
|
|
816
|
+
const prev = existing.riskTier ?? "low";
|
|
817
|
+
if (order[evt.riskTier] >= order[prev]) {
|
|
818
|
+
existing.riskTier = evt.riskTier;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
sliceState.set(evt.sliceId, existing);
|
|
822
|
+
}
|
|
823
|
+
const slices = [...sliceState.values()];
|
|
824
|
+
if (slices.some((s) => s.riskTier === "high")) {
|
|
825
|
+
reasons.push("high-risk-slice");
|
|
826
|
+
}
|
|
827
|
+
// Shared-directory heuristic — two distinct slices with overlapping
|
|
828
|
+
// first-2-segment directory prefixes count as shared boundary.
|
|
829
|
+
const sliceDirs = new Map();
|
|
830
|
+
for (const s of slices) {
|
|
831
|
+
const dirs = new Set();
|
|
832
|
+
for (const raw of s.claimedPaths ?? []) {
|
|
833
|
+
const segments = raw.split("/").filter((seg) => seg.length > 0);
|
|
834
|
+
if (segments.length === 0)
|
|
835
|
+
continue;
|
|
836
|
+
// For top-level files like `package.json`, fall back to the
|
|
837
|
+
// first segment so single-segment paths still count as a shared
|
|
838
|
+
// directory when two slices both claim the file.
|
|
839
|
+
const prefix = segments.slice(0, Math.max(1, Math.min(2, segments.length))).join("/");
|
|
840
|
+
dirs.add(prefix);
|
|
841
|
+
}
|
|
842
|
+
if (dirs.size > 0)
|
|
843
|
+
sliceDirs.set(s.sliceId, dirs);
|
|
844
|
+
}
|
|
845
|
+
let sharedFound = false;
|
|
846
|
+
const ids = [...sliceDirs.keys()];
|
|
847
|
+
outer: for (let i = 0; i < ids.length; i += 1) {
|
|
848
|
+
const a = sliceDirs.get(ids[i]);
|
|
849
|
+
for (let j = i + 1; j < ids.length; j += 1) {
|
|
850
|
+
const b = sliceDirs.get(ids[j]);
|
|
851
|
+
for (const dir of a) {
|
|
852
|
+
if (b.has(dir)) {
|
|
853
|
+
sharedFound = true;
|
|
854
|
+
break outer;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (sharedFound)
|
|
860
|
+
reasons.push("shared-import-boundary");
|
|
861
|
+
// Fan-in conflict trigger — any `cclaw_fanin_conflict` in the supplied
|
|
862
|
+
// audits forces the overseer regardless of paths/risk.
|
|
863
|
+
if (Array.isArray(fanInAudits) && fanInAudits.some((a) => a.event === "cclaw_fanin_conflict")) {
|
|
864
|
+
reasons.push("fanin-conflict");
|
|
865
|
+
}
|
|
866
|
+
if (reasons.length > 0) {
|
|
867
|
+
return { required: true, reasons };
|
|
868
|
+
}
|
|
869
|
+
return { required: false, reasons: ["disjoint-paths"] };
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* v6.14.0 — append a non-delegation audit event recording that the
|
|
873
|
+
* integration-overseer dispatch was skipped because
|
|
874
|
+
* `integrationCheckRequired()` returned `required: false`. Best-effort;
|
|
875
|
+
* never throws.
|
|
876
|
+
*/
|
|
877
|
+
export async function recordIntegrationOverseerSkipped(projectRoot, params) {
|
|
878
|
+
const eventsPath = delegationEventsPath(projectRoot);
|
|
879
|
+
const payload = {
|
|
880
|
+
event: "cclaw_integration_overseer_skipped",
|
|
881
|
+
runId: params.runId,
|
|
882
|
+
reasons: params.reasons,
|
|
883
|
+
sliceIds: params.sliceIds,
|
|
884
|
+
ts: new Date().toISOString()
|
|
885
|
+
};
|
|
886
|
+
try {
|
|
887
|
+
await fs.mkdir(path.dirname(eventsPath), { recursive: true });
|
|
888
|
+
await fs.appendFile(eventsPath, `${JSON.stringify(payload)}\n`, "utf8");
|
|
889
|
+
}
|
|
890
|
+
catch {
|
|
891
|
+
// best-effort audit; never block stage advance.
|
|
892
|
+
}
|
|
893
|
+
}
|
|
774
894
|
/**
|
|
775
895
|
* v6.13.1 — load merged wave plan (Parallel Execution Plan block + wave-plans/) and map to `ReadySliceUnit[]`.
|
|
776
896
|
*/
|
package/dist/flow-state.d.ts
CHANGED
|
@@ -151,12 +151,57 @@ export interface FlowState {
|
|
|
151
151
|
* defaults scheduler parallelism to opt-in only for those units.
|
|
152
152
|
*/
|
|
153
153
|
legacyContinuation?: boolean;
|
|
154
|
+
/**
|
|
155
|
+
* v6.14.0 — TDD wave checkpoint mode (stream-style parallel TDD).
|
|
156
|
+
*
|
|
157
|
+
* - `per-slice` — default for new projects. Each lane runs RED→GREEN as
|
|
158
|
+
* soon as its `dependsOn` closes; the linter enforces RED-before-GREEN
|
|
159
|
+
* per slice only (`tdd_slice_red_completed_before_green`). No global
|
|
160
|
+
* barrier between Phase A REDs and Phase B GREENs.
|
|
161
|
+
* - `global-red` — legacy v6.12/v6.13 behavior. ALL Phase A REDs in a
|
|
162
|
+
* wave must complete before ANY Phase B GREEN starts. Auto-applied
|
|
163
|
+
* for projects with `legacyContinuation: true` so hox-style runs
|
|
164
|
+
* continue to enforce the wave barrier.
|
|
165
|
+
*
|
|
166
|
+
* Omitted on legacy state files (treated as `"global-red"` for
|
|
167
|
+
* `legacyContinuation: true` and `"per-slice"` otherwise via
|
|
168
|
+
* `effectiveTddCheckpointMode`).
|
|
169
|
+
*/
|
|
170
|
+
tddCheckpointMode?: "per-slice" | "global-red";
|
|
171
|
+
/**
|
|
172
|
+
* v6.14.0 — integration-overseer dispatch mode.
|
|
173
|
+
*
|
|
174
|
+
* - `conditional` — default for new projects. The controller calls
|
|
175
|
+
* `integrationCheckRequired(events)` after wave closeout; the
|
|
176
|
+
* integration-overseer is dispatched only when (a) two or more
|
|
177
|
+
* closed slices share import boundaries (heuristic: shared
|
|
178
|
+
* directory in `evidenceRefs`/`claimedPaths`), (b) any slice has
|
|
179
|
+
* `riskTier === "high"`, or (c) deterministic fan-in reported a
|
|
180
|
+
* `cclaw_fanin_conflict`. Otherwise the linter emits the audit
|
|
181
|
+
* row `cclaw_integration_overseer_skipped` and skips dispatch.
|
|
182
|
+
* - `always` — legacy v6.13 behavior. Run integration-overseer
|
|
183
|
+
* after every multi-slice wave regardless of trigger.
|
|
184
|
+
*
|
|
185
|
+
* Omitted on legacy state files (treated as `"always"`).
|
|
186
|
+
*/
|
|
187
|
+
integrationOverseerMode?: "conditional" | "always";
|
|
154
188
|
}
|
|
155
189
|
/**
|
|
156
190
|
* Effective worktree mode: legacy state files without the field keep
|
|
157
191
|
* single-tree scheduling to avoid breaking existing runs on upgrade.
|
|
158
192
|
*/
|
|
159
193
|
export declare function effectiveWorktreeExecutionMode(state: FlowState): "single-tree" | "worktree-first";
|
|
194
|
+
/**
|
|
195
|
+
* Effective v6.14 TDD checkpoint mode: legacy state files without the
|
|
196
|
+
* field default to `global-red` when `legacyContinuation: true` (hox)
|
|
197
|
+
* and `per-slice` otherwise. Explicit values always win.
|
|
198
|
+
*/
|
|
199
|
+
export declare function effectiveTddCheckpointMode(state: FlowState): "per-slice" | "global-red";
|
|
200
|
+
/**
|
|
201
|
+
* Effective v6.14 integration-overseer mode: legacy state files without
|
|
202
|
+
* the field default to `always` (matches v6.13 behavior).
|
|
203
|
+
*/
|
|
204
|
+
export declare function effectiveIntegrationOverseerMode(state: FlowState): "conditional" | "always";
|
|
160
205
|
export interface StageInteractionHint {
|
|
161
206
|
skipQuestions?: boolean;
|
|
162
207
|
sourceStage?: FlowStage;
|
package/dist/flow-state.js
CHANGED
|
@@ -51,6 +51,24 @@ export function createInitialCloseoutState() {
|
|
|
51
51
|
export function effectiveWorktreeExecutionMode(state) {
|
|
52
52
|
return state.worktreeExecutionMode ?? "single-tree";
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Effective v6.14 TDD checkpoint mode: legacy state files without the
|
|
56
|
+
* field default to `global-red` when `legacyContinuation: true` (hox)
|
|
57
|
+
* and `per-slice` otherwise. Explicit values always win.
|
|
58
|
+
*/
|
|
59
|
+
export function effectiveTddCheckpointMode(state) {
|
|
60
|
+
if (state.tddCheckpointMode === "per-slice" || state.tddCheckpointMode === "global-red") {
|
|
61
|
+
return state.tddCheckpointMode;
|
|
62
|
+
}
|
|
63
|
+
return state.legacyContinuation === true ? "global-red" : "per-slice";
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Effective v6.14 integration-overseer mode: legacy state files without
|
|
67
|
+
* the field default to `always` (matches v6.13 behavior).
|
|
68
|
+
*/
|
|
69
|
+
export function effectiveIntegrationOverseerMode(state) {
|
|
70
|
+
return state.integrationOverseerMode === "conditional" ? "conditional" : "always";
|
|
71
|
+
}
|
|
54
72
|
export function isFlowTrack(value) {
|
|
55
73
|
return typeof value === "string" && FLOW_TRACKS.includes(value);
|
|
56
74
|
}
|