cclaw-cli 6.13.0 → 6.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@ import { HARNESS_ADAPTERS } from "./harness-adapters.js";
9
9
  import { readFlowState } from "./runs.js";
10
10
  import { mandatoryAgentsFor, stageSchema } from "./content/stage-schema.js";
11
11
  import { effectiveWorktreeExecutionMode } from "./flow-state.js";
12
- import { compareCanonicalUnitIds } from "./internal/plan-split-waves.js";
12
+ import { compareCanonicalUnitIds, mergeParallelWaveDefinitions, parseImplementationUnitParallelFields, parseImplementationUnits, parseParallelExecutionPlanWaves, parseWavePlanDirectory } from "./internal/plan-split-waves.js";
13
13
  const execFileAsync = promisify(execFile);
14
14
  const TERMINAL_DELEGATION_STATUSES = new Set(["completed", "failed", "waived", "stale"]);
15
15
  export const DELEGATION_DISPATCH_SURFACES = [
@@ -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))
@@ -730,6 +746,158 @@ export function selectReadySlices(units, opts) {
730
746
  }
731
747
  return selected;
732
748
  }
749
+ /**
750
+ * v6.13.1 — build scheduler rows from merged parallel wave definitions + plan units.
751
+ */
752
+ export function readySliceUnitsFromMergedWaves(mergedWaves, planMarkdown, options) {
753
+ const units = parseImplementationUnits(planMarkdown);
754
+ const metaByUnit = new Map(units.map((u) => {
755
+ const m = parseImplementationUnitParallelFields(u, options);
756
+ return [m.unitId, m];
757
+ }));
758
+ const sliceSet = new Set();
759
+ for (const w of mergedWaves) {
760
+ for (const m of w.members) {
761
+ sliceSet.add(m.sliceId);
762
+ }
763
+ }
764
+ const out = [];
765
+ for (const sliceId of [...sliceSet].sort((a, b) => a.localeCompare(b))) {
766
+ const member = mergedWaves.flatMap((w) => w.members).find((x) => x.sliceId === sliceId);
767
+ if (!member)
768
+ continue;
769
+ const meta = metaByUnit.get(member.unitId);
770
+ if (!meta) {
771
+ out.push({
772
+ unitId: member.unitId,
773
+ sliceId,
774
+ dependsOn: [],
775
+ claimedPaths: [],
776
+ parallelizable: true
777
+ });
778
+ continue;
779
+ }
780
+ out.push({
781
+ unitId: meta.unitId,
782
+ sliceId,
783
+ dependsOn: meta.dependsOn,
784
+ claimedPaths: meta.claimedPaths,
785
+ parallelizable: meta.parallelizable
786
+ });
787
+ }
788
+ return out;
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
+ }
894
+ /**
895
+ * v6.13.1 — load merged wave plan (Parallel Execution Plan block + wave-plans/) and map to `ReadySliceUnit[]`.
896
+ */
897
+ export async function loadTddReadySlicePool(planMarkdown, artifactsDir, options) {
898
+ const merged = mergeParallelWaveDefinitions(parseParallelExecutionPlanWaves(planMarkdown), await parseWavePlanDirectory(artifactsDir));
899
+ return readySliceUnitsFromMergedWaves(merged, planMarkdown, options);
900
+ }
733
901
  function readMaxParallelOverrideFromEnv() {
734
902
  const raw = process.env.CCLAW_MAX_PARALLEL_SLICE_IMPLEMENTERS;
735
903
  if (typeof raw !== "string" || raw.trim().length === 0)
@@ -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;
@@ -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
  }
package/dist/install.js CHANGED
@@ -25,7 +25,7 @@ import { LANGUAGE_RULE_PACK_DIR, LEGACY_LANGUAGE_RULE_PACK_FOLDERS } from "./con
25
25
  import { RESEARCH_PLAYBOOKS } from "./content/research-playbooks.js";
26
26
  import { SUBAGENT_CONTEXT_SKILLS } from "./content/subagent-context-skills.js";
27
27
  import { CCLAW_AGENTS } from "./content/core-agents.js";
28
- import { createInitialFlowState } from "./flow-state.js";
28
+ import { createInitialFlowState, effectiveWorktreeExecutionMode } from "./flow-state.js";
29
29
  import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
30
30
  import { ManagedResourceSession, setActiveManagedResourceSession } from "./managed-resources.js";
31
31
  import { ensureGitignore, removeGitignorePatterns } from "./gitignore.js";
@@ -34,7 +34,7 @@ import { validateHookDocument } from "./hook-schema.js";
34
34
  import { detectHarnesses } from "./init-detect.js";
35
35
  import { classifyCodexHooksFlag, codexConfigPath, readCodexConfig } from "./codex-feature-flag.js";
36
36
  import { CorruptFlowStateError, ensureRunSystem, readFlowState, writeFlowState } from "./runs.js";
37
- import { PLAN_SPLIT_DEFAULT_WAVE_SIZE, buildParallelExecutionPlanSection, planArtifactLacksV613ParallelMetadata, upsertParallelExecutionPlanSection } from "./internal/plan-split-waves.js";
37
+ import { PLAN_SPLIT_DEFAULT_WAVE_SIZE, buildParallelExecutionPlanSection, formatNextParallelWaveSyncHint, mergeParallelWaveDefinitions, parseParallelExecutionPlanWaves, parseWavePlanDirectory, planArtifactLacksV613ParallelMetadata, upsertParallelExecutionPlanSection } from "./internal/plan-split-waves.js";
38
38
  import { FLOW_STAGES } from "./types.js";
39
39
  const OPENCODE_PLUGIN_REL_PATH = ".opencode/plugins/cclaw-plugin.mjs";
40
40
  const CURSOR_RULE_REL_PATH = ".cursor/rules/cclaw-workflow.mdc";
@@ -1003,6 +1003,90 @@ async function applyPlanLegacyContinuationIfNeeded(projectRoot) {
1003
1003
  // Best-effort: corrupt/missing state is handled elsewhere on sync.
1004
1004
  }
1005
1005
  }
1006
+ /**
1007
+ * v6.14.0 — set stream-style defaults on `cclaw-cli sync` and print a
1008
+ * one-line hint when defaults change. Strategy:
1009
+ *
1010
+ * - When `legacyContinuation: true` and `tddCheckpointMode` is unset, force
1011
+ * `tddCheckpointMode: "global-red"` (preserves hox wave protocol).
1012
+ * - When `legacyContinuation: true` and `integrationOverseerMode` is unset,
1013
+ * force `integrationOverseerMode: "always"` (preserves v6.13 behavior).
1014
+ * - When `legacyContinuation` is NOT true (new / standard projects) and
1015
+ * neither field is set, default to `tddCheckpointMode: "per-slice"`,
1016
+ * `integrationOverseerMode: "conditional"`. Also default
1017
+ * `worktreeExecutionMode: "worktree-first"` if unset.
1018
+ *
1019
+ * Returns a one-line hint string (or `null` if nothing changed) so callers
1020
+ * can print it through the standard sync hint surface.
1021
+ */
1022
+ async function applyV614DefaultsIfNeeded(projectRoot) {
1023
+ // Defensive read — match `applyTddCutoverIfNeeded`'s pattern (raw +
1024
+ // JSON.parse) so corrupt state is left untouched for the downstream
1025
+ // fail-fast check in `materializeRuntime` (which expects to see the
1026
+ // CorruptFlowStateError surfaced via `ensureRunSystem`). Calling
1027
+ // `readFlowState` directly would quarantine the corrupt file and hide
1028
+ // the failure from the caller.
1029
+ const flowStatePath = runtimePath(projectRoot, "state", "flow-state.json");
1030
+ let flowStateRaw;
1031
+ try {
1032
+ flowStateRaw = await fs.readFile(flowStatePath, "utf8");
1033
+ }
1034
+ catch {
1035
+ return null;
1036
+ }
1037
+ let parsed;
1038
+ try {
1039
+ parsed = JSON.parse(flowStateRaw);
1040
+ }
1041
+ catch {
1042
+ return null;
1043
+ }
1044
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1045
+ return null;
1046
+ }
1047
+ const obj = parsed;
1048
+ const updates = {};
1049
+ const summary = [];
1050
+ const tddCheckpointModeSet = obj.tddCheckpointMode === "per-slice" || obj.tddCheckpointMode === "global-red";
1051
+ const integrationOverseerModeSet = obj.integrationOverseerMode === "conditional" || obj.integrationOverseerMode === "always";
1052
+ const worktreeExecutionModeSet = obj.worktreeExecutionMode === "worktree-first" || obj.worktreeExecutionMode === "single-tree";
1053
+ const legacyContinuation = obj.legacyContinuation === true;
1054
+ if (legacyContinuation) {
1055
+ if (!tddCheckpointModeSet) {
1056
+ updates.tddCheckpointMode = "global-red";
1057
+ summary.push("tddCheckpointMode=global-red (legacyContinuation)");
1058
+ }
1059
+ if (!integrationOverseerModeSet) {
1060
+ updates.integrationOverseerMode = "always";
1061
+ summary.push("integrationOverseerMode=always (legacyContinuation)");
1062
+ }
1063
+ }
1064
+ else {
1065
+ if (!tddCheckpointModeSet) {
1066
+ updates.tddCheckpointMode = "per-slice";
1067
+ summary.push("tddCheckpointMode=per-slice");
1068
+ }
1069
+ if (!integrationOverseerModeSet) {
1070
+ updates.integrationOverseerMode = "conditional";
1071
+ summary.push("integrationOverseerMode=conditional");
1072
+ }
1073
+ if (!worktreeExecutionModeSet) {
1074
+ updates.worktreeExecutionMode = "worktree-first";
1075
+ summary.push("worktreeExecutionMode=worktree-first");
1076
+ }
1077
+ }
1078
+ if (summary.length === 0) {
1079
+ return null;
1080
+ }
1081
+ const merged = { ...obj, ...updates };
1082
+ try {
1083
+ await writeFileSafe(flowStatePath, `${JSON.stringify(merged, null, 2)}\n`, { mode: 0o600 });
1084
+ }
1085
+ catch {
1086
+ return null;
1087
+ }
1088
+ return `v6.14.0 stream-style defaults applied: ${summary.join(", ")}. To opt out, edit .cclaw/state/flow-state.json directly or pin the legacy mode (tddCheckpointMode="global-red", integrationOverseerMode="always").`;
1089
+ }
1006
1090
  async function cleanLegacyArtifacts(projectRoot) {
1007
1091
  for (const legacyFolder of DEPRECATED_UTILITY_SKILL_FOLDERS) {
1008
1092
  await removeBestEffort(runtimePath(projectRoot, "skills", legacyFolder), true);
@@ -1135,6 +1219,28 @@ async function assertExpectedHarnessShims(projectRoot, harnesses) {
1135
1219
  }
1136
1220
  }
1137
1221
  }
1222
+ async function maybeLogParallelWaveDispatchHint(projectRoot) {
1223
+ const flowPath = runtimePath(projectRoot, "state", "flow-state.json");
1224
+ if (!(await exists(flowPath)))
1225
+ return;
1226
+ try {
1227
+ const state = await readFlowState(projectRoot);
1228
+ if (effectiveWorktreeExecutionMode(state) !== "worktree-first")
1229
+ return;
1230
+ const planPath = runtimePath(projectRoot, "artifacts", "05-plan.md");
1231
+ if (!(await exists(planPath)))
1232
+ return;
1233
+ const planRaw = await fs.readFile(planPath, "utf8");
1234
+ const merged = mergeParallelWaveDefinitions(parseParallelExecutionPlanWaves(planRaw), await parseWavePlanDirectory(runtimePath(projectRoot, "artifacts")));
1235
+ const hint = formatNextParallelWaveSyncHint(merged);
1236
+ if (hint) {
1237
+ process.stdout.write(`cclaw: ${hint}\n`);
1238
+ }
1239
+ }
1240
+ catch {
1241
+ // best-effort note only
1242
+ }
1243
+ }
1138
1244
  async function materializeRuntime(projectRoot, config, forceStateReset, operation = "sync") {
1139
1245
  await warnStaleInitSentinel(projectRoot, operation);
1140
1246
  const sentinelPath = await writeInitSentinel(projectRoot, operation);
@@ -1156,6 +1262,10 @@ async function materializeRuntime(projectRoot, config, forceStateReset, operatio
1156
1262
  if (operation === "sync" || operation === "upgrade") {
1157
1263
  await applyTddCutoverIfNeeded(projectRoot);
1158
1264
  await applyPlanLegacyContinuationIfNeeded(projectRoot);
1265
+ const v614Hint = await applyV614DefaultsIfNeeded(projectRoot);
1266
+ if (v614Hint) {
1267
+ process.stdout.write(`cclaw: ${v614Hint}\n`);
1268
+ }
1159
1269
  }
1160
1270
  try {
1161
1271
  await ensureRunSystem(projectRoot, { createIfMissing: false });
@@ -1175,6 +1285,9 @@ async function materializeRuntime(projectRoot, config, forceStateReset, operatio
1175
1285
  await assertExpectedHarnessShims(projectRoot, harnesses);
1176
1286
  await writeCursorWorkflowRule(projectRoot, harnesses);
1177
1287
  await ensureGitignore(projectRoot);
1288
+ if (operation === "sync" || operation === "upgrade") {
1289
+ await maybeLogParallelWaveDispatchHint(projectRoot);
1290
+ }
1178
1291
  await managedSession.commit();
1179
1292
  await fs.unlink(sentinelPath).catch(() => undefined);
1180
1293
  }
@@ -31,6 +31,52 @@ export interface PlanSplitWavesArgs {
31
31
  }
32
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
  /**