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.
- package/README.md +0 -2
- package/dist/artifact-linter/brainstorm.js +1 -1
- package/dist/artifact-linter/design.js +2 -2
- package/dist/artifact-linter/findings-dedup.js +1 -1
- package/dist/artifact-linter/plan.js +6 -6
- package/dist/artifact-linter/review-army.d.ts +1 -1
- package/dist/artifact-linter/review-army.js +1 -1
- package/dist/artifact-linter/scope.js +6 -6
- package/dist/artifact-linter/shared.d.ts +37 -73
- package/dist/artifact-linter/shared.js +30 -37
- package/dist/artifact-linter/spec.js +1 -1
- package/dist/artifact-linter/tdd.d.ts +20 -33
- package/dist/artifact-linter/tdd.js +89 -617
- package/dist/artifact-linter.js +11 -32
- package/dist/cli.js +1 -1
- package/dist/config.js +1 -1
- package/dist/constants.js +1 -1
- package/dist/content/core-agents.d.ts +8 -26
- package/dist/content/core-agents.js +48 -94
- package/dist/content/examples.d.ts +1 -1
- package/dist/content/examples.js +4 -4
- package/dist/content/hooks.js +62 -149
- package/dist/content/idea.js +2 -2
- package/dist/content/iron-laws.js +1 -1
- package/dist/content/node-hooks.js +2 -2
- package/dist/content/skills-elicitation.js +2 -2
- package/dist/content/skills.d.ts +4 -6
- package/dist/content/skills.js +14 -53
- package/dist/content/stage-schema.d.ts +3 -3
- package/dist/content/stage-schema.js +8 -46
- package/dist/content/stages/brainstorm.js +5 -5
- package/dist/content/stages/plan.js +2 -2
- package/dist/content/stages/review.js +1 -1
- package/dist/content/stages/schema-types.d.ts +1 -1
- package/dist/content/stages/scope.js +1 -1
- package/dist/content/stages/spec.js +2 -2
- package/dist/content/stages/tdd.js +43 -108
- package/dist/content/start-command.js +3 -3
- package/dist/content/subagent-context-skills.js +5 -3
- package/dist/content/subagents.js +13 -74
- package/dist/content/templates.d.ts +6 -6
- package/dist/content/templates.js +23 -24
- package/dist/content/utility-skills.d.ts +1 -1
- package/dist/content/utility-skills.js +1 -1
- package/dist/delegation.d.ts +79 -139
- package/dist/delegation.js +83 -215
- package/dist/early-loop.js +1 -1
- package/dist/flow-state.d.ts +24 -129
- package/dist/flow-state.js +5 -30
- package/dist/gate-evidence.d.ts +2 -7
- package/dist/gate-evidence.js +2 -59
- package/dist/harness-adapters.d.ts +1 -1
- package/dist/harness-adapters.js +11 -10
- package/dist/install.js +24 -459
- package/dist/internal/advance-stage/advance.d.ts +5 -5
- package/dist/internal/advance-stage/advance.js +9 -24
- package/dist/internal/advance-stage/parsers.d.ts +1 -1
- package/dist/internal/advance-stage/review-loop.d.ts +1 -1
- package/dist/internal/advance-stage/review-loop.js +3 -3
- package/dist/internal/advance-stage/start-flow.js +1 -3
- package/dist/internal/advance-stage.js +4 -23
- package/dist/internal/cohesion-contract-stub.d.ts +8 -13
- package/dist/internal/cohesion-contract-stub.js +18 -24
- package/dist/internal/flow-state-repair.d.ts +1 -1
- package/dist/internal/plan-split-waves.d.ts +44 -7
- package/dist/internal/plan-split-waves.js +113 -12
- package/dist/internal/wave-status.d.ts +3 -6
- package/dist/internal/wave-status.js +5 -27
- package/dist/policy.js +1 -1
- package/dist/run-persistence.js +10 -44
- package/dist/runtime/run-hook.mjs +3 -3
- package/dist/track-heuristics.js +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
- package/dist/integration-fanin.d.ts +0 -44
- package/dist/integration-fanin.js +0 -180
- package/dist/internal/set-checkpoint-mode.d.ts +0 -16
- package/dist/internal/set-checkpoint-mode.js +0 -72
- package/dist/internal/set-integration-overseer-mode.d.ts +0 -14
- package/dist/internal/set-integration-overseer-mode.js +0 -69
- package/dist/internal/set-worktree-mode.d.ts +0 -10
- package/dist/internal/set-worktree-mode.js +0 -28
- package/dist/worktree-manager.d.ts +0 -50
- package/dist/worktree-manager.js +0 -136
- package/dist/worktree-types.d.ts +0 -36
- 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
|
-
*
|
|
43
|
-
*
|
|
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
|
|
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
|
|
124
|
-
//
|
|
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-
|
|
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" },
|
package/dist/run-persistence.js
CHANGED
|
@@ -232,10 +232,10 @@ function coerceRepoSignals(value) {
|
|
|
232
232
|
};
|
|
233
233
|
}
|
|
234
234
|
/**
|
|
235
|
-
*
|
|
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
|
|
238
|
-
* (`mandatoryAgentsFor` short-circuit) and the
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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) {
|
package/dist/track-heuristics.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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,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>;
|