cclaw-cli 7.6.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.
- package/README.md +2 -1
- package/dist/artifact-linter/plan.js +98 -25
- package/dist/artifact-linter/tdd.js +2 -2
- package/dist/config.d.ts +15 -1
- package/dist/config.js +152 -2
- package/dist/content/core-agents.d.ts +1 -1
- package/dist/content/core-agents.js +2 -2
- package/dist/content/meta-skill.js +4 -4
- package/dist/content/skills.js +12 -8
- package/dist/content/stage-schema.js +2 -2
- package/dist/content/stages/plan.js +17 -17
- package/dist/content/stages/tdd.js +13 -10
- package/dist/content/start-command.js +3 -3
- package/dist/content/subagent-context-skills.js +2 -2
- package/dist/content/subagents.js +6 -6
- package/dist/content/templates.js +12 -7
- package/dist/delegation.d.ts +4 -3
- package/dist/delegation.js +14 -8
- package/dist/execution-topology.d.ts +36 -0
- package/dist/execution-topology.js +73 -0
- package/dist/internal/plan-split-waves.d.ts +5 -2
- package/dist/internal/plan-split-waves.js +14 -8
- package/dist/internal/wave-status.d.ts +4 -0
- package/dist/internal/wave-status.js +47 -8
- package/dist/types.d.ts +45 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,7 +68,8 @@ That gives you:
|
|
|
68
68
|
|
|
69
69
|
- **One path** from idea to ship, with one user-chosen discovery mode (`lean`, `guided`, `deep`) and internal `quick` / `medium` / `standard` tracks.
|
|
70
70
|
- **Real gates** for evidence, tests, review, delegation, stale-stage recovery, and closeout.
|
|
71
|
-
- **
|
|
71
|
+
- **Adaptive TDD execution**: feature-atomic slices carry internal 2-5 minute RED/GREEN/REFACTOR steps; cclaw routes them inline, through one builder, through parallel builders, or through strict micro-slices when risk demands it.
|
|
72
|
+
- **Subagents with accountability**: controller owns state, workers do bounded implementation units, overseers validate, evidence lands in `delegation-log.json`.
|
|
72
73
|
- **Recovery instead of confusion**: `npx cclaw-cli sync` tells you blockers and next fixes.
|
|
73
74
|
- **Portable harness behavior** across Claude Code, Cursor, OpenCode, and Codex.
|
|
74
75
|
|
|
@@ -4,15 +4,17 @@ import { exists } from "../fs-utils.js";
|
|
|
4
4
|
import { FORBIDDEN_PLACEHOLDER_TOKENS, CONFIDENCE_FINDING_REGEX_SOURCE } from "../content/skills.js";
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import { PLAN_SPLIT_SMALL_PLAN_THRESHOLD, parseImplementationUnits, parseImplementationUnitParallelFields } from "../internal/plan-split-waves.js";
|
|
7
|
+
import { PLAN_SPLIT_SMALL_PLAN_THRESHOLD, parseImplementationUnits, parseImplementationUnitParallelFields, parseParallelExecutionPlanWaves } from "../internal/plan-split-waves.js";
|
|
8
8
|
import { compareSliceIds, parseSliceId } from "../util/slice-id.js";
|
|
9
9
|
import { execFile } from "node:child_process";
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
11
|
import { loadStackAdapter } from "../stack-detection.js";
|
|
12
|
+
import { readConfig, resolveExecutionStrictness, resolveExecutionTopology, resolvePlanMicroTaskPolicy, resolvePlanSliceGranularity } from "../config.js";
|
|
12
13
|
const execFileAsync = promisify(execFile);
|
|
13
14
|
const PARALLEL_EXEC_MANAGED_START = "<!-- parallel-exec-managed-start -->";
|
|
14
15
|
const PARALLEL_EXEC_MANAGED_END = "<!-- parallel-exec-managed-end -->";
|
|
15
16
|
const TASK_ID_PATTERN = /\bT-\d{3}[a-z]?(?:\.\d{1,3})?\b/giu;
|
|
17
|
+
const UNIT_ID_PATTERN = /\bU-\d+(?:[a-z][a-z0-9]*)?\b/giu;
|
|
16
18
|
const ACCEPTANCE_ID_PATTERN = /\bAC-\d+\b/giu;
|
|
17
19
|
const PLAN_LANE_WHITELIST = new Set([
|
|
18
20
|
"production",
|
|
@@ -36,6 +38,13 @@ function extractTaskIds(body) {
|
|
|
36
38
|
}
|
|
37
39
|
return ids;
|
|
38
40
|
}
|
|
41
|
+
function extractUnitIds(body) {
|
|
42
|
+
const ids = new Set();
|
|
43
|
+
for (const match of body.matchAll(UNIT_ID_PATTERN)) {
|
|
44
|
+
ids.add(match[0].toUpperCase());
|
|
45
|
+
}
|
|
46
|
+
return ids;
|
|
47
|
+
}
|
|
39
48
|
function extractAcceptanceTaskLinks(body) {
|
|
40
49
|
const links = [];
|
|
41
50
|
for (const line of body.split(/\r?\n/u)) {
|
|
@@ -116,7 +125,11 @@ function parseParallelWaveTableMetadata(planMarkdown) {
|
|
|
116
125
|
if (cells.length === 0)
|
|
117
126
|
continue;
|
|
118
127
|
const first = cells[0].toLowerCase();
|
|
119
|
-
const isHeader = first === "sliceid" ||
|
|
128
|
+
const isHeader = first === "sliceid" ||
|
|
129
|
+
first === "slice id" ||
|
|
130
|
+
first === "unitid" ||
|
|
131
|
+
first === "unit id" ||
|
|
132
|
+
first === "unit";
|
|
120
133
|
if (isHeader) {
|
|
121
134
|
headerIdx = headerIndexByName(cells);
|
|
122
135
|
continue;
|
|
@@ -124,10 +137,12 @@ function parseParallelWaveTableMetadata(planMarkdown) {
|
|
|
124
137
|
if (cells.every((cell) => /^:?-{3,}:?$/u.test(cell))) {
|
|
125
138
|
continue;
|
|
126
139
|
}
|
|
127
|
-
const sliceCell = cells[0];
|
|
140
|
+
const sliceCell = cells[0].replace(/^`|`$/gu, "").trim();
|
|
128
141
|
const parsedSlice = parseSliceId(sliceCell);
|
|
129
|
-
|
|
142
|
+
const parsedUnit = /^U-(\d+(?:[a-z][a-z0-9]*)?)$/iu.exec(sliceCell);
|
|
143
|
+
if (!parsedSlice && !parsedUnit)
|
|
130
144
|
continue;
|
|
145
|
+
const sliceId = parsedSlice?.id ?? `S-${parsedUnit[1].toLowerCase()}`;
|
|
131
146
|
const idx = headerIdx ?? new Map();
|
|
132
147
|
const unitIdx = idx.get("unit") ?? idx.get("taskid") ?? 1;
|
|
133
148
|
const pathsIdx = idx.get("claimedpaths");
|
|
@@ -158,7 +173,7 @@ function parseParallelWaveTableMetadata(planMarkdown) {
|
|
|
158
173
|
.map((token) => parseSliceId(token)?.id ?? "")
|
|
159
174
|
.filter((id) => id.length > 0);
|
|
160
175
|
current.rows.push({
|
|
161
|
-
sliceId
|
|
176
|
+
sliceId,
|
|
162
177
|
unit: (cells[unitIdx] ?? "").trim(),
|
|
163
178
|
claimedPaths,
|
|
164
179
|
parallelizable,
|
|
@@ -234,6 +249,11 @@ function transitivePredecessors(sliceId, graph) {
|
|
|
234
249
|
}
|
|
235
250
|
export async function lintPlanStage(ctx) {
|
|
236
251
|
const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
|
|
252
|
+
const config = await readConfig(projectRoot).catch(() => null);
|
|
253
|
+
const executionStrictness = resolveExecutionStrictness(config);
|
|
254
|
+
const executionTopology = resolveExecutionTopology(config);
|
|
255
|
+
const planSliceGranularity = resolvePlanSliceGranularity(config);
|
|
256
|
+
const planMicroTaskPolicy = resolvePlanMicroTaskPolicy(config);
|
|
237
257
|
evaluateInvestigationTrace(ctx, "Implementation Units");
|
|
238
258
|
const strictPlanGuards = parsedFrontmatter.hasFrontmatter ||
|
|
239
259
|
headingPresent(sections, "Plan Quality Scan") ||
|
|
@@ -500,6 +520,29 @@ export async function lintPlanStage(ctx) {
|
|
|
500
520
|
});
|
|
501
521
|
}
|
|
502
522
|
const planUnits = parseImplementationUnits(raw);
|
|
523
|
+
const authoredTaskIdsForShape = extractTaskIds(sectionBodyByName(sections, "Task List") ?? "");
|
|
524
|
+
const microtaskOnlyPlan = authoredTaskIdsForShape.size > 1 &&
|
|
525
|
+
planUnits.length === 0 &&
|
|
526
|
+
executionTopology !== "strict-micro" &&
|
|
527
|
+
planSliceGranularity !== "strict-micro";
|
|
528
|
+
const strictMicroPolicy = executionStrictness === "strict" ||
|
|
529
|
+
executionTopology === "strict-micro" ||
|
|
530
|
+
planSliceGranularity === "strict-micro" ||
|
|
531
|
+
planMicroTaskPolicy === "strict";
|
|
532
|
+
const microtaskOnlyAdvisoryApplies = microtaskOnlyPlan &&
|
|
533
|
+
!strictMicroPolicy &&
|
|
534
|
+
(executionStrictness === "fast" || executionStrictness === "balanced");
|
|
535
|
+
findings.push({
|
|
536
|
+
section: "plan_microtask_only_advisory",
|
|
537
|
+
required: false,
|
|
538
|
+
rule: "Balanced/fast execution should plan feature-atomic implementation units/slices with internal 2-5 minute TDD steps; reserve one-task-one-slice microtask plans for `execution.topology: strict-micro`, `execution.strictness: strict`, or `plan.microTaskPolicy: strict`.",
|
|
539
|
+
found: !microtaskOnlyAdvisoryApplies,
|
|
540
|
+
details: microtaskOnlyAdvisoryApplies
|
|
541
|
+
? `Task List has ${authoredTaskIdsForShape.size} tiny task id(s) but no Implementation Units. In execution.strictness=${executionStrictness} with plan.microTaskPolicy=${planMicroTaskPolicy}, group related tasks into U-* feature-atomic slices with internal RED/GREEN/REFACTOR steps, or set execution.topology=strict-micro / plan.microTaskPolicy=strict for high-risk micro-slice execution.`
|
|
542
|
+
: strictMicroPolicy
|
|
543
|
+
? "Strict micro-slice posture is configured; microtask-only planning is allowed."
|
|
544
|
+
: "Plan includes implementation units or does not look microtask-only."
|
|
545
|
+
});
|
|
503
546
|
const parallelMetaApplies = strictPlanGuards && planUnits.length > 0;
|
|
504
547
|
if (parallelMetaApplies) {
|
|
505
548
|
const metaRulesRequired = true;
|
|
@@ -573,6 +616,7 @@ export async function lintPlanStage(ctx) {
|
|
|
573
616
|
if (strictPlanGuards) {
|
|
574
617
|
const taskListSection = sectionBodyByName(sections, "Task List") ?? "";
|
|
575
618
|
const authoredTaskIds = extractTaskIds(taskListSection);
|
|
619
|
+
const authoredUnitIds = new Set(planUnits.map((unit) => unit.id.toUpperCase()));
|
|
576
620
|
// Collect deferred / backlog task ids so they don't trigger the
|
|
577
621
|
// "uncovered" finding. Both heading variants are accepted.
|
|
578
622
|
const deferredBody = (sectionBodyByName(sections, "Deferred Tasks") ?? "") +
|
|
@@ -581,29 +625,58 @@ export async function lintPlanStage(ctx) {
|
|
|
581
625
|
const deferredIds = extractTaskIds(deferredBody);
|
|
582
626
|
const parallelExecBody = extractParallelExecManagedBody(raw);
|
|
583
627
|
const claimedIds = extractTaskIds(parallelExecBody);
|
|
628
|
+
const claimedUnitIds = extractUnitIds(parallelExecBody);
|
|
629
|
+
try {
|
|
630
|
+
for (const wave of parseParallelExecutionPlanWaves(raw)) {
|
|
631
|
+
for (const member of wave.members) {
|
|
632
|
+
if (/^U-\d+(?:[a-z][a-z0-9]*)?$/iu.test(member.unitId)) {
|
|
633
|
+
claimedUnitIds.add(member.unitId.toUpperCase());
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
catch {
|
|
639
|
+
// Duplicate/malformed wave plans are reported by the wave parser/status
|
|
640
|
+
// path; this coverage gate falls back to raw token extraction.
|
|
641
|
+
}
|
|
642
|
+
const useImplementationUnitCoverage = authoredUnitIds.size > 0;
|
|
584
643
|
const uncovered = [];
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
644
|
+
if (useImplementationUnitCoverage) {
|
|
645
|
+
for (const id of authoredUnitIds) {
|
|
646
|
+
if (claimedUnitIds.has(id))
|
|
647
|
+
continue;
|
|
648
|
+
uncovered.push(id);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
for (const id of authoredTaskIds) {
|
|
653
|
+
if (claimedIds.has(id))
|
|
654
|
+
continue;
|
|
655
|
+
if (deferredIds.has(id))
|
|
656
|
+
continue;
|
|
657
|
+
uncovered.push(id);
|
|
658
|
+
}
|
|
591
659
|
}
|
|
592
660
|
uncovered.sort();
|
|
593
661
|
const blockPresent = parallelExecBody.length > 0;
|
|
594
|
-
const
|
|
662
|
+
const coverageTargetPresent = useImplementationUnitCoverage || authoredTaskIds.size > 0;
|
|
663
|
+
const coverageTargetLabel = useImplementationUnitCoverage
|
|
664
|
+
? "implementation unit"
|
|
665
|
+
: "task id";
|
|
595
666
|
findings.push({
|
|
596
667
|
section: "plan_parallel_exec_full_coverage",
|
|
597
|
-
required:
|
|
598
|
-
rule: "Every
|
|
599
|
-
found:
|
|
600
|
-
details: !
|
|
601
|
-
? "
|
|
668
|
+
required: coverageTargetPresent,
|
|
669
|
+
rule: "Every feature-atomic Implementation Unit (`U-*`) must be assigned to at least one slice/wave inside the `<!-- parallel-exec-managed-start -->` block. Legacy strict-micro plans without units may instead cover every non-deferred `T-NNN` task. TDD cannot fan out waves the plan never authored.",
|
|
670
|
+
found: coverageTargetPresent && blockPresent && uncovered.length === 0,
|
|
671
|
+
details: !coverageTargetPresent
|
|
672
|
+
? "No Implementation Units or T-NNN task ids found; full-coverage check skipped."
|
|
602
673
|
: !blockPresent
|
|
603
|
-
? "`<!-- parallel-exec-managed-start -->` block is missing or empty. Author the Parallel Execution Plan with W-02..W-N covering every
|
|
674
|
+
? "`<!-- parallel-exec-managed-start -->` block is missing or empty. Author the Parallel Execution Plan with W-02..W-N covering every implementation unit/slice before plan-final-approval."
|
|
604
675
|
: uncovered.length === 0
|
|
605
|
-
?
|
|
606
|
-
|
|
676
|
+
? useImplementationUnitCoverage
|
|
677
|
+
? `Parallel Execution Plan covers all ${authoredUnitIds.size} implementation unit(s); internal ${authoredTaskIds.size} T-NNN step(s) remain inside those units.`
|
|
678
|
+
: `Parallel Execution Plan covers all ${authoredTaskIds.size} authored task id(s); ${deferredIds.size} task id(s) are explicitly deferred.`
|
|
679
|
+
: `Uncovered ${coverageTargetLabel}(s) — author waves for: ${uncovered.slice(0, 25).join(", ")}${uncovered.length > 25 ? `, … (${uncovered.length - 25} more)` : ""}. ${useImplementationUnitCoverage ? "Add U-* rows/members inside <!-- parallel-exec-managed-start -->." : "Either add slices for them inside <!-- parallel-exec-managed-start --> or move them under `## Deferred Tasks` with a reason."}`
|
|
607
680
|
});
|
|
608
681
|
const waveMeta = parseParallelWaveTableMetadata(raw);
|
|
609
682
|
const pathConflicts = [];
|
|
@@ -623,11 +696,11 @@ export async function lintPlanStage(ctx) {
|
|
|
623
696
|
}
|
|
624
697
|
findings.push({
|
|
625
698
|
section: "plan_wave_paths_disjoint",
|
|
626
|
-
required:
|
|
699
|
+
required: coverageTargetPresent,
|
|
627
700
|
rule: "Slices within the same wave must keep `claimedPaths` disjoint so TDD can safely fan out parallel slice-builders.",
|
|
628
|
-
found:
|
|
629
|
-
details: !
|
|
630
|
-
? "
|
|
701
|
+
found: coverageTargetPresent && blockPresent && pathConflicts.length === 0,
|
|
702
|
+
details: !coverageTargetPresent
|
|
703
|
+
? "No Implementation Units or T-NNN task ids found; disjoint-path wave check skipped."
|
|
631
704
|
: !blockPresent
|
|
632
705
|
? "`<!-- parallel-exec-managed-start -->` block is missing or empty; cannot validate wave path disjointness."
|
|
633
706
|
: pathConflicts.length === 0
|
|
@@ -740,7 +813,7 @@ export async function lintPlanStage(ctx) {
|
|
|
740
813
|
const wiringApplies = stackAdapter.wiringAggregator !== undefined;
|
|
741
814
|
findings.push({
|
|
742
815
|
section: "plan_module_introducing_slice_wires_root",
|
|
743
|
-
required:
|
|
816
|
+
required: coverageTargetPresent && wiringApplies,
|
|
744
817
|
rule: "When a slice introduces a new module file, the stack-adapter's wiring aggregator (e.g. Rust `lib.rs`, Python `__init__.py`, Node-TS barrel `index.*` when present) must be in the same slice's claim or in a transitive predecessor's claim, otherwise the new module is dead code and RED can't be expressed.",
|
|
745
818
|
found: !wiringApplies || wiringIssues.length === 0,
|
|
746
819
|
details: !wiringApplies
|
|
@@ -996,9 +996,9 @@ export async function evaluateWavePlanDispatchIgnored(params) {
|
|
|
996
996
|
return {
|
|
997
997
|
section: "tdd_wave_plan_ignored",
|
|
998
998
|
required: true,
|
|
999
|
-
rule: "When the Parallel Execution Plan (or wave-plans/) defines an open wave with two or more ready parallelizable slices, the controller must
|
|
999
|
+
rule: "When the Parallel Execution Plan (or wave-plans/) defines an open wave with two or more ready parallelizable units/slices, the controller must honor the parallel-builders topology instead of serializing to one slice only.",
|
|
1000
1000
|
found: false,
|
|
1001
|
-
details: `Wave ${wave.waveId}: scheduler-ready members ${ready.map((r) => r.sliceId).join(", ")}; last 20 delegation events show slice workers only for ${only}. Missed parallel dispatch: ${missed.join(", ")}. Remediation: load \`05-plan.md\` (Parallel Execution Plan) and \`wave-plans/\` before routing,
|
|
1001
|
+
details: `Wave ${wave.waveId}: scheduler-ready members ${ready.map((r) => r.sliceId).join(", ")}; last 20 delegation events show slice workers only for ${only}. Missed parallel dispatch: ${missed.join(", ")}. Remediation: load \`05-plan.md\` (Parallel Execution Plan) and \`wave-plans/\` before routing, honor \`nextDispatch.topology=parallel-builders\`, then dispatch the routed ready builders in one controller message.`
|
|
1002
1002
|
};
|
|
1003
1003
|
}
|
|
1004
1004
|
return null;
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CclawConfig, FlowTrack, HarnessId, LanguageRulePack, LockfileTwinPolicy, TddCommitMode, TddIsolationMode } from "./types.js";
|
|
1
|
+
import type { CclawConfig, ExecutionStrictnessProfile, ExecutionTopology, FlowTrack, HarnessId, LanguageRulePack, LockfileTwinPolicy, PlanMicroTaskPolicy, PlanSliceGranularity, TddCommitMode, TddIsolationMode } from "./types.js";
|
|
2
2
|
export declare const TDD_COMMIT_MODES: readonly ["managed-per-slice", "agent-required", "checkpoint-only", "off"];
|
|
3
3
|
export declare const DEFAULT_TDD_COMMIT_MODE: TddCommitMode;
|
|
4
4
|
export declare const TDD_ISOLATION_MODES: readonly ["worktree", "in-place", "auto"];
|
|
@@ -6,6 +6,15 @@ export declare const DEFAULT_TDD_ISOLATION_MODE: TddIsolationMode;
|
|
|
6
6
|
export declare const DEFAULT_TDD_WORKTREE_ROOT = ".cclaw/worktrees";
|
|
7
7
|
export declare const LOCKFILE_TWIN_POLICIES: readonly ["auto-include", "auto-revert", "strict-fence"];
|
|
8
8
|
export declare const DEFAULT_LOCKFILE_TWIN_POLICY: LockfileTwinPolicy;
|
|
9
|
+
export declare const EXECUTION_TOPOLOGIES: readonly ["auto", "inline", "single-builder", "parallel-builders", "strict-micro"];
|
|
10
|
+
export declare const DEFAULT_EXECUTION_TOPOLOGY: ExecutionTopology;
|
|
11
|
+
export declare const EXECUTION_STRICTNESS_PROFILES: readonly ["fast", "balanced", "strict"];
|
|
12
|
+
export declare const DEFAULT_EXECUTION_STRICTNESS: ExecutionStrictnessProfile;
|
|
13
|
+
export declare const DEFAULT_MAX_BUILDERS = 5;
|
|
14
|
+
export declare const PLAN_SLICE_GRANULARITIES: readonly ["feature-atomic", "strict-micro"];
|
|
15
|
+
export declare const DEFAULT_PLAN_SLICE_GRANULARITY: PlanSliceGranularity;
|
|
16
|
+
export declare const PLAN_MICRO_TASK_POLICIES: readonly ["advisory", "strict"];
|
|
17
|
+
export declare const DEFAULT_PLAN_MICRO_TASK_POLICY: PlanMicroTaskPolicy;
|
|
9
18
|
export declare const DEFAULT_TDD_TEST_PATH_PATTERNS: readonly string[];
|
|
10
19
|
export declare const DEFAULT_TDD_TEST_GLOBS: readonly string[];
|
|
11
20
|
export declare const DEFAULT_TDD_PRODUCTION_PATH_PATTERNS: readonly string[];
|
|
@@ -27,6 +36,11 @@ export declare function resolveTddCommitMode(config: Pick<CclawConfig, "tdd"> |
|
|
|
27
36
|
export declare function resolveTddIsolationMode(config: Pick<CclawConfig, "tdd"> | null | undefined): TddIsolationMode;
|
|
28
37
|
export declare function resolveTddWorktreeRoot(config: Pick<CclawConfig, "tdd"> | null | undefined): string;
|
|
29
38
|
export declare function resolveLockfileTwinPolicy(config: Pick<CclawConfig, "tdd"> | null | undefined): LockfileTwinPolicy;
|
|
39
|
+
export declare function resolveExecutionTopology(config: Pick<CclawConfig, "execution"> | null | undefined): ExecutionTopology;
|
|
40
|
+
export declare function resolveExecutionStrictness(config: Pick<CclawConfig, "execution"> | null | undefined): ExecutionStrictnessProfile;
|
|
41
|
+
export declare function resolveMaxBuilders(config: Pick<CclawConfig, "execution"> | null | undefined): number;
|
|
42
|
+
export declare function resolvePlanSliceGranularity(config: Pick<CclawConfig, "plan"> | null | undefined): PlanSliceGranularity;
|
|
43
|
+
export declare function resolvePlanMicroTaskPolicy(config: Pick<CclawConfig, "plan"> | null | undefined): PlanMicroTaskPolicy;
|
|
30
44
|
export declare function detectLanguageRulePacks(_projectRoot: string): Promise<LanguageRulePack[]>;
|
|
31
45
|
export declare function readConfig(projectRoot: string, _options?: ReadConfigOptions): Promise<CclawConfig>;
|
|
32
46
|
export interface WriteConfigOptions {
|
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([
|
|
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",
|
|
@@ -23,6 +30,25 @@ export const DEFAULT_TDD_WORKTREE_ROOT = `${RUNTIME_ROOT}/worktrees`;
|
|
|
23
30
|
export const LOCKFILE_TWIN_POLICIES = ["auto-include", "auto-revert", "strict-fence"];
|
|
24
31
|
const LOCKFILE_TWIN_POLICY_SET = new Set(LOCKFILE_TWIN_POLICIES);
|
|
25
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";
|
|
26
52
|
// Kept for runtime modules that use these defaults directly.
|
|
27
53
|
export const DEFAULT_TDD_TEST_PATH_PATTERNS = [
|
|
28
54
|
"**/*.test.*",
|
|
@@ -49,7 +75,14 @@ function configFixExample() {
|
|
|
49
75
|
tdd:
|
|
50
76
|
commitMode: managed-per-slice
|
|
51
77
|
isolationMode: worktree
|
|
52
|
-
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`;
|
|
53
86
|
}
|
|
54
87
|
function configValidationError(configFilePath, reason) {
|
|
55
88
|
return new InvalidConfigError(`Invalid cclaw config at ${configFilePath}: ${reason}\n` +
|
|
@@ -73,6 +106,15 @@ export function createDefaultConfig(harnesses = DEFAULT_HARNESSES, _defaultTrack
|
|
|
73
106
|
isolationMode: DEFAULT_TDD_ISOLATION_MODE,
|
|
74
107
|
worktreeRoot: DEFAULT_TDD_WORKTREE_ROOT,
|
|
75
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
|
|
76
118
|
}
|
|
77
119
|
};
|
|
78
120
|
}
|
|
@@ -104,6 +146,41 @@ export function resolveLockfileTwinPolicy(config) {
|
|
|
104
146
|
}
|
|
105
147
|
return DEFAULT_LOCKFILE_TWIN_POLICY;
|
|
106
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
|
+
}
|
|
107
184
|
function assertOnlySupportedKeys(parsed, fullPath) {
|
|
108
185
|
const unknownKeys = Object.keys(parsed).filter((key) => !ALLOWED_CONFIG_KEYS.has(key));
|
|
109
186
|
if (unknownKeys.length === 0)
|
|
@@ -141,6 +218,14 @@ export async function readConfig(projectRoot, _options = {}) {
|
|
|
141
218
|
!isRecord(parsed.tdd)) {
|
|
142
219
|
throw configValidationError(fullPath, `"tdd" must be an object when provided`);
|
|
143
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
|
+
}
|
|
144
229
|
const rawHarnesses = Array.isArray(parsed.harnesses) ? parsed.harnesses : DEFAULT_HARNESSES;
|
|
145
230
|
const normalizedHarnesses = [];
|
|
146
231
|
for (const harness of rawHarnesses) {
|
|
@@ -165,6 +250,13 @@ export async function readConfig(projectRoot, _options = {}) {
|
|
|
165
250
|
const rawIsolationMode = parsedTdd.isolationMode;
|
|
166
251
|
const rawWorktreeRoot = parsedTdd.worktreeRoot;
|
|
167
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;
|
|
168
260
|
if (rawCommitMode !== undefined &&
|
|
169
261
|
(typeof rawCommitMode !== "string" || !TDD_COMMIT_MODE_SET.has(rawCommitMode))) {
|
|
170
262
|
throw configValidationError(fullPath, `"tdd.commitMode" must be one of: ${TDD_COMMIT_MODES.join(", ")}`);
|
|
@@ -181,6 +273,29 @@ export async function readConfig(projectRoot, _options = {}) {
|
|
|
181
273
|
(typeof rawLockfileTwinPolicy !== "string" || !LOCKFILE_TWIN_POLICY_SET.has(rawLockfileTwinPolicy))) {
|
|
182
274
|
throw configValidationError(fullPath, `"tdd.lockfileTwinPolicy" must be one of: ${LOCKFILE_TWIN_POLICIES.join(", ")}`);
|
|
183
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
|
+
}
|
|
184
299
|
const commitMode = typeof rawCommitMode === "string"
|
|
185
300
|
? rawCommitMode
|
|
186
301
|
: DEFAULT_TDD_COMMIT_MODE;
|
|
@@ -193,6 +308,23 @@ export async function readConfig(projectRoot, _options = {}) {
|
|
|
193
308
|
const lockfileTwinPolicy = typeof rawLockfileTwinPolicy === "string"
|
|
194
309
|
? rawLockfileTwinPolicy
|
|
195
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;
|
|
196
328
|
return {
|
|
197
329
|
version,
|
|
198
330
|
flowVersion,
|
|
@@ -202,6 +334,15 @@ export async function readConfig(projectRoot, _options = {}) {
|
|
|
202
334
|
isolationMode,
|
|
203
335
|
worktreeRoot,
|
|
204
336
|
lockfileTwinPolicy
|
|
337
|
+
},
|
|
338
|
+
execution: {
|
|
339
|
+
topology: executionTopology,
|
|
340
|
+
strictness: executionStrictness,
|
|
341
|
+
maxBuilders
|
|
342
|
+
},
|
|
343
|
+
plan: {
|
|
344
|
+
sliceGranularity: planSliceGranularity,
|
|
345
|
+
microTaskPolicy: planMicroTaskPolicy
|
|
205
346
|
}
|
|
206
347
|
};
|
|
207
348
|
}
|
|
@@ -215,6 +356,15 @@ export async function writeConfig(projectRoot, config, _options = {}) {
|
|
|
215
356
|
isolationMode: resolveTddIsolationMode(config),
|
|
216
357
|
worktreeRoot: resolveTddWorktreeRoot(config),
|
|
217
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)
|
|
218
368
|
}
|
|
219
369
|
};
|
|
220
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
|
|
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
|
|
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.",
|
|
@@ -589,7 +589,7 @@ export const CCLAW_AGENTS = [
|
|
|
589
589
|
},
|
|
590
590
|
{
|
|
591
591
|
name: "slice-builder",
|
|
592
|
-
description: "MANDATORY for
|
|
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.",
|
|
593
593
|
tools: ["Read", "Write", "Edit", "Grep", "Glob", "Bash"],
|
|
594
594
|
model: "balanced",
|
|
595
595
|
activation: "mandatory",
|
|
@@ -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." ->
|
|
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
|
|
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.
|
|
67
|
-
- **Parallel
|
|
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
|
|
package/dist/content/skills.js
CHANGED
|
@@ -183,22 +183,24 @@ export function tddTopOfSkillBlock(stage) {
|
|
|
183
183
|
return "";
|
|
184
184
|
return `## TDD orchestration primer
|
|
185
185
|
|
|
186
|
-
**MANDATE — controller
|
|
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
|
-
| \`
|
|
196
|
-
|
|
197
|
-
| \`
|
|
198
|
-
| \`
|
|
199
|
-
| \`
|
|
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\`).
|
|
@@ -678,8 +678,8 @@ const STAGE_AUTO_SUBAGENT_DISPATCH = {
|
|
|
678
678
|
agent: "slice-builder",
|
|
679
679
|
mode: "mandatory",
|
|
680
680
|
requiredAtTier: "lightweight",
|
|
681
|
-
when: "
|
|
682
|
-
purpose: "Own one
|
|
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.",
|
|
683
683
|
requiresUserGate: false,
|
|
684
684
|
skill: "tdd-cycle-evidence"
|
|
685
685
|
},
|