cclaw-cli 6.12.0 → 6.13.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/plan.js +60 -2
- package/dist/artifact-linter/shared.d.ts +9 -0
- package/dist/artifact-linter/spec.js +14 -0
- package/dist/artifact-linter/tdd.d.ts +19 -6
- package/dist/artifact-linter/tdd.js +225 -47
- package/dist/artifact-linter.js +10 -1
- package/dist/content/hooks.js +88 -1
- package/dist/content/skills.js +17 -10
- package/dist/content/stages/plan.js +2 -1
- package/dist/content/stages/spec.js +2 -2
- package/dist/content/stages/tdd.js +7 -6
- package/dist/content/start-command.js +6 -3
- package/dist/content/templates.js +10 -4
- package/dist/delegation.d.ts +82 -3
- package/dist/delegation.js +244 -6
- package/dist/flow-state.d.ts +20 -0
- package/dist/flow-state.js +7 -0
- package/dist/gate-evidence.d.ts +5 -0
- package/dist/gate-evidence.js +58 -1
- package/dist/install.js +90 -2
- package/dist/integration-fanin.d.ts +44 -0
- package/dist/integration-fanin.js +180 -0
- package/dist/internal/advance-stage/advance.js +16 -1
- package/dist/internal/advance-stage/start-flow.js +3 -1
- package/dist/internal/advance-stage.js +13 -4
- package/dist/internal/plan-split-waves.d.ts +85 -1
- package/dist/internal/plan-split-waves.js +409 -6
- package/dist/internal/set-worktree-mode.d.ts +10 -0
- package/dist/internal/set-worktree-mode.js +28 -0
- package/dist/managed-resources.js +2 -0
- package/dist/run-persistence.js +9 -0
- package/dist/worktree-manager.d.ts +50 -0
- package/dist/worktree-manager.js +136 -0
- package/dist/worktree-types.d.ts +36 -0
- package/dist/worktree-types.js +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { exists } from "./fs-utils.js";
|
|
6
|
+
import { readDelegationEvents, recordCclawFanInAudit } from "./delegation.js";
|
|
7
|
+
import { effectiveWorktreeExecutionMode } from "./flow-state.js";
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
const WORKTREES_SEG = ".cclaw/worktrees";
|
|
10
|
+
/**
|
|
11
|
+
* Build a unified diff from `baseRef..HEAD` in the lane worktree and apply it
|
|
12
|
+
* to the integration branch in the main repo using three-way merge.
|
|
13
|
+
* On conflict, the integration branch working tree is reset and, when possible,
|
|
14
|
+
* git HEAD is restored to the branch that was checked out before fan-in.
|
|
15
|
+
*/
|
|
16
|
+
export async function fanInLane(options) {
|
|
17
|
+
const { projectRoot, laneId, integrationBranch } = options;
|
|
18
|
+
const workdir = path.join(projectRoot, WORKTREES_SEG, laneId);
|
|
19
|
+
if (!(await exists(workdir))) {
|
|
20
|
+
return { ok: false, event: "abandoned", details: `missing lane workdir ${workdir}` };
|
|
21
|
+
}
|
|
22
|
+
let integrationRef;
|
|
23
|
+
try {
|
|
24
|
+
integrationRef = (await execFileAsync("git", ["rev-parse", "--verify", integrationBranch], {
|
|
25
|
+
cwd: projectRoot
|
|
26
|
+
})).stdout.trim();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return {
|
|
30
|
+
ok: false,
|
|
31
|
+
event: "abandoned",
|
|
32
|
+
details: `integration branch/ref not found: ${integrationBranch}`
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
let baseRef = options.baseRef?.trim() ?? "";
|
|
36
|
+
if (baseRef.length === 0) {
|
|
37
|
+
try {
|
|
38
|
+
baseRef = (await execFileAsync("git", ["merge-base", integrationRef, "HEAD"], { cwd: workdir })).stdout.trim();
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
event: "abandoned",
|
|
44
|
+
details: `cannot merge-base lane ${laneId} with ${integrationBranch}: ${err instanceof Error ? err.message : String(err)}`
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const patchFile = path.join(projectRoot, WORKTREES_SEG, `.fanin-${laneId}.patch`);
|
|
49
|
+
let restoreBranch = null;
|
|
50
|
+
try {
|
|
51
|
+
let curBranch = "";
|
|
52
|
+
try {
|
|
53
|
+
curBranch = (await execFileAsync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: projectRoot })).stdout.trim();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
curBranch = "";
|
|
57
|
+
}
|
|
58
|
+
if (curBranch.length > 0 && curBranch !== integrationBranch && curBranch !== "HEAD") {
|
|
59
|
+
restoreBranch = curBranch;
|
|
60
|
+
}
|
|
61
|
+
const { stdout: diffOut } = await execFileAsync("git", ["diff", `${baseRef}..HEAD`], { cwd: workdir, maxBuffer: 64 * 1024 * 1024 });
|
|
62
|
+
if (diffOut.trim().length === 0) {
|
|
63
|
+
return { ok: true, event: "applied", details: "empty diff; nothing to merge" };
|
|
64
|
+
}
|
|
65
|
+
await fs.writeFile(patchFile, diffOut, "utf8");
|
|
66
|
+
await execFileAsync("git", ["checkout", integrationBranch], { cwd: projectRoot });
|
|
67
|
+
try {
|
|
68
|
+
await execFileAsync("git", ["apply", "--3way", patchFile], { cwd: projectRoot });
|
|
69
|
+
return { ok: true, event: "applied", details: `applied lane ${laneId} onto ${integrationBranch}` };
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
await execFileAsync("git", ["checkout", "--", "."], { cwd: projectRoot }).catch(() => undefined);
|
|
73
|
+
if (restoreBranch) {
|
|
74
|
+
await execFileAsync("git", ["checkout", restoreBranch], { cwd: projectRoot }).catch(() => undefined);
|
|
75
|
+
}
|
|
76
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
event: "conflict",
|
|
80
|
+
details: `git apply --3way reported conflicts for lane ${laneId}: ${msg}`
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
await fs.rm(patchFile, { force: true });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Returns the canonical CLI hint for resolving fan-in conflicts for a slice.
|
|
90
|
+
*/
|
|
91
|
+
export function buildResolveConflictDispatchHint(sliceId) {
|
|
92
|
+
return {
|
|
93
|
+
sliceId,
|
|
94
|
+
command: `slice-implementer --phase resolve-conflict --slice ${sliceId}`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Merge every lane that recorded a completed GREEN `ownerLaneId` for the
|
|
99
|
+
* active run, then emit `cclaw_fanin_*` audit rows. Does nothing in
|
|
100
|
+
* `single-tree` mode or when git is unavailable.
|
|
101
|
+
*/
|
|
102
|
+
export async function runTddDeterministicFanInBeforeAdvance(projectRoot, flowState) {
|
|
103
|
+
if (effectiveWorktreeExecutionMode(flowState) !== "worktree-first") {
|
|
104
|
+
return { ok: true, issues: [] };
|
|
105
|
+
}
|
|
106
|
+
let integrationBranch;
|
|
107
|
+
try {
|
|
108
|
+
integrationBranch = (await execFileAsync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: projectRoot })).stdout.trim();
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
issues: ["worktree fan-in: cannot read current git branch (not a repository or detached HEAD unsupported here)."]
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const { events } = await readDelegationEvents(projectRoot);
|
|
117
|
+
const runId = flowState.activeRunId;
|
|
118
|
+
const laneToSlices = new Map();
|
|
119
|
+
for (const e of events) {
|
|
120
|
+
if (e.runId !== runId || e.stage !== "tdd")
|
|
121
|
+
continue;
|
|
122
|
+
if (e.agent !== "slice-implementer")
|
|
123
|
+
continue;
|
|
124
|
+
if (e.status !== "completed" || e.phase !== "green")
|
|
125
|
+
continue;
|
|
126
|
+
const lane = e.ownerLaneId?.trim();
|
|
127
|
+
const sid = e.sliceId?.trim();
|
|
128
|
+
if (!lane || !sid)
|
|
129
|
+
continue;
|
|
130
|
+
if (!laneToSlices.has(lane))
|
|
131
|
+
laneToSlices.set(lane, new Set());
|
|
132
|
+
laneToSlices.get(lane).add(sid);
|
|
133
|
+
}
|
|
134
|
+
if (laneToSlices.size === 0) {
|
|
135
|
+
return { ok: true, issues: [] };
|
|
136
|
+
}
|
|
137
|
+
const issues = [];
|
|
138
|
+
for (const [laneId, sliceSet] of laneToSlices) {
|
|
139
|
+
const result = await fanInLane({
|
|
140
|
+
projectRoot,
|
|
141
|
+
laneId: laneId,
|
|
142
|
+
integrationBranch,
|
|
143
|
+
baseRef: undefined
|
|
144
|
+
});
|
|
145
|
+
const sliceIds = [...sliceSet].sort();
|
|
146
|
+
if (!result.ok && result.event === "conflict") {
|
|
147
|
+
await recordCclawFanInAudit(projectRoot, {
|
|
148
|
+
kind: "cclaw_fanin_conflict",
|
|
149
|
+
runId,
|
|
150
|
+
laneId,
|
|
151
|
+
sliceIds,
|
|
152
|
+
integrationBranch,
|
|
153
|
+
details: result.details
|
|
154
|
+
});
|
|
155
|
+
issues.push(`${result.details} — ${buildResolveConflictDispatchHint(sliceIds[0] ?? "S-1").command}`);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (!result.ok) {
|
|
159
|
+
await recordCclawFanInAudit(projectRoot, {
|
|
160
|
+
kind: "cclaw_fanin_abandoned",
|
|
161
|
+
runId,
|
|
162
|
+
laneId,
|
|
163
|
+
sliceIds,
|
|
164
|
+
integrationBranch,
|
|
165
|
+
details: result.details
|
|
166
|
+
});
|
|
167
|
+
issues.push(result.details);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
await recordCclawFanInAudit(projectRoot, {
|
|
171
|
+
kind: "cclaw_fanin_applied",
|
|
172
|
+
runId,
|
|
173
|
+
laneId,
|
|
174
|
+
sliceIds,
|
|
175
|
+
integrationBranch,
|
|
176
|
+
details: result.details
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return { ok: issues.length === 0, issues };
|
|
180
|
+
}
|
|
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { resolveArtifactPath } from "../../artifact-paths.js";
|
|
4
4
|
import { appendDelegation, checkMandatoryDelegations, readDelegationEvents, readDelegationLedger } from "../../delegation.js";
|
|
5
|
-
import { verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence } from "../../gate-evidence.js";
|
|
5
|
+
import { verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence, verifyTddWorktreeFanInClosure } from "../../gate-evidence.js";
|
|
6
6
|
import { extractMarkdownSectionBody, learningsParseFailureHumanSummary, parseLearningsSection } from "../../artifact-linter.js";
|
|
7
7
|
import { getAvailableTransitions, getTransitionGuards } from "../../flow-state.js";
|
|
8
8
|
import { appendKnowledge } from "../../knowledge-store.js";
|
|
@@ -13,6 +13,7 @@ import { unique } from "./helpers.js";
|
|
|
13
13
|
import { AUTO_REVIEW_LOOP_GATE_BY_STAGE, reviewLoopArtifactFixHint, reviewLoopEnvelopeExample, validateGateEvidenceShape } from "./review-loop.js";
|
|
14
14
|
import { ensureProactiveDelegationTrace } from "./proactive-delegation-trace.js";
|
|
15
15
|
import { consumeWaiverToken } from "../waiver-grant.js";
|
|
16
|
+
import { runTddDeterministicFanInBeforeAdvance } from "../../integration-fanin.js";
|
|
16
17
|
function resolveSuccessorTransition(stage, track, transitionTargets, satisfiedGuards, selectedTransitionGuards) {
|
|
17
18
|
const natural = transitionTargets[0] ?? null;
|
|
18
19
|
const specialTargets = transitionTargets.filter((target) => target !== natural);
|
|
@@ -602,6 +603,20 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
602
603
|
}
|
|
603
604
|
const satisfiedGuards = new Set([...nextPassed, ...selectedTransitionGuards]);
|
|
604
605
|
const successor = resolveSuccessorTransition(args.stage, flowState.track, transitionTargets, satisfiedGuards, new Set(selectedTransitionGuards));
|
|
606
|
+
if (args.stage === "tdd" && successor !== null && successor !== "tdd") {
|
|
607
|
+
const fanIn = await runTddDeterministicFanInBeforeAdvance(projectRoot, flowState);
|
|
608
|
+
if (!fanIn.ok) {
|
|
609
|
+
io.stderr.write(`cclaw internal advance-stage: deterministic worktree fan-in failed:\n${fanIn.issues
|
|
610
|
+
.map((line) => ` - ${line}`)
|
|
611
|
+
.join("\n")}\n`);
|
|
612
|
+
return 1;
|
|
613
|
+
}
|
|
614
|
+
const closure = await verifyTddWorktreeFanInClosure(projectRoot, flowState);
|
|
615
|
+
if (closure.length > 0) {
|
|
616
|
+
io.stderr.write(`cclaw internal advance-stage: ${closure.join(" | ")}\n`);
|
|
617
|
+
return 1;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
605
620
|
const completedStages = blockedReviewRoute
|
|
606
621
|
? flowState.completedStages.filter((finished) => finished !== args.stage)
|
|
607
622
|
: flowState.completedStages.includes(args.stage)
|
|
@@ -175,7 +175,8 @@ export async function runStartFlow(projectRoot, args, io) {
|
|
|
175
175
|
guardEvidence,
|
|
176
176
|
stageGateCatalog,
|
|
177
177
|
rewinds: current.rewinds,
|
|
178
|
-
staleStages: current.staleStages
|
|
178
|
+
staleStages: current.staleStages,
|
|
179
|
+
worktreeExecutionMode: current.worktreeExecutionMode ?? "worktree-first"
|
|
179
180
|
};
|
|
180
181
|
const validation = await buildValidationReport(projectRoot, nextState);
|
|
181
182
|
const evidenceIssues = completedStageClosureEvidenceIssues(nextState);
|
|
@@ -193,6 +194,7 @@ export async function runStartFlow(projectRoot, args, io) {
|
|
|
193
194
|
if (nextTaskClass !== undefined) {
|
|
194
195
|
nextState = { ...nextState, taskClass: nextTaskClass };
|
|
195
196
|
}
|
|
197
|
+
nextState = { ...nextState, worktreeExecutionMode: "worktree-first" };
|
|
196
198
|
}
|
|
197
199
|
if (args.fromIdeaArtifact) {
|
|
198
200
|
const existingHints = nextState.interactionHints ?? {};
|
|
@@ -14,8 +14,9 @@ import { parseAdvanceStageArgs, parseCancelRunArgs, parseHookArgs, parseRewindAr
|
|
|
14
14
|
import { parseFlowStateRepairArgs, runFlowStateRepair } from "./flow-state-repair.js";
|
|
15
15
|
import { parseWaiverGrantArgs, runWaiverGrant } from "./waiver-grant.js";
|
|
16
16
|
import { FlowStateGuardMismatchError, verifyFlowStateGuard } from "../run-persistence.js";
|
|
17
|
-
import { DelegationTimestampError, DispatchCapError, DispatchDuplicateError, DispatchOverlapError } from "../delegation.js";
|
|
17
|
+
import { DelegationTimestampError, DispatchCapError, DispatchClaimInvalidError, DispatchDuplicateError, DispatchOverlapError } from "../delegation.js";
|
|
18
18
|
import { parsePlanSplitWavesArgs, runPlanSplitWaves } from "./plan-split-waves.js";
|
|
19
|
+
import { runSetWorktreeMode } from "./set-worktree-mode.js";
|
|
19
20
|
/**
|
|
20
21
|
* Subcommands that mutate or consult flow-state.json via the CLI runtime.
|
|
21
22
|
* They all require the sha256 sidecar to match before continuing so a
|
|
@@ -28,12 +29,13 @@ const GUARD_ENFORCED_SUBCOMMANDS = new Set([
|
|
|
28
29
|
"cancel-run",
|
|
29
30
|
"rewind",
|
|
30
31
|
"verify-flow-state-diff",
|
|
31
|
-
"verify-current-state"
|
|
32
|
+
"verify-current-state",
|
|
33
|
+
"set-worktree-mode"
|
|
32
34
|
]);
|
|
33
35
|
export async function runInternalCommand(projectRoot, argv, io) {
|
|
34
36
|
const [subcommand, ...tokens] = argv;
|
|
35
37
|
if (!subcommand) {
|
|
36
|
-
io.stderr.write("cclaw internal requires a subcommand: advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves\n");
|
|
38
|
+
io.stderr.write("cclaw internal requires a subcommand: advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves | set-worktree-mode\n");
|
|
37
39
|
return 1;
|
|
38
40
|
}
|
|
39
41
|
try {
|
|
@@ -88,7 +90,10 @@ export async function runInternalCommand(projectRoot, argv, io) {
|
|
|
88
90
|
if (subcommand === "plan-split-waves") {
|
|
89
91
|
return await runPlanSplitWaves(projectRoot, parsePlanSplitWavesArgs(tokens), io);
|
|
90
92
|
}
|
|
91
|
-
|
|
93
|
+
if (subcommand === "set-worktree-mode") {
|
|
94
|
+
return await runSetWorktreeMode(projectRoot, tokens, io);
|
|
95
|
+
}
|
|
96
|
+
io.stderr.write(`Unknown internal subcommand: ${subcommand}. Expected advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves | set-worktree-mode\n`);
|
|
92
97
|
return 1;
|
|
93
98
|
}
|
|
94
99
|
catch (err) {
|
|
@@ -112,6 +117,10 @@ export async function runInternalCommand(projectRoot, argv, io) {
|
|
|
112
117
|
io.stderr.write(`error: dispatch_cap — ${err.message}\n`);
|
|
113
118
|
return 2;
|
|
114
119
|
}
|
|
120
|
+
if (err instanceof DispatchClaimInvalidError) {
|
|
121
|
+
io.stderr.write(`error: dispatch_claim_invalid — ${err.message}\n`);
|
|
122
|
+
return 2;
|
|
123
|
+
}
|
|
115
124
|
io.stderr.write(`cclaw internal ${subcommand} failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
116
125
|
return 1;
|
|
117
126
|
}
|
|
@@ -29,8 +29,54 @@ export interface PlanSplitWavesArgs {
|
|
|
29
29
|
force: boolean;
|
|
30
30
|
json: boolean;
|
|
31
31
|
}
|
|
32
|
-
export declare const PLAN_SPLIT_DEFAULT_WAVE_SIZE =
|
|
32
|
+
export declare const PLAN_SPLIT_DEFAULT_WAVE_SIZE = 5;
|
|
33
33
|
export declare const PLAN_SPLIT_SMALL_PLAN_THRESHOLD = 50;
|
|
34
|
+
/** v6.13.1 — member line in Parallel Execution Plan or wave-NN.md */
|
|
35
|
+
export interface ParsedParallelWaveMember {
|
|
36
|
+
sliceId: string;
|
|
37
|
+
unitId: string;
|
|
38
|
+
}
|
|
39
|
+
export interface ParsedParallelWave {
|
|
40
|
+
waveId: string;
|
|
41
|
+
members: ParsedParallelWaveMember[];
|
|
42
|
+
}
|
|
43
|
+
export declare class WavePlanDuplicateSliceError extends Error {
|
|
44
|
+
constructor(message: string);
|
|
45
|
+
}
|
|
46
|
+
export declare class WavePlanMergeConflictError extends Error {
|
|
47
|
+
constructor(message: string);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Raw body between parallel execution managed markers (no markers included).
|
|
51
|
+
*/
|
|
52
|
+
export declare function extractParallelExecutionManagedBody(planMarkdown: string): string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Members list after `Members:` in Parallel Execution Plan / wave-NN headers.
|
|
55
|
+
* Supports markdown bold `**Members:**` (colon between Members and closing `**`)
|
|
56
|
+
* and plain `Members:`.
|
|
57
|
+
*/
|
|
58
|
+
export declare function extractMembersListFromLine(trimmedLine: string): string | null;
|
|
59
|
+
/**
|
|
60
|
+
* Parse `## Parallel Execution Plan` managed block for wave headings and Members lines.
|
|
61
|
+
* Malformed member tokens are skipped. Duplicate slice ids in one plan source throw.
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseParallelExecutionPlanWaves(planMarkdown: string): ParsedParallelWave[];
|
|
64
|
+
/**
|
|
65
|
+
* Parse a single wave-NN.md: prefer a `Members:` line in the header; otherwise
|
|
66
|
+
* collect distinct S-N tokens in the first lines (legacy).
|
|
67
|
+
*/
|
|
68
|
+
export declare function parseWavePlanFileBody(body: string, waveId: string): ParsedParallelWave;
|
|
69
|
+
export declare function parseWavePlanDirectory(artifactsDir: string): Promise<ParsedParallelWave[]>;
|
|
70
|
+
/**
|
|
71
|
+
* Merge wave definitions: managed Parallel Execution Plan first, then wave-NN.md.
|
|
72
|
+
* Same slice must map to the same wave id and unit id in both sources or a
|
|
73
|
+
* `WavePlanMergeConflictError` is thrown.
|
|
74
|
+
*/
|
|
75
|
+
export declare function mergeParallelWaveDefinitions(primary: ParsedParallelWave[], secondary: ParsedParallelWave[]): ParsedParallelWave[];
|
|
76
|
+
/**
|
|
77
|
+
* One-line operator hint after sync when a multi-member wave exists.
|
|
78
|
+
*/
|
|
79
|
+
export declare function formatNextParallelWaveSyncHint(merged: ParsedParallelWave[]): string | null;
|
|
34
80
|
export interface ParsedImplementationUnit {
|
|
35
81
|
id: string;
|
|
36
82
|
/**
|
|
@@ -42,6 +88,44 @@ export interface ParsedImplementationUnit {
|
|
|
42
88
|
/** Repo-relative path declarations from the optional `Files:` line. */
|
|
43
89
|
paths: string[];
|
|
44
90
|
}
|
|
91
|
+
export interface ImplementationUnitParallelFields {
|
|
92
|
+
unitId: string;
|
|
93
|
+
dependsOn: string[];
|
|
94
|
+
claimedPaths: string[];
|
|
95
|
+
parallelizable: boolean;
|
|
96
|
+
riskTier: "low" | "standard" | "high";
|
|
97
|
+
lane?: string;
|
|
98
|
+
}
|
|
99
|
+
export interface ParseImplementationUnitParallelOptions {
|
|
100
|
+
/**
|
|
101
|
+
* Legacy continuation (v6.13.0): when the plan predates explicit parallel
|
|
102
|
+
* bullets, units without a `parallelizable:` line default to serial eligibility
|
|
103
|
+
* in the scheduler (`parallelizable: false`).
|
|
104
|
+
*/
|
|
105
|
+
legacyParallelDefaultSerial?: boolean;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Parse v6.13 parallel-metadata bullets from an implementation unit body.
|
|
109
|
+
* Missing keys use conservative defaults (`dependsOn: []`, `parallelizable: true`
|
|
110
|
+
* unless `legacyParallelDefaultSerial` is set).
|
|
111
|
+
*/
|
|
112
|
+
export declare function parseImplementationUnitParallelFields(unit: ParsedImplementationUnit, options?: ParseImplementationUnitParallelOptions): ImplementationUnitParallelFields;
|
|
113
|
+
/**
|
|
114
|
+
* True when the plan has implementation units but any unit is missing v6.13.0
|
|
115
|
+
* `dependsOn` / `claimedPaths` / `parallelizable` / `riskTier` bullets.
|
|
116
|
+
*/
|
|
117
|
+
export declare function planArtifactLacksV613ParallelMetadata(planMarkdown: string): boolean;
|
|
118
|
+
export declare function compareCanonicalUnitIds(a: string, b: string): number;
|
|
119
|
+
/**
|
|
120
|
+
* Group implementation units into waves: topological order, then greedy
|
|
121
|
+
* placement with disjoint `claimedPaths` and `cap` members per wave.
|
|
122
|
+
*/
|
|
123
|
+
export declare function buildConflictAwareWavesFromUnits(units: ParsedImplementationUnit[], cap: number): ParsedImplementationUnit[][];
|
|
124
|
+
export declare function buildParallelExecutionPlanSection(waves: ParsedImplementationUnit[][], cap: number): string;
|
|
125
|
+
/**
|
|
126
|
+
* Replace or append the managed Parallel Execution Plan block.
|
|
127
|
+
*/
|
|
128
|
+
export declare function upsertParallelExecutionPlanSection(planMarkdown: string, managedBlock: string): string;
|
|
45
129
|
/**
|
|
46
130
|
* Parse `## Implementation Units` section into individual unit blocks.
|
|
47
131
|
* Recognizes the canonical heading shape in the TDD-velocity plan template
|