substrate-ai 0.20.147 → 0.20.149

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.
@@ -4,13 +4,15 @@ 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$2 from "node:path";
8
+ import * as path$1 from "node:path";
7
9
  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";
10
+ import os, { freemem, homedir, platform } from "node:os";
9
11
  import { createHash, randomUUID } from "node:crypto";
10
12
  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";
13
+ 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
14
  import { readFileSync as readFileSync$1, writeFileSync as writeFileSync$1 } from "fs";
13
- import { homedir } from "os";
15
+ import { homedir as homedir$1 } from "os";
14
16
  import { createServer } from "node:http";
15
17
  import { createGunzip, createInflate } from "node:zlib";
16
18
  import { promisify } from "node:util";
@@ -919,6 +921,26 @@ function createStderrLogger(prefix) {
919
921
 
920
922
  //#endregion
921
923
  //#region packages/core/dist/dispatch/dispatcher-impl.js
924
+ const WORKTREE_COUPLED_TASK_TYPES = new Set([
925
+ "create-story",
926
+ "dev-story",
927
+ "code-review",
928
+ "minor-fixes",
929
+ "major-rework",
930
+ "build-fix",
931
+ "test-plan",
932
+ "test-expansion",
933
+ "probe-author"
934
+ ]);
935
+ const GIT_STATE_ENV_KEYS = [
936
+ "PWD",
937
+ "OLDPWD",
938
+ "INIT_CWD",
939
+ "GIT_DIR",
940
+ "GIT_WORK_TREE",
941
+ "GIT_INDEX_FILE",
942
+ "GIT_COMMON_DIR"
943
+ ];
922
944
  const SHUTDOWN_GRACE_MS = 1e4;
923
945
  const SHUTDOWN_MAX_WAIT_MS = 3e4;
924
946
  const CHARS_PER_TOKEN$3 = 4;
@@ -1242,6 +1264,29 @@ var DispatcherImpl = class {
1242
1264
  });
1243
1265
  return;
1244
1266
  }
1267
+ if (workingDirectory === void 0 && WORKTREE_COUPLED_TASK_TYPES.has(taskType)) {
1268
+ this._logger.error({
1269
+ id,
1270
+ agent,
1271
+ taskType
1272
+ }, "dispatch rejected: workingDirectory is required for worktree-coupled task types (H4.1)");
1273
+ this._running.delete(id);
1274
+ this._drainQueue();
1275
+ resolve$2({
1276
+ id,
1277
+ status: "failed",
1278
+ exitCode: -1,
1279
+ output: "",
1280
+ parsed: null,
1281
+ parseError: `workingDirectory is required for taskType "${taskType}" — coding-pipeline dispatches must name their worktree explicitly (H4.1 AC2)`,
1282
+ durationMs: 0,
1283
+ tokenEstimate: {
1284
+ input: Math.ceil(prompt.length / CHARS_PER_TOKEN$3),
1285
+ output: 0
1286
+ }
1287
+ });
1288
+ return;
1289
+ }
1245
1290
  const worktreePath = workingDirectory ?? process.cwd();
1246
1291
  const resolvedMaxTurns = maxTurns ?? DEFAULT_MAX_TURNS[taskType];
1247
1292
  const capabilities = adapter.getCapabilities();
@@ -1265,6 +1310,8 @@ var DispatcherImpl = class {
1265
1310
  const env = { ...process.env };
1266
1311
  const parentNodeOpts = env["NODE_OPTIONS"] ?? "";
1267
1312
  if (!parentNodeOpts.includes("--max-old-space-size")) env["NODE_OPTIONS"] = `${parentNodeOpts} --max-old-space-size=512`.trim();
1313
+ for (const key of GIT_STATE_ENV_KEYS) delete env[key];
1314
+ env["GIT_CEILING_DIRECTORIES"] = dirname$1(worktreePath);
1268
1315
  if (cmd.env !== void 0) Object.assign(env, cmd.env);
1269
1316
  if (cmd.unsetEnvKeys !== void 0) for (const key of cmd.unsetEnvKeys) delete env[key];
1270
1317
  const proc = spawn(cmd.binary, cmd.args, {
@@ -6233,7 +6280,10 @@ const TelemetryConfigSchema = z.object({
6233
6280
  meshUrl: z.string().url().optional(),
6234
6281
  projectId: z.string().optional()
6235
6282
  }).strict();
6236
- const WorktreeConfigSchema = z.object({ copy_files: z.array(z.string()).default([]) }).strict();
6283
+ const WorktreeConfigSchema = z.object({
6284
+ copy_files: z.array(z.string()).default([]),
6285
+ base: z.enum(["in-repo", "external"]).optional()
6286
+ }).strict();
6237
6287
  /** Current supported config format version */
6238
6288
  const CURRENT_CONFIG_FORMAT_VERSION = "1";
6239
6289
  /** Current supported task graph version */
@@ -6252,11 +6302,15 @@ const SubstrateConfigSchema = z.object({
6252
6302
  telemetry: TelemetryConfigSchema.optional(),
6253
6303
  worktree: WorktreeConfigSchema.optional(),
6254
6304
  trivialOutputThreshold: z.number().int().nonnegative().optional(),
6255
- finalization: z.object({ mode: z.enum([
6256
- "merge",
6257
- "branch",
6258
- "pr"
6259
- ]).optional() }).strict().optional()
6305
+ finalization: z.object({
6306
+ mode: z.enum([
6307
+ "merge",
6308
+ "branch",
6309
+ "pr"
6310
+ ]).optional(),
6311
+ merge_strategy: z.enum(["ff-only", "three-way"]).optional(),
6312
+ epic_gate_command: z.string().optional()
6313
+ }).strict().optional()
6260
6314
  }).passthrough();
6261
6315
  const PartialProviderConfigSchema = ProviderConfigSchema.partial();
6262
6316
  const PartialGlobalSettingsSchema = GlobalSettingsSchema.partial();
@@ -6274,11 +6328,15 @@ const PartialSubstrateConfigSchema = z.object({
6274
6328
  telemetry: TelemetryConfigSchema.partial().optional(),
6275
6329
  worktree: WorktreeConfigSchema.partial().optional(),
6276
6330
  trivialOutputThreshold: z.number().int().nonnegative().optional(),
6277
- finalization: z.object({ mode: z.enum([
6278
- "merge",
6279
- "branch",
6280
- "pr"
6281
- ]).optional() }).strict().optional()
6331
+ finalization: z.object({
6332
+ mode: z.enum([
6333
+ "merge",
6334
+ "branch",
6335
+ "pr"
6336
+ ]).optional(),
6337
+ merge_strategy: z.enum(["ff-only", "three-way"]).optional(),
6338
+ epic_gate_command: z.string().optional()
6339
+ }).strict().optional()
6282
6340
  }).passthrough();
6283
6341
 
6284
6342
  //#endregion
@@ -6610,8 +6668,8 @@ function readEnvOverrides(logger) {
6610
6668
  /**
6611
6669
  * Get a value from a nested object using dot-notation key.
6612
6670
  */
6613
- function getByPath(obj, path$1) {
6614
- const parts = path$1.split(".");
6671
+ function getByPath(obj, path$3) {
6672
+ const parts = path$3.split(".");
6615
6673
  let cursor = obj;
6616
6674
  for (const part of parts) {
6617
6675
  if (cursor === null || cursor === void 0 || typeof cursor !== "object") return void 0;
@@ -6623,8 +6681,8 @@ function getByPath(obj, path$1) {
6623
6681
  * Return a deep clone of `obj` with `path` set to `value`.
6624
6682
  * Creates intermediate objects as needed.
6625
6683
  */
6626
- function setByPath(obj, path$1, value) {
6627
- const parts = path$1.split(".");
6684
+ function setByPath(obj, path$3, value) {
6685
+ const parts = path$3.split(".");
6628
6686
  const result = { ...obj };
6629
6687
  let cursor = result;
6630
6688
  for (let i = 0; i < parts.length - 1; i++) {
@@ -6646,7 +6704,7 @@ var ConfigSystemImpl = class {
6646
6704
  _logger;
6647
6705
  constructor(options = {}) {
6648
6706
  this._projectConfigDir = options.projectConfigDir ? resolve(options.projectConfigDir) : resolve(process.cwd(), ".substrate");
6649
- this._globalConfigDir = options.globalConfigDir ? resolve(options.globalConfigDir) : resolve(homedir(), ".substrate");
6707
+ this._globalConfigDir = options.globalConfigDir ? resolve(options.globalConfigDir) : resolve(homedir$1(), ".substrate");
6650
6708
  this._cliOverrides = options.cliOverrides ?? {};
6651
6709
  this._logger = options.logger ?? createStderrLogger("config-system");
6652
6710
  }
@@ -10771,6 +10829,833 @@ const CompileResultSchema = z.object({
10771
10829
  truncated: z.boolean()
10772
10830
  });
10773
10831
 
10832
+ //#endregion
10833
+ //#region packages/core/dist/git/git-utils.js
10834
+ const MIN_GIT_MAJOR = 2;
10835
+ const MIN_GIT_MINOR = 20;
10836
+ /**
10837
+ * Spawn a git subprocess with the given args.
10838
+ *
10839
+ * @param args - Arguments to pass to git (e.g., ['worktree', 'add', ...])
10840
+ * @param options - Optional spawn options (cwd, env)
10841
+ * @returns - Object with stdout, stderr, and exit code
10842
+ */
10843
+ function spawnGit(args, options) {
10844
+ return new Promise((resolve$2) => {
10845
+ const proc = spawn("git", args, {
10846
+ cwd: options?.cwd,
10847
+ env: options?.env ?? process.env,
10848
+ stdio: [
10849
+ "ignore",
10850
+ "pipe",
10851
+ "pipe"
10852
+ ]
10853
+ });
10854
+ let stdout = "";
10855
+ let stderr = "";
10856
+ proc.stdout?.on("data", (chunk) => {
10857
+ stdout += chunk.toString();
10858
+ });
10859
+ proc.stderr?.on("data", (chunk) => {
10860
+ stderr += chunk.toString();
10861
+ });
10862
+ proc.on("close", (code) => {
10863
+ resolve$2({
10864
+ stdout: stdout.trim(),
10865
+ stderr: stderr.trim(),
10866
+ code: code ?? 1
10867
+ });
10868
+ });
10869
+ proc.on("error", (err) => {
10870
+ resolve$2({
10871
+ stdout: "",
10872
+ stderr: err.message,
10873
+ code: 1
10874
+ });
10875
+ });
10876
+ });
10877
+ }
10878
+ /**
10879
+ * Get the installed git version string.
10880
+ *
10881
+ * @returns Version string like "2.42.0"
10882
+ * @throws Error if git is not installed or version cannot be parsed
10883
+ */
10884
+ async function getGitVersion() {
10885
+ const result = await spawnGit(["--version"]);
10886
+ if (result.code !== 0) throw new Error(`git --version failed: ${result.stderr}`);
10887
+ const match = /git version\s+(\d+\.\d+(?:\.\d+)?)/.exec(result.stdout);
10888
+ if (match === null || match[1] === void 0) throw new Error(`Unable to parse git version from output: "${result.stdout}"`);
10889
+ return match[1];
10890
+ }
10891
+ /**
10892
+ * Parse a git version string into major/minor/patch components.
10893
+ *
10894
+ * @param versionString - Version string like "2.42.0" or "2.20"
10895
+ * @returns - Parsed version components
10896
+ */
10897
+ function parseGitVersion(versionString) {
10898
+ const parts = versionString.split(".").map(Number);
10899
+ return {
10900
+ major: parts[0] ?? 0,
10901
+ minor: parts[1] ?? 0,
10902
+ patch: parts[2] ?? 0
10903
+ };
10904
+ }
10905
+ /**
10906
+ * Check if the given git version string is >= 2.20.0.
10907
+ *
10908
+ * @param version - Version string like "2.42.0"
10909
+ * @returns - true if version >= 2.20.0
10910
+ */
10911
+ function isGitVersionSupported(version) {
10912
+ const { major, minor } = parseGitVersion(version);
10913
+ if (major > MIN_GIT_MAJOR) return true;
10914
+ if (major === MIN_GIT_MAJOR && minor >= MIN_GIT_MINOR) return true;
10915
+ return false;
10916
+ }
10917
+ /**
10918
+ * Verify that git is installed and version >= 2.20.
10919
+ *
10920
+ * @throws Error with clear message if git is not installed or too old
10921
+ */
10922
+ async function verifyGitVersion() {
10923
+ let version;
10924
+ try {
10925
+ version = await getGitVersion();
10926
+ } catch (err) {
10927
+ throw new Error(`git is not installed or could not be executed. Please install git 2.20 or newer. Details: ${String(err)}`);
10928
+ }
10929
+ if (!isGitVersionSupported(version)) {
10930
+ const { major, minor, patch } = parseGitVersion(version);
10931
+ 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`);
10932
+ }
10933
+ }
10934
+ /**
10935
+ * Decide whether a registered worktree from a prior dispatch can be reclaimed
10936
+ * for a fresh re-run. Safe only when there is nothing to lose: no uncommitted
10937
+ * changes AND no commits on the branch beyond the base branch. A negative
10938
+ * `commitsAhead` means the ahead-count could not be determined → not safe.
10939
+ *
10940
+ * Pure + exported for testing (the git I/O that produces the inputs lives in
10941
+ * createWorktree).
10942
+ */
10943
+ function decideWorktreeReclaim(hasUncommittedChanges, commitsAhead, baseBranch) {
10944
+ if (hasUncommittedChanges) return {
10945
+ safe: false,
10946
+ reason: "it has uncommitted changes that are NOT on the branch"
10947
+ };
10948
+ if (commitsAhead > 0) return {
10949
+ safe: false,
10950
+ reason: `its branch has ${String(commitsAhead)} commit(s) beyond ${baseBranch}`
10951
+ };
10952
+ if (commitsAhead < 0) return {
10953
+ safe: false,
10954
+ reason: "its state could not be verified as safe to discard"
10955
+ };
10956
+ return { safe: true };
10957
+ }
10958
+ /**
10959
+ * Decide whether removing a story worktree AND deleting its branch is safe
10960
+ * (H0.3, field findings #17/#19). Unsafe when:
10961
+ * - the worktree has uncommitted changes (removal destroys the only copy), or
10962
+ * - the branch carries commits not reachable from the project's current HEAD
10963
+ * (branch -D destroys them — this is where H0.1's wip checkpoints live), or
10964
+ * - either state could not be determined (negative unmergedCommits).
10965
+ *
10966
+ * Pure + exported for testing (mirrors decideWorktreeReclaim; the git I/O that
10967
+ * produces the inputs lives in inspectWorktreeRemovalSafety).
10968
+ */
10969
+ function decideWorktreeRemoval(hasUncommittedChanges, uncommittedFiles, unmergedCommits, branchName) {
10970
+ const reasons = [];
10971
+ if (hasUncommittedChanges) {
10972
+ const preview = uncommittedFiles.slice(0, 10).join(", ");
10973
+ const more = uncommittedFiles.length > 10 ? ` (+${String(uncommittedFiles.length - 10)} more)` : "";
10974
+ reasons.push(`the worktree has ${String(uncommittedFiles.length)} uncommitted change(s) that removal would destroy` + (preview.length > 0 ? `: ${preview}${more}` : ""));
10975
+ }
10976
+ if (unmergedCommits > 0) reasons.push(`branch ${branchName} carries ${String(unmergedCommits)} commit(s) not reachable from the current HEAD — deleting the branch would destroy them`);
10977
+ if (unmergedCommits < 0) reasons.push("the branch state could not be verified as safe to discard");
10978
+ return {
10979
+ safe: reasons.length === 0,
10980
+ reasons
10981
+ };
10982
+ }
10983
+ /**
10984
+ * Gather the inputs for `decideWorktreeRemoval` from git. Thin I/O wrapper:
10985
+ * - `git status --porcelain` inside the worktree (skipped when the directory
10986
+ * is missing — nothing on disk to lose)
10987
+ * - `git rev-list --count HEAD..<branch>` in the project root (commits the
10988
+ * branch has that the current checkout does not; 0 when the branch is
10989
+ * merged or absent, -1 when the count could not be determined)
10990
+ */
10991
+ async function inspectWorktreeRemovalSafety(worktreePath, projectRoot, branchName) {
10992
+ let hasUncommittedChanges = false;
10993
+ let uncommittedFiles = [];
10994
+ const worktreeOnDisk = await access$1(worktreePath).then(() => true).catch(() => false);
10995
+ if (worktreeOnDisk) {
10996
+ const statusResult = await spawnGit(["status", "--porcelain"], { cwd: worktreePath });
10997
+ if (statusResult.code === 0) {
10998
+ uncommittedFiles = statusResult.stdout.split("\n").filter((line) => line.trim().length > 0).map((line) => line.slice(3).trim());
10999
+ hasUncommittedChanges = uncommittedFiles.length > 0;
11000
+ } else return decideWorktreeRemoval(false, [], -1, branchName);
11001
+ }
11002
+ const branchExists = (await spawnGit([
11003
+ "rev-parse",
11004
+ "--verify",
11005
+ branchName
11006
+ ], { cwd: projectRoot })).code === 0;
11007
+ let unmergedCommits = 0;
11008
+ if (branchExists) {
11009
+ const aheadResult = await spawnGit([
11010
+ "rev-list",
11011
+ "--count",
11012
+ `HEAD..${branchName}`
11013
+ ], { cwd: projectRoot });
11014
+ unmergedCommits = aheadResult.code === 0 ? Number.parseInt(aheadResult.stdout.trim(), 10) || 0 : -1;
11015
+ }
11016
+ return decideWorktreeRemoval(hasUncommittedChanges, uncommittedFiles, unmergedCommits, branchName);
11017
+ }
11018
+ async function createWorktree(projectRoot, taskId, branchName, baseBranch, copyFiles = [], baseDirectory = ".substrate-worktrees") {
11019
+ const worktreePath = path$2.resolve(projectRoot, baseDirectory, taskId);
11020
+ await mkdir$1(path$2.dirname(worktreePath), { recursive: true });
11021
+ const worktreeExists = await access$1(worktreePath).then(() => true).catch((err) => {
11022
+ if (err.code === "ENOENT") return false;
11023
+ throw err;
11024
+ });
11025
+ if (worktreeExists) {
11026
+ const listResult = await spawnGit([
11027
+ "worktree",
11028
+ "list",
11029
+ "--porcelain"
11030
+ ], { cwd: projectRoot });
11031
+ const registeredPaths = listResult.stdout.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => line.slice(9).trim());
11032
+ const isRegistered = registeredPaths.includes(worktreePath);
11033
+ if (!isRegistered) await rm(worktreePath, {
11034
+ recursive: true,
11035
+ force: true
11036
+ });
11037
+ else {
11038
+ const statusResult = await spawnGit(["status", "--porcelain"], { cwd: worktreePath });
11039
+ const hasUncommittedChanges = statusResult.code === 0 && statusResult.stdout.trim().length > 0;
11040
+ const aheadResult = await spawnGit([
11041
+ "rev-list",
11042
+ "--count",
11043
+ `${baseBranch}..${branchName}`
11044
+ ], { cwd: projectRoot });
11045
+ const commitsAhead = aheadResult.code === 0 ? Number.parseInt(aheadResult.stdout.trim(), 10) || 0 : -1;
11046
+ const decision = decideWorktreeReclaim(hasUncommittedChanges, commitsAhead, baseBranch);
11047
+ 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`);
11048
+ await spawnGit([
11049
+ "worktree",
11050
+ "remove",
11051
+ "--force",
11052
+ worktreePath
11053
+ ], { cwd: projectRoot });
11054
+ await spawnGit([
11055
+ "branch",
11056
+ "-D",
11057
+ branchName
11058
+ ], { cwd: projectRoot });
11059
+ }
11060
+ }
11061
+ const addResult = await spawnGit([
11062
+ "worktree",
11063
+ "add",
11064
+ worktreePath,
11065
+ "-b",
11066
+ branchName,
11067
+ baseBranch
11068
+ ], { cwd: projectRoot });
11069
+ if (addResult.code !== 0) throw new Error(`git worktree add failed for task "${taskId}": ${addResult.stderr || addResult.stdout}`);
11070
+ await copyFilesToWorktree(projectRoot, worktreePath, copyFiles);
11071
+ return { worktreePath };
11072
+ }
11073
+ /**
11074
+ * Copy files from a source directory into a target worktree.
11075
+ *
11076
+ * Skips missing files silently (intentional — config like `[".env",
11077
+ * ".env.local"]` should not blow up if `.env.local` doesn't exist in the
11078
+ * parent checkout). Creates parent directories for nested paths.
11079
+ *
11080
+ * Exported for testability; not part of the public worktree API.
11081
+ */
11082
+ async function copyFilesToWorktree(sourceRoot, worktreePath, files) {
11083
+ if (files.length === 0) return;
11084
+ for (const relativePath of files) {
11085
+ if (path$2.isAbsolute(relativePath) || relativePath.split(path$2.sep).includes("..")) continue;
11086
+ const sourcePath = path$2.join(sourceRoot, relativePath);
11087
+ const destPath = path$2.join(worktreePath, relativePath);
11088
+ try {
11089
+ await access$1(sourcePath);
11090
+ } catch {
11091
+ continue;
11092
+ }
11093
+ const destDir = path$2.dirname(destPath);
11094
+ if (destDir !== worktreePath) await mkdir$1(destDir, { recursive: true });
11095
+ await copyFile(sourcePath, destPath);
11096
+ }
11097
+ }
11098
+ /**
11099
+ * Remove a git worktree by path.
11100
+ *
11101
+ * Uses `git worktree remove --force` to handle unclean worktrees.
11102
+ *
11103
+ * @param worktreePath - Absolute path to the worktree directory
11104
+ * @param projectRoot - Absolute path to the git repository root
11105
+ * @throws - Error if git command fails
11106
+ */
11107
+ async function removeWorktree(worktreePath, projectRoot) {
11108
+ const spawnOpts = {};
11109
+ if (projectRoot !== void 0) spawnOpts.cwd = projectRoot;
11110
+ const result = await spawnGit([
11111
+ "worktree",
11112
+ "remove",
11113
+ "--force",
11114
+ worktreePath
11115
+ ], spawnOpts);
11116
+ if (result.code !== 0) throw new Error(`git worktree remove failed for "${worktreePath}": ${result.stderr || result.stdout}`);
11117
+ }
11118
+ /**
11119
+ * Delete a git branch using `git branch -D`.
11120
+ *
11121
+ * @param branchName - Branch name to delete (e.g., "substrate/story-abc123")
11122
+ * @param projectRoot - Absolute path to the git repository root
11123
+ * @returns - true if branch was successfully deleted, false otherwise
11124
+ */
11125
+ async function removeBranch(branchName, projectRoot) {
11126
+ const spawnOpts = {};
11127
+ if (projectRoot !== void 0) spawnOpts.cwd = projectRoot;
11128
+ const result = await spawnGit([
11129
+ "branch",
11130
+ "-D",
11131
+ branchName
11132
+ ], spawnOpts);
11133
+ if (result.code !== 0) return false;
11134
+ return true;
11135
+ }
11136
+ /**
11137
+ * Scan the worktrees base directory and return paths of all found worktree directories.
11138
+ *
11139
+ * @param projectRoot - Absolute path to the git repository root
11140
+ * @param baseDirectory - Relative directory name for worktrees (default: '.substrate-worktrees')
11141
+ * @returns - Array of absolute worktree directory paths
11142
+ */
11143
+ async function getOrphanedWorktrees(projectRoot, baseDirectory = ".substrate-worktrees") {
11144
+ const worktreesDir = path$2.resolve(projectRoot, baseDirectory);
11145
+ try {
11146
+ await access$1(worktreesDir);
11147
+ } catch {
11148
+ return [];
11149
+ }
11150
+ let entries;
11151
+ try {
11152
+ entries = await readdir$1(worktreesDir, { withFileTypes: true });
11153
+ } catch {
11154
+ return [];
11155
+ }
11156
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => path$2.join(worktreesDir, entry.name));
11157
+ }
11158
+ /**
11159
+ * Simulate a merge using git merge --no-commit --no-ff without committing.
11160
+ *
11161
+ * This runs in the target branch's working directory (the project root or
11162
+ * worktree path). The simulation must be aborted after checking conflicts
11163
+ * via abortMerge().
11164
+ *
11165
+ * @param branchName - The source branch to simulate merging
11166
+ * @param cwd - Working directory (must be on the target branch)
11167
+ * @returns - true if merge would be clean, false if there are conflicts
11168
+ */
11169
+ async function simulateMerge(branchName, cwd) {
11170
+ const result = await spawnGit([
11171
+ "merge",
11172
+ "--no-commit",
11173
+ "--no-ff",
11174
+ branchName
11175
+ ], { cwd });
11176
+ if (result.code === 0) return true;
11177
+ return false;
11178
+ }
11179
+ /**
11180
+ * Abort a merge in progress using git merge --abort.
11181
+ *
11182
+ * Should be called after simulateMerge() to clean up the merge state,
11183
+ * regardless of whether conflicts were found.
11184
+ *
11185
+ * @param cwd - Working directory (same as used for simulateMerge)
11186
+ */
11187
+ async function abortMerge(cwd) {
11188
+ await spawnGit(["merge", "--abort"], { cwd });
11189
+ }
11190
+ /**
11191
+ * Get a list of files with conflicts during a merge.
11192
+ *
11193
+ * Must be called while a merge is in progress (after simulateMerge() and
11194
+ * before abortMerge()).
11195
+ *
11196
+ * @param cwd - Working directory of the repository
11197
+ * @returns - Array of conflicting file paths
11198
+ */
11199
+ async function getConflictingFiles(cwd) {
11200
+ const result = await spawnGit([
11201
+ "diff",
11202
+ "--name-only",
11203
+ "--diff-filter=U"
11204
+ ], { cwd });
11205
+ if (result.code !== 0) return [];
11206
+ if (result.stdout.trim() === "") return [];
11207
+ return result.stdout.trim().split("\n").filter((f) => f.trim().length > 0);
11208
+ }
11209
+ /**
11210
+ * Perform an actual merge using git merge --no-ff.
11211
+ *
11212
+ * Should only be called after detectConflicts() confirms there are no conflicts.
11213
+ * Creates a merge commit even if fast-forward is possible (--no-ff ensures history).
11214
+ *
11215
+ * @param branchName - The source branch to merge
11216
+ * @param cwd - Working directory (must be on the target branch)
11217
+ * @returns - true if merge succeeded, false otherwise
11218
+ */
11219
+ async function performMerge(branchName, cwd) {
11220
+ const result = await spawnGit([
11221
+ "merge",
11222
+ "--no-ff",
11223
+ branchName
11224
+ ], { cwd });
11225
+ if (result.code !== 0) return false;
11226
+ return true;
11227
+ }
11228
+ /**
11229
+ * Get a list of files changed in the most recent merge.
11230
+ *
11231
+ * Uses git diff --name-only HEAD~1..HEAD to find files in the merge commit.
11232
+ * Falls back to empty array if commit history is insufficient.
11233
+ *
11234
+ * @param cwd - Working directory of the repository
11235
+ * @returns - Array of file paths that were merged
11236
+ */
11237
+ async function getMergedFiles(cwd) {
11238
+ const result = await spawnGit([
11239
+ "diff",
11240
+ "--name-only",
11241
+ "HEAD~1..HEAD"
11242
+ ], { cwd });
11243
+ if (result.code !== 0) {
11244
+ const altResult = await spawnGit([
11245
+ "show",
11246
+ "--name-only",
11247
+ "--format=",
11248
+ "HEAD"
11249
+ ], { cwd });
11250
+ if (altResult.code !== 0) return [];
11251
+ return altResult.stdout.trim().split("\n").filter((f) => f.trim().length > 0);
11252
+ }
11253
+ if (result.stdout.trim() === "") return [];
11254
+ return result.stdout.trim().split("\n").filter((f) => f.trim().length > 0);
11255
+ }
11256
+
11257
+ //#endregion
11258
+ //#region packages/core/dist/git/git-worktree-manager-impl.js
11259
+ /**
11260
+ * Branch name prefix for substrate per-story branches.
11261
+ *
11262
+ * Exported as the canonical source of truth so consumers (orchestrator,
11263
+ * integration tests, tooling) can compose branch names without
11264
+ * independently encoding the prefix. v0.20.82 production bug:
11265
+ * `orchestrator-impl.ts:4290` hardcoded `substrate/story-${storyKey}`
11266
+ * while this module created `substrate/task-${taskId}` — the resulting
11267
+ * merge-to-main looked at a non-existent branch. Recurrence prevention:
11268
+ * all branch-name construction MUST import this constant.
11269
+ */
11270
+ const BRANCH_PREFIX = "substrate/story-";
11271
+ const DEFAULT_WORKTREE_BASE = ".substrate-worktrees";
11272
+ /**
11273
+ * H4.2 (AC2): resolve the worktree base directory for a project.
11274
+ *
11275
+ * 'external' (the NEW DEFAULT) puts worktrees OUTSIDE the parent tree at
11276
+ * `~/.substrate/worktrees/<projectname>-<hash8>/` — an agent inside its
11277
+ * worktree has no parent repo above it to leak into (composes with H4.1's
11278
+ * GIT_CEILING_DIRECTORIES, which points at this base). 'in-repo' retains the
11279
+ * pre-H4.2 `<projectRoot>/.substrate-worktrees/` for tooling that assumed
11280
+ * the old path (MIGRATION NOTE: reconcile-from-disk, editor bookmarks, and
11281
+ * scripts that globbed `.substrate-worktrees/` should use
11282
+ * `substrate worktrees list` or set `worktree.base: in-repo`).
11283
+ *
11284
+ * When `baseOverride` is not given, reads `worktree.base` from
11285
+ * `.substrate/config.yaml` directly (same no-threading pattern as
11286
+ * `resolveEpicsPathOverride`) so EVERY construction site — orchestrator,
11287
+ * `substrate merge`, `substrate worktrees` — resolves identically.
11288
+ */
11289
+ function resolveWorktreeBaseDirectory(projectRoot, baseOverride) {
11290
+ let mode = baseOverride;
11291
+ if (mode === void 0) try {
11292
+ const raw = readFileSync(path$1.join(projectRoot, ".substrate", "config.yaml"), "utf-8");
11293
+ const parsed = yaml.load(raw);
11294
+ if (parsed?.worktree?.base === "in-repo" || parsed?.worktree?.base === "external") mode = parsed.worktree.base;
11295
+ } catch {}
11296
+ if ((mode ?? "external") === "in-repo") return DEFAULT_WORKTREE_BASE;
11297
+ const hash = createHash("sha256").update(path$1.resolve(projectRoot)).digest("hex").slice(0, 8);
11298
+ return path$1.join(homedir(), ".substrate", "worktrees", `${path$1.basename(projectRoot)}-${hash}`);
11299
+ }
11300
+ var GitWorktreeManagerImpl = class {
11301
+ _eventBus;
11302
+ _projectRoot;
11303
+ _baseDirectory;
11304
+ _db;
11305
+ _logger;
11306
+ /** v0.20.109: files to copy from project root into each new worktree (e.g. `.env`). */
11307
+ _copyFiles;
11308
+ /** Bound listener references for cleanup in shutdown() */
11309
+ _onTaskReady;
11310
+ _onTaskComplete;
11311
+ _onTaskFailed;
11312
+ constructor(eventBus, projectRoot, baseDirectory = DEFAULT_WORKTREE_BASE, db = null, logger, copyFiles = []) {
11313
+ this._eventBus = eventBus;
11314
+ this._projectRoot = projectRoot;
11315
+ this._baseDirectory = baseDirectory;
11316
+ this._db = db;
11317
+ this._logger = logger ?? createStderrLogger("git-worktree-manager");
11318
+ this._copyFiles = copyFiles;
11319
+ this._onTaskReady = ({ taskId }) => {
11320
+ this._handleTaskReady(taskId).catch((err) => {
11321
+ this._logger.error({
11322
+ taskId,
11323
+ err
11324
+ }, "Unhandled error in _handleTaskReady");
11325
+ });
11326
+ };
11327
+ this._onTaskComplete = ({ taskId }) => {
11328
+ this._handleTaskDone(taskId);
11329
+ };
11330
+ this._onTaskFailed = ({ taskId }) => {
11331
+ this._handleTaskDone(taskId);
11332
+ };
11333
+ }
11334
+ async initialize() {
11335
+ this._logger.info({ projectRoot: this._projectRoot }, "GitWorktreeManager.initialize()");
11336
+ await this.verifyGitVersion();
11337
+ const cleaned = await this.cleanupAllWorktrees();
11338
+ if (cleaned > 0) this._logger.info({ cleaned }, "Recovered orphaned worktrees on startup");
11339
+ this._eventBus.on("task:ready", this._onTaskReady);
11340
+ this._eventBus.on("task:complete", this._onTaskComplete);
11341
+ this._eventBus.on("task:failed", this._onTaskFailed);
11342
+ this._logger.info("GitWorktreeManager initialized");
11343
+ }
11344
+ async shutdown() {
11345
+ this._logger.info("GitWorktreeManager.shutdown()");
11346
+ this._eventBus.off("task:ready", this._onTaskReady);
11347
+ this._eventBus.off("task:complete", this._onTaskComplete);
11348
+ this._eventBus.off("task:failed", this._onTaskFailed);
11349
+ await this.cleanupAllWorktrees();
11350
+ this._logger.info("GitWorktreeManager shutdown complete");
11351
+ }
11352
+ async _handleTaskReady(taskId) {
11353
+ this._logger.debug({ taskId }, "task:ready — creating worktree");
11354
+ try {
11355
+ await this.createWorktree(taskId);
11356
+ } catch (err) {
11357
+ this._logger.error({
11358
+ taskId,
11359
+ err
11360
+ }, "Failed to create worktree for task");
11361
+ }
11362
+ }
11363
+ async _handleTaskDone(taskId) {
11364
+ this._logger.debug({ taskId }, "task done — cleaning up worktree");
11365
+ try {
11366
+ await this.cleanupWorktree(taskId);
11367
+ } catch (err) {
11368
+ this._logger.warn({
11369
+ taskId,
11370
+ err
11371
+ }, "Failed to cleanup worktree for task");
11372
+ }
11373
+ }
11374
+ async createWorktree(taskId, baseBranch) {
11375
+ if (!taskId || taskId.trim().length === 0) throw new Error("createWorktree: taskId must be a non-empty string");
11376
+ const resolvedBaseBranch = baseBranch ?? await this._detectCurrentBranch() ?? "main";
11377
+ const branchName = BRANCH_PREFIX + taskId;
11378
+ const worktreePath = this.getWorktreePath(taskId);
11379
+ this._logger.debug({
11380
+ taskId,
11381
+ branchName,
11382
+ worktreePath,
11383
+ baseBranch: resolvedBaseBranch,
11384
+ copyFiles: this._copyFiles
11385
+ }, "createWorktree");
11386
+ const copyFiles = this._copyFiles.includes(".substrate/project-profile.yaml") ? this._copyFiles : [...this._copyFiles, ".substrate/project-profile.yaml"];
11387
+ await createWorktree(this._projectRoot, taskId, branchName, resolvedBaseBranch, copyFiles, this._baseDirectory);
11388
+ const createdAt = new Date();
11389
+ this._eventBus.emit("worktree:created", {
11390
+ taskId,
11391
+ branchName,
11392
+ worktreePath,
11393
+ createdAt
11394
+ });
11395
+ const info = {
11396
+ taskId,
11397
+ branchName,
11398
+ worktreePath,
11399
+ createdAt
11400
+ };
11401
+ this._logger.info({
11402
+ taskId,
11403
+ branchName,
11404
+ worktreePath
11405
+ }, "Worktree created");
11406
+ return info;
11407
+ }
11408
+ async cleanupWorktree(taskId, opts) {
11409
+ const branchName = BRANCH_PREFIX + taskId;
11410
+ const worktreePath = this.getWorktreePath(taskId);
11411
+ this._logger.debug({
11412
+ taskId,
11413
+ branchName,
11414
+ worktreePath,
11415
+ force: opts?.force === true
11416
+ }, "cleanupWorktree");
11417
+ if (opts?.force !== true) {
11418
+ const decision = await inspectWorktreeRemovalSafety(worktreePath, this._projectRoot, branchName);
11419
+ const blockingReasons = opts?.keepBranch === true ? decision.reasons.filter((r) => r.includes("uncommitted")) : decision.reasons;
11420
+ 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.`);
11421
+ }
11422
+ let worktreeExists = false;
11423
+ try {
11424
+ await access$1(worktreePath);
11425
+ worktreeExists = true;
11426
+ } catch {
11427
+ this._logger.debug({
11428
+ taskId,
11429
+ worktreePath
11430
+ }, "cleanupWorktree: worktree does not exist, skipping removal");
11431
+ }
11432
+ if (worktreeExists) try {
11433
+ await removeWorktree(worktreePath, this._projectRoot);
11434
+ } catch (err) {
11435
+ this._logger.warn({
11436
+ taskId,
11437
+ worktreePath,
11438
+ err
11439
+ }, "removeWorktree failed during cleanup");
11440
+ }
11441
+ if (opts?.keepBranch !== true) try {
11442
+ await removeBranch(branchName, this._projectRoot);
11443
+ } catch (err) {
11444
+ this._logger.warn({
11445
+ taskId,
11446
+ branchName,
11447
+ err
11448
+ }, "removeBranch failed during cleanup");
11449
+ }
11450
+ this._eventBus.emit("worktree:removed", {
11451
+ taskId,
11452
+ branchName
11453
+ });
11454
+ this._logger.info({
11455
+ taskId,
11456
+ branchName
11457
+ }, "Worktree cleaned up");
11458
+ }
11459
+ async cleanupAllWorktrees(opts) {
11460
+ this._logger.debug({
11461
+ projectRoot: this._projectRoot,
11462
+ force: opts?.force === true
11463
+ }, "cleanupAllWorktrees");
11464
+ const orphanedPaths = await getOrphanedWorktrees(this._projectRoot, this._baseDirectory);
11465
+ let cleaned = 0;
11466
+ for (const worktreePath of orphanedPaths) {
11467
+ const taskId = path$1.basename(worktreePath);
11468
+ const branchGuardName = BRANCH_PREFIX + taskId;
11469
+ const decision = opts?.force === true ? {
11470
+ safe: true,
11471
+ reasons: []
11472
+ } : await inspectWorktreeRemovalSafety(worktreePath, this._projectRoot, branchGuardName);
11473
+ if (!decision.safe) {
11474
+ this._logger.warn({
11475
+ taskId,
11476
+ worktreePath,
11477
+ reasons: decision.reasons
11478
+ }, "cleanupAllWorktrees: preserving worktree — removal would destroy work (use `substrate worktrees cleanup --force` to discard)");
11479
+ continue;
11480
+ }
11481
+ let worktreeRemoved = false;
11482
+ try {
11483
+ await removeWorktree(worktreePath, this._projectRoot);
11484
+ worktreeRemoved = true;
11485
+ this._logger.debug({
11486
+ taskId,
11487
+ worktreePath
11488
+ }, "cleanupAllWorktrees: removed orphaned worktree");
11489
+ } catch (err) {
11490
+ this._logger.warn({
11491
+ taskId,
11492
+ worktreePath,
11493
+ err
11494
+ }, "cleanupAllWorktrees: failed to remove worktree");
11495
+ }
11496
+ const branchName = BRANCH_PREFIX + taskId;
11497
+ try {
11498
+ const branchRemoved = await removeBranch(branchName, this._projectRoot);
11499
+ if (branchRemoved) this._logger.debug({
11500
+ taskId,
11501
+ branchName
11502
+ }, "cleanupAllWorktrees: removed orphaned branch");
11503
+ } catch (err) {
11504
+ this._logger.warn({
11505
+ taskId,
11506
+ branchName,
11507
+ err
11508
+ }, "cleanupAllWorktrees: failed to remove branch");
11509
+ }
11510
+ if (worktreeRemoved) cleaned++;
11511
+ }
11512
+ if (cleaned > 0) this._logger.info({ cleaned }, "cleanupAllWorktrees: recovered orphaned worktrees");
11513
+ return cleaned;
11514
+ }
11515
+ async detectConflicts(taskId, targetBranch = "main") {
11516
+ if (!taskId || taskId.trim().length === 0) throw new Error("detectConflicts: taskId must be a non-empty string");
11517
+ const branchName = BRANCH_PREFIX + taskId;
11518
+ const worktreePath = this.getWorktreePath(taskId);
11519
+ this._logger.debug({
11520
+ taskId,
11521
+ branchName,
11522
+ targetBranch
11523
+ }, "detectConflicts");
11524
+ try {
11525
+ await access$1(worktreePath);
11526
+ } catch {
11527
+ throw new Error(`detectConflicts: Worktree for task "${taskId}" not found at "${worktreePath}". The worktree may have already been cleaned up.`);
11528
+ }
11529
+ const mergeClean = await simulateMerge(branchName, this._projectRoot);
11530
+ let conflictingFiles = [];
11531
+ try {
11532
+ if (!mergeClean) conflictingFiles = await getConflictingFiles(this._projectRoot);
11533
+ } finally {
11534
+ await abortMerge(this._projectRoot);
11535
+ }
11536
+ const report = {
11537
+ hasConflicts: !mergeClean || conflictingFiles.length > 0,
11538
+ conflictingFiles,
11539
+ taskId,
11540
+ targetBranch
11541
+ };
11542
+ if (report.hasConflicts) this._eventBus.emit("worktree:conflict", {
11543
+ taskId,
11544
+ branch: branchName,
11545
+ conflictingFiles: report.conflictingFiles
11546
+ });
11547
+ this._logger.info({
11548
+ taskId,
11549
+ hasConflicts: report.hasConflicts,
11550
+ conflictCount: conflictingFiles.length
11551
+ }, "Conflict detection complete");
11552
+ return report;
11553
+ }
11554
+ async mergeWorktree(taskId, targetBranch = "main") {
11555
+ if (!taskId || taskId.trim().length === 0) throw new Error("mergeWorktree: taskId must be a non-empty string");
11556
+ const branchName = BRANCH_PREFIX + taskId;
11557
+ this._logger.debug({
11558
+ taskId,
11559
+ branchName,
11560
+ targetBranch
11561
+ }, "mergeWorktree");
11562
+ const conflictReport = await this.detectConflicts(taskId, targetBranch);
11563
+ if (conflictReport.hasConflicts) {
11564
+ this._logger.info({
11565
+ taskId,
11566
+ conflictCount: conflictReport.conflictingFiles.length
11567
+ }, "Merge skipped due to conflicts");
11568
+ return {
11569
+ success: false,
11570
+ mergedFiles: [],
11571
+ conflicts: conflictReport
11572
+ };
11573
+ }
11574
+ const mergeSuccess = await performMerge(branchName, this._projectRoot);
11575
+ if (!mergeSuccess) throw new Error(`mergeWorktree: git merge --no-ff failed for task "${taskId}" branch "${branchName}"`);
11576
+ const mergedFiles = await getMergedFiles(this._projectRoot);
11577
+ this._eventBus.emit("worktree:merged", {
11578
+ taskId,
11579
+ branch: branchName,
11580
+ mergedFiles
11581
+ });
11582
+ const result = {
11583
+ success: true,
11584
+ mergedFiles
11585
+ };
11586
+ this._logger.info({
11587
+ taskId,
11588
+ branchName,
11589
+ mergedFileCount: mergedFiles.length
11590
+ }, "Worktree merged successfully");
11591
+ return result;
11592
+ }
11593
+ async listWorktrees() {
11594
+ this._logger.debug({
11595
+ projectRoot: this._projectRoot,
11596
+ baseDirectory: this._baseDirectory
11597
+ }, "listWorktrees");
11598
+ const worktreePaths = await getOrphanedWorktrees(this._projectRoot, this._baseDirectory);
11599
+ const results = [];
11600
+ for (const worktreePath of worktreePaths) {
11601
+ const taskId = path$1.basename(worktreePath);
11602
+ const branchName = BRANCH_PREFIX + taskId;
11603
+ let createdAt;
11604
+ try {
11605
+ const { stat: stat$2 } = await import("node:fs/promises");
11606
+ const stats = await stat$2(worktreePath);
11607
+ createdAt = stats.birthtime ?? stats.ctime;
11608
+ } catch {
11609
+ createdAt = new Date();
11610
+ }
11611
+ results.push({
11612
+ taskId,
11613
+ branchName,
11614
+ worktreePath,
11615
+ createdAt
11616
+ });
11617
+ }
11618
+ this._logger.debug({ count: results.length }, "listWorktrees: found worktrees");
11619
+ return results;
11620
+ }
11621
+ /** H4.2: current branch of the parent repo (undefined when detached/unreadable). */
11622
+ async _detectCurrentBranch() {
11623
+ try {
11624
+ const result = await spawnGit([
11625
+ "rev-parse",
11626
+ "--abbrev-ref",
11627
+ "HEAD"
11628
+ ], { cwd: this._projectRoot });
11629
+ const name = result.code === 0 ? result.stdout.trim() : "";
11630
+ return name !== "" && name !== "HEAD" ? name : void 0;
11631
+ } catch {
11632
+ return void 0;
11633
+ }
11634
+ }
11635
+ getWorktreePath(taskId) {
11636
+ return path$1.resolve(this._projectRoot, this._baseDirectory, taskId);
11637
+ }
11638
+ async verifyGitVersion() {
11639
+ try {
11640
+ await verifyGitVersion();
11641
+ } catch (err) {
11642
+ throw new Error(`GitWorktreeManager: git version check failed: ${String(err)}`);
11643
+ }
11644
+ }
11645
+ };
11646
+ function createGitWorktreeManager(options) {
11647
+ return new GitWorktreeManagerImpl(
11648
+ options.eventBus,
11649
+ options.projectRoot,
11650
+ // H4.2: single resolution point — explicit option, else `worktree.base`
11651
+ // from .substrate/config.yaml, else the external default.
11652
+ options.baseDirectory ?? resolveWorktreeBaseDirectory(options.projectRoot),
11653
+ options.db ?? null,
11654
+ options.logger,
11655
+ options.copyFiles
11656
+ );
11657
+ }
11658
+
10774
11659
  //#endregion
10775
11660
  //#region packages/core/dist/version-manager/update-checker.js
10776
11661
  /**
@@ -11089,9 +11974,9 @@ var VersionManagerImpl = class {
11089
11974
  try {
11090
11975
  const _require = createRequire(import.meta.url);
11091
11976
  const fs = _require("fs");
11092
- const path$1 = _require("path");
11977
+ const path$3 = _require("path");
11093
11978
  const yaml$1 = _require("js-yaml");
11094
- const configPath = path$1.join(process.cwd(), ".substrate", "config.yaml");
11979
+ const configPath = path$3.join(process.cwd(), ".substrate", "config.yaml");
11095
11980
  try {
11096
11981
  const raw = fs.readFileSync(configPath, "utf-8");
11097
11982
  configObj = yaml$1.load(raw) ?? {};
@@ -11333,7 +12218,7 @@ function createExperimenter(config, deps) {
11333
12218
  ], { cwd: config.projectRoot });
11334
12219
  return result.stdout.trim();
11335
12220
  }
11336
- async function createWorktree(worktreePath, branchName) {
12221
+ async function createWorktree$1(worktreePath, branchName) {
11337
12222
  const result = await git([
11338
12223
  "worktree",
11339
12224
  "add",
@@ -11343,7 +12228,7 @@ function createExperimenter(config, deps) {
11343
12228
  ], { cwd: config.projectRoot });
11344
12229
  if (result.code !== 0) throw new Error(`Failed to create worktree ${worktreePath}: ${result.stderr}`);
11345
12230
  }
11346
- async function removeWorktree(worktreePath) {
12231
+ async function removeWorktree$1(worktreePath) {
11347
12232
  const result = await git([
11348
12233
  "worktree",
11349
12234
  "remove",
@@ -11453,7 +12338,7 @@ function createExperimenter(config, deps) {
11453
12338
  try {
11454
12339
  currentPhase = "BRANCHING";
11455
12340
  log(`[experimenter] Creating worktree: ${worktreePath} on branch ${branchName}`);
11456
- await createWorktree(worktreePath, branchName);
12341
+ await createWorktree$1(worktreePath, branchName);
11457
12342
  worktreeCreated = true;
11458
12343
  currentPhase = "MODIFYING";
11459
12344
  const promptFile = resolvePromptFile(rec, worktreePath, config.pack);
@@ -11496,7 +12381,7 @@ function createExperimenter(config, deps) {
11496
12381
  log(`[experimenter] Error in phase ${currentPhase}: ${caughtError}`);
11497
12382
  } finally {
11498
12383
  if (worktreeCreated) try {
11499
- await removeWorktree(worktreePath);
12384
+ await removeWorktree$1(worktreePath);
11500
12385
  } catch {
11501
12386
  log(`[experimenter] Warning: could not remove worktree ${worktreePath}`);
11502
12387
  }
@@ -11645,5 +12530,5 @@ async function callLLM(params) {
11645
12530
  }
11646
12531
 
11647
12532
  //#endregion
11648
- 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 };
11649
- //# sourceMappingURL=dist-DT378h1M.js.map
12533
+ 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 };
12534
+ //# sourceMappingURL=dist-C2CAZDep.js.map