substrate-ai 0.20.148 → 0.20.150
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter-registry-DPE1OgrZ.js +4 -0
- package/dist/cli/index.js +26 -16
- package/dist/{decisions-DSDqOEN4.js → decisions-7ALaVvR9.js} +1 -1
- package/dist/{dist-CZk5SeSt.js → dist-C6pEdj12.js} +961 -20
- package/dist/{errors-_tbIg6Cv.js → errors-1xcRMTz_.js} +2 -2
- package/dist/{experimenter-RnaEiLUD.js → experimenter-CRt7RrrW.js} +1 -1
- package/dist/{health-Cm0aQCQL.js → health-Bhwd_HX9.js} +3 -3
- package/dist/{health-Aqc5cghP.js → health-CcOYQsgZ.js} +3 -3
- package/dist/{index-CvNLhgKV.d.ts → index-CnV5PcSR.d.ts} +27 -1
- package/dist/index.d.ts +30 -1
- package/dist/index.js +3 -3
- package/dist/{interactive-prompt-BLkX0iE_.js → interactive-prompt-BVTTIU3t.js} +2 -2
- package/dist/{manifest-read-D6NAZgt8.js → manifest-read-8oNapRvD.js} +2 -2
- package/dist/modules/interactive-prompt/index.js +3 -3
- package/dist/{routing-QBUaShFv.js → routing-BCXJlbUS.js} +1 -1
- package/dist/{run-B9DV9sro.js → run-B9lhuahO.js} +63 -832
- package/dist/{run-BX-__EJm.js → run-B_G9KCd-.js} +5 -5
- package/dist/src/modules/recovery-engine/index.d.ts +1 -1
- package/dist/{upgrade-emlrK3YW.js → upgrade-CT6TGMup.js} +2 -2
- package/dist/{upgrade-BEfZJU4K.js → upgrade-DcvPseav.js} +2 -2
- package/dist/{version-manager-impl-_x1n4xZk.js → version-manager-impl-zazA11ph.js} +1 -1
- package/package.json +1 -1
- package/dist/adapter-registry-hLwwiJGM.js +0 -4
- /package/dist/{adapter-registry-DIcrxjH8.js → adapter-registry-DXLMTmfD.js} +0 -0
|
@@ -4,13 +4,16 @@ import { EventEmitter } from "node:events";
|
|
|
4
4
|
import yaml, { dump, load } from "js-yaml";
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { exec, execFile, execSync, spawn, spawnSync } from "node:child_process";
|
|
7
|
+
import * as path$3 from "node:path";
|
|
8
|
+
import * as path$2 from "node:path";
|
|
9
|
+
import * as path$1 from "node:path";
|
|
7
10
|
import path, { dirname as dirname$1, join as join$1, resolve as resolve$1 } from "node:path";
|
|
8
|
-
import os, { freemem, platform } from "node:os";
|
|
11
|
+
import os, { freemem, homedir, platform } from "node:os";
|
|
9
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
10
13
|
import { z } from "zod";
|
|
11
|
-
import { access as access$1, mkdir as mkdir$1, readFile as readFile$1, writeFile as writeFile$1 } from "node:fs/promises";
|
|
14
|
+
import { access as access$1, copyFile, mkdir as mkdir$1, readFile as readFile$1, readdir as readdir$1, rm, writeFile as writeFile$1 } from "node:fs/promises";
|
|
12
15
|
import { readFileSync as readFileSync$1, writeFileSync as writeFileSync$1 } from "fs";
|
|
13
|
-
import { homedir } from "os";
|
|
16
|
+
import { homedir as homedir$1 } from "os";
|
|
14
17
|
import { createServer } from "node:http";
|
|
15
18
|
import { createGunzip, createInflate } from "node:zlib";
|
|
16
19
|
import { promisify } from "node:util";
|
|
@@ -919,6 +922,26 @@ function createStderrLogger(prefix) {
|
|
|
919
922
|
|
|
920
923
|
//#endregion
|
|
921
924
|
//#region packages/core/dist/dispatch/dispatcher-impl.js
|
|
925
|
+
const WORKTREE_COUPLED_TASK_TYPES = new Set([
|
|
926
|
+
"create-story",
|
|
927
|
+
"dev-story",
|
|
928
|
+
"code-review",
|
|
929
|
+
"minor-fixes",
|
|
930
|
+
"major-rework",
|
|
931
|
+
"build-fix",
|
|
932
|
+
"test-plan",
|
|
933
|
+
"test-expansion",
|
|
934
|
+
"probe-author"
|
|
935
|
+
]);
|
|
936
|
+
const GIT_STATE_ENV_KEYS = [
|
|
937
|
+
"PWD",
|
|
938
|
+
"OLDPWD",
|
|
939
|
+
"INIT_CWD",
|
|
940
|
+
"GIT_DIR",
|
|
941
|
+
"GIT_WORK_TREE",
|
|
942
|
+
"GIT_INDEX_FILE",
|
|
943
|
+
"GIT_COMMON_DIR"
|
|
944
|
+
];
|
|
922
945
|
const SHUTDOWN_GRACE_MS = 1e4;
|
|
923
946
|
const SHUTDOWN_MAX_WAIT_MS = 3e4;
|
|
924
947
|
const CHARS_PER_TOKEN$3 = 4;
|
|
@@ -1242,6 +1265,29 @@ var DispatcherImpl = class {
|
|
|
1242
1265
|
});
|
|
1243
1266
|
return;
|
|
1244
1267
|
}
|
|
1268
|
+
if (workingDirectory === void 0 && WORKTREE_COUPLED_TASK_TYPES.has(taskType)) {
|
|
1269
|
+
this._logger.error({
|
|
1270
|
+
id,
|
|
1271
|
+
agent,
|
|
1272
|
+
taskType
|
|
1273
|
+
}, "dispatch rejected: workingDirectory is required for worktree-coupled task types (H4.1)");
|
|
1274
|
+
this._running.delete(id);
|
|
1275
|
+
this._drainQueue();
|
|
1276
|
+
resolve$2({
|
|
1277
|
+
id,
|
|
1278
|
+
status: "failed",
|
|
1279
|
+
exitCode: -1,
|
|
1280
|
+
output: "",
|
|
1281
|
+
parsed: null,
|
|
1282
|
+
parseError: `workingDirectory is required for taskType "${taskType}" — coding-pipeline dispatches must name their worktree explicitly (H4.1 AC2)`,
|
|
1283
|
+
durationMs: 0,
|
|
1284
|
+
tokenEstimate: {
|
|
1285
|
+
input: Math.ceil(prompt.length / CHARS_PER_TOKEN$3),
|
|
1286
|
+
output: 0
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1245
1291
|
const worktreePath = workingDirectory ?? process.cwd();
|
|
1246
1292
|
const resolvedMaxTurns = maxTurns ?? DEFAULT_MAX_TURNS[taskType];
|
|
1247
1293
|
const capabilities = adapter.getCapabilities();
|
|
@@ -1257,7 +1303,8 @@ var DispatcherImpl = class {
|
|
|
1257
1303
|
...storyKey !== void 0 ? { storyKey } : {},
|
|
1258
1304
|
...optimizationDirectives !== void 0 ? { optimizationDirectives } : {},
|
|
1259
1305
|
taskType,
|
|
1260
|
-
dispatchId: id
|
|
1306
|
+
dispatchId: id,
|
|
1307
|
+
...this._config.permissionProfile !== void 0 ? { permissionProfile: this._config.permissionProfile } : {}
|
|
1261
1308
|
});
|
|
1262
1309
|
const baseTimeoutMs = timeout ?? this._config.defaultTimeouts[taskType] ?? DEFAULT_TIMEOUTS[taskType] ?? 3e5;
|
|
1263
1310
|
const timeoutMultiplier = capabilities.timeoutMultiplier ?? 1;
|
|
@@ -1265,8 +1312,33 @@ var DispatcherImpl = class {
|
|
|
1265
1312
|
const env = { ...process.env };
|
|
1266
1313
|
const parentNodeOpts = env["NODE_OPTIONS"] ?? "";
|
|
1267
1314
|
if (!parentNodeOpts.includes("--max-old-space-size")) env["NODE_OPTIONS"] = `${parentNodeOpts} --max-old-space-size=512`.trim();
|
|
1315
|
+
for (const key of GIT_STATE_ENV_KEYS) delete env[key];
|
|
1316
|
+
env["GIT_CEILING_DIRECTORIES"] = dirname$1(worktreePath);
|
|
1268
1317
|
if (cmd.env !== void 0) Object.assign(env, cmd.env);
|
|
1269
1318
|
if (cmd.unsetEnvKeys !== void 0) for (const key of cmd.unsetEnvKeys) delete env[key];
|
|
1319
|
+
if (cmd.executionMode !== void 0 && cmd.executionMode !== "spawn") {
|
|
1320
|
+
this._logger.error({
|
|
1321
|
+
id,
|
|
1322
|
+
agent,
|
|
1323
|
+
executionMode: cmd.executionMode
|
|
1324
|
+
}, "dispatch rejected: execution mode not implemented");
|
|
1325
|
+
this._running.delete(id);
|
|
1326
|
+
this._drainQueue();
|
|
1327
|
+
resolve$2({
|
|
1328
|
+
id,
|
|
1329
|
+
status: "failed",
|
|
1330
|
+
exitCode: -1,
|
|
1331
|
+
output: "",
|
|
1332
|
+
parsed: null,
|
|
1333
|
+
parseError: `executionMode "${cmd.executionMode}" is not implemented — only 'spawn' is available (H4.4 seam)`,
|
|
1334
|
+
durationMs: 0,
|
|
1335
|
+
tokenEstimate: {
|
|
1336
|
+
input: Math.ceil(prompt.length / CHARS_PER_TOKEN$3),
|
|
1337
|
+
output: 0
|
|
1338
|
+
}
|
|
1339
|
+
});
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1270
1342
|
const proc = spawn(cmd.binary, cmd.args, {
|
|
1271
1343
|
cwd: cmd.cwd,
|
|
1272
1344
|
env,
|
|
@@ -6233,7 +6305,10 @@ const TelemetryConfigSchema = z.object({
|
|
|
6233
6305
|
meshUrl: z.string().url().optional(),
|
|
6234
6306
|
projectId: z.string().optional()
|
|
6235
6307
|
}).strict();
|
|
6236
|
-
const WorktreeConfigSchema = z.object({
|
|
6308
|
+
const WorktreeConfigSchema = z.object({
|
|
6309
|
+
copy_files: z.array(z.string()).default([]),
|
|
6310
|
+
base: z.enum(["in-repo", "external"]).optional()
|
|
6311
|
+
}).strict();
|
|
6237
6312
|
/** Current supported config format version */
|
|
6238
6313
|
const CURRENT_CONFIG_FORMAT_VERSION = "1";
|
|
6239
6314
|
/** Current supported task graph version */
|
|
@@ -6252,6 +6327,7 @@ const SubstrateConfigSchema = z.object({
|
|
|
6252
6327
|
telemetry: TelemetryConfigSchema.optional(),
|
|
6253
6328
|
worktree: WorktreeConfigSchema.optional(),
|
|
6254
6329
|
trivialOutputThreshold: z.number().int().nonnegative().optional(),
|
|
6330
|
+
dispatch: z.object({ permission_profile: z.enum(["skip", "scoped"]).optional() }).strict().optional(),
|
|
6255
6331
|
finalization: z.object({
|
|
6256
6332
|
mode: z.enum([
|
|
6257
6333
|
"merge",
|
|
@@ -6278,6 +6354,7 @@ const PartialSubstrateConfigSchema = z.object({
|
|
|
6278
6354
|
telemetry: TelemetryConfigSchema.partial().optional(),
|
|
6279
6355
|
worktree: WorktreeConfigSchema.partial().optional(),
|
|
6280
6356
|
trivialOutputThreshold: z.number().int().nonnegative().optional(),
|
|
6357
|
+
dispatch: z.object({ permission_profile: z.enum(["skip", "scoped"]).optional() }).strict().optional(),
|
|
6281
6358
|
finalization: z.object({
|
|
6282
6359
|
mode: z.enum([
|
|
6283
6360
|
"merge",
|
|
@@ -6618,8 +6695,8 @@ function readEnvOverrides(logger) {
|
|
|
6618
6695
|
/**
|
|
6619
6696
|
* Get a value from a nested object using dot-notation key.
|
|
6620
6697
|
*/
|
|
6621
|
-
function getByPath(obj, path$
|
|
6622
|
-
const parts = path$
|
|
6698
|
+
function getByPath(obj, path$4) {
|
|
6699
|
+
const parts = path$4.split(".");
|
|
6623
6700
|
let cursor = obj;
|
|
6624
6701
|
for (const part of parts) {
|
|
6625
6702
|
if (cursor === null || cursor === void 0 || typeof cursor !== "object") return void 0;
|
|
@@ -6631,8 +6708,8 @@ function getByPath(obj, path$1) {
|
|
|
6631
6708
|
* Return a deep clone of `obj` with `path` set to `value`.
|
|
6632
6709
|
* Creates intermediate objects as needed.
|
|
6633
6710
|
*/
|
|
6634
|
-
function setByPath(obj, path$
|
|
6635
|
-
const parts = path$
|
|
6711
|
+
function setByPath(obj, path$4, value) {
|
|
6712
|
+
const parts = path$4.split(".");
|
|
6636
6713
|
const result = { ...obj };
|
|
6637
6714
|
let cursor = result;
|
|
6638
6715
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
@@ -6654,7 +6731,7 @@ var ConfigSystemImpl = class {
|
|
|
6654
6731
|
_logger;
|
|
6655
6732
|
constructor(options = {}) {
|
|
6656
6733
|
this._projectConfigDir = options.projectConfigDir ? resolve(options.projectConfigDir) : resolve(process.cwd(), ".substrate");
|
|
6657
|
-
this._globalConfigDir = options.globalConfigDir ? resolve(options.globalConfigDir) : resolve(homedir(), ".substrate");
|
|
6734
|
+
this._globalConfigDir = options.globalConfigDir ? resolve(options.globalConfigDir) : resolve(homedir$1(), ".substrate");
|
|
6658
6735
|
this._cliOverrides = options.cliOverrides ?? {};
|
|
6659
6736
|
this._logger = options.logger ?? createStderrLogger("config-system");
|
|
6660
6737
|
}
|
|
@@ -9478,6 +9555,40 @@ function checkAdapterVersionCompat(adapterName, actualVersion, tested) {
|
|
|
9478
9555
|
//#endregion
|
|
9479
9556
|
//#region packages/core/dist/adapters/claude-adapter.js
|
|
9480
9557
|
const execAsync$2 = promisify(exec);
|
|
9558
|
+
/**
|
|
9559
|
+
* H4.3: generate the per-worktree Claude Code settings file for the
|
|
9560
|
+
* 'scoped' permission profile. Written NEXT TO the worktree (its parent
|
|
9561
|
+
* directory — the worktree base, outside any repo after H4.2) so it can
|
|
9562
|
+
* never be committed by commit-first.
|
|
9563
|
+
*
|
|
9564
|
+
* Rules: reads/search/bash allowed everywhere (verification needs the
|
|
9565
|
+
* suite + git); file MUTATION allowed only under the worktree — anything
|
|
9566
|
+
* else falls to "ask", which headless -p mode denies visibly (there is no
|
|
9567
|
+
* interactive prompt to stall on).
|
|
9568
|
+
*/
|
|
9569
|
+
function writeScopedPermissionSettings(worktreePath) {
|
|
9570
|
+
const wt = path$3.resolve(worktreePath);
|
|
9571
|
+
const settings = { permissions: {
|
|
9572
|
+
allow: [
|
|
9573
|
+
"Read",
|
|
9574
|
+
"Glob",
|
|
9575
|
+
"Grep",
|
|
9576
|
+
"Bash",
|
|
9577
|
+
"WebFetch",
|
|
9578
|
+
"WebSearch",
|
|
9579
|
+
"TodoWrite",
|
|
9580
|
+
"Task",
|
|
9581
|
+
`Edit(${wt}/**)`,
|
|
9582
|
+
`Write(${wt}/**)`,
|
|
9583
|
+
`NotebookEdit(${wt}/**)`
|
|
9584
|
+
],
|
|
9585
|
+
deny: []
|
|
9586
|
+
} };
|
|
9587
|
+
const settingsPath = path$3.join(path$3.dirname(wt), `.agent-settings-${path$3.basename(wt)}.json`);
|
|
9588
|
+
mkdirSync(path$3.dirname(settingsPath), { recursive: true });
|
|
9589
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
9590
|
+
return settingsPath;
|
|
9591
|
+
}
|
|
9481
9592
|
/** Default model used when none is specified */
|
|
9482
9593
|
const DEFAULT_MODEL$1 = "claude-sonnet-4-6";
|
|
9483
9594
|
/**
|
|
@@ -9600,9 +9711,12 @@ var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
|
9600
9711
|
const args = [
|
|
9601
9712
|
"-p",
|
|
9602
9713
|
"--model",
|
|
9603
|
-
model
|
|
9604
|
-
"--dangerously-skip-permissions"
|
|
9714
|
+
model
|
|
9605
9715
|
];
|
|
9716
|
+
if (options.permissionProfile === "scoped") {
|
|
9717
|
+
const settingsPath = writeScopedPermissionSettings(options.worktreePath);
|
|
9718
|
+
args.push("--permission-mode", "acceptEdits", "--settings", settingsPath);
|
|
9719
|
+
} else args.push("--dangerously-skip-permissions");
|
|
9606
9720
|
args.push("--output-format", "stream-json", "--verbose");
|
|
9607
9721
|
if (options.additionalFlags && options.additionalFlags.length > 0) args.push(...options.additionalFlags);
|
|
9608
9722
|
const systemPromptParts = [BASE_SYSTEM_PROMPT];
|
|
@@ -10779,6 +10893,833 @@ const CompileResultSchema = z.object({
|
|
|
10779
10893
|
truncated: z.boolean()
|
|
10780
10894
|
});
|
|
10781
10895
|
|
|
10896
|
+
//#endregion
|
|
10897
|
+
//#region packages/core/dist/git/git-utils.js
|
|
10898
|
+
const MIN_GIT_MAJOR = 2;
|
|
10899
|
+
const MIN_GIT_MINOR = 20;
|
|
10900
|
+
/**
|
|
10901
|
+
* Spawn a git subprocess with the given args.
|
|
10902
|
+
*
|
|
10903
|
+
* @param args - Arguments to pass to git (e.g., ['worktree', 'add', ...])
|
|
10904
|
+
* @param options - Optional spawn options (cwd, env)
|
|
10905
|
+
* @returns - Object with stdout, stderr, and exit code
|
|
10906
|
+
*/
|
|
10907
|
+
function spawnGit(args, options) {
|
|
10908
|
+
return new Promise((resolve$2) => {
|
|
10909
|
+
const proc = spawn("git", args, {
|
|
10910
|
+
cwd: options?.cwd,
|
|
10911
|
+
env: options?.env ?? process.env,
|
|
10912
|
+
stdio: [
|
|
10913
|
+
"ignore",
|
|
10914
|
+
"pipe",
|
|
10915
|
+
"pipe"
|
|
10916
|
+
]
|
|
10917
|
+
});
|
|
10918
|
+
let stdout = "";
|
|
10919
|
+
let stderr = "";
|
|
10920
|
+
proc.stdout?.on("data", (chunk) => {
|
|
10921
|
+
stdout += chunk.toString();
|
|
10922
|
+
});
|
|
10923
|
+
proc.stderr?.on("data", (chunk) => {
|
|
10924
|
+
stderr += chunk.toString();
|
|
10925
|
+
});
|
|
10926
|
+
proc.on("close", (code) => {
|
|
10927
|
+
resolve$2({
|
|
10928
|
+
stdout: stdout.trim(),
|
|
10929
|
+
stderr: stderr.trim(),
|
|
10930
|
+
code: code ?? 1
|
|
10931
|
+
});
|
|
10932
|
+
});
|
|
10933
|
+
proc.on("error", (err) => {
|
|
10934
|
+
resolve$2({
|
|
10935
|
+
stdout: "",
|
|
10936
|
+
stderr: err.message,
|
|
10937
|
+
code: 1
|
|
10938
|
+
});
|
|
10939
|
+
});
|
|
10940
|
+
});
|
|
10941
|
+
}
|
|
10942
|
+
/**
|
|
10943
|
+
* Get the installed git version string.
|
|
10944
|
+
*
|
|
10945
|
+
* @returns Version string like "2.42.0"
|
|
10946
|
+
* @throws Error if git is not installed or version cannot be parsed
|
|
10947
|
+
*/
|
|
10948
|
+
async function getGitVersion() {
|
|
10949
|
+
const result = await spawnGit(["--version"]);
|
|
10950
|
+
if (result.code !== 0) throw new Error(`git --version failed: ${result.stderr}`);
|
|
10951
|
+
const match = /git version\s+(\d+\.\d+(?:\.\d+)?)/.exec(result.stdout);
|
|
10952
|
+
if (match === null || match[1] === void 0) throw new Error(`Unable to parse git version from output: "${result.stdout}"`);
|
|
10953
|
+
return match[1];
|
|
10954
|
+
}
|
|
10955
|
+
/**
|
|
10956
|
+
* Parse a git version string into major/minor/patch components.
|
|
10957
|
+
*
|
|
10958
|
+
* @param versionString - Version string like "2.42.0" or "2.20"
|
|
10959
|
+
* @returns - Parsed version components
|
|
10960
|
+
*/
|
|
10961
|
+
function parseGitVersion(versionString) {
|
|
10962
|
+
const parts = versionString.split(".").map(Number);
|
|
10963
|
+
return {
|
|
10964
|
+
major: parts[0] ?? 0,
|
|
10965
|
+
minor: parts[1] ?? 0,
|
|
10966
|
+
patch: parts[2] ?? 0
|
|
10967
|
+
};
|
|
10968
|
+
}
|
|
10969
|
+
/**
|
|
10970
|
+
* Check if the given git version string is >= 2.20.0.
|
|
10971
|
+
*
|
|
10972
|
+
* @param version - Version string like "2.42.0"
|
|
10973
|
+
* @returns - true if version >= 2.20.0
|
|
10974
|
+
*/
|
|
10975
|
+
function isGitVersionSupported(version) {
|
|
10976
|
+
const { major, minor } = parseGitVersion(version);
|
|
10977
|
+
if (major > MIN_GIT_MAJOR) return true;
|
|
10978
|
+
if (major === MIN_GIT_MAJOR && minor >= MIN_GIT_MINOR) return true;
|
|
10979
|
+
return false;
|
|
10980
|
+
}
|
|
10981
|
+
/**
|
|
10982
|
+
* Verify that git is installed and version >= 2.20.
|
|
10983
|
+
*
|
|
10984
|
+
* @throws Error with clear message if git is not installed or too old
|
|
10985
|
+
*/
|
|
10986
|
+
async function verifyGitVersion() {
|
|
10987
|
+
let version;
|
|
10988
|
+
try {
|
|
10989
|
+
version = await getGitVersion();
|
|
10990
|
+
} catch (err) {
|
|
10991
|
+
throw new Error(`git is not installed or could not be executed. Please install git 2.20 or newer. Details: ${String(err)}`);
|
|
10992
|
+
}
|
|
10993
|
+
if (!isGitVersionSupported(version)) {
|
|
10994
|
+
const { major, minor, patch } = parseGitVersion(version);
|
|
10995
|
+
throw new Error(`Git version ${major}.${minor}.${patch} is too old. Substrate requires git 2.20 or newer. Please upgrade git: https://git-scm.com/downloads`);
|
|
10996
|
+
}
|
|
10997
|
+
}
|
|
10998
|
+
/**
|
|
10999
|
+
* Decide whether a registered worktree from a prior dispatch can be reclaimed
|
|
11000
|
+
* for a fresh re-run. Safe only when there is nothing to lose: no uncommitted
|
|
11001
|
+
* changes AND no commits on the branch beyond the base branch. A negative
|
|
11002
|
+
* `commitsAhead` means the ahead-count could not be determined → not safe.
|
|
11003
|
+
*
|
|
11004
|
+
* Pure + exported for testing (the git I/O that produces the inputs lives in
|
|
11005
|
+
* createWorktree).
|
|
11006
|
+
*/
|
|
11007
|
+
function decideWorktreeReclaim(hasUncommittedChanges, commitsAhead, baseBranch) {
|
|
11008
|
+
if (hasUncommittedChanges) return {
|
|
11009
|
+
safe: false,
|
|
11010
|
+
reason: "it has uncommitted changes that are NOT on the branch"
|
|
11011
|
+
};
|
|
11012
|
+
if (commitsAhead > 0) return {
|
|
11013
|
+
safe: false,
|
|
11014
|
+
reason: `its branch has ${String(commitsAhead)} commit(s) beyond ${baseBranch}`
|
|
11015
|
+
};
|
|
11016
|
+
if (commitsAhead < 0) return {
|
|
11017
|
+
safe: false,
|
|
11018
|
+
reason: "its state could not be verified as safe to discard"
|
|
11019
|
+
};
|
|
11020
|
+
return { safe: true };
|
|
11021
|
+
}
|
|
11022
|
+
/**
|
|
11023
|
+
* Decide whether removing a story worktree AND deleting its branch is safe
|
|
11024
|
+
* (H0.3, field findings #17/#19). Unsafe when:
|
|
11025
|
+
* - the worktree has uncommitted changes (removal destroys the only copy), or
|
|
11026
|
+
* - the branch carries commits not reachable from the project's current HEAD
|
|
11027
|
+
* (branch -D destroys them — this is where H0.1's wip checkpoints live), or
|
|
11028
|
+
* - either state could not be determined (negative unmergedCommits).
|
|
11029
|
+
*
|
|
11030
|
+
* Pure + exported for testing (mirrors decideWorktreeReclaim; the git I/O that
|
|
11031
|
+
* produces the inputs lives in inspectWorktreeRemovalSafety).
|
|
11032
|
+
*/
|
|
11033
|
+
function decideWorktreeRemoval(hasUncommittedChanges, uncommittedFiles, unmergedCommits, branchName) {
|
|
11034
|
+
const reasons = [];
|
|
11035
|
+
if (hasUncommittedChanges) {
|
|
11036
|
+
const preview = uncommittedFiles.slice(0, 10).join(", ");
|
|
11037
|
+
const more = uncommittedFiles.length > 10 ? ` (+${String(uncommittedFiles.length - 10)} more)` : "";
|
|
11038
|
+
reasons.push(`the worktree has ${String(uncommittedFiles.length)} uncommitted change(s) that removal would destroy` + (preview.length > 0 ? `: ${preview}${more}` : ""));
|
|
11039
|
+
}
|
|
11040
|
+
if (unmergedCommits > 0) reasons.push(`branch ${branchName} carries ${String(unmergedCommits)} commit(s) not reachable from the current HEAD — deleting the branch would destroy them`);
|
|
11041
|
+
if (unmergedCommits < 0) reasons.push("the branch state could not be verified as safe to discard");
|
|
11042
|
+
return {
|
|
11043
|
+
safe: reasons.length === 0,
|
|
11044
|
+
reasons
|
|
11045
|
+
};
|
|
11046
|
+
}
|
|
11047
|
+
/**
|
|
11048
|
+
* Gather the inputs for `decideWorktreeRemoval` from git. Thin I/O wrapper:
|
|
11049
|
+
* - `git status --porcelain` inside the worktree (skipped when the directory
|
|
11050
|
+
* is missing — nothing on disk to lose)
|
|
11051
|
+
* - `git rev-list --count HEAD..<branch>` in the project root (commits the
|
|
11052
|
+
* branch has that the current checkout does not; 0 when the branch is
|
|
11053
|
+
* merged or absent, -1 when the count could not be determined)
|
|
11054
|
+
*/
|
|
11055
|
+
async function inspectWorktreeRemovalSafety(worktreePath, projectRoot, branchName) {
|
|
11056
|
+
let hasUncommittedChanges = false;
|
|
11057
|
+
let uncommittedFiles = [];
|
|
11058
|
+
const worktreeOnDisk = await access$1(worktreePath).then(() => true).catch(() => false);
|
|
11059
|
+
if (worktreeOnDisk) {
|
|
11060
|
+
const statusResult = await spawnGit(["status", "--porcelain"], { cwd: worktreePath });
|
|
11061
|
+
if (statusResult.code === 0) {
|
|
11062
|
+
uncommittedFiles = statusResult.stdout.split("\n").filter((line) => line.trim().length > 0).map((line) => line.slice(3).trim());
|
|
11063
|
+
hasUncommittedChanges = uncommittedFiles.length > 0;
|
|
11064
|
+
} else return decideWorktreeRemoval(false, [], -1, branchName);
|
|
11065
|
+
}
|
|
11066
|
+
const branchExists = (await spawnGit([
|
|
11067
|
+
"rev-parse",
|
|
11068
|
+
"--verify",
|
|
11069
|
+
branchName
|
|
11070
|
+
], { cwd: projectRoot })).code === 0;
|
|
11071
|
+
let unmergedCommits = 0;
|
|
11072
|
+
if (branchExists) {
|
|
11073
|
+
const aheadResult = await spawnGit([
|
|
11074
|
+
"rev-list",
|
|
11075
|
+
"--count",
|
|
11076
|
+
`HEAD..${branchName}`
|
|
11077
|
+
], { cwd: projectRoot });
|
|
11078
|
+
unmergedCommits = aheadResult.code === 0 ? Number.parseInt(aheadResult.stdout.trim(), 10) || 0 : -1;
|
|
11079
|
+
}
|
|
11080
|
+
return decideWorktreeRemoval(hasUncommittedChanges, uncommittedFiles, unmergedCommits, branchName);
|
|
11081
|
+
}
|
|
11082
|
+
async function createWorktree(projectRoot, taskId, branchName, baseBranch, copyFiles = [], baseDirectory = ".substrate-worktrees") {
|
|
11083
|
+
const worktreePath = path$2.resolve(projectRoot, baseDirectory, taskId);
|
|
11084
|
+
await mkdir$1(path$2.dirname(worktreePath), { recursive: true });
|
|
11085
|
+
const worktreeExists = await access$1(worktreePath).then(() => true).catch((err) => {
|
|
11086
|
+
if (err.code === "ENOENT") return false;
|
|
11087
|
+
throw err;
|
|
11088
|
+
});
|
|
11089
|
+
if (worktreeExists) {
|
|
11090
|
+
const listResult = await spawnGit([
|
|
11091
|
+
"worktree",
|
|
11092
|
+
"list",
|
|
11093
|
+
"--porcelain"
|
|
11094
|
+
], { cwd: projectRoot });
|
|
11095
|
+
const registeredPaths = listResult.stdout.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => line.slice(9).trim());
|
|
11096
|
+
const isRegistered = registeredPaths.includes(worktreePath);
|
|
11097
|
+
if (!isRegistered) await rm(worktreePath, {
|
|
11098
|
+
recursive: true,
|
|
11099
|
+
force: true
|
|
11100
|
+
});
|
|
11101
|
+
else {
|
|
11102
|
+
const statusResult = await spawnGit(["status", "--porcelain"], { cwd: worktreePath });
|
|
11103
|
+
const hasUncommittedChanges = statusResult.code === 0 && statusResult.stdout.trim().length > 0;
|
|
11104
|
+
const aheadResult = await spawnGit([
|
|
11105
|
+
"rev-list",
|
|
11106
|
+
"--count",
|
|
11107
|
+
`${baseBranch}..${branchName}`
|
|
11108
|
+
], { cwd: projectRoot });
|
|
11109
|
+
const commitsAhead = aheadResult.code === 0 ? Number.parseInt(aheadResult.stdout.trim(), 10) || 0 : -1;
|
|
11110
|
+
const decision = decideWorktreeReclaim(hasUncommittedChanges, commitsAhead, baseBranch);
|
|
11111
|
+
if (!decision.safe) throw new Error(`Worktree at ${worktreePath} is already registered (branch: ${branchName}) and ${decision.reason}.\nIt was preserved from a prior dispatch for inspection — inspect before removing.\n\nTo remove and re-dispatch:\n substrate worktrees --cleanup ${taskId}\n\nTo remove all substrate worktrees:\n substrate worktrees --cleanup`);
|
|
11112
|
+
await spawnGit([
|
|
11113
|
+
"worktree",
|
|
11114
|
+
"remove",
|
|
11115
|
+
"--force",
|
|
11116
|
+
worktreePath
|
|
11117
|
+
], { cwd: projectRoot });
|
|
11118
|
+
await spawnGit([
|
|
11119
|
+
"branch",
|
|
11120
|
+
"-D",
|
|
11121
|
+
branchName
|
|
11122
|
+
], { cwd: projectRoot });
|
|
11123
|
+
}
|
|
11124
|
+
}
|
|
11125
|
+
const addResult = await spawnGit([
|
|
11126
|
+
"worktree",
|
|
11127
|
+
"add",
|
|
11128
|
+
worktreePath,
|
|
11129
|
+
"-b",
|
|
11130
|
+
branchName,
|
|
11131
|
+
baseBranch
|
|
11132
|
+
], { cwd: projectRoot });
|
|
11133
|
+
if (addResult.code !== 0) throw new Error(`git worktree add failed for task "${taskId}": ${addResult.stderr || addResult.stdout}`);
|
|
11134
|
+
await copyFilesToWorktree(projectRoot, worktreePath, copyFiles);
|
|
11135
|
+
return { worktreePath };
|
|
11136
|
+
}
|
|
11137
|
+
/**
|
|
11138
|
+
* Copy files from a source directory into a target worktree.
|
|
11139
|
+
*
|
|
11140
|
+
* Skips missing files silently (intentional — config like `[".env",
|
|
11141
|
+
* ".env.local"]` should not blow up if `.env.local` doesn't exist in the
|
|
11142
|
+
* parent checkout). Creates parent directories for nested paths.
|
|
11143
|
+
*
|
|
11144
|
+
* Exported for testability; not part of the public worktree API.
|
|
11145
|
+
*/
|
|
11146
|
+
async function copyFilesToWorktree(sourceRoot, worktreePath, files) {
|
|
11147
|
+
if (files.length === 0) return;
|
|
11148
|
+
for (const relativePath of files) {
|
|
11149
|
+
if (path$2.isAbsolute(relativePath) || relativePath.split(path$2.sep).includes("..")) continue;
|
|
11150
|
+
const sourcePath = path$2.join(sourceRoot, relativePath);
|
|
11151
|
+
const destPath = path$2.join(worktreePath, relativePath);
|
|
11152
|
+
try {
|
|
11153
|
+
await access$1(sourcePath);
|
|
11154
|
+
} catch {
|
|
11155
|
+
continue;
|
|
11156
|
+
}
|
|
11157
|
+
const destDir = path$2.dirname(destPath);
|
|
11158
|
+
if (destDir !== worktreePath) await mkdir$1(destDir, { recursive: true });
|
|
11159
|
+
await copyFile(sourcePath, destPath);
|
|
11160
|
+
}
|
|
11161
|
+
}
|
|
11162
|
+
/**
|
|
11163
|
+
* Remove a git worktree by path.
|
|
11164
|
+
*
|
|
11165
|
+
* Uses `git worktree remove --force` to handle unclean worktrees.
|
|
11166
|
+
*
|
|
11167
|
+
* @param worktreePath - Absolute path to the worktree directory
|
|
11168
|
+
* @param projectRoot - Absolute path to the git repository root
|
|
11169
|
+
* @throws - Error if git command fails
|
|
11170
|
+
*/
|
|
11171
|
+
async function removeWorktree(worktreePath, projectRoot) {
|
|
11172
|
+
const spawnOpts = {};
|
|
11173
|
+
if (projectRoot !== void 0) spawnOpts.cwd = projectRoot;
|
|
11174
|
+
const result = await spawnGit([
|
|
11175
|
+
"worktree",
|
|
11176
|
+
"remove",
|
|
11177
|
+
"--force",
|
|
11178
|
+
worktreePath
|
|
11179
|
+
], spawnOpts);
|
|
11180
|
+
if (result.code !== 0) throw new Error(`git worktree remove failed for "${worktreePath}": ${result.stderr || result.stdout}`);
|
|
11181
|
+
}
|
|
11182
|
+
/**
|
|
11183
|
+
* Delete a git branch using `git branch -D`.
|
|
11184
|
+
*
|
|
11185
|
+
* @param branchName - Branch name to delete (e.g., "substrate/story-abc123")
|
|
11186
|
+
* @param projectRoot - Absolute path to the git repository root
|
|
11187
|
+
* @returns - true if branch was successfully deleted, false otherwise
|
|
11188
|
+
*/
|
|
11189
|
+
async function removeBranch(branchName, projectRoot) {
|
|
11190
|
+
const spawnOpts = {};
|
|
11191
|
+
if (projectRoot !== void 0) spawnOpts.cwd = projectRoot;
|
|
11192
|
+
const result = await spawnGit([
|
|
11193
|
+
"branch",
|
|
11194
|
+
"-D",
|
|
11195
|
+
branchName
|
|
11196
|
+
], spawnOpts);
|
|
11197
|
+
if (result.code !== 0) return false;
|
|
11198
|
+
return true;
|
|
11199
|
+
}
|
|
11200
|
+
/**
|
|
11201
|
+
* Scan the worktrees base directory and return paths of all found worktree directories.
|
|
11202
|
+
*
|
|
11203
|
+
* @param projectRoot - Absolute path to the git repository root
|
|
11204
|
+
* @param baseDirectory - Relative directory name for worktrees (default: '.substrate-worktrees')
|
|
11205
|
+
* @returns - Array of absolute worktree directory paths
|
|
11206
|
+
*/
|
|
11207
|
+
async function getOrphanedWorktrees(projectRoot, baseDirectory = ".substrate-worktrees") {
|
|
11208
|
+
const worktreesDir = path$2.resolve(projectRoot, baseDirectory);
|
|
11209
|
+
try {
|
|
11210
|
+
await access$1(worktreesDir);
|
|
11211
|
+
} catch {
|
|
11212
|
+
return [];
|
|
11213
|
+
}
|
|
11214
|
+
let entries;
|
|
11215
|
+
try {
|
|
11216
|
+
entries = await readdir$1(worktreesDir, { withFileTypes: true });
|
|
11217
|
+
} catch {
|
|
11218
|
+
return [];
|
|
11219
|
+
}
|
|
11220
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => path$2.join(worktreesDir, entry.name));
|
|
11221
|
+
}
|
|
11222
|
+
/**
|
|
11223
|
+
* Simulate a merge using git merge --no-commit --no-ff without committing.
|
|
11224
|
+
*
|
|
11225
|
+
* This runs in the target branch's working directory (the project root or
|
|
11226
|
+
* worktree path). The simulation must be aborted after checking conflicts
|
|
11227
|
+
* via abortMerge().
|
|
11228
|
+
*
|
|
11229
|
+
* @param branchName - The source branch to simulate merging
|
|
11230
|
+
* @param cwd - Working directory (must be on the target branch)
|
|
11231
|
+
* @returns - true if merge would be clean, false if there are conflicts
|
|
11232
|
+
*/
|
|
11233
|
+
async function simulateMerge(branchName, cwd) {
|
|
11234
|
+
const result = await spawnGit([
|
|
11235
|
+
"merge",
|
|
11236
|
+
"--no-commit",
|
|
11237
|
+
"--no-ff",
|
|
11238
|
+
branchName
|
|
11239
|
+
], { cwd });
|
|
11240
|
+
if (result.code === 0) return true;
|
|
11241
|
+
return false;
|
|
11242
|
+
}
|
|
11243
|
+
/**
|
|
11244
|
+
* Abort a merge in progress using git merge --abort.
|
|
11245
|
+
*
|
|
11246
|
+
* Should be called after simulateMerge() to clean up the merge state,
|
|
11247
|
+
* regardless of whether conflicts were found.
|
|
11248
|
+
*
|
|
11249
|
+
* @param cwd - Working directory (same as used for simulateMerge)
|
|
11250
|
+
*/
|
|
11251
|
+
async function abortMerge(cwd) {
|
|
11252
|
+
await spawnGit(["merge", "--abort"], { cwd });
|
|
11253
|
+
}
|
|
11254
|
+
/**
|
|
11255
|
+
* Get a list of files with conflicts during a merge.
|
|
11256
|
+
*
|
|
11257
|
+
* Must be called while a merge is in progress (after simulateMerge() and
|
|
11258
|
+
* before abortMerge()).
|
|
11259
|
+
*
|
|
11260
|
+
* @param cwd - Working directory of the repository
|
|
11261
|
+
* @returns - Array of conflicting file paths
|
|
11262
|
+
*/
|
|
11263
|
+
async function getConflictingFiles(cwd) {
|
|
11264
|
+
const result = await spawnGit([
|
|
11265
|
+
"diff",
|
|
11266
|
+
"--name-only",
|
|
11267
|
+
"--diff-filter=U"
|
|
11268
|
+
], { cwd });
|
|
11269
|
+
if (result.code !== 0) return [];
|
|
11270
|
+
if (result.stdout.trim() === "") return [];
|
|
11271
|
+
return result.stdout.trim().split("\n").filter((f) => f.trim().length > 0);
|
|
11272
|
+
}
|
|
11273
|
+
/**
|
|
11274
|
+
* Perform an actual merge using git merge --no-ff.
|
|
11275
|
+
*
|
|
11276
|
+
* Should only be called after detectConflicts() confirms there are no conflicts.
|
|
11277
|
+
* Creates a merge commit even if fast-forward is possible (--no-ff ensures history).
|
|
11278
|
+
*
|
|
11279
|
+
* @param branchName - The source branch to merge
|
|
11280
|
+
* @param cwd - Working directory (must be on the target branch)
|
|
11281
|
+
* @returns - true if merge succeeded, false otherwise
|
|
11282
|
+
*/
|
|
11283
|
+
async function performMerge(branchName, cwd) {
|
|
11284
|
+
const result = await spawnGit([
|
|
11285
|
+
"merge",
|
|
11286
|
+
"--no-ff",
|
|
11287
|
+
branchName
|
|
11288
|
+
], { cwd });
|
|
11289
|
+
if (result.code !== 0) return false;
|
|
11290
|
+
return true;
|
|
11291
|
+
}
|
|
11292
|
+
/**
|
|
11293
|
+
* Get a list of files changed in the most recent merge.
|
|
11294
|
+
*
|
|
11295
|
+
* Uses git diff --name-only HEAD~1..HEAD to find files in the merge commit.
|
|
11296
|
+
* Falls back to empty array if commit history is insufficient.
|
|
11297
|
+
*
|
|
11298
|
+
* @param cwd - Working directory of the repository
|
|
11299
|
+
* @returns - Array of file paths that were merged
|
|
11300
|
+
*/
|
|
11301
|
+
async function getMergedFiles(cwd) {
|
|
11302
|
+
const result = await spawnGit([
|
|
11303
|
+
"diff",
|
|
11304
|
+
"--name-only",
|
|
11305
|
+
"HEAD~1..HEAD"
|
|
11306
|
+
], { cwd });
|
|
11307
|
+
if (result.code !== 0) {
|
|
11308
|
+
const altResult = await spawnGit([
|
|
11309
|
+
"show",
|
|
11310
|
+
"--name-only",
|
|
11311
|
+
"--format=",
|
|
11312
|
+
"HEAD"
|
|
11313
|
+
], { cwd });
|
|
11314
|
+
if (altResult.code !== 0) return [];
|
|
11315
|
+
return altResult.stdout.trim().split("\n").filter((f) => f.trim().length > 0);
|
|
11316
|
+
}
|
|
11317
|
+
if (result.stdout.trim() === "") return [];
|
|
11318
|
+
return result.stdout.trim().split("\n").filter((f) => f.trim().length > 0);
|
|
11319
|
+
}
|
|
11320
|
+
|
|
11321
|
+
//#endregion
|
|
11322
|
+
//#region packages/core/dist/git/git-worktree-manager-impl.js
|
|
11323
|
+
/**
|
|
11324
|
+
* Branch name prefix for substrate per-story branches.
|
|
11325
|
+
*
|
|
11326
|
+
* Exported as the canonical source of truth so consumers (orchestrator,
|
|
11327
|
+
* integration tests, tooling) can compose branch names without
|
|
11328
|
+
* independently encoding the prefix. v0.20.82 production bug:
|
|
11329
|
+
* `orchestrator-impl.ts:4290` hardcoded `substrate/story-${storyKey}`
|
|
11330
|
+
* while this module created `substrate/task-${taskId}` — the resulting
|
|
11331
|
+
* merge-to-main looked at a non-existent branch. Recurrence prevention:
|
|
11332
|
+
* all branch-name construction MUST import this constant.
|
|
11333
|
+
*/
|
|
11334
|
+
const BRANCH_PREFIX = "substrate/story-";
|
|
11335
|
+
const DEFAULT_WORKTREE_BASE = ".substrate-worktrees";
|
|
11336
|
+
/**
|
|
11337
|
+
* H4.2 (AC2): resolve the worktree base directory for a project.
|
|
11338
|
+
*
|
|
11339
|
+
* 'external' (the NEW DEFAULT) puts worktrees OUTSIDE the parent tree at
|
|
11340
|
+
* `~/.substrate/worktrees/<projectname>-<hash8>/` — an agent inside its
|
|
11341
|
+
* worktree has no parent repo above it to leak into (composes with H4.1's
|
|
11342
|
+
* GIT_CEILING_DIRECTORIES, which points at this base). 'in-repo' retains the
|
|
11343
|
+
* pre-H4.2 `<projectRoot>/.substrate-worktrees/` for tooling that assumed
|
|
11344
|
+
* the old path (MIGRATION NOTE: reconcile-from-disk, editor bookmarks, and
|
|
11345
|
+
* scripts that globbed `.substrate-worktrees/` should use
|
|
11346
|
+
* `substrate worktrees list` or set `worktree.base: in-repo`).
|
|
11347
|
+
*
|
|
11348
|
+
* When `baseOverride` is not given, reads `worktree.base` from
|
|
11349
|
+
* `.substrate/config.yaml` directly (same no-threading pattern as
|
|
11350
|
+
* `resolveEpicsPathOverride`) so EVERY construction site — orchestrator,
|
|
11351
|
+
* `substrate merge`, `substrate worktrees` — resolves identically.
|
|
11352
|
+
*/
|
|
11353
|
+
function resolveWorktreeBaseDirectory(projectRoot, baseOverride) {
|
|
11354
|
+
let mode = baseOverride;
|
|
11355
|
+
if (mode === void 0) try {
|
|
11356
|
+
const raw = readFileSync(path$1.join(projectRoot, ".substrate", "config.yaml"), "utf-8");
|
|
11357
|
+
const parsed = yaml.load(raw);
|
|
11358
|
+
if (parsed?.worktree?.base === "in-repo" || parsed?.worktree?.base === "external") mode = parsed.worktree.base;
|
|
11359
|
+
} catch {}
|
|
11360
|
+
if ((mode ?? "external") === "in-repo") return DEFAULT_WORKTREE_BASE;
|
|
11361
|
+
const hash = createHash("sha256").update(path$1.resolve(projectRoot)).digest("hex").slice(0, 8);
|
|
11362
|
+
return path$1.join(homedir(), ".substrate", "worktrees", `${path$1.basename(projectRoot)}-${hash}`);
|
|
11363
|
+
}
|
|
11364
|
+
var GitWorktreeManagerImpl = class {
|
|
11365
|
+
_eventBus;
|
|
11366
|
+
_projectRoot;
|
|
11367
|
+
_baseDirectory;
|
|
11368
|
+
_db;
|
|
11369
|
+
_logger;
|
|
11370
|
+
/** v0.20.109: files to copy from project root into each new worktree (e.g. `.env`). */
|
|
11371
|
+
_copyFiles;
|
|
11372
|
+
/** Bound listener references for cleanup in shutdown() */
|
|
11373
|
+
_onTaskReady;
|
|
11374
|
+
_onTaskComplete;
|
|
11375
|
+
_onTaskFailed;
|
|
11376
|
+
constructor(eventBus, projectRoot, baseDirectory = DEFAULT_WORKTREE_BASE, db = null, logger, copyFiles = []) {
|
|
11377
|
+
this._eventBus = eventBus;
|
|
11378
|
+
this._projectRoot = projectRoot;
|
|
11379
|
+
this._baseDirectory = baseDirectory;
|
|
11380
|
+
this._db = db;
|
|
11381
|
+
this._logger = logger ?? createStderrLogger("git-worktree-manager");
|
|
11382
|
+
this._copyFiles = copyFiles;
|
|
11383
|
+
this._onTaskReady = ({ taskId }) => {
|
|
11384
|
+
this._handleTaskReady(taskId).catch((err) => {
|
|
11385
|
+
this._logger.error({
|
|
11386
|
+
taskId,
|
|
11387
|
+
err
|
|
11388
|
+
}, "Unhandled error in _handleTaskReady");
|
|
11389
|
+
});
|
|
11390
|
+
};
|
|
11391
|
+
this._onTaskComplete = ({ taskId }) => {
|
|
11392
|
+
this._handleTaskDone(taskId);
|
|
11393
|
+
};
|
|
11394
|
+
this._onTaskFailed = ({ taskId }) => {
|
|
11395
|
+
this._handleTaskDone(taskId);
|
|
11396
|
+
};
|
|
11397
|
+
}
|
|
11398
|
+
async initialize() {
|
|
11399
|
+
this._logger.info({ projectRoot: this._projectRoot }, "GitWorktreeManager.initialize()");
|
|
11400
|
+
await this.verifyGitVersion();
|
|
11401
|
+
const cleaned = await this.cleanupAllWorktrees();
|
|
11402
|
+
if (cleaned > 0) this._logger.info({ cleaned }, "Recovered orphaned worktrees on startup");
|
|
11403
|
+
this._eventBus.on("task:ready", this._onTaskReady);
|
|
11404
|
+
this._eventBus.on("task:complete", this._onTaskComplete);
|
|
11405
|
+
this._eventBus.on("task:failed", this._onTaskFailed);
|
|
11406
|
+
this._logger.info("GitWorktreeManager initialized");
|
|
11407
|
+
}
|
|
11408
|
+
async shutdown() {
|
|
11409
|
+
this._logger.info("GitWorktreeManager.shutdown()");
|
|
11410
|
+
this._eventBus.off("task:ready", this._onTaskReady);
|
|
11411
|
+
this._eventBus.off("task:complete", this._onTaskComplete);
|
|
11412
|
+
this._eventBus.off("task:failed", this._onTaskFailed);
|
|
11413
|
+
await this.cleanupAllWorktrees();
|
|
11414
|
+
this._logger.info("GitWorktreeManager shutdown complete");
|
|
11415
|
+
}
|
|
11416
|
+
async _handleTaskReady(taskId) {
|
|
11417
|
+
this._logger.debug({ taskId }, "task:ready — creating worktree");
|
|
11418
|
+
try {
|
|
11419
|
+
await this.createWorktree(taskId);
|
|
11420
|
+
} catch (err) {
|
|
11421
|
+
this._logger.error({
|
|
11422
|
+
taskId,
|
|
11423
|
+
err
|
|
11424
|
+
}, "Failed to create worktree for task");
|
|
11425
|
+
}
|
|
11426
|
+
}
|
|
11427
|
+
async _handleTaskDone(taskId) {
|
|
11428
|
+
this._logger.debug({ taskId }, "task done — cleaning up worktree");
|
|
11429
|
+
try {
|
|
11430
|
+
await this.cleanupWorktree(taskId);
|
|
11431
|
+
} catch (err) {
|
|
11432
|
+
this._logger.warn({
|
|
11433
|
+
taskId,
|
|
11434
|
+
err
|
|
11435
|
+
}, "Failed to cleanup worktree for task");
|
|
11436
|
+
}
|
|
11437
|
+
}
|
|
11438
|
+
async createWorktree(taskId, baseBranch) {
|
|
11439
|
+
if (!taskId || taskId.trim().length === 0) throw new Error("createWorktree: taskId must be a non-empty string");
|
|
11440
|
+
const resolvedBaseBranch = baseBranch ?? await this._detectCurrentBranch() ?? "main";
|
|
11441
|
+
const branchName = BRANCH_PREFIX + taskId;
|
|
11442
|
+
const worktreePath = this.getWorktreePath(taskId);
|
|
11443
|
+
this._logger.debug({
|
|
11444
|
+
taskId,
|
|
11445
|
+
branchName,
|
|
11446
|
+
worktreePath,
|
|
11447
|
+
baseBranch: resolvedBaseBranch,
|
|
11448
|
+
copyFiles: this._copyFiles
|
|
11449
|
+
}, "createWorktree");
|
|
11450
|
+
const copyFiles = this._copyFiles.includes(".substrate/project-profile.yaml") ? this._copyFiles : [...this._copyFiles, ".substrate/project-profile.yaml"];
|
|
11451
|
+
await createWorktree(this._projectRoot, taskId, branchName, resolvedBaseBranch, copyFiles, this._baseDirectory);
|
|
11452
|
+
const createdAt = new Date();
|
|
11453
|
+
this._eventBus.emit("worktree:created", {
|
|
11454
|
+
taskId,
|
|
11455
|
+
branchName,
|
|
11456
|
+
worktreePath,
|
|
11457
|
+
createdAt
|
|
11458
|
+
});
|
|
11459
|
+
const info = {
|
|
11460
|
+
taskId,
|
|
11461
|
+
branchName,
|
|
11462
|
+
worktreePath,
|
|
11463
|
+
createdAt
|
|
11464
|
+
};
|
|
11465
|
+
this._logger.info({
|
|
11466
|
+
taskId,
|
|
11467
|
+
branchName,
|
|
11468
|
+
worktreePath
|
|
11469
|
+
}, "Worktree created");
|
|
11470
|
+
return info;
|
|
11471
|
+
}
|
|
11472
|
+
async cleanupWorktree(taskId, opts) {
|
|
11473
|
+
const branchName = BRANCH_PREFIX + taskId;
|
|
11474
|
+
const worktreePath = this.getWorktreePath(taskId);
|
|
11475
|
+
this._logger.debug({
|
|
11476
|
+
taskId,
|
|
11477
|
+
branchName,
|
|
11478
|
+
worktreePath,
|
|
11479
|
+
force: opts?.force === true
|
|
11480
|
+
}, "cleanupWorktree");
|
|
11481
|
+
if (opts?.force !== true) {
|
|
11482
|
+
const decision = await inspectWorktreeRemovalSafety(worktreePath, this._projectRoot, branchName);
|
|
11483
|
+
const blockingReasons = opts?.keepBranch === true ? decision.reasons.filter((r) => r.includes("uncommitted")) : decision.reasons;
|
|
11484
|
+
if (blockingReasons.length > 0) throw new Error(`refusing to clean up worktree for "${taskId}": ${blockingReasons.join("; ")}.\nInspect first (git -C ${worktreePath} status; git log ${branchName} --oneline) or re-run with --force to discard.`);
|
|
11485
|
+
}
|
|
11486
|
+
let worktreeExists = false;
|
|
11487
|
+
try {
|
|
11488
|
+
await access$1(worktreePath);
|
|
11489
|
+
worktreeExists = true;
|
|
11490
|
+
} catch {
|
|
11491
|
+
this._logger.debug({
|
|
11492
|
+
taskId,
|
|
11493
|
+
worktreePath
|
|
11494
|
+
}, "cleanupWorktree: worktree does not exist, skipping removal");
|
|
11495
|
+
}
|
|
11496
|
+
if (worktreeExists) try {
|
|
11497
|
+
await removeWorktree(worktreePath, this._projectRoot);
|
|
11498
|
+
} catch (err) {
|
|
11499
|
+
this._logger.warn({
|
|
11500
|
+
taskId,
|
|
11501
|
+
worktreePath,
|
|
11502
|
+
err
|
|
11503
|
+
}, "removeWorktree failed during cleanup");
|
|
11504
|
+
}
|
|
11505
|
+
if (opts?.keepBranch !== true) try {
|
|
11506
|
+
await removeBranch(branchName, this._projectRoot);
|
|
11507
|
+
} catch (err) {
|
|
11508
|
+
this._logger.warn({
|
|
11509
|
+
taskId,
|
|
11510
|
+
branchName,
|
|
11511
|
+
err
|
|
11512
|
+
}, "removeBranch failed during cleanup");
|
|
11513
|
+
}
|
|
11514
|
+
this._eventBus.emit("worktree:removed", {
|
|
11515
|
+
taskId,
|
|
11516
|
+
branchName
|
|
11517
|
+
});
|
|
11518
|
+
this._logger.info({
|
|
11519
|
+
taskId,
|
|
11520
|
+
branchName
|
|
11521
|
+
}, "Worktree cleaned up");
|
|
11522
|
+
}
|
|
11523
|
+
async cleanupAllWorktrees(opts) {
|
|
11524
|
+
this._logger.debug({
|
|
11525
|
+
projectRoot: this._projectRoot,
|
|
11526
|
+
force: opts?.force === true
|
|
11527
|
+
}, "cleanupAllWorktrees");
|
|
11528
|
+
const orphanedPaths = await getOrphanedWorktrees(this._projectRoot, this._baseDirectory);
|
|
11529
|
+
let cleaned = 0;
|
|
11530
|
+
for (const worktreePath of orphanedPaths) {
|
|
11531
|
+
const taskId = path$1.basename(worktreePath);
|
|
11532
|
+
const branchGuardName = BRANCH_PREFIX + taskId;
|
|
11533
|
+
const decision = opts?.force === true ? {
|
|
11534
|
+
safe: true,
|
|
11535
|
+
reasons: []
|
|
11536
|
+
} : await inspectWorktreeRemovalSafety(worktreePath, this._projectRoot, branchGuardName);
|
|
11537
|
+
if (!decision.safe) {
|
|
11538
|
+
this._logger.warn({
|
|
11539
|
+
taskId,
|
|
11540
|
+
worktreePath,
|
|
11541
|
+
reasons: decision.reasons
|
|
11542
|
+
}, "cleanupAllWorktrees: preserving worktree — removal would destroy work (use `substrate worktrees cleanup --force` to discard)");
|
|
11543
|
+
continue;
|
|
11544
|
+
}
|
|
11545
|
+
let worktreeRemoved = false;
|
|
11546
|
+
try {
|
|
11547
|
+
await removeWorktree(worktreePath, this._projectRoot);
|
|
11548
|
+
worktreeRemoved = true;
|
|
11549
|
+
this._logger.debug({
|
|
11550
|
+
taskId,
|
|
11551
|
+
worktreePath
|
|
11552
|
+
}, "cleanupAllWorktrees: removed orphaned worktree");
|
|
11553
|
+
} catch (err) {
|
|
11554
|
+
this._logger.warn({
|
|
11555
|
+
taskId,
|
|
11556
|
+
worktreePath,
|
|
11557
|
+
err
|
|
11558
|
+
}, "cleanupAllWorktrees: failed to remove worktree");
|
|
11559
|
+
}
|
|
11560
|
+
const branchName = BRANCH_PREFIX + taskId;
|
|
11561
|
+
try {
|
|
11562
|
+
const branchRemoved = await removeBranch(branchName, this._projectRoot);
|
|
11563
|
+
if (branchRemoved) this._logger.debug({
|
|
11564
|
+
taskId,
|
|
11565
|
+
branchName
|
|
11566
|
+
}, "cleanupAllWorktrees: removed orphaned branch");
|
|
11567
|
+
} catch (err) {
|
|
11568
|
+
this._logger.warn({
|
|
11569
|
+
taskId,
|
|
11570
|
+
branchName,
|
|
11571
|
+
err
|
|
11572
|
+
}, "cleanupAllWorktrees: failed to remove branch");
|
|
11573
|
+
}
|
|
11574
|
+
if (worktreeRemoved) cleaned++;
|
|
11575
|
+
}
|
|
11576
|
+
if (cleaned > 0) this._logger.info({ cleaned }, "cleanupAllWorktrees: recovered orphaned worktrees");
|
|
11577
|
+
return cleaned;
|
|
11578
|
+
}
|
|
11579
|
+
async detectConflicts(taskId, targetBranch = "main") {
|
|
11580
|
+
if (!taskId || taskId.trim().length === 0) throw new Error("detectConflicts: taskId must be a non-empty string");
|
|
11581
|
+
const branchName = BRANCH_PREFIX + taskId;
|
|
11582
|
+
const worktreePath = this.getWorktreePath(taskId);
|
|
11583
|
+
this._logger.debug({
|
|
11584
|
+
taskId,
|
|
11585
|
+
branchName,
|
|
11586
|
+
targetBranch
|
|
11587
|
+
}, "detectConflicts");
|
|
11588
|
+
try {
|
|
11589
|
+
await access$1(worktreePath);
|
|
11590
|
+
} catch {
|
|
11591
|
+
throw new Error(`detectConflicts: Worktree for task "${taskId}" not found at "${worktreePath}". The worktree may have already been cleaned up.`);
|
|
11592
|
+
}
|
|
11593
|
+
const mergeClean = await simulateMerge(branchName, this._projectRoot);
|
|
11594
|
+
let conflictingFiles = [];
|
|
11595
|
+
try {
|
|
11596
|
+
if (!mergeClean) conflictingFiles = await getConflictingFiles(this._projectRoot);
|
|
11597
|
+
} finally {
|
|
11598
|
+
await abortMerge(this._projectRoot);
|
|
11599
|
+
}
|
|
11600
|
+
const report = {
|
|
11601
|
+
hasConflicts: !mergeClean || conflictingFiles.length > 0,
|
|
11602
|
+
conflictingFiles,
|
|
11603
|
+
taskId,
|
|
11604
|
+
targetBranch
|
|
11605
|
+
};
|
|
11606
|
+
if (report.hasConflicts) this._eventBus.emit("worktree:conflict", {
|
|
11607
|
+
taskId,
|
|
11608
|
+
branch: branchName,
|
|
11609
|
+
conflictingFiles: report.conflictingFiles
|
|
11610
|
+
});
|
|
11611
|
+
this._logger.info({
|
|
11612
|
+
taskId,
|
|
11613
|
+
hasConflicts: report.hasConflicts,
|
|
11614
|
+
conflictCount: conflictingFiles.length
|
|
11615
|
+
}, "Conflict detection complete");
|
|
11616
|
+
return report;
|
|
11617
|
+
}
|
|
11618
|
+
async mergeWorktree(taskId, targetBranch = "main") {
|
|
11619
|
+
if (!taskId || taskId.trim().length === 0) throw new Error("mergeWorktree: taskId must be a non-empty string");
|
|
11620
|
+
const branchName = BRANCH_PREFIX + taskId;
|
|
11621
|
+
this._logger.debug({
|
|
11622
|
+
taskId,
|
|
11623
|
+
branchName,
|
|
11624
|
+
targetBranch
|
|
11625
|
+
}, "mergeWorktree");
|
|
11626
|
+
const conflictReport = await this.detectConflicts(taskId, targetBranch);
|
|
11627
|
+
if (conflictReport.hasConflicts) {
|
|
11628
|
+
this._logger.info({
|
|
11629
|
+
taskId,
|
|
11630
|
+
conflictCount: conflictReport.conflictingFiles.length
|
|
11631
|
+
}, "Merge skipped due to conflicts");
|
|
11632
|
+
return {
|
|
11633
|
+
success: false,
|
|
11634
|
+
mergedFiles: [],
|
|
11635
|
+
conflicts: conflictReport
|
|
11636
|
+
};
|
|
11637
|
+
}
|
|
11638
|
+
const mergeSuccess = await performMerge(branchName, this._projectRoot);
|
|
11639
|
+
if (!mergeSuccess) throw new Error(`mergeWorktree: git merge --no-ff failed for task "${taskId}" branch "${branchName}"`);
|
|
11640
|
+
const mergedFiles = await getMergedFiles(this._projectRoot);
|
|
11641
|
+
this._eventBus.emit("worktree:merged", {
|
|
11642
|
+
taskId,
|
|
11643
|
+
branch: branchName,
|
|
11644
|
+
mergedFiles
|
|
11645
|
+
});
|
|
11646
|
+
const result = {
|
|
11647
|
+
success: true,
|
|
11648
|
+
mergedFiles
|
|
11649
|
+
};
|
|
11650
|
+
this._logger.info({
|
|
11651
|
+
taskId,
|
|
11652
|
+
branchName,
|
|
11653
|
+
mergedFileCount: mergedFiles.length
|
|
11654
|
+
}, "Worktree merged successfully");
|
|
11655
|
+
return result;
|
|
11656
|
+
}
|
|
11657
|
+
async listWorktrees() {
|
|
11658
|
+
this._logger.debug({
|
|
11659
|
+
projectRoot: this._projectRoot,
|
|
11660
|
+
baseDirectory: this._baseDirectory
|
|
11661
|
+
}, "listWorktrees");
|
|
11662
|
+
const worktreePaths = await getOrphanedWorktrees(this._projectRoot, this._baseDirectory);
|
|
11663
|
+
const results = [];
|
|
11664
|
+
for (const worktreePath of worktreePaths) {
|
|
11665
|
+
const taskId = path$1.basename(worktreePath);
|
|
11666
|
+
const branchName = BRANCH_PREFIX + taskId;
|
|
11667
|
+
let createdAt;
|
|
11668
|
+
try {
|
|
11669
|
+
const { stat: stat$2 } = await import("node:fs/promises");
|
|
11670
|
+
const stats = await stat$2(worktreePath);
|
|
11671
|
+
createdAt = stats.birthtime ?? stats.ctime;
|
|
11672
|
+
} catch {
|
|
11673
|
+
createdAt = new Date();
|
|
11674
|
+
}
|
|
11675
|
+
results.push({
|
|
11676
|
+
taskId,
|
|
11677
|
+
branchName,
|
|
11678
|
+
worktreePath,
|
|
11679
|
+
createdAt
|
|
11680
|
+
});
|
|
11681
|
+
}
|
|
11682
|
+
this._logger.debug({ count: results.length }, "listWorktrees: found worktrees");
|
|
11683
|
+
return results;
|
|
11684
|
+
}
|
|
11685
|
+
/** H4.2: current branch of the parent repo (undefined when detached/unreadable). */
|
|
11686
|
+
async _detectCurrentBranch() {
|
|
11687
|
+
try {
|
|
11688
|
+
const result = await spawnGit([
|
|
11689
|
+
"rev-parse",
|
|
11690
|
+
"--abbrev-ref",
|
|
11691
|
+
"HEAD"
|
|
11692
|
+
], { cwd: this._projectRoot });
|
|
11693
|
+
const name = result.code === 0 ? result.stdout.trim() : "";
|
|
11694
|
+
return name !== "" && name !== "HEAD" ? name : void 0;
|
|
11695
|
+
} catch {
|
|
11696
|
+
return void 0;
|
|
11697
|
+
}
|
|
11698
|
+
}
|
|
11699
|
+
getWorktreePath(taskId) {
|
|
11700
|
+
return path$1.resolve(this._projectRoot, this._baseDirectory, taskId);
|
|
11701
|
+
}
|
|
11702
|
+
async verifyGitVersion() {
|
|
11703
|
+
try {
|
|
11704
|
+
await verifyGitVersion();
|
|
11705
|
+
} catch (err) {
|
|
11706
|
+
throw new Error(`GitWorktreeManager: git version check failed: ${String(err)}`);
|
|
11707
|
+
}
|
|
11708
|
+
}
|
|
11709
|
+
};
|
|
11710
|
+
function createGitWorktreeManager(options) {
|
|
11711
|
+
return new GitWorktreeManagerImpl(
|
|
11712
|
+
options.eventBus,
|
|
11713
|
+
options.projectRoot,
|
|
11714
|
+
// H4.2: single resolution point — explicit option, else `worktree.base`
|
|
11715
|
+
// from .substrate/config.yaml, else the external default.
|
|
11716
|
+
options.baseDirectory ?? resolveWorktreeBaseDirectory(options.projectRoot),
|
|
11717
|
+
options.db ?? null,
|
|
11718
|
+
options.logger,
|
|
11719
|
+
options.copyFiles
|
|
11720
|
+
);
|
|
11721
|
+
}
|
|
11722
|
+
|
|
10782
11723
|
//#endregion
|
|
10783
11724
|
//#region packages/core/dist/version-manager/update-checker.js
|
|
10784
11725
|
/**
|
|
@@ -11097,9 +12038,9 @@ var VersionManagerImpl = class {
|
|
|
11097
12038
|
try {
|
|
11098
12039
|
const _require = createRequire(import.meta.url);
|
|
11099
12040
|
const fs = _require("fs");
|
|
11100
|
-
const path$
|
|
12041
|
+
const path$4 = _require("path");
|
|
11101
12042
|
const yaml$1 = _require("js-yaml");
|
|
11102
|
-
const configPath = path$
|
|
12043
|
+
const configPath = path$4.join(process.cwd(), ".substrate", "config.yaml");
|
|
11103
12044
|
try {
|
|
11104
12045
|
const raw = fs.readFileSync(configPath, "utf-8");
|
|
11105
12046
|
configObj = yaml$1.load(raw) ?? {};
|
|
@@ -11341,7 +12282,7 @@ function createExperimenter(config, deps) {
|
|
|
11341
12282
|
], { cwd: config.projectRoot });
|
|
11342
12283
|
return result.stdout.trim();
|
|
11343
12284
|
}
|
|
11344
|
-
async function createWorktree(worktreePath, branchName) {
|
|
12285
|
+
async function createWorktree$1(worktreePath, branchName) {
|
|
11345
12286
|
const result = await git([
|
|
11346
12287
|
"worktree",
|
|
11347
12288
|
"add",
|
|
@@ -11351,7 +12292,7 @@ function createExperimenter(config, deps) {
|
|
|
11351
12292
|
], { cwd: config.projectRoot });
|
|
11352
12293
|
if (result.code !== 0) throw new Error(`Failed to create worktree ${worktreePath}: ${result.stderr}`);
|
|
11353
12294
|
}
|
|
11354
|
-
async function removeWorktree(worktreePath) {
|
|
12295
|
+
async function removeWorktree$1(worktreePath) {
|
|
11355
12296
|
const result = await git([
|
|
11356
12297
|
"worktree",
|
|
11357
12298
|
"remove",
|
|
@@ -11461,7 +12402,7 @@ function createExperimenter(config, deps) {
|
|
|
11461
12402
|
try {
|
|
11462
12403
|
currentPhase = "BRANCHING";
|
|
11463
12404
|
log(`[experimenter] Creating worktree: ${worktreePath} on branch ${branchName}`);
|
|
11464
|
-
await createWorktree(worktreePath, branchName);
|
|
12405
|
+
await createWorktree$1(worktreePath, branchName);
|
|
11465
12406
|
worktreeCreated = true;
|
|
11466
12407
|
currentPhase = "MODIFYING";
|
|
11467
12408
|
const promptFile = resolvePromptFile(rec, worktreePath, config.pack);
|
|
@@ -11504,7 +12445,7 @@ function createExperimenter(config, deps) {
|
|
|
11504
12445
|
log(`[experimenter] Error in phase ${currentPhase}: ${caughtError}`);
|
|
11505
12446
|
} finally {
|
|
11506
12447
|
if (worktreeCreated) try {
|
|
11507
|
-
await removeWorktree(worktreePath);
|
|
12448
|
+
await removeWorktree$1(worktreePath);
|
|
11508
12449
|
} catch {
|
|
11509
12450
|
log(`[experimenter] Warning: could not remove worktree ${worktreePath}`);
|
|
11510
12451
|
}
|
|
@@ -11653,5 +12594,5 @@ async function callLLM(params) {
|
|
|
11653
12594
|
}
|
|
11654
12595
|
|
|
11655
12596
|
//#endregion
|
|
11656
|
-
export { ADVISORY_NOTES, AdapterRegistry, AdtError, BudgetConfigSchema, CLAUDE_AUTH_FAILURE_HINT, CODEX_SANDBOX_BLOCK_HINT, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, Categorizer, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, ConsumerAnalyzer, CostTrackerConfigSchema, DEFAULT_CONFIG, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, DoltNotInstalled, DoltQueryError, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, GeminiCLIAdapter, GlobalSettingsSchema, InMemoryDatabaseAdapter, IngestionServer, LEARNING_FINDING, LogTurnAnalyzer, ModelRoutingConfigSchema, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProviderPolicySchema, ProvidersSchema, Recommender, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TASK_TYPE_PHASE_MAP, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryConfigSchema, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, VersionManagerImpl, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, callLLM, checkDoltInstalled, classifyVersionGap, compareRunMetrics, createAmendmentRun, createConfigSystem, createDatabaseAdapter as createDatabaseAdapter$1, createDecision, createExperimenter, createPipelineRun, createRequirement, createStderrLogger, createVersionManager, detectClaudeAuthFailure, detectCodexSandboxBlock, detectInterfaceChanges, determineVerdict, getActiveDecisions, getAllCostEntriesFiltered, getArtifactByTypeForRun, getArtifactsByRun, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getModelTier, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initWorkGraphSchema, initializeDolt, listRequirements, listRunMetrics, loadModelRoutingConfig, loadParentRunDecisions, registerArtifact, resolvePromptFile, supersedeDecision, swallowDebug, tagRunAsBaseline, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics };
|
|
11657
|
-
//# sourceMappingURL=dist-
|
|
12597
|
+
export { ADVISORY_NOTES, AdapterRegistry, AdtError, BRANCH_PREFIX, BudgetConfigSchema, CLAUDE_AUTH_FAILURE_HINT, CODEX_SANDBOX_BLOCK_HINT, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, Categorizer, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, ConsumerAnalyzer, CostTrackerConfigSchema, DEFAULT_CONFIG, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, DoltNotInstalled, DoltQueryError, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, GeminiCLIAdapter, GlobalSettingsSchema, InMemoryDatabaseAdapter, IngestionServer, LEARNING_FINDING, LogTurnAnalyzer, ModelRoutingConfigSchema, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProviderPolicySchema, ProvidersSchema, Recommender, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TASK_TYPE_PHASE_MAP, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryConfigSchema, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, VersionManagerImpl, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, callLLM, checkDoltInstalled, classifyVersionGap, compareRunMetrics, createAmendmentRun, createConfigSystem, createDatabaseAdapter as createDatabaseAdapter$1, createDecision, createExperimenter, createGitWorktreeManager, createPipelineRun, createRequirement, createStderrLogger, createVersionManager, detectClaudeAuthFailure, detectCodexSandboxBlock, detectInterfaceChanges, determineVerdict, getActiveDecisions, getAllCostEntriesFiltered, getArtifactByTypeForRun, getArtifactsByRun, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getModelTier, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initWorkGraphSchema, initializeDolt, listRequirements, listRunMetrics, loadModelRoutingConfig, loadParentRunDecisions, registerArtifact, resolvePromptFile, supersedeDecision, swallowDebug, tagRunAsBaseline, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics };
|
|
12598
|
+
//# sourceMappingURL=dist-C6pEdj12.js.map
|