cclaw-cli 6.14.3 → 7.0.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.
Files changed (86) hide show
  1. package/README.md +0 -2
  2. package/dist/artifact-linter/brainstorm.js +1 -1
  3. package/dist/artifact-linter/design.js +2 -2
  4. package/dist/artifact-linter/findings-dedup.js +1 -1
  5. package/dist/artifact-linter/plan.js +6 -6
  6. package/dist/artifact-linter/review-army.d.ts +1 -1
  7. package/dist/artifact-linter/review-army.js +1 -1
  8. package/dist/artifact-linter/scope.js +6 -6
  9. package/dist/artifact-linter/shared.d.ts +37 -73
  10. package/dist/artifact-linter/shared.js +30 -37
  11. package/dist/artifact-linter/spec.js +1 -1
  12. package/dist/artifact-linter/tdd.d.ts +20 -33
  13. package/dist/artifact-linter/tdd.js +89 -617
  14. package/dist/artifact-linter.js +11 -32
  15. package/dist/cli.js +1 -1
  16. package/dist/config.js +1 -1
  17. package/dist/constants.js +1 -1
  18. package/dist/content/core-agents.d.ts +8 -26
  19. package/dist/content/core-agents.js +48 -94
  20. package/dist/content/examples.d.ts +1 -1
  21. package/dist/content/examples.js +4 -4
  22. package/dist/content/hooks.js +62 -149
  23. package/dist/content/idea.js +2 -2
  24. package/dist/content/iron-laws.js +1 -1
  25. package/dist/content/node-hooks.js +2 -2
  26. package/dist/content/skills-elicitation.js +2 -2
  27. package/dist/content/skills.d.ts +4 -6
  28. package/dist/content/skills.js +14 -53
  29. package/dist/content/stage-schema.d.ts +3 -3
  30. package/dist/content/stage-schema.js +8 -46
  31. package/dist/content/stages/brainstorm.js +5 -5
  32. package/dist/content/stages/plan.js +2 -2
  33. package/dist/content/stages/review.js +1 -1
  34. package/dist/content/stages/schema-types.d.ts +1 -1
  35. package/dist/content/stages/scope.js +1 -1
  36. package/dist/content/stages/spec.js +2 -2
  37. package/dist/content/stages/tdd.js +43 -108
  38. package/dist/content/start-command.js +3 -3
  39. package/dist/content/subagent-context-skills.js +5 -3
  40. package/dist/content/subagents.js +13 -74
  41. package/dist/content/templates.d.ts +6 -6
  42. package/dist/content/templates.js +23 -24
  43. package/dist/content/utility-skills.d.ts +1 -1
  44. package/dist/content/utility-skills.js +1 -1
  45. package/dist/delegation.d.ts +79 -139
  46. package/dist/delegation.js +83 -215
  47. package/dist/early-loop.js +1 -1
  48. package/dist/flow-state.d.ts +24 -129
  49. package/dist/flow-state.js +5 -30
  50. package/dist/gate-evidence.d.ts +2 -7
  51. package/dist/gate-evidence.js +2 -59
  52. package/dist/harness-adapters.d.ts +1 -1
  53. package/dist/harness-adapters.js +11 -10
  54. package/dist/install.js +24 -459
  55. package/dist/internal/advance-stage/advance.d.ts +5 -5
  56. package/dist/internal/advance-stage/advance.js +9 -24
  57. package/dist/internal/advance-stage/parsers.d.ts +1 -1
  58. package/dist/internal/advance-stage/review-loop.d.ts +1 -1
  59. package/dist/internal/advance-stage/review-loop.js +3 -3
  60. package/dist/internal/advance-stage/start-flow.js +1 -3
  61. package/dist/internal/advance-stage.js +4 -23
  62. package/dist/internal/cohesion-contract-stub.d.ts +8 -13
  63. package/dist/internal/cohesion-contract-stub.js +18 -24
  64. package/dist/internal/flow-state-repair.d.ts +1 -1
  65. package/dist/internal/plan-split-waves.d.ts +44 -7
  66. package/dist/internal/plan-split-waves.js +113 -12
  67. package/dist/internal/wave-status.d.ts +3 -6
  68. package/dist/internal/wave-status.js +5 -27
  69. package/dist/policy.js +1 -1
  70. package/dist/run-persistence.js +10 -44
  71. package/dist/runtime/run-hook.mjs +3 -3
  72. package/dist/track-heuristics.js +1 -1
  73. package/dist/types.d.ts +2 -2
  74. package/package.json +1 -1
  75. package/dist/integration-fanin.d.ts +0 -44
  76. package/dist/integration-fanin.js +0 -180
  77. package/dist/internal/set-checkpoint-mode.d.ts +0 -16
  78. package/dist/internal/set-checkpoint-mode.js +0 -72
  79. package/dist/internal/set-integration-overseer-mode.d.ts +0 -14
  80. package/dist/internal/set-integration-overseer-mode.js +0 -69
  81. package/dist/internal/set-worktree-mode.d.ts +0 -10
  82. package/dist/internal/set-worktree-mode.js +0 -28
  83. package/dist/worktree-manager.d.ts +0 -50
  84. package/dist/worktree-manager.js +0 -136
  85. package/dist/worktree-types.d.ts +0 -36
  86. package/dist/worktree-types.js +0 -6
@@ -39,11 +39,11 @@ const TERMINAL_PHASES = new Set([
39
39
  "resolve-conflict"
40
40
  ]);
41
41
  /**
42
- * v6.14.2 — deterministic helper for the TDD controller. Reads the
43
- * managed `<!-- parallel-exec-managed-start -->` block from
42
+ * Deterministic helper for the TDD controller. Reads the managed
43
+ * `<!-- parallel-exec-managed-start -->` block from
44
44
  * `<artifacts-dir>/05-plan.md` plus the `wave-plans/` directory and
45
45
  * reports waves + the next dispatchable members so the controller does
46
- * NOT have to page through a 1400-line plan to find the active wave.
46
+ * NOT have to page through a long plan to find the active wave.
47
47
  *
48
48
  * Always exits 0 unless the plan is malformed (no managed block AND no
49
49
  * wave-plans directory), in which case exit 2 with a structured error.
@@ -53,9 +53,6 @@ export async function runWaveStatus(projectRoot, options = {}) {
53
53
  const flowState = await readFlowState(projectRoot).catch(() => null);
54
54
  const activeRunId = flowState?.activeRunId ?? "unknown-run";
55
55
  const currentStage = flowState?.currentStage ?? "tdd";
56
- const tddCutoverSliceId = flowState?.tddCutoverSliceId ?? null;
57
- const tddWorktreeCutoverSliceId = flowState?.tddWorktreeCutoverSliceId ?? null;
58
- const legacyContinuation = flowState?.legacyContinuation === true;
59
56
  let planRaw = "";
60
57
  try {
61
58
  planRaw = await fs.readFile(path.join(artifactsDir, "05-plan.md"), "utf8");
@@ -71,9 +68,6 @@ export async function runWaveStatus(projectRoot, options = {}) {
71
68
  return {
72
69
  activeRunId,
73
70
  currentStage,
74
- tddCutoverSliceId,
75
- tddWorktreeCutoverSliceId,
76
- legacyContinuation,
77
71
  waves: [],
78
72
  nextDispatch: {
79
73
  waveId: null,
@@ -102,9 +96,6 @@ export async function runWaveStatus(projectRoot, options = {}) {
102
96
  return {
103
97
  activeRunId,
104
98
  currentStage,
105
- tddCutoverSliceId,
106
- tddWorktreeCutoverSliceId,
107
- legacyContinuation,
108
99
  waves: [],
109
100
  nextDispatch: {
110
101
  waveId: null,
@@ -120,8 +111,8 @@ export async function runWaveStatus(projectRoot, options = {}) {
120
111
  // Collect closed slice ids from the active run delegation ledger +
121
112
  // events. A slice is "closed" once it carries a terminal phase
122
113
  // (refactor, refactor-deferred, resolve-conflict) OR a phase=green
123
- // event with refactorOutcome (v6.14.0 fold-inline path). Anything else
124
- // we treat as still open so the helper never falsely advances.
114
+ // event with `refactorOutcome` recorded inline. Anything else we treat
115
+ // as still open so the helper never falsely advances.
125
116
  const closedSlices = new Set();
126
117
  let ledgerEntries = [];
127
118
  try {
@@ -194,9 +185,6 @@ export async function runWaveStatus(projectRoot, options = {}) {
194
185
  });
195
186
  const firstOpenWave = waves.find((w) => w.status === "open" || w.status === "partial") ?? null;
196
187
  const warnings = [];
197
- if (tddCutoverSliceId) {
198
- warnings.push("tddCutoverSliceId is a historical boundary; do not use it to find the active slice.");
199
- }
200
188
  if (merged.length === 0 && planRaw.length === 0) {
201
189
  warnings.push("wave_plan_missing: 05-plan.md not found or empty under <artifacts-dir>.");
202
190
  }
@@ -224,9 +212,6 @@ export async function runWaveStatus(projectRoot, options = {}) {
224
212
  return {
225
213
  activeRunId,
226
214
  currentStage,
227
- tddCutoverSliceId,
228
- tddWorktreeCutoverSliceId,
229
- legacyContinuation,
230
215
  waves,
231
216
  nextDispatch,
232
217
  warnings
@@ -236,13 +221,6 @@ function formatHumanReport(report) {
236
221
  const lines = [];
237
222
  lines.push(`activeRunId: ${report.activeRunId}`);
238
223
  lines.push(`currentStage: ${report.currentStage}`);
239
- if (report.tddCutoverSliceId) {
240
- lines.push(`tddCutoverSliceId: ${report.tddCutoverSliceId} (HISTORICAL)`);
241
- }
242
- if (report.tddWorktreeCutoverSliceId) {
243
- lines.push(`tddWorktreeCutoverSliceId: ${report.tddWorktreeCutoverSliceId}`);
244
- }
245
- lines.push(`legacyContinuation: ${report.legacyContinuation}`);
246
224
  lines.push("waves:");
247
225
  if (report.waves.length === 0) {
248
226
  lines.push(" (no waves discovered)");
package/dist/policy.js CHANGED
@@ -71,7 +71,7 @@ export async function policyChecks(projectRoot, options = {}) {
71
71
  { file: runtimeFile("skills/flow-view/SKILL.md"), needle: "## Diff Subcommand", name: "utility_skill:view:diff_section" },
72
72
  { file: runtimeFile("skills/subagent-dev/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:sdd:hard_gate" },
73
73
  { file: runtimeFile("skills/subagent-dev/SKILL.md"), needle: "## Status Contract", name: "utility_skill:sdd:status_contract" },
74
- { file: runtimeFile("skills/subagent-dev/SKILL.md"), needle: "slice-implementer", name: "utility_skill:sdd:implementer_template" },
74
+ { file: runtimeFile("skills/subagent-dev/SKILL.md"), needle: "slice-builder", name: "utility_skill:sdd:implementer_template" },
75
75
  { file: runtimeFile("skills/subagent-dev/SKILL.md"), needle: "## Model & Harness Routing Notes", name: "utility_skill:sdd:routing_notes" },
76
76
  { file: runtimeFile("skills/parallel-dispatch/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:parallel:hard_gate" },
77
77
  { file: runtimeFile("skills/parallel-dispatch/SKILL.md"), needle: "Review Army", name: "utility_skill:parallel:review_army" },
@@ -232,10 +232,10 @@ function coerceRepoSignals(value) {
232
232
  };
233
233
  }
234
234
  /**
235
- * Wave 24 follow-up (v6.1.1) — preserve `flow-state.json#taskClass`
235
+ * preserve `flow-state.json#taskClass`
236
236
  * across read/write round-trips. Before this audit fix the persistence
237
- * layer silently dropped the field, which made the Wave 24 bugfix-skip
238
- * (`mandatoryAgentsFor` short-circuit) and the Wave 25 artifact-validation
237
+ * layer silently dropped the field, which made the bugfix-skip
238
+ * (`mandatoryAgentsFor` short-circuit) and the artifact-validation
239
239
  * demotion both dead in practice: the only entry point that classified
240
240
  * a run was the unit-test harness passing `options.taskClass` directly
241
241
  * to `checkMandatoryDelegations`. The accepted union mirrors
@@ -471,13 +471,10 @@ function coerceFlowState(parsed) {
471
471
  const taskClass = coerceTaskClass(parsed.taskClass);
472
472
  const repoSignals = coerceRepoSignals(parsed.repoSignals);
473
473
  const completedStageMeta = sanitizeCompletedStageMeta(parsed.completedStageMeta);
474
- const tddCutoverSliceId = coerceTddCutoverSliceId(parsed.tddCutoverSliceId);
475
- const tddWorktreeCutoverSliceId = coerceTddCutoverSliceId(parsed.tddWorktreeCutoverSliceId);
476
- const worktreeExecutionMode = coerceWorktreeExecutionMode(parsed.worktreeExecutionMode);
477
- const tddCheckpointMode = coerceTddCheckpointMode(parsed.tddCheckpointMode);
478
- const integrationOverseerMode = coerceIntegrationOverseerMode(parsed.integrationOverseerMode);
479
- const legacyContinuation = typeof parsed.legacyContinuation === "boolean" ? parsed.legacyContinuation : undefined;
480
474
  const tddGreenMinElapsedMs = coerceTddGreenMinElapsedMs(parsed.tddGreenMinElapsedMs);
475
+ const packageVersion = typeof parsed.packageVersion === "string" && parsed.packageVersion.trim().length > 0
476
+ ? parsed.packageVersion.trim()
477
+ : undefined;
481
478
  const state = {
482
479
  schemaVersion: FLOW_STATE_SCHEMA_VERSION,
483
480
  activeRunId,
@@ -490,13 +487,8 @@ function coerceFlowState(parsed) {
490
487
  ...(taskClass !== undefined ? { taskClass } : {}),
491
488
  ...(repoSignals ? { repoSignals } : {}),
492
489
  ...(completedStageMeta ? { completedStageMeta } : {}),
493
- ...(tddCutoverSliceId ? { tddCutoverSliceId } : {}),
494
- ...(tddWorktreeCutoverSliceId ? { tddWorktreeCutoverSliceId } : {}),
495
- ...(worktreeExecutionMode !== undefined ? { worktreeExecutionMode } : {}),
496
- ...(tddCheckpointMode !== undefined ? { tddCheckpointMode } : {}),
497
- ...(integrationOverseerMode !== undefined ? { integrationOverseerMode } : {}),
498
- ...(legacyContinuation !== undefined ? { legacyContinuation } : {}),
499
490
  ...(tddGreenMinElapsedMs !== undefined ? { tddGreenMinElapsedMs } : {}),
491
+ ...(packageVersion ? { packageVersion } : {}),
500
492
  skippedStages: sanitizeSkippedStages(parsed.skippedStages, track),
501
493
  staleStages: sanitizeStaleStages(parsed.staleStages),
502
494
  rewinds: sanitizeRewinds(parsed.rewinds),
@@ -507,33 +499,7 @@ function coerceFlowState(parsed) {
507
499
  return { state };
508
500
  }
509
501
  /**
510
- * v6.12.0 best-effort coercion for `tddCutoverSliceId`. Returns the value
511
- * only when it matches the canonical slice id shape `S-<digits>`; otherwise
512
- * returns null so the field is omitted from the rehydrated state.
513
- */
514
- function coerceTddCutoverSliceId(value) {
515
- if (typeof value !== "string")
516
- return null;
517
- const trimmed = value.trim();
518
- return /^S-\d+$/u.test(trimmed) ? trimmed : null;
519
- }
520
- function coerceWorktreeExecutionMode(value) {
521
- if (value === "single-tree" || value === "worktree-first")
522
- return value;
523
- return undefined;
524
- }
525
- function coerceTddCheckpointMode(value) {
526
- if (value === "per-slice" || value === "global-red")
527
- return value;
528
- return undefined;
529
- }
530
- function coerceIntegrationOverseerMode(value) {
531
- if (value === "conditional" || value === "always")
532
- return value;
533
- return undefined;
534
- }
535
- /**
536
- * v6.14.2 — coerce `tddGreenMinElapsedMs` from disk. Mirrors the
502
+ * coerce `tddGreenMinElapsedMs` from disk. Mirrors the
537
503
  * defensive read in `effectiveTddGreenMinElapsedMs`: numbers ≥ 0 round
538
504
  * down to integers; everything else (NaN, strings, negatives) returns
539
505
  * undefined so the field is omitted from the rehydrated state and the
@@ -757,10 +723,10 @@ export async function writeFlowStateGuarded(projectRoot, state, options = {}) {
757
723
  await writeFlowState(projectRoot, state, options);
758
724
  }
759
725
  /**
760
- * v6.9.0 — backfill missing `completedStageMeta` rows for any stage that
726
+ * backfill missing `completedStageMeta` rows for any stage that
761
727
  * already lives in `completedStages` but has no audit timestamp. Uses the
762
728
  * stage's artifact mtime when available, otherwise the current time. This
763
- * runs as part of `flow-state-repair` so legacy v6.8 flow-state.json files
729
+ * runs as part of `flow-state-repair` so older flow-state.json files
764
730
  * get their meta carried forward without a destructive rewrite.
765
731
  */
766
732
  async function backfillCompletedStageMeta(projectRoot, state) {
@@ -56,7 +56,7 @@ var REQUIRED_GITIGNORE_PATTERNS = [
56
56
  ".opencode/commands/cc-*.md",
57
57
  ".opencode/commands/cc.md",
58
58
  // Codex uses skill-kind shims under `.agents/skills/cc*/` since
59
- // v0.40.0 (renamed from the `cclaw-cc*` layout in v0.39.0/v0.39.1).
59
+ // Codex shim layout (renamed from the older `cclaw-cc*` layout).
60
60
  // `cclaw sync` and `cclaw uninstall` both auto-remove the legacy
61
61
  // `cclaw-cc*` directories.
62
62
  ".agents/skills/cc/SKILL.md",
@@ -676,7 +676,7 @@ async function handleSessionStart(runtime) {
676
676
  );
677
677
  const knowledge = await buildKnowledgeDigest(runtime.root, state.currentStage, knowledgeRaw);
678
678
 
679
- // Wave 21 honest-core: session-start no longer runs background helper
679
+ // honest-core: session-start no longer runs background helper
680
680
  // pipelines or digest caches. It rehydrates flow + knowledge only.
681
681
  const ralphLoopLine = "";
682
682
  const earlyLoopLine = "";
@@ -746,7 +746,7 @@ async function handleSessionStart(runtime) {
746
746
  if (metaContent.length > 0) {
747
747
  parts.push(metaContent);
748
748
  }
749
- // v6.9.0: load iron-laws content into the session-start digest so the
749
+ // load iron-laws content into the session-start digest so the
750
750
  // non-negotiable workflow constraints are visible from the first turn,
751
751
  // not lazily on tool dispatch.
752
752
  if (ironLawsContent.length > 0) {
@@ -51,7 +51,7 @@ const DEFAULT_RULES = {
51
51
  }
52
52
  };
53
53
  // Fixed evaluation order: narrow-to-broad. Overriding this was never wired
54
- // into runtime, so cclaw stopped offering the knob in v0.38.0.
54
+ // into runtime, so cclaw no longer offers that knob.
55
55
  const EVALUATION_ORDER = ["standard", "medium", "quick"];
56
56
  const DEFAULT_FALLBACK = "standard";
57
57
  const ADAPTIVE_ELICITATION_STAGES = new Set(["brainstorm", "scope", "design"]);
package/dist/types.d.ts CHANGED
@@ -34,7 +34,7 @@ export type LanguageRulePack = (typeof LANGUAGE_RULE_PACKS)[number];
34
34
  * - `triggers`: additional substrings that push a prompt toward this track.
35
35
  * - `veto`: substrings that forbid this track even if a trigger matches.
36
36
  *
37
- * Removed in v0.38.0:
37
+ * Removed:
38
38
  * - `patterns` (regex): no runtime ever consumed them; kept authors honest
39
39
  * about what cclaw actually enforces.
40
40
  */
@@ -51,7 +51,7 @@ export interface TrackHeuristicRule {
51
51
  * result — which is why we only ship `triggers`, `veto`, and `fallback`, not
52
52
  * regex patterns or priority overrides.
53
53
  *
54
- * Removed in v0.38.0:
54
+ * Removed:
55
55
  * - `priority`: track evaluation order is always `standard -> medium -> quick`
56
56
  * (narrow-to-broad matching). Overriding it was never wired.
57
57
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "6.14.3",
3
+ "version": "7.0.0",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,44 +0,0 @@
1
- import { type FlowState } from "./flow-state.js";
2
- import type { WorktreeLaneId } from "./worktree-types.js";
3
- export type FanInEventKind = "applied" | "conflict" | "resolved" | "abandoned";
4
- export interface FanInLaneOptions {
5
- projectRoot: string;
6
- /** Lane directory under `.cclaw/worktrees/<laneId>`. */
7
- laneId: WorktreeLaneId;
8
- /** Integration branch to receive the patch (must already exist locally). */
9
- integrationBranch: string;
10
- /**
11
- * Baseline ref for `git diff` in the lane (fork point vs integration).
12
- * When omitted, computed as `git merge-base <integration> HEAD` in the lane.
13
- */
14
- baseRef?: string;
15
- }
16
- export interface FanInLaneResult {
17
- ok: boolean;
18
- event: FanInEventKind;
19
- details: string;
20
- }
21
- /**
22
- * Build a unified diff from `baseRef..HEAD` in the lane worktree and apply it
23
- * to the integration branch in the main repo using three-way merge.
24
- * On conflict, the integration branch working tree is reset and, when possible,
25
- * git HEAD is restored to the branch that was checked out before fan-in.
26
- */
27
- export declare function fanInLane(options: FanInLaneOptions): Promise<FanInLaneResult>;
28
- export interface ResolverDispatchHint {
29
- sliceId: string;
30
- command: string;
31
- }
32
- /**
33
- * Returns the canonical CLI hint for resolving fan-in conflicts for a slice.
34
- */
35
- export declare function buildResolveConflictDispatchHint(sliceId: string): ResolverDispatchHint;
36
- /**
37
- * Merge every lane that recorded a completed GREEN `ownerLaneId` for the
38
- * active run, then emit `cclaw_fanin_*` audit rows. Does nothing in
39
- * `single-tree` mode or when git is unavailable.
40
- */
41
- export declare function runTddDeterministicFanInBeforeAdvance(projectRoot: string, flowState: FlowState): Promise<{
42
- ok: boolean;
43
- issues: string[];
44
- }>;
@@ -1,180 +0,0 @@
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
- }
@@ -1,16 +0,0 @@
1
- import type { Writable } from "node:stream";
2
- export interface SetCheckpointModeArgs {
3
- mode: "per-slice" | "global-red";
4
- reason: string | null;
5
- }
6
- export declare function parseSetCheckpointModeArgs(tokens: string[]): SetCheckpointModeArgs | null;
7
- /**
8
- * v6.14.2 — set `flow-state.json::tddCheckpointMode` without advancing
9
- * the stage DAG. Mirrors `set-worktree-mode`. The `--reason` flag is
10
- * optional but recommended for the audit trail; it is currently passed
11
- * through to the writer subsystem string so operators can grep the
12
- * `.flow-state.guard.json` sidecar.
13
- */
14
- export declare function runSetCheckpointMode(projectRoot: string, tokens: string[], io: {
15
- stderr: Writable;
16
- }): Promise<number>;
@@ -1,72 +0,0 @@
1
- import { readFlowState, writeFlowState } from "../runs.js";
2
- export function parseSetCheckpointModeArgs(tokens) {
3
- let mode = null;
4
- let reason = null;
5
- let positional = null;
6
- for (const token of tokens) {
7
- if (token.startsWith("--mode=")) {
8
- const raw = token.slice("--mode=".length).trim();
9
- if (raw === "per-slice" || raw === "global-red") {
10
- mode = raw;
11
- }
12
- else {
13
- return null;
14
- }
15
- continue;
16
- }
17
- if (token.startsWith("--reason=")) {
18
- const raw = token.slice("--reason=".length).trim();
19
- if (raw.length > 0)
20
- reason = raw;
21
- continue;
22
- }
23
- if (token.startsWith("--")) {
24
- // unknown flag — let the caller surface usage.
25
- return null;
26
- }
27
- if (positional === null) {
28
- positional = token.trim();
29
- continue;
30
- }
31
- return null;
32
- }
33
- if (mode === null && positional !== null) {
34
- if (positional === "per-slice" || positional === "global-red") {
35
- mode = positional;
36
- }
37
- else {
38
- return null;
39
- }
40
- }
41
- if (mode === null)
42
- return null;
43
- return { mode, reason };
44
- }
45
- /**
46
- * v6.14.2 — set `flow-state.json::tddCheckpointMode` without advancing
47
- * the stage DAG. Mirrors `set-worktree-mode`. The `--reason` flag is
48
- * optional but recommended for the audit trail; it is currently passed
49
- * through to the writer subsystem string so operators can grep the
50
- * `.flow-state.guard.json` sidecar.
51
- */
52
- export async function runSetCheckpointMode(projectRoot, tokens, io) {
53
- const parsed = parseSetCheckpointModeArgs(tokens);
54
- if (!parsed) {
55
- io.stderr.write("cclaw internal set-checkpoint-mode: usage: <per-slice|global-red> [--reason=\"<short>\"] " +
56
- "(or --mode=<per-slice|global-red>)\n");
57
- return 1;
58
- }
59
- const state = await readFlowState(projectRoot);
60
- const writerSubsystem = parsed.reason
61
- ? `set-checkpoint-mode:${slugifyReason(parsed.reason)}`
62
- : "set-checkpoint-mode";
63
- await writeFlowState(projectRoot, { ...state, tddCheckpointMode: parsed.mode }, { writerSubsystem });
64
- return 0;
65
- }
66
- function slugifyReason(reason) {
67
- return (reason
68
- .toLowerCase()
69
- .replace(/[^a-z0-9_-]+/gu, "-")
70
- .replace(/^-+|-+$/gu, "")
71
- .slice(0, 60) || "unspecified");
72
- }
@@ -1,14 +0,0 @@
1
- import type { Writable } from "node:stream";
2
- export interface SetIntegrationOverseerModeArgs {
3
- mode: "conditional" | "always";
4
- reason: string | null;
5
- }
6
- export declare function parseSetIntegrationOverseerModeArgs(tokens: string[]): SetIntegrationOverseerModeArgs | null;
7
- /**
8
- * v6.14.2 — set `flow-state.json::integrationOverseerMode` without
9
- * advancing the stage DAG. Mirrors `set-worktree-mode` and
10
- * `set-checkpoint-mode`.
11
- */
12
- export declare function runSetIntegrationOverseerMode(projectRoot: string, tokens: string[], io: {
13
- stderr: Writable;
14
- }): Promise<number>;
@@ -1,69 +0,0 @@
1
- import { readFlowState, writeFlowState } from "../runs.js";
2
- export function parseSetIntegrationOverseerModeArgs(tokens) {
3
- let mode = null;
4
- let reason = null;
5
- let positional = null;
6
- for (const token of tokens) {
7
- if (token.startsWith("--mode=")) {
8
- const raw = token.slice("--mode=".length).trim();
9
- if (raw === "conditional" || raw === "always") {
10
- mode = raw;
11
- }
12
- else {
13
- return null;
14
- }
15
- continue;
16
- }
17
- if (token.startsWith("--reason=")) {
18
- const raw = token.slice("--reason=".length).trim();
19
- if (raw.length > 0)
20
- reason = raw;
21
- continue;
22
- }
23
- if (token.startsWith("--")) {
24
- return null;
25
- }
26
- if (positional === null) {
27
- positional = token.trim();
28
- continue;
29
- }
30
- return null;
31
- }
32
- if (mode === null && positional !== null) {
33
- if (positional === "conditional" || positional === "always") {
34
- mode = positional;
35
- }
36
- else {
37
- return null;
38
- }
39
- }
40
- if (mode === null)
41
- return null;
42
- return { mode, reason };
43
- }
44
- /**
45
- * v6.14.2 — set `flow-state.json::integrationOverseerMode` without
46
- * advancing the stage DAG. Mirrors `set-worktree-mode` and
47
- * `set-checkpoint-mode`.
48
- */
49
- export async function runSetIntegrationOverseerMode(projectRoot, tokens, io) {
50
- const parsed = parseSetIntegrationOverseerModeArgs(tokens);
51
- if (!parsed) {
52
- io.stderr.write("cclaw internal set-integration-overseer-mode: usage: <conditional|always> [--reason=\"<short>\"] " +
53
- "(or --mode=<conditional|always>)\n");
54
- return 1;
55
- }
56
- const state = await readFlowState(projectRoot);
57
- const writerSubsystem = parsed.reason
58
- ? `set-integration-overseer-mode:${slugifyReason(parsed.reason)}`
59
- : "set-integration-overseer-mode";
60
- await writeFlowState(projectRoot, { ...state, integrationOverseerMode: parsed.mode }, { writerSubsystem });
61
- return 0;
62
- }
63
- function slugifyReason(reason) {
64
- return (reason
65
- .toLowerCase()
66
- .replace(/[^a-z0-9_-]+/gu, "-")
67
- .replace(/^-+|-+$/gu, "")
68
- .slice(0, 60) || "unspecified");
69
- }
@@ -1,10 +0,0 @@
1
- import type { Writable } from "node:stream";
2
- export declare function parseSetWorktreeModeArgs(tokens: string[]): {
3
- mode: "single-tree" | "worktree-first";
4
- } | null;
5
- /**
6
- * Set `flow-state.json::worktreeExecutionMode` without advancing the stage DAG.
7
- */
8
- export declare function runSetWorktreeMode(projectRoot: string, tokens: string[], io: {
9
- stderr: Writable;
10
- }): Promise<number>;