@buildautomaton/cli 0.1.49 → 0.1.51

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/cli.js CHANGED
@@ -973,8 +973,8 @@ var require_command = __commonJS({
973
973
  "../../node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/command.js"(exports) {
974
974
  var EventEmitter2 = __require("node:events").EventEmitter;
975
975
  var childProcess2 = __require("node:child_process");
976
- var path48 = __require("node:path");
977
- var fs42 = __require("node:fs");
976
+ var path62 = __require("node:path");
977
+ var fs47 = __require("node:fs");
978
978
  var process8 = __require("node:process");
979
979
  var { Argument: Argument2, humanReadableArgName } = require_argument();
980
980
  var { CommanderError: CommanderError2 } = require_error();
@@ -1906,11 +1906,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
1906
1906
  let launchWithNode = false;
1907
1907
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1908
1908
  function findFile(baseDir, baseName) {
1909
- const localBin = path48.resolve(baseDir, baseName);
1910
- if (fs42.existsSync(localBin)) return localBin;
1911
- if (sourceExt.includes(path48.extname(baseName))) return void 0;
1909
+ const localBin = path62.resolve(baseDir, baseName);
1910
+ if (fs47.existsSync(localBin)) return localBin;
1911
+ if (sourceExt.includes(path62.extname(baseName))) return void 0;
1912
1912
  const foundExt = sourceExt.find(
1913
- (ext) => fs42.existsSync(`${localBin}${ext}`)
1913
+ (ext) => fs47.existsSync(`${localBin}${ext}`)
1914
1914
  );
1915
1915
  if (foundExt) return `${localBin}${foundExt}`;
1916
1916
  return void 0;
@@ -1922,21 +1922,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
1922
1922
  if (this._scriptPath) {
1923
1923
  let resolvedScriptPath;
1924
1924
  try {
1925
- resolvedScriptPath = fs42.realpathSync(this._scriptPath);
1925
+ resolvedScriptPath = fs47.realpathSync(this._scriptPath);
1926
1926
  } catch (err) {
1927
1927
  resolvedScriptPath = this._scriptPath;
1928
1928
  }
1929
- executableDir = path48.resolve(
1930
- path48.dirname(resolvedScriptPath),
1929
+ executableDir = path62.resolve(
1930
+ path62.dirname(resolvedScriptPath),
1931
1931
  executableDir
1932
1932
  );
1933
1933
  }
1934
1934
  if (executableDir) {
1935
1935
  let localFile = findFile(executableDir, executableFile);
1936
1936
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
1937
- const legacyName = path48.basename(
1937
+ const legacyName = path62.basename(
1938
1938
  this._scriptPath,
1939
- path48.extname(this._scriptPath)
1939
+ path62.extname(this._scriptPath)
1940
1940
  );
1941
1941
  if (legacyName !== this._name) {
1942
1942
  localFile = findFile(
@@ -1947,7 +1947,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1947
1947
  }
1948
1948
  executableFile = localFile || executableFile;
1949
1949
  }
1950
- launchWithNode = sourceExt.includes(path48.extname(executableFile));
1950
+ launchWithNode = sourceExt.includes(path62.extname(executableFile));
1951
1951
  let proc;
1952
1952
  if (process8.platform !== "win32") {
1953
1953
  if (launchWithNode) {
@@ -2787,7 +2787,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2787
2787
  * @return {Command}
2788
2788
  */
2789
2789
  nameFromFilename(filename) {
2790
- this._name = path48.basename(filename, path48.extname(filename));
2790
+ this._name = path62.basename(filename, path62.extname(filename));
2791
2791
  return this;
2792
2792
  }
2793
2793
  /**
@@ -2801,9 +2801,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2801
2801
  * @param {string} [path]
2802
2802
  * @return {(string|null|Command)}
2803
2803
  */
2804
- executableDir(path49) {
2805
- if (path49 === void 0) return this._executableDir;
2806
- this._executableDir = path49;
2804
+ executableDir(path63) {
2805
+ if (path63 === void 0) return this._executableDir;
2806
+ this._executableDir = path63;
2807
2807
  return this;
2808
2808
  }
2809
2809
  /**
@@ -7061,8 +7061,8 @@ var init_parseUtil = __esm({
7061
7061
  init_errors();
7062
7062
  init_en();
7063
7063
  makeIssue = (params) => {
7064
- const { data, path: path48, errorMaps, issueData } = params;
7065
- const fullPath = [...path48, ...issueData.path || []];
7064
+ const { data, path: path62, errorMaps, issueData } = params;
7065
+ const fullPath = [...path62, ...issueData.path || []];
7066
7066
  const fullIssue = {
7067
7067
  ...issueData,
7068
7068
  path: fullPath
@@ -7370,11 +7370,11 @@ var init_types = __esm({
7370
7370
  init_parseUtil();
7371
7371
  init_util();
7372
7372
  ParseInputLazyPath = class {
7373
- constructor(parent, value, path48, key) {
7373
+ constructor(parent, value, path62, key) {
7374
7374
  this._cachedPath = [];
7375
7375
  this.parent = parent;
7376
7376
  this.data = value;
7377
- this._path = path48;
7377
+ this._path = path62;
7378
7378
  this._key = key;
7379
7379
  }
7380
7380
  get path() {
@@ -11099,15 +11099,15 @@ function assignProp(target, prop, value) {
11099
11099
  configurable: true
11100
11100
  });
11101
11101
  }
11102
- function getElementAtPath(obj, path48) {
11103
- if (!path48)
11102
+ function getElementAtPath(obj, path62) {
11103
+ if (!path62)
11104
11104
  return obj;
11105
- return path48.reduce((acc, key) => acc?.[key], obj);
11105
+ return path62.reduce((acc, key) => acc?.[key], obj);
11106
11106
  }
11107
11107
  function promiseAllObject(promisesObj) {
11108
11108
  const keys = Object.keys(promisesObj);
11109
- const promises6 = keys.map((key) => promisesObj[key]);
11110
- return Promise.all(promises6).then((results) => {
11109
+ const promises11 = keys.map((key) => promisesObj[key]);
11110
+ return Promise.all(promises11).then((results) => {
11111
11111
  const resolvedObj = {};
11112
11112
  for (let i = 0; i < keys.length; i++) {
11113
11113
  resolvedObj[keys[i]] = results[i];
@@ -11351,11 +11351,11 @@ function aborted(x, startIndex = 0) {
11351
11351
  }
11352
11352
  return false;
11353
11353
  }
11354
- function prefixIssues(path48, issues) {
11354
+ function prefixIssues(path62, issues) {
11355
11355
  return issues.map((iss) => {
11356
11356
  var _a2;
11357
11357
  (_a2 = iss).path ?? (_a2.path = []);
11358
- iss.path.unshift(path48);
11358
+ iss.path.unshift(path62);
11359
11359
  return iss;
11360
11360
  });
11361
11361
  }
@@ -11544,7 +11544,7 @@ function treeifyError(error40, _mapper) {
11544
11544
  return issue2.message;
11545
11545
  };
11546
11546
  const result = { errors: [] };
11547
- const processError = (error41, path48 = []) => {
11547
+ const processError = (error41, path62 = []) => {
11548
11548
  var _a2, _b;
11549
11549
  for (const issue2 of error41.issues) {
11550
11550
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -11554,7 +11554,7 @@ function treeifyError(error40, _mapper) {
11554
11554
  } else if (issue2.code === "invalid_element") {
11555
11555
  processError({ issues: issue2.issues }, issue2.path);
11556
11556
  } else {
11557
- const fullpath = [...path48, ...issue2.path];
11557
+ const fullpath = [...path62, ...issue2.path];
11558
11558
  if (fullpath.length === 0) {
11559
11559
  result.errors.push(mapper(issue2));
11560
11560
  continue;
@@ -11584,9 +11584,9 @@ function treeifyError(error40, _mapper) {
11584
11584
  processError(error40);
11585
11585
  return result;
11586
11586
  }
11587
- function toDotPath(path48) {
11587
+ function toDotPath(path62) {
11588
11588
  const segs = [];
11589
- for (const seg of path48) {
11589
+ for (const seg of path62) {
11590
11590
  if (typeof seg === "number")
11591
11591
  segs.push(`[${seg}]`);
11592
11592
  else if (typeof seg === "symbol")
@@ -24049,8 +24049,8 @@ var init_acp = __esm({
24049
24049
  this.#requestHandler = requestHandler;
24050
24050
  this.#notificationHandler = notificationHandler;
24051
24051
  this.#stream = stream;
24052
- this.#closedPromise = new Promise((resolve22) => {
24053
- this.#abortController.signal.addEventListener("abort", () => resolve22());
24052
+ this.#closedPromise = new Promise((resolve34) => {
24053
+ this.#abortController.signal.addEventListener("abort", () => resolve34());
24054
24054
  });
24055
24055
  this.#receive();
24056
24056
  }
@@ -24199,8 +24199,8 @@ var init_acp = __esm({
24199
24199
  }
24200
24200
  async sendRequest(method, params) {
24201
24201
  const id = this.#nextRequestId++;
24202
- const responsePromise = new Promise((resolve22, reject) => {
24203
- this.#pendingResponses.set(id, { resolve: resolve22, reject });
24202
+ const responsePromise = new Promise((resolve34, reject) => {
24203
+ this.#pendingResponses.set(id, { resolve: resolve34, reject });
24204
24204
  });
24205
24205
  await this.#sendMessage({ jsonrpc: "2.0", id, method, params });
24206
24206
  return responsePromise;
@@ -25069,10 +25069,10 @@ var require_src2 = __commonJS({
25069
25069
  var fs_1 = __require("fs");
25070
25070
  var debug_1 = __importDefault(require_src());
25071
25071
  var log2 = debug_1.default("@kwsites/file-exists");
25072
- function check2(path48, isFile, isDirectory) {
25073
- log2(`checking %s`, path48);
25072
+ function check2(path62, isFile, isDirectory) {
25073
+ log2(`checking %s`, path62);
25074
25074
  try {
25075
- const stat3 = fs_1.statSync(path48);
25075
+ const stat3 = fs_1.statSync(path62);
25076
25076
  if (stat3.isFile() && isFile) {
25077
25077
  log2(`[OK] path represents a file`);
25078
25078
  return true;
@@ -25092,8 +25092,8 @@ var require_src2 = __commonJS({
25092
25092
  throw e;
25093
25093
  }
25094
25094
  }
25095
- function exists2(path48, type = exports.READABLE) {
25096
- return check2(path48, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
25095
+ function exists2(path62, type = exports.READABLE) {
25096
+ return check2(path62, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
25097
25097
  }
25098
25098
  exports.exists = exists2;
25099
25099
  exports.FILE = 1;
@@ -25174,15 +25174,15 @@ var {
25174
25174
  } = import_index.default;
25175
25175
 
25176
25176
  // src/cli-version.ts
25177
- var CLI_VERSION = "0.1.49".length > 0 ? "0.1.49" : "0.0.0-dev";
25177
+ var CLI_VERSION = "0.1.51".length > 0 ? "0.1.51" : "0.0.0-dev";
25178
25178
 
25179
25179
  // src/cli/defaults.ts
25180
25180
  var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
25181
25181
  var DEFAULT_FIREHOSE_URL = "https://buildautomaton-firehose.fly.dev";
25182
25182
 
25183
25183
  // src/cli/run-cli-action.ts
25184
- import * as fs41 from "node:fs";
25185
- import * as path47 from "node:path";
25184
+ import * as fs46 from "node:fs";
25185
+ import * as path61 from "node:path";
25186
25186
 
25187
25187
  // src/cli-log-level.ts
25188
25188
  var verbosity = "info";
@@ -26297,14 +26297,14 @@ var baseOpen = async (options) => {
26297
26297
  }
26298
26298
  const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
26299
26299
  if (options.wait) {
26300
- return new Promise((resolve22, reject) => {
26300
+ return new Promise((resolve34, reject) => {
26301
26301
  subprocess.once("error", reject);
26302
26302
  subprocess.once("close", (exitCode) => {
26303
26303
  if (!options.allowNonzeroExitCode && exitCode > 0) {
26304
26304
  reject(new Error(`Exited with code ${exitCode}`));
26305
26305
  return;
26306
26306
  }
26307
- resolve22(subprocess);
26307
+ resolve34(subprocess);
26308
26308
  });
26309
26309
  });
26310
26310
  }
@@ -26848,8 +26848,8 @@ function runPendingAuth(options) {
26848
26848
  let hasOpenedBrowser = false;
26849
26849
  let resolved = false;
26850
26850
  let resolveAuth;
26851
- const authPromise = new Promise((resolve22) => {
26852
- resolveAuth = resolve22;
26851
+ const authPromise = new Promise((resolve34) => {
26852
+ resolveAuth = resolve34;
26853
26853
  });
26854
26854
  let reconnectAttempt = 0;
26855
26855
  const signInQuiet = createEmptyReconnectQuietSlot();
@@ -27000,7 +27000,7 @@ import sqliteWasm from "node-sqlite3-wasm";
27000
27000
 
27001
27001
  // src/runtime/yield-to-event-loop.ts
27002
27002
  function yieldToEventLoop() {
27003
- return new Promise((resolve22) => setImmediate(resolve22));
27003
+ return new Promise((resolve34) => setImmediate(resolve34));
27004
27004
  }
27005
27005
 
27006
27006
  // src/sqlite/cli-sqlite-paths.ts
@@ -28117,9 +28117,9 @@ function parseChangeSummaryJson(raw, allowedPaths, options) {
28117
28117
  const rawPath = typeof o.path === "string" ? o.path.trim() : "";
28118
28118
  const summary = typeof o.summary === "string" ? o.summary.trim() : "";
28119
28119
  if (!rawPath || !summary) continue;
28120
- const path48 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
28121
- if (!path48) continue;
28122
- rows.push({ path: path48, summary: clampSummaryToAtMostTwoLines(summary) });
28120
+ const path62 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
28121
+ if (!path62) continue;
28122
+ rows.push({ path: path62, summary: clampSummaryToAtMostTwoLines(summary) });
28123
28123
  }
28124
28124
  return rows;
28125
28125
  }
@@ -28337,7 +28337,7 @@ var GitRepoMetaSchema = external_exports.object({
28337
28337
  updatedAt: external_exports.string()
28338
28338
  });
28339
28339
 
28340
- // ../types/src/bridge-mcp-tool-display.ts
28340
+ // ../types/src/mcp/bridge-mcp-constants.ts
28341
28341
  var GET_INTEGRATION_CONTENT_TOOL = "get_integration_content";
28342
28342
  var LIST_PARENT_SESSION_PROMPTS_TOOL = "list_parent_session_prompts";
28343
28343
  var GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL = "get_parent_session_turn_transcript";
@@ -28631,10 +28631,10 @@ async function killChildProcessTreeGracefully(child, graceMs = ACP_PROCESS_TREE_
28631
28631
  }
28632
28632
  await killProcessTree(pid, "SIGTERM");
28633
28633
  if (graceMs <= 0) return;
28634
- const exited = new Promise((resolve22) => {
28635
- child.once("exit", () => resolve22());
28634
+ const exited = new Promise((resolve34) => {
28635
+ child.once("exit", () => resolve34());
28636
28636
  });
28637
- await Promise.race([exited, new Promise((resolve22) => setTimeout(resolve22, graceMs))]);
28637
+ await Promise.race([exited, new Promise((resolve34) => setTimeout(resolve34, graceMs))]);
28638
28638
  if (child.exitCode == null && child.signalCode == null) {
28639
28639
  await killProcessTree(pid, "SIGKILL");
28640
28640
  }
@@ -28651,8 +28651,8 @@ async function gracefulAcpSubprocessDisconnect(params) {
28651
28651
  await transport.closeSession?.(sessionId);
28652
28652
  } catch {
28653
28653
  }
28654
- await new Promise((resolve22) => {
28655
- const timer = setTimeout(resolve22, ACP_GRACEFUL_SESSION_CLOSE_WAIT_MS);
28654
+ await new Promise((resolve34) => {
28655
+ const timer = setTimeout(resolve34, ACP_GRACEFUL_SESSION_CLOSE_WAIT_MS);
28656
28656
  timer.unref?.();
28657
28657
  });
28658
28658
  }
@@ -28927,11 +28927,11 @@ function createSdkStdioExtNotificationHandler(options) {
28927
28927
  // src/agents/acp/clients/sdk/sdk-stdio-permission-request-handshake.ts
28928
28928
  function awaitSdkStdioPermissionRequestHandshake(params) {
28929
28929
  const { requestId, paramsRecord, pending, onRequest } = params;
28930
- return new Promise((resolve22) => {
28931
- pending.set(requestId, { resolve: resolve22, params: paramsRecord });
28930
+ return new Promise((resolve34) => {
28931
+ pending.set(requestId, { resolve: resolve34, params: paramsRecord });
28932
28932
  if (onRequest == null) {
28933
28933
  pending.delete(requestId);
28934
- resolve22({ outcome: { outcome: "denied" } });
28934
+ resolve34({ outcome: { outcome: "denied" } });
28935
28935
  return;
28936
28936
  }
28937
28937
  try {
@@ -29441,10 +29441,10 @@ function createSdkStdioInitSettle(child) {
29441
29441
  forceAcpSubprocessDisconnect(child);
29442
29442
  reject(err);
29443
29443
  },
29444
- settleResolve(resolve22, handle) {
29444
+ settleResolve(resolve34, handle) {
29445
29445
  if (initSettled) return;
29446
29446
  initSettled = true;
29447
- resolve22(handle);
29447
+ resolve34(handle);
29448
29448
  }
29449
29449
  };
29450
29450
  }
@@ -29513,7 +29513,7 @@ async function createSdkStdioAcpClient(options) {
29513
29513
  onFileChange,
29514
29514
  stderrCapture
29515
29515
  });
29516
- return new Promise((resolve22, reject) => {
29516
+ return new Promise((resolve34, reject) => {
29517
29517
  const init = createSdkStdioInitSettle(child);
29518
29518
  child.on("error", (err) => {
29519
29519
  init.settleReject(reject, new Error(formatSpawnError(err, command[0])));
@@ -29538,7 +29538,7 @@ async function createSdkStdioAcpClient(options) {
29538
29538
  protocolVersion: PROTOCOL_VERSION2
29539
29539
  });
29540
29540
  init.settleResolve(
29541
- resolve22,
29541
+ resolve34,
29542
29542
  createSdkStdioHandle({
29543
29543
  child,
29544
29544
  sessionId: established.sessionId,
@@ -30071,7 +30071,7 @@ async function createCursorAcpClient(options) {
30071
30071
  onFileChange,
30072
30072
  stderrCapture
30073
30073
  });
30074
- return new Promise((resolve22, reject) => {
30074
+ return new Promise((resolve34, reject) => {
30075
30075
  child.on("error", (err) => {
30076
30076
  killChildProcessTree(child, "SIGKILL");
30077
30077
  reject(new Error(formatSpawnError(err, command[0])));
@@ -30083,7 +30083,7 @@ async function createCursorAcpClient(options) {
30083
30083
  sessionCtx,
30084
30084
  incomingDeps: { dbgFs, sessionCtx, onSessionUpdate, onRequest }
30085
30085
  });
30086
- resolve22(
30086
+ resolve34(
30087
30087
  createCursorAcpHandle({
30088
30088
  child,
30089
30089
  sessionId: established.sessionId,
@@ -31021,7 +31021,7 @@ function computeAcpAgentKey(preferredAgentType, mode, agentConfig) {
31021
31021
  }
31022
31022
 
31023
31023
  // src/mcp/bridge-mcp-constants.ts
31024
- var BRIDGE_MCP_SERVER_NAME = "buildautomaton-bridge";
31024
+ var BRIDGE_MCP_SERVER_NAME2 = "buildautomaton-bridge";
31025
31025
 
31026
31026
  // src/mcp/resolve-bridge-mcp-server-path.ts
31027
31027
  import { dirname as dirname4, join as join3 } from "node:path";
@@ -31036,7 +31036,7 @@ function buildAcpMcpServers(bridgeAccessPort) {
31036
31036
  if (!Number.isFinite(bridgeAccessPort) || bridgeAccessPort <= 0) return [];
31037
31037
  return [
31038
31038
  {
31039
- name: BRIDGE_MCP_SERVER_NAME,
31039
+ name: BRIDGE_MCP_SERVER_NAME2,
31040
31040
  command: process.execPath,
31041
31041
  args: [resolveBridgeMcpServerScriptPath(), String(bridgeAccessPort)],
31042
31042
  env: []
@@ -31289,8 +31289,8 @@ function pathspec(...paths) {
31289
31289
  cache.set(key, paths);
31290
31290
  return key;
31291
31291
  }
31292
- function isPathSpec(path48) {
31293
- return path48 instanceof String && cache.has(path48);
31292
+ function isPathSpec(path62) {
31293
+ return path62 instanceof String && cache.has(path62);
31294
31294
  }
31295
31295
  function toPaths(pathSpec) {
31296
31296
  return cache.get(pathSpec) || [];
@@ -31379,8 +31379,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
31379
31379
  function forEachLineWithContent(input, callback) {
31380
31380
  return toLinesWithContent(input, true).map((line) => callback(line));
31381
31381
  }
31382
- function folderExists(path48) {
31383
- return (0, import_file_exists.exists)(path48, import_file_exists.FOLDER);
31382
+ function folderExists(path62) {
31383
+ return (0, import_file_exists.exists)(path62, import_file_exists.FOLDER);
31384
31384
  }
31385
31385
  function append(target, item) {
31386
31386
  if (Array.isArray(target)) {
@@ -31784,8 +31784,8 @@ function checkIsRepoRootTask() {
31784
31784
  commands,
31785
31785
  format: "utf-8",
31786
31786
  onError,
31787
- parser(path48) {
31788
- return /^\.(git)?$/.test(path48.trim());
31787
+ parser(path62) {
31788
+ return /^\.(git)?$/.test(path62.trim());
31789
31789
  }
31790
31790
  };
31791
31791
  }
@@ -32219,11 +32219,11 @@ function parseGrep(grep) {
32219
32219
  const paths = /* @__PURE__ */ new Set();
32220
32220
  const results = {};
32221
32221
  forEachLineWithContent(grep, (input) => {
32222
- const [path48, line, preview] = input.split(NULL);
32223
- paths.add(path48);
32224
- (results[path48] = results[path48] || []).push({
32222
+ const [path62, line, preview] = input.split(NULL);
32223
+ paths.add(path62);
32224
+ (results[path62] = results[path62] || []).push({
32225
32225
  line: asNumber(line),
32226
- path: path48,
32226
+ path: path62,
32227
32227
  preview
32228
32228
  });
32229
32229
  });
@@ -32988,14 +32988,14 @@ var init_hash_object = __esm2({
32988
32988
  init_task();
32989
32989
  }
32990
32990
  });
32991
- function parseInit(bare, path48, text) {
32991
+ function parseInit(bare, path62, text) {
32992
32992
  const response = String(text).trim();
32993
32993
  let result;
32994
32994
  if (result = initResponseRegex.exec(response)) {
32995
- return new InitSummary(bare, path48, false, result[1]);
32995
+ return new InitSummary(bare, path62, false, result[1]);
32996
32996
  }
32997
32997
  if (result = reInitResponseRegex.exec(response)) {
32998
- return new InitSummary(bare, path48, true, result[1]);
32998
+ return new InitSummary(bare, path62, true, result[1]);
32999
32999
  }
33000
33000
  let gitDir = "";
33001
33001
  const tokens = response.split(" ");
@@ -33006,7 +33006,7 @@ function parseInit(bare, path48, text) {
33006
33006
  break;
33007
33007
  }
33008
33008
  }
33009
- return new InitSummary(bare, path48, /^re/i.test(response), gitDir);
33009
+ return new InitSummary(bare, path62, /^re/i.test(response), gitDir);
33010
33010
  }
33011
33011
  var InitSummary;
33012
33012
  var initResponseRegex;
@@ -33015,9 +33015,9 @@ var init_InitSummary = __esm2({
33015
33015
  "src/lib/responses/InitSummary.ts"() {
33016
33016
  "use strict";
33017
33017
  InitSummary = class {
33018
- constructor(bare, path48, existing, gitDir) {
33018
+ constructor(bare, path62, existing, gitDir) {
33019
33019
  this.bare = bare;
33020
- this.path = path48;
33020
+ this.path = path62;
33021
33021
  this.existing = existing;
33022
33022
  this.gitDir = gitDir;
33023
33023
  }
@@ -33029,7 +33029,7 @@ var init_InitSummary = __esm2({
33029
33029
  function hasBareCommand(command) {
33030
33030
  return command.includes(bareCommand);
33031
33031
  }
33032
- function initTask(bare = false, path48, customArgs) {
33032
+ function initTask(bare = false, path62, customArgs) {
33033
33033
  const commands = ["init", ...customArgs];
33034
33034
  if (bare && !hasBareCommand(commands)) {
33035
33035
  commands.splice(1, 0, bareCommand);
@@ -33038,7 +33038,7 @@ function initTask(bare = false, path48, customArgs) {
33038
33038
  commands,
33039
33039
  format: "utf-8",
33040
33040
  parser(text) {
33041
- return parseInit(commands.includes("--bare"), path48, text);
33041
+ return parseInit(commands.includes("--bare"), path62, text);
33042
33042
  }
33043
33043
  };
33044
33044
  }
@@ -33854,12 +33854,12 @@ var init_FileStatusSummary = __esm2({
33854
33854
  "use strict";
33855
33855
  fromPathRegex = /^(.+)\0(.+)$/;
33856
33856
  FileStatusSummary = class {
33857
- constructor(path48, index, working_dir) {
33858
- this.path = path48;
33857
+ constructor(path62, index, working_dir) {
33858
+ this.path = path62;
33859
33859
  this.index = index;
33860
33860
  this.working_dir = working_dir;
33861
33861
  if (index === "R" || working_dir === "R") {
33862
- const detail = fromPathRegex.exec(path48) || [null, path48, path48];
33862
+ const detail = fromPathRegex.exec(path62) || [null, path62, path62];
33863
33863
  this.from = detail[2] || "";
33864
33864
  this.path = detail[1] || "";
33865
33865
  }
@@ -33890,14 +33890,14 @@ function splitLine(result, lineStr) {
33890
33890
  default:
33891
33891
  return;
33892
33892
  }
33893
- function data(index, workingDir, path48) {
33893
+ function data(index, workingDir, path62) {
33894
33894
  const raw = `${index}${workingDir}`;
33895
33895
  const handler = parsers6.get(raw);
33896
33896
  if (handler) {
33897
- handler(result, path48);
33897
+ handler(result, path62);
33898
33898
  }
33899
33899
  if (raw !== "##" && raw !== "!!") {
33900
- result.files.push(new FileStatusSummary(path48, index, workingDir));
33900
+ result.files.push(new FileStatusSummary(path62, index, workingDir));
33901
33901
  }
33902
33902
  }
33903
33903
  }
@@ -34206,9 +34206,9 @@ var init_simple_git_api = __esm2({
34206
34206
  next
34207
34207
  );
34208
34208
  }
34209
- hashObject(path48, write) {
34209
+ hashObject(path62, write) {
34210
34210
  return this._runTask(
34211
- hashObjectTask(path48, write === true),
34211
+ hashObjectTask(path62, write === true),
34212
34212
  trailingFunctionArgument(arguments)
34213
34213
  );
34214
34214
  }
@@ -34561,8 +34561,8 @@ var init_branch = __esm2({
34561
34561
  }
34562
34562
  });
34563
34563
  function toPath(input) {
34564
- const path48 = input.trim().replace(/^["']|["']$/g, "");
34565
- return path48 && normalize3(path48);
34564
+ const path62 = input.trim().replace(/^["']|["']$/g, "");
34565
+ return path62 && normalize3(path62);
34566
34566
  }
34567
34567
  var parseCheckIgnore;
34568
34568
  var init_CheckIgnore = __esm2({
@@ -34876,8 +34876,8 @@ __export2(sub_module_exports, {
34876
34876
  subModuleTask: () => subModuleTask,
34877
34877
  updateSubModuleTask: () => updateSubModuleTask
34878
34878
  });
34879
- function addSubModuleTask(repo, path48) {
34880
- return subModuleTask(["add", repo, path48]);
34879
+ function addSubModuleTask(repo, path62) {
34880
+ return subModuleTask(["add", repo, path62]);
34881
34881
  }
34882
34882
  function initSubModuleTask(customArgs) {
34883
34883
  return subModuleTask(["init", ...customArgs]);
@@ -35210,8 +35210,8 @@ var require_git = __commonJS2({
35210
35210
  }
35211
35211
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
35212
35212
  };
35213
- Git2.prototype.submoduleAdd = function(repo, path48, then) {
35214
- return this._runTask(addSubModuleTask2(repo, path48), trailingFunctionArgument2(arguments));
35213
+ Git2.prototype.submoduleAdd = function(repo, path62, then) {
35214
+ return this._runTask(addSubModuleTask2(repo, path62), trailingFunctionArgument2(arguments));
35215
35215
  };
35216
35216
  Git2.prototype.submoduleUpdate = function(args, then) {
35217
35217
  return this._runTask(
@@ -35848,14 +35848,14 @@ async function runGitTask(fn) {
35848
35848
  }
35849
35849
  async function yieldToEventLoop2() {
35850
35850
  throwIfGitShutdownRequested();
35851
- await new Promise((resolve22) => {
35852
- setImmediate(resolve22);
35851
+ await new Promise((resolve34) => {
35852
+ setImmediate(resolve34);
35853
35853
  });
35854
35854
  throwIfGitShutdownRequested();
35855
35855
  }
35856
35856
  async function forEachWithGitYield(items, fn) {
35857
35857
  for (let i = 0; i < items.length; i++) {
35858
- if (i > 0 || items.length > 1) await yieldToEventLoop2();
35858
+ await yieldToEventLoop2();
35859
35859
  await fn(items[i], i);
35860
35860
  }
35861
35861
  }
@@ -35889,11 +35889,8 @@ function cliSimpleGit(baseDir) {
35889
35889
  async function getRemoteOriginUrl(gitDir) {
35890
35890
  try {
35891
35891
  const git = cliSimpleGit(gitDir);
35892
- const remotes = await git.getRemotes(true);
35893
- const list = Array.isArray(remotes) ? remotes : [];
35894
- const origin = list.find((r) => r.name === "origin");
35895
- if (!origin?.refs?.fetch && !origin?.refs?.push) return "";
35896
- return (origin.refs.fetch ?? origin.refs.push ?? "").trim();
35892
+ const out = await git.raw(["config", "--get", "remote.origin.url"]);
35893
+ return String(out).trim();
35897
35894
  } catch {
35898
35895
  return "";
35899
35896
  }
@@ -35909,6 +35906,12 @@ async function isGitRepoDirectory(dirPath) {
35909
35906
  }
35910
35907
 
35911
35908
  // src/git/discover-repos.ts
35909
+ var GIT_DISCOVER_YIELD_EVERY = 32;
35910
+ async function yieldGitDiscoverWork(entryCount) {
35911
+ if (entryCount > 0 && entryCount % GIT_DISCOVER_YIELD_EVERY === 0) {
35912
+ await yieldToEventLoop();
35913
+ }
35914
+ }
35912
35915
  async function discoverGitRepos(cwd = getBridgeRoot()) {
35913
35916
  const result = [];
35914
35917
  const cwdResolved = path17.resolve(cwd);
@@ -35922,7 +35925,9 @@ async function discoverGitRepos(cwd = getBridgeRoot()) {
35922
35925
  } catch {
35923
35926
  return result;
35924
35927
  }
35925
- for (const ent of entries) {
35928
+ for (let i = 0; i < entries.length; i++) {
35929
+ await yieldGitDiscoverWork(i + 1);
35930
+ const ent = entries[i];
35926
35931
  if (!ent.isDirectory()) continue;
35927
35932
  const childPath = path17.join(cwdResolved, ent.name);
35928
35933
  if (await isGitRepoDirectory(childPath)) {
@@ -35935,6 +35940,7 @@ async function discoverGitRepos(cwd = getBridgeRoot()) {
35935
35940
  async function discoverGitReposUnderRoot(rootPath) {
35936
35941
  const root = path17.resolve(rootPath);
35937
35942
  const roots = [];
35943
+ let walkEntries = 0;
35938
35944
  async function walk(dir) {
35939
35945
  if (await isGitRepoDirectory(dir)) {
35940
35946
  roots.push(path17.resolve(dir));
@@ -35946,7 +35952,9 @@ async function discoverGitReposUnderRoot(rootPath) {
35946
35952
  } catch {
35947
35953
  return;
35948
35954
  }
35949
- for (const ent of entries) {
35955
+ for (let i = 0; i < entries.length; i++) {
35956
+ await yieldGitDiscoverWork(++walkEntries);
35957
+ const ent = entries[i];
35950
35958
  if (!ent.isDirectory() || ent.name === ".git") continue;
35951
35959
  await walk(path17.join(dir, ent.name));
35952
35960
  }
@@ -35954,7 +35962,9 @@ async function discoverGitReposUnderRoot(rootPath) {
35954
35962
  await walk(root);
35955
35963
  const uniq = [...new Set(roots)];
35956
35964
  const out = [];
35957
- for (const p of uniq) {
35965
+ for (let i = 0; i < uniq.length; i++) {
35966
+ await yieldGitDiscoverWork(i + 1);
35967
+ const p = uniq[i];
35958
35968
  const remoteUrl = await getRemoteOriginUrl(p);
35959
35969
  out.push({ absolutePath: p, remoteUrl });
35960
35970
  }
@@ -36174,9 +36184,9 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
36174
36184
  // src/agents/acp/put-summarize-change-summaries.ts
36175
36185
  async function putEncryptedChangeSummaryRows(params) {
36176
36186
  const base = params.apiBaseUrl.replace(/\/+$/, "");
36177
- const entries = params.rows.map(({ path: path48, summary }) => {
36187
+ const entries = params.rows.map(({ path: path62, summary }) => {
36178
36188
  const enc = params.e2ee.encryptFields({ summary }, ["summary"]);
36179
- return { path: path48, summary: JSON.stringify(enc) };
36189
+ return { path: path62, summary: JSON.stringify(enc) };
36180
36190
  });
36181
36191
  const res = await fetch(
36182
36192
  `${base}/internal/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
@@ -36530,7 +36540,7 @@ function buildIntegrationContentAgentPromptNote(sessionId) {
36530
36540
  "Use the get_integration_content MCP tool on the buildautomaton-bridge server to fetch the full content of each link.",
36531
36541
  `When calling the tool, pass sessionId "${sessionRef}" and the URL as url.`,
36532
36542
  "By default the tool links the fetched URL as this session external ticket when the session has none yet. Pass associateAsSessionTicket: false when fetching additional links for context only (after the primary ticket is linked, or when you do not want association).",
36533
- "If the tool reports unsupported_url, do not retry with the same URL. If it reports temporarily_unavailable, you may retry later."
36543
+ "If the tool reports unsupported_url, do not retry with the same URL. If it reports integration_reconnect_required, ask the user to reconnect the integration in workspace settings. If it reports temporarily_unavailable, you may retry later."
36534
36544
  ].join("\n");
36535
36545
  }
36536
36546
  function injectIntegrationContentAgentPromptNote(agentPrompt, sessionId) {
@@ -36565,7 +36575,7 @@ async function sendPromptToAgent(options) {
36565
36575
  runId,
36566
36576
  agentType,
36567
36577
  agentCwd,
36568
- sendResult: sendResult2,
36578
+ sendResult,
36569
36579
  sendSessionUpdate,
36570
36580
  log: log2,
36571
36581
  followUpCatalogPromptId,
@@ -36595,7 +36605,7 @@ async function sendPromptToAgent(options) {
36595
36605
  let sendOpts = {};
36596
36606
  if (attachments && attachments.length > 0) {
36597
36607
  if (!sessionId || !cloudApiBaseUrl || !getCloudAccessToken) {
36598
- sendResult2({
36608
+ sendResult({
36599
36609
  type: "prompt_result",
36600
36610
  id: promptId,
36601
36611
  ...sessionId ? { sessionId } : {},
@@ -36616,7 +36626,7 @@ async function sendPromptToAgent(options) {
36616
36626
  log: log2
36617
36627
  });
36618
36628
  if (!resolved.ok) {
36619
- sendResult2({
36629
+ sendResult({
36620
36630
  type: "prompt_result",
36621
36631
  id: promptId,
36622
36632
  ...sessionId ? { sessionId } : {},
@@ -36655,7 +36665,7 @@ async function sendPromptToAgent(options) {
36655
36665
  const errStr = typeof result.error === "string" ? result.error : void 0;
36656
36666
  const resultStop = typeof result.stopReason === "string" ? result.stopReason.trim() : "";
36657
36667
  const cancelledByAgent = resultStop.toLowerCase() === "cancelled" || errStr != null && isUserEndedSessionTurnErrorText(errStr);
36658
- sendResult2({
36668
+ sendResult({
36659
36669
  type: "prompt_result",
36660
36670
  id: promptId,
36661
36671
  ...sessionId ? { sessionId } : {},
@@ -36677,7 +36687,7 @@ async function sendPromptToAgent(options) {
36677
36687
  } catch (err) {
36678
36688
  const errMsg = err instanceof Error ? err.message : String(err);
36679
36689
  log2(`[Agent] Send failed: ${errMsg}`);
36680
- sendResult2({
36690
+ sendResult({
36681
36691
  type: "prompt_result",
36682
36692
  id: promptId,
36683
36693
  ...sessionId ? { sessionId } : {},
@@ -36711,7 +36721,7 @@ function handlePrompt(ctx, opts) {
36711
36721
  agentType,
36712
36722
  agentConfig,
36713
36723
  sessionParentPath,
36714
- sendResult: sendResult2,
36724
+ sendResult,
36715
36725
  sendSessionUpdate,
36716
36726
  followUpCatalogPromptId,
36717
36727
  sessionChangeSummaryFilePaths,
@@ -36728,7 +36738,7 @@ function handlePrompt(ctx, opts) {
36728
36738
  const activeRunId = runId;
36729
36739
  const acpAgentKey = computeAcpAgentKey(preferredForPrompt, mode, agentConfig ?? null);
36730
36740
  if (!acpAgentKey) {
36731
- sendResult2({
36741
+ sendResult({
36732
36742
  type: "prompt_result",
36733
36743
  id: promptId,
36734
36744
  ...sessionId ? { sessionId } : {},
@@ -36765,7 +36775,7 @@ function handlePrompt(ctx, opts) {
36765
36775
  const evaluated = Boolean(preferredForPrompt && errMsg.trim());
36766
36776
  const suggestsAuth = evaluated ? localAgentErrorSuggestsAuth(preferredForPrompt, errMsg) : false;
36767
36777
  const auth = suggestsAuth && preferredForPrompt ? { agentAuthRequired: true, agentType: preferredForPrompt } : {};
36768
- sendResult2({
36778
+ sendResult({
36769
36779
  type: "prompt_result",
36770
36780
  id: promptId,
36771
36781
  ...sessionId ? { sessionId } : {},
@@ -36785,7 +36795,7 @@ function handlePrompt(ctx, opts) {
36785
36795
  await handle.cancel?.();
36786
36796
  } catch {
36787
36797
  }
36788
- sendResult2({
36798
+ sendResult({
36789
36799
  type: "prompt_result",
36790
36800
  id: promptId,
36791
36801
  ...sessionId ? { sessionId } : {},
@@ -36806,7 +36816,7 @@ function handlePrompt(ctx, opts) {
36806
36816
  runId: activeRunId,
36807
36817
  agentType: preferredForPrompt,
36808
36818
  agentCwd: resolveSessionParentPathForAgentProcess(sessionParentPath),
36809
- sendResult: sendResult2,
36819
+ sendResult,
36810
36820
  sendSessionUpdate,
36811
36821
  log: ctx.log,
36812
36822
  followUpCatalogPromptId,
@@ -36862,6 +36872,496 @@ async function createAcpManager(options) {
36862
36872
  };
36863
36873
  }
36864
36874
 
36875
+ // src/worktrees/worktree-layout-file.ts
36876
+ import * as fs17 from "node:fs";
36877
+ import * as path20 from "node:path";
36878
+ import os7 from "node:os";
36879
+ var LAYOUT_FILENAME = "worktree-launcher-layout.json";
36880
+ function defaultWorktreeLayoutPath() {
36881
+ return path20.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
36882
+ }
36883
+ function normalizeLoadedLayout(raw) {
36884
+ if (raw && typeof raw === "object" && "launcherCwds" in raw) {
36885
+ const j = raw;
36886
+ if (Array.isArray(j.launcherCwds)) return { launcherCwds: j.launcherCwds };
36887
+ }
36888
+ return { launcherCwds: [] };
36889
+ }
36890
+ function loadWorktreeLayout() {
36891
+ try {
36892
+ const p = defaultWorktreeLayoutPath();
36893
+ if (!fs17.existsSync(p)) return { launcherCwds: [] };
36894
+ const raw = JSON.parse(fs17.readFileSync(p, "utf8"));
36895
+ return normalizeLoadedLayout(raw);
36896
+ } catch {
36897
+ return { launcherCwds: [] };
36898
+ }
36899
+ }
36900
+ function saveWorktreeLayout(layout) {
36901
+ try {
36902
+ const dir = path20.dirname(defaultWorktreeLayoutPath());
36903
+ fs17.mkdirSync(dir, { recursive: true });
36904
+ fs17.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
36905
+ } catch {
36906
+ }
36907
+ }
36908
+ function baseNameSafe(pathString) {
36909
+ return path20.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
36910
+ }
36911
+ function getLauncherDirNameIfPresent(layout, bridgeRootPath2) {
36912
+ const norm = path20.resolve(bridgeRootPath2);
36913
+ const existing = layout.launcherCwds.find((e) => path20.resolve(e.absolutePath) === norm);
36914
+ return existing?.dirName;
36915
+ }
36916
+ function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
36917
+ const existing = getLauncherDirNameIfPresent(layout, bridgeRootPath2);
36918
+ if (existing) return existing;
36919
+ const norm = path20.resolve(bridgeRootPath2);
36920
+ const base = baseNameSafe(norm);
36921
+ const used = new Set(layout.launcherCwds.map((e) => e.dirName));
36922
+ let name = base;
36923
+ let n = 2;
36924
+ while (used.has(name)) {
36925
+ name = `${base}-${n}`;
36926
+ n += 1;
36927
+ }
36928
+ layout.launcherCwds.push({ absolutePath: norm, dirName: name });
36929
+ saveWorktreeLayout(layout);
36930
+ return name;
36931
+ }
36932
+
36933
+ // src/worktrees/manager/session-worktree-cache.ts
36934
+ import * as path21 from "node:path";
36935
+ var SessionWorktreeCache = class {
36936
+ sessionRepoCheckoutPaths = /* @__PURE__ */ new Map();
36937
+ sessionParentPathBySession = /* @__PURE__ */ new Map();
36938
+ sessionWorkingTreeRelRootBySession = /* @__PURE__ */ new Map();
36939
+ remember(sessionId, binding) {
36940
+ const paths = binding.repoCheckoutPaths.map((p) => path21.resolve(p));
36941
+ this.sessionRepoCheckoutPaths.set(sessionId, paths);
36942
+ this.sessionParentPathBySession.set(sessionId, path21.resolve(binding.sessionParentPath));
36943
+ this.sessionWorkingTreeRelRootBySession.set(sessionId, path21.resolve(binding.workingTreeRelRoot));
36944
+ }
36945
+ clearSession(sessionId) {
36946
+ const paths = this.sessionRepoCheckoutPaths.get(sessionId);
36947
+ this.sessionRepoCheckoutPaths.delete(sessionId);
36948
+ this.sessionParentPathBySession.delete(sessionId);
36949
+ this.sessionWorkingTreeRelRootBySession.delete(sessionId);
36950
+ return paths;
36951
+ }
36952
+ getSessionParentPath(sessionId) {
36953
+ return this.sessionParentPathBySession.get(sessionId);
36954
+ }
36955
+ getWorkingTreeRelRoot(sessionId) {
36956
+ return this.sessionWorkingTreeRelRootBySession.get(sessionId) ?? null;
36957
+ }
36958
+ hasSession(sessionId) {
36959
+ return this.sessionParentPathBySession.has(sessionId);
36960
+ }
36961
+ getRepoCheckoutPaths(sessionId) {
36962
+ const paths = this.sessionRepoCheckoutPaths.get(sessionId);
36963
+ return paths?.length ? [...paths] : void 0;
36964
+ }
36965
+ getRepoCheckoutPathsRef(sessionId) {
36966
+ const paths = this.sessionRepoCheckoutPaths.get(sessionId);
36967
+ return paths?.length ? paths : void 0;
36968
+ }
36969
+ isLegacyNestedLayout(sessionId) {
36970
+ const parent = this.sessionParentPathBySession.get(sessionId);
36971
+ const relRoot = this.sessionWorkingTreeRelRootBySession.get(sessionId);
36972
+ if (!parent || !relRoot) return false;
36973
+ return path21.resolve(parent) !== path21.resolve(relRoot);
36974
+ }
36975
+ };
36976
+
36977
+ // src/worktrees/manager/session-worktree-manager-context.ts
36978
+ function createSessionWorktreeManagerContext(options) {
36979
+ return {
36980
+ worktreesRootPath: options.worktreesRootPath,
36981
+ log: options.log,
36982
+ cache: new SessionWorktreeCache(),
36983
+ layout: loadWorktreeLayout()
36984
+ };
36985
+ }
36986
+
36987
+ // src/git/branches/commit-and-push.ts
36988
+ async function gitCommitAllIfDirty(repoDir, message, options) {
36989
+ const g = cliSimpleGit(repoDir);
36990
+ const st = await g.status();
36991
+ if (!st.files?.length) return;
36992
+ const branch = options.branch.trim();
36993
+ if (!branch) {
36994
+ throw new Error("Branch name is required");
36995
+ }
36996
+ const branches = await g.branchLocal();
36997
+ const localNames = new Set(branches.all.map((b) => b.replace(/^\*\s*/, "").trim()));
36998
+ if (!localNames.has(branch)) {
36999
+ await g.checkoutLocalBranch(branch);
37000
+ } else {
37001
+ await g.checkout(branch);
37002
+ }
37003
+ await g.add(".");
37004
+ await g.commit(message);
37005
+ if (options.push) {
37006
+ await g.push(["-u", "origin", branch]);
37007
+ }
37008
+ }
37009
+
37010
+ // src/worktrees/commit-session-worktrees.ts
37011
+ async function commitSessionWorktrees(options) {
37012
+ const { paths, branch, message, push } = options;
37013
+ try {
37014
+ for (const wt of paths) {
37015
+ await gitCommitAllIfDirty(wt, message, { push, branch });
37016
+ }
37017
+ return { ok: true };
37018
+ } catch (e) {
37019
+ const err = e instanceof Error ? e.message : String(e);
37020
+ return { ok: false, error: err };
37021
+ }
37022
+ }
37023
+
37024
+ // src/worktrees/manager/git/commit-session-worktree.ts
37025
+ async function commitSessionWorktree(cache2, params) {
37026
+ const paths = cache2.getRepoCheckoutPathsRef(params.sessionId);
37027
+ const targets = paths?.length ? paths : [getBridgeRoot()];
37028
+ return commitSessionWorktrees({
37029
+ paths: targets,
37030
+ branch: params.branch,
37031
+ message: params.message,
37032
+ push: params.push
37033
+ });
37034
+ }
37035
+
37036
+ // src/git/changes/parse/normalize-git-diff-path.ts
37037
+ function normalizeGitDiffPath(path62) {
37038
+ return path62.replace(/\\/g, "/").replace(/\/ +/g, "/").trim();
37039
+ }
37040
+
37041
+ // src/git/changes/parse/expand-git-rename-numstat-path.ts
37042
+ function expandGitRenameNumstatPath(rawPath) {
37043
+ const open2 = rawPath.indexOf("{");
37044
+ const arrow = rawPath.indexOf("=>");
37045
+ const close = rawPath.lastIndexOf("}");
37046
+ if (open2 === -1 || arrow === -1 || close === -1 || open2 > arrow || arrow > close) {
37047
+ return null;
37048
+ }
37049
+ const prefix = rawPath.slice(0, open2);
37050
+ const suffix = rawPath.slice(close + 1);
37051
+ const oldPart = rawPath.slice(open2 + 1, arrow).trim();
37052
+ const newPart = rawPath.slice(arrow + 2, close).trim();
37053
+ return {
37054
+ oldPath: normalizeGitDiffPath(`${prefix}${oldPart}${suffix}`),
37055
+ newPath: normalizeGitDiffPath(`${prefix}${newPart}${suffix}`)
37056
+ };
37057
+ }
37058
+ function isGitRenameNumstatPath(rawPath) {
37059
+ return expandGitRenameNumstatPath(rawPath) != null;
37060
+ }
37061
+
37062
+ // src/git/changes/parse/parse-name-status-entries.ts
37063
+ function parseNameStatusEntries(lines) {
37064
+ const entries = [];
37065
+ for (const line of lines) {
37066
+ if (!line.trim()) continue;
37067
+ const tabParts = line.split(" ");
37068
+ if (tabParts.length < 2) continue;
37069
+ const status = tabParts[0].trim();
37070
+ const code = status[0];
37071
+ if (code === "A") {
37072
+ entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "added" });
37073
+ } else if (code === "D") {
37074
+ entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "removed" });
37075
+ } else if (code === "R" || code === "C") {
37076
+ if (tabParts.length >= 3) {
37077
+ entries.push({
37078
+ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]),
37079
+ oldPathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 2]),
37080
+ change: "moved"
37081
+ });
37082
+ }
37083
+ } else if (code === "M" || code === "U" || code === "T") {
37084
+ entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "modified" });
37085
+ }
37086
+ }
37087
+ return entries;
37088
+ }
37089
+ function parseNameStatusLines(lines) {
37090
+ const m = /* @__PURE__ */ new Map();
37091
+ for (const entry of parseNameStatusEntries(lines)) {
37092
+ m.set(entry.pathInRepo, entry.change);
37093
+ }
37094
+ return m;
37095
+ }
37096
+
37097
+ // src/git/changes/parse/parse-numstat-entries.ts
37098
+ function parseNumstatCounts(parts) {
37099
+ const [a, d] = parts;
37100
+ return {
37101
+ additions: a === "-" ? 0 : parseInt(String(a), 10) || 0,
37102
+ deletions: d === "-" ? 0 : parseInt(String(d), 10) || 0
37103
+ };
37104
+ }
37105
+ function parseNumstatLine(line) {
37106
+ const parts = line.split(" ");
37107
+ if (parts.length < 3) return null;
37108
+ const rawPath = parts[parts.length - 1];
37109
+ const { additions, deletions } = parseNumstatCounts(parts);
37110
+ const expanded = expandGitRenameNumstatPath(rawPath);
37111
+ const pathInRepo = normalizeGitDiffPath(expanded?.newPath ?? rawPath);
37112
+ return {
37113
+ pathInRepo,
37114
+ additions,
37115
+ deletions,
37116
+ ...expanded ? { oldPathInRepo: expanded.oldPath } : {}
37117
+ };
37118
+ }
37119
+ function parseNumstatEntries(lines) {
37120
+ const m = /* @__PURE__ */ new Map();
37121
+ for (const line of lines) {
37122
+ if (!line.trim()) continue;
37123
+ const parts = line.split(" ");
37124
+ if (parts.length < 3) continue;
37125
+ const rawPath = parts[parts.length - 1];
37126
+ const expanded = expandGitRenameNumstatPath(rawPath);
37127
+ const entry = parseNumstatLine(line);
37128
+ if (!entry) continue;
37129
+ const existing = m.get(entry.pathInRepo);
37130
+ if (!existing || expanded) {
37131
+ m.set(entry.pathInRepo, entry);
37132
+ }
37133
+ }
37134
+ return m;
37135
+ }
37136
+ function parseNumstat(lines) {
37137
+ const m = /* @__PURE__ */ new Map();
37138
+ for (const [path62, entry] of parseNumstatEntries(lines)) {
37139
+ m.set(path62, { additions: entry.additions, deletions: entry.deletions });
37140
+ }
37141
+ return m;
37142
+ }
37143
+
37144
+ // src/git/changes/parse/numstat-from-git-no-index.ts
37145
+ async function numstatFromGitNoIndex(g, pathInRepo) {
37146
+ const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
37147
+ try {
37148
+ const out = await g.raw(["diff", "--numstat", "--no-index", "--", devNull, pathInRepo]);
37149
+ const first2 = String(out).split("\n").find((l) => l.trim()) ?? "";
37150
+ const parsed = parseNumstatLine(first2);
37151
+ if (!parsed) return null;
37152
+ return { additions: parsed.additions, deletions: parsed.deletions };
37153
+ } catch {
37154
+ return null;
37155
+ }
37156
+ }
37157
+
37158
+ // src/git/changes/status/working-tree-changed-path-count.ts
37159
+ function workingTreeChangedPathCount(nameStatusLines, numstatLines, untrackedLines) {
37160
+ const kindByPath = parseNameStatusLines(nameStatusLines);
37161
+ const numByPath = parseNumstat(numstatLines);
37162
+ const paths = /* @__PURE__ */ new Set([...kindByPath.keys(), ...numByPath.keys()]);
37163
+ for (const p of untrackedLines.map((s) => s.trim()).filter(Boolean)) {
37164
+ paths.add(p);
37165
+ }
37166
+ return paths.size;
37167
+ }
37168
+
37169
+ // src/git/changes/count-working-tree-changed-files.ts
37170
+ async function countWorkingTreeChangedFilesForRepo(repoGitCwd) {
37171
+ return runGitTask(async () => {
37172
+ const g = cliSimpleGit(repoGitCwd);
37173
+ const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
37174
+ g.raw(["diff", "--name-status", "HEAD"]).catch(() => ""),
37175
+ g.raw(["diff", "HEAD", "--numstat"]).catch(() => ""),
37176
+ g.raw(["ls-files", "--others", "--exclude-standard"]).catch(() => "")
37177
+ ]);
37178
+ return workingTreeChangedPathCount(
37179
+ String(nameStatusRaw).split("\n"),
37180
+ String(numstatRaw).split("\n"),
37181
+ String(untrackedRaw).split("\n")
37182
+ );
37183
+ });
37184
+ }
37185
+
37186
+ // src/git/commits/remote-tracking/rev-parse-safe.ts
37187
+ async function revParseSafe(g, ref) {
37188
+ try {
37189
+ const v = (await g.raw(["rev-parse", "--verify", ref])).trim();
37190
+ return v || null;
37191
+ } catch {
37192
+ return null;
37193
+ }
37194
+ }
37195
+
37196
+ // src/git/commits/remote-tracking/git-config.ts
37197
+ async function tryConfigGet(g, key) {
37198
+ try {
37199
+ const out = await g.raw(["config", "--get", key]);
37200
+ const v = String(out).trim();
37201
+ return v || null;
37202
+ } catch {
37203
+ return null;
37204
+ }
37205
+ }
37206
+
37207
+ // src/git/commits/remote-tracking/branch-remote-config.ts
37208
+ async function readBranchRemoteConfig(g) {
37209
+ const branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
37210
+ if (!branch || branch === "HEAD") {
37211
+ return { branch: null, remote: "origin", merge: null };
37212
+ }
37213
+ const [remote, merge2] = await Promise.all([
37214
+ tryConfigGet(g, `branch.${branch}.remote`),
37215
+ tryConfigGet(g, `branch.${branch}.merge`)
37216
+ ]);
37217
+ return { branch, remote: remote ?? "origin", merge: merge2 };
37218
+ }
37219
+ function trackingRefCandidates(config2) {
37220
+ const candidates = ["HEAD@{upstream}", "@{u}"];
37221
+ if (!config2.branch) return candidates;
37222
+ if (config2.merge) {
37223
+ candidates.push(
37224
+ `refs/remotes/${config2.remote}/${config2.merge.replace(/^refs\/heads\//, "")}`
37225
+ );
37226
+ }
37227
+ candidates.push(`refs/remotes/${config2.remote}/${config2.branch}`);
37228
+ return candidates;
37229
+ }
37230
+
37231
+ // src/git/commits/remote-tracking/first-resolved-ref.ts
37232
+ async function firstResolvedSha(g, candidates) {
37233
+ const resolved = await Promise.all(
37234
+ candidates.map(async (ref) => ({ ref, sha: await revParseSafe(g, ref) }))
37235
+ );
37236
+ return resolved.find((entry) => entry.sha)?.sha ?? null;
37237
+ }
37238
+
37239
+ // src/git/commits/remote-tracking/resolve-default-remote-branch-ref.ts
37240
+ var DEFAULT_REMOTE_BRANCH_NAMES = ["main", "master", "trunk", "develop"];
37241
+ function defaultRemoteBranchNameRefs(remote) {
37242
+ return DEFAULT_REMOTE_BRANCH_NAMES.map((name) => `refs/remotes/${remote}/${name}`);
37243
+ }
37244
+ async function resolveRemoteHeadSymbolicRef(g, remote) {
37245
+ const headSym = `refs/remotes/${remote}/HEAD`;
37246
+ try {
37247
+ const out = await g.raw(["symbolic-ref", "-q", "--verify", headSym]);
37248
+ const resolved = String(out).trim();
37249
+ return resolved.startsWith("refs/remotes/") ? resolved : null;
37250
+ } catch {
37251
+ return null;
37252
+ }
37253
+ }
37254
+ async function resolveDefaultRemoteBranchSha(g, remote) {
37255
+ const nameRefs = defaultRemoteBranchNameRefs(remote);
37256
+ const [symRef, ...nameShas] = await Promise.all([
37257
+ resolveRemoteHeadSymbolicRef(g, remote),
37258
+ ...nameRefs.map((ref) => revParseSafe(g, ref))
37259
+ ]);
37260
+ if (symRef) return revParseSafe(g, symRef);
37261
+ for (let i = 0; i < nameRefs.length; i++) {
37262
+ if (nameShas[i]) return nameShas[i];
37263
+ }
37264
+ return null;
37265
+ }
37266
+
37267
+ // src/git/commits/remote-tracking/resolve-base-sha-for-unpushed-commits.ts
37268
+ import * as path22 from "node:path";
37269
+ var BASE_SHA_CACHE_TTL_MS = 3e4;
37270
+ var baseShaCache = /* @__PURE__ */ new Map();
37271
+ async function resolveBaseShaForUnpushedCommitsUncached(g) {
37272
+ const config2 = await readBranchRemoteConfig(g);
37273
+ const trackingSha = await firstResolvedSha(g, trackingRefCandidates(config2));
37274
+ if (trackingSha) return trackingSha;
37275
+ return resolveDefaultRemoteBranchSha(g, config2.remote);
37276
+ }
37277
+ async function resolveBaseShaForUnpushedCommits(g, repoDir) {
37278
+ if (repoDir) {
37279
+ const key = path22.resolve(repoDir);
37280
+ const now = Date.now();
37281
+ const cached2 = baseShaCache.get(key);
37282
+ if (cached2 && cached2.expiresAt > now) return cached2.baseSha;
37283
+ const baseSha = await resolveBaseShaForUnpushedCommitsUncached(g);
37284
+ baseShaCache.set(key, { baseSha, expiresAt: now + BASE_SHA_CACHE_TTL_MS });
37285
+ return baseSha;
37286
+ }
37287
+ return resolveBaseShaForUnpushedCommitsUncached(g);
37288
+ }
37289
+
37290
+ // src/git/commits/remote-tracking/commits-ahead-of-remote-tracking.ts
37291
+ async function commitsAheadOfRemoteTracking(repoDir) {
37292
+ const g = cliSimpleGit(repoDir);
37293
+ const headSha = await revParseSafe(g, "HEAD");
37294
+ if (!headSha) return 0;
37295
+ const baseSha = await resolveBaseShaForUnpushedCommits(g, repoDir);
37296
+ if (!baseSha || baseSha === headSha) return 0;
37297
+ try {
37298
+ const out = await g.raw(["rev-list", "--count", `${baseSha}..${headSha}`]);
37299
+ const n = parseInt(String(out).trim(), 10);
37300
+ return Number.isNaN(n) ? 0 : n;
37301
+ } catch {
37302
+ return 0;
37303
+ }
37304
+ }
37305
+
37306
+ // src/git/status/working-tree-status.ts
37307
+ async function getRepoWorkingTreeStatus(repoDir) {
37308
+ return runGitTask(async () => {
37309
+ const uncommittedFileCount = await countWorkingTreeChangedFilesForRepo(repoDir);
37310
+ const hasUncommittedChanges = uncommittedFileCount > 0;
37311
+ const ahead = await commitsAheadOfRemoteTracking(repoDir);
37312
+ return { hasUncommittedChanges, hasUnpushedCommits: ahead > 0, uncommittedFileCount };
37313
+ });
37314
+ }
37315
+ async function aggregateSessionPathsWorkingTreeStatus(paths) {
37316
+ let hasUncommittedChanges = false;
37317
+ let hasUnpushedCommits = false;
37318
+ let uncommittedFileCount = 0;
37319
+ await forEachWithGitYield(paths, async (p) => {
37320
+ const s = await getRepoWorkingTreeStatus(p);
37321
+ uncommittedFileCount += s.uncommittedFileCount;
37322
+ if (s.hasUncommittedChanges) hasUncommittedChanges = true;
37323
+ if (s.hasUnpushedCommits) hasUnpushedCommits = true;
37324
+ });
37325
+ return { hasUncommittedChanges, hasUnpushedCommits, uncommittedFileCount };
37326
+ }
37327
+ async function pushAheadOfUpstreamForPaths(paths) {
37328
+ await forEachWithGitYield(paths, async (p) => {
37329
+ const g = cliSimpleGit(p);
37330
+ const ahead = await commitsAheadOfRemoteTracking(p);
37331
+ if (ahead <= 0) return;
37332
+ await g.push();
37333
+ });
37334
+ }
37335
+
37336
+ // src/worktrees/manager/resolve-commit-targets.ts
37337
+ function bridgeRootBinding(bridgeRoot) {
37338
+ return {
37339
+ sessionParentPath: bridgeRoot,
37340
+ workingTreeRelRoot: bridgeRoot,
37341
+ repoCheckoutPaths: [bridgeRoot]
37342
+ };
37343
+ }
37344
+ async function resolveCommitTargetsAsync(sessionId, cache2, discover) {
37345
+ const sid = sessionId.trim();
37346
+ const paths = cache2.getRepoCheckoutPathsRef(sid);
37347
+ if (paths?.length) return paths;
37348
+ const disc = await discover(sid);
37349
+ if (disc?.repoCheckoutPaths.length) {
37350
+ cache2.remember(sid, disc);
37351
+ return disc.repoCheckoutPaths;
37352
+ }
37353
+ const bridgeRoot = getBridgeRoot();
37354
+ cache2.remember(sid, bridgeRootBinding(bridgeRoot));
37355
+ return [bridgeRoot];
37356
+ }
37357
+
37358
+ // src/worktrees/manager/git/get-session-working-tree-status.ts
37359
+ async function getSessionWorkingTreeStatus(cache2, sessionId, discover) {
37360
+ return aggregateSessionPathsWorkingTreeStatus(
37361
+ await resolveCommitTargetsAsync(sessionId, cache2, discover)
37362
+ );
37363
+ }
37364
+
36865
37365
  // src/git/changes/types.ts
36866
37366
  var MAX_PATCH_CHARS = 35e4;
36867
37367
 
@@ -36919,106 +37419,14 @@ function formatRemoteDisplayLabel(remoteUrl) {
36919
37419
  return `origin \xB7 ${hostPath}`;
36920
37420
  }
36921
37421
 
36922
- // src/git/changes/get-working-tree-change-repo-details.ts
36923
- import * as path23 from "node:path";
37422
+ // src/git/changes/repo/get-working-tree-change-repo-details.ts
37423
+ import * as path29 from "node:path";
36924
37424
 
36925
- // src/git/commits/resolve-remote-tracking.ts
36926
- async function tryConfigGet(g, key) {
36927
- try {
36928
- const out = await g.raw(["config", "--get", key]);
36929
- const v = String(out).trim();
36930
- return v || null;
36931
- } catch {
36932
- return null;
36933
- }
36934
- }
36935
- async function revParseSafe(g, ref) {
36936
- try {
36937
- const v = (await g.raw(["rev-parse", ref])).trim();
36938
- return v || null;
36939
- } catch {
36940
- return null;
36941
- }
36942
- }
36943
- async function resolveRemoteTrackingRefForAhead(g) {
36944
- try {
36945
- await g.raw(["rev-parse", "--verify", "HEAD@{upstream}"]);
36946
- return "HEAD@{upstream}";
36947
- } catch {
36948
- try {
36949
- await g.raw(["rev-parse", "--verify", "@{u}"]);
36950
- return "@{u}";
36951
- } catch {
36952
- }
36953
- }
36954
- const branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
36955
- if (!branch || branch === "HEAD") return null;
36956
- const remote = await tryConfigGet(g, `branch.${branch}.remote`) ?? "origin";
36957
- const merge2 = await tryConfigGet(g, `branch.${branch}.merge`);
36958
- if (merge2) {
36959
- const upstreamBranch = merge2.replace(/^refs\/heads\//, "");
36960
- const ref = `refs/remotes/${remote}/${upstreamBranch}`;
36961
- try {
36962
- await g.raw(["rev-parse", "--verify", ref]);
36963
- return ref;
36964
- } catch {
36965
- }
36966
- }
36967
- const fallbackRef = `refs/remotes/${remote}/${branch}`;
36968
- try {
36969
- await g.raw(["rev-parse", "--verify", fallbackRef]);
36970
- return fallbackRef;
36971
- } catch {
36972
- return null;
36973
- }
36974
- }
36975
- async function remoteForCurrentBranch(g) {
36976
- const branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
36977
- if (!branch || branch === "HEAD") return "origin";
36978
- return await tryConfigGet(g, `branch.${branch}.remote`) ?? "origin";
36979
- }
36980
- async function resolveDefaultRemoteBranchRef(g, remote) {
36981
- const headSym = `refs/remotes/${remote}/HEAD`;
36982
- try {
36983
- const resolved = (await g.raw(["symbolic-ref", "-q", "--verify", headSym])).trim();
36984
- if (resolved.startsWith("refs/remotes/")) return resolved;
36985
- } catch {
36986
- }
36987
- for (const name of ["main", "master", "trunk", "develop"]) {
36988
- const r = `refs/remotes/${remote}/${name}`;
36989
- try {
36990
- await g.raw(["rev-parse", "--verify", r]);
36991
- return r;
36992
- } catch {
36993
- }
36994
- }
36995
- return null;
36996
- }
36997
- async function resolveBaseShaForUnpushedCommits(g) {
36998
- const trackingRef = await resolveRemoteTrackingRefForAhead(g);
36999
- if (trackingRef) {
37000
- const sha = await revParseSafe(g, trackingRef);
37001
- if (sha) return sha;
37002
- }
37003
- const remote = await remoteForCurrentBranch(g);
37004
- const defaultRef = await resolveDefaultRemoteBranchRef(g, remote);
37005
- if (!defaultRef) return null;
37006
- return revParseSafe(g, defaultRef);
37007
- }
37008
- async function commitsAheadOfRemoteTracking(repoDir) {
37009
- const g = cliSimpleGit(repoDir);
37010
- const headSha = await revParseSafe(g, "HEAD");
37011
- if (!headSha) return 0;
37012
- const baseSha = await resolveBaseShaForUnpushedCommits(g);
37013
- if (!baseSha || baseSha === headSha) return 0;
37014
- try {
37015
- const out = await g.raw(["rev-list", "--count", `${baseSha}..${headSha}`]);
37016
- const n = parseInt(String(out).trim(), 10);
37017
- return Number.isNaN(n) ? 0 : n;
37018
- } catch {
37019
- return 0;
37020
- }
37021
- }
37425
+ // src/git/changes/repo/build-working-tree-change-repo-detail.ts
37426
+ import * as path28 from "node:path";
37427
+
37428
+ // src/git/commits/list-unpushed-commits.ts
37429
+ import * as path23 from "node:path";
37022
37430
 
37023
37431
  // src/git/commits/lib/parse-log-lines.ts
37024
37432
  function parseLogShaDateSubjectLines(raw) {
@@ -37038,9 +37446,14 @@ function parseLogShaDateSubjectLines(raw) {
37038
37446
  }
37039
37447
 
37040
37448
  // src/git/commits/list-unpushed-commits.ts
37449
+ var UNPUSHED_CACHE_TTL_MS = 3e4;
37450
+ var unpushedCache = /* @__PURE__ */ new Map();
37041
37451
  async function gitLogNotReachableFromBase(g, baseSha, headSha) {
37042
37452
  if (baseSha === headSha) return [];
37043
37453
  try {
37454
+ const countOut = await g.raw(["rev-list", "--count", `${baseSha}..${headSha}`]);
37455
+ const count = parseInt(String(countOut).trim(), 10);
37456
+ if (!Number.isFinite(count) || count <= 0) return [];
37044
37457
  const logOut = await g.raw(["log", "--format=%H %cI %s", `${baseSha}..${headSha}`]);
37045
37458
  return parseLogShaDateSubjectLines(logOut);
37046
37459
  } catch {
@@ -37048,12 +37461,22 @@ async function gitLogNotReachableFromBase(g, baseSha, headSha) {
37048
37461
  }
37049
37462
  }
37050
37463
  async function listUnpushedCommits(repoDir) {
37464
+ const key = path23.resolve(repoDir);
37465
+ const now = Date.now();
37466
+ const cached2 = unpushedCache.get(key);
37467
+ if (cached2 && cached2.expiresAt > now) return cached2.commits;
37051
37468
  const g = cliSimpleGit(repoDir);
37052
- const headSha = await revParseSafe(g, "HEAD");
37053
- if (!headSha) return [];
37054
- const baseSha = await resolveBaseShaForUnpushedCommits(g);
37055
- if (!baseSha) return [];
37056
- return gitLogNotReachableFromBase(g, baseSha, headSha);
37469
+ const [headSha, baseSha] = await Promise.all([
37470
+ revParseSafe(g, "HEAD"),
37471
+ resolveBaseShaForUnpushedCommits(g, repoDir)
37472
+ ]);
37473
+ if (!headSha || !baseSha) {
37474
+ unpushedCache.set(key, { commits: [], expiresAt: now + UNPUSHED_CACHE_TTL_MS });
37475
+ return [];
37476
+ }
37477
+ const commits = await gitLogNotReachableFromBase(g, baseSha, headSha);
37478
+ unpushedCache.set(key, { commits, expiresAt: now + UNPUSHED_CACHE_TTL_MS });
37479
+ return commits;
37057
37480
  }
37058
37481
 
37059
37482
  // src/git/commits/lib/sanitize-recent-commits-limit.ts
@@ -37067,72 +37490,40 @@ function sanitizeRecentCommitsLimit(value) {
37067
37490
  }
37068
37491
 
37069
37492
  // src/git/commits/list-recent-commits.ts
37070
- async function listRecentCommits(repoDir, limitInput) {
37493
+ async function listRecentCommitRefs(repoDir, limitInput) {
37071
37494
  const limit = sanitizeRecentCommitsLimit(limitInput);
37072
37495
  const g = cliSimpleGit(repoDir);
37073
- const headSha = await revParseSafe(g, "HEAD");
37074
- if (!headSha) return { commits: [], hasMore: false };
37075
- const unpushedSet = new Set((await listUnpushedCommits(repoDir)).map((c) => c.sha));
37076
37496
  try {
37077
- const logOut = await g.raw(["log", "--format=%H %cI %s", `-n`, String(limit), "HEAD"]);
37078
- const commits = parseLogShaDateSubjectLines(logOut).map((c) => ({
37079
- ...c,
37080
- needsPush: unpushedSet.has(c.sha)
37081
- }));
37082
- let hasMore = false;
37083
- if (commits.length === limit) {
37084
- const nextOut = await g.raw(["log", "-n", "1", `--skip=${limit}`, "--format=%H", "HEAD"]);
37085
- hasMore = /^[0-9a-f]{7,40}$/i.test(String(nextOut).trim());
37086
- }
37497
+ const logOut = await g.raw(["log", "--format=%H %cI %s", `-n`, String(limit + 1), "HEAD"]);
37498
+ const parsed = parseLogShaDateSubjectLines(logOut);
37499
+ const hasMore = parsed.length > limit;
37500
+ const commits = parsed.slice(0, limit);
37087
37501
  return { commits, hasMore };
37088
37502
  } catch {
37089
37503
  return { commits: [], hasMore: false };
37090
37504
  }
37091
37505
  }
37092
37506
 
37093
- // src/git/changes/parse/normalize-git-diff-path.ts
37094
- function normalizeGitDiffPath(path48) {
37095
- return path48.replace(/\\/g, "/").replace(/\/ +/g, "/").trim();
37096
- }
37097
-
37098
- // src/git/changes/parse/expand-git-rename-numstat-path.ts
37099
- function expandGitRenameNumstatPath(rawPath) {
37100
- const open2 = rawPath.indexOf("{");
37101
- const arrow = rawPath.indexOf("=>");
37102
- const close = rawPath.lastIndexOf("}");
37103
- if (open2 === -1 || arrow === -1 || close === -1 || open2 > arrow || arrow > close) {
37104
- return null;
37105
- }
37106
- const prefix = rawPath.slice(0, open2);
37107
- const suffix = rawPath.slice(close + 1);
37108
- const oldPart = rawPath.slice(open2 + 1, arrow).trim();
37109
- const newPart = rawPath.slice(arrow + 2, close).trim();
37110
- return {
37111
- oldPath: normalizeGitDiffPath(`${prefix}${oldPart}${suffix}`),
37112
- newPath: normalizeGitDiffPath(`${prefix}${newPart}${suffix}`)
37113
- };
37114
- }
37115
- function isGitRenameNumstatPath(rawPath) {
37116
- return expandGitRenameNumstatPath(rawPath) != null;
37117
- }
37507
+ // src/git/changes/listing/list-changed-files-for-commit.ts
37508
+ import * as path25 from "node:path";
37118
37509
 
37119
37510
  // src/git/changes/rows/build-changed-file-row.ts
37120
37511
  function buildChangedFileRow(options) {
37121
37512
  const pathInRepo = normalizeGitDiffPath(options.pathInRepo);
37122
- const relLauncher = posixJoinDirFile(options.repoRelPath, pathInRepo);
37513
+ const workspaceRelPath = posixJoinDirFile(options.repoRelPath, pathInRepo);
37123
37514
  const additions = options.numEntry?.additions ?? 0;
37124
37515
  const deletions = options.numEntry?.deletions ?? 0;
37125
37516
  let change = options.nameEntry?.change ?? "modified";
37126
- let movedFromPathRelLauncher;
37517
+ let movedFromWorkspaceRelPath;
37127
37518
  let movedFromPathInRepo;
37128
37519
  const oldPathInRepo = options.nameEntry?.oldPathInRepo ?? options.numEntry?.oldPathInRepo;
37129
37520
  if (change === "moved" && oldPathInRepo) {
37130
37521
  movedFromPathInRepo = normalizeGitDiffPath(oldPathInRepo);
37131
- movedFromPathRelLauncher = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
37522
+ movedFromWorkspaceRelPath = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
37132
37523
  } else if (oldPathInRepo && change === "modified") {
37133
37524
  change = "moved";
37134
37525
  movedFromPathInRepo = normalizeGitDiffPath(oldPathInRepo);
37135
- movedFromPathRelLauncher = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
37526
+ movedFromWorkspaceRelPath = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
37136
37527
  }
37137
37528
  if (options.untracked && !options.nameEntry) {
37138
37529
  change = "added";
@@ -37142,11 +37533,11 @@ function buildChangedFileRow(options) {
37142
37533
  else change = "modified";
37143
37534
  }
37144
37535
  return {
37145
- pathRelLauncher: relLauncher,
37536
+ workspaceRelPath,
37146
37537
  additions,
37147
37538
  deletions,
37148
37539
  change,
37149
- ...movedFromPathRelLauncher ? { movedFromPathRelLauncher } : {},
37540
+ ...movedFromWorkspaceRelPath ? { movedFromWorkspaceRelPath } : {},
37150
37541
  ...movedFromPathInRepo ? { movedFromPathInRepo } : {}
37151
37542
  };
37152
37543
  }
@@ -37205,8 +37596,8 @@ async function unifiedDiffForFileInRange(repoCwd, range, pathInRepo, change, mov
37205
37596
  }
37206
37597
 
37207
37598
  // src/git/changes/total-lines/resolve-changed-file-total-lines.ts
37208
- import * as fs17 from "node:fs";
37209
- import * as path20 from "node:path";
37599
+ import * as fs18 from "node:fs";
37600
+ import * as path24 from "node:path";
37210
37601
 
37211
37602
  // src/git/changes/lines/count-lines.ts
37212
37603
  import { createReadStream } from "node:fs";
@@ -37252,13 +37643,13 @@ async function countLinesInGitRef(repoGitCwd, ref, pathInRepo) {
37252
37643
  }
37253
37644
  async function resolveWorkingTreeFileTotalLines(options) {
37254
37645
  if (options.isBinary) return null;
37255
- const filePath = path20.join(options.repoGitCwd, options.pathInRepo);
37646
+ const filePath = path24.join(options.repoGitCwd, options.pathInRepo);
37256
37647
  if (options.change === "removed") {
37257
37648
  return countLinesInGitRef(options.repoGitCwd, "HEAD", options.pathInRepo);
37258
37649
  }
37259
37650
  if (options.change === "moved" || options.change === "modified") {
37260
37651
  try {
37261
- const st = await fs17.promises.stat(filePath);
37652
+ const st = await fs18.promises.stat(filePath);
37262
37653
  if (!st.isFile()) return null;
37263
37654
  return await countTextFileLines(filePath);
37264
37655
  } catch {
@@ -37278,14 +37669,14 @@ async function resolveCommitFileTotalLines(options) {
37278
37669
  return null;
37279
37670
  }
37280
37671
 
37281
- // src/git/changes/listing/path-in-repo-from-launcher.ts
37282
- function pathInRepoFromLauncher(pathRelLauncher, repoRelPath) {
37672
+ // src/git/changes/listing/path-in-repo-from-workspace.ts
37673
+ function pathInRepoFromWorkspace(workspaceRelPath, repoRelPath) {
37283
37674
  const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
37284
- if (normRel === ".") return pathRelLauncher;
37285
- if (pathRelLauncher.startsWith(`${normRel}/`)) {
37286
- return pathRelLauncher.slice(normRel.length + 1);
37675
+ if (normRel === ".") return workspaceRelPath;
37676
+ if (workspaceRelPath.startsWith(`${normRel}/`)) {
37677
+ return workspaceRelPath.slice(normRel.length + 1);
37287
37678
  }
37288
- return pathRelLauncher;
37679
+ return workspaceRelPath;
37289
37680
  }
37290
37681
  function normalizeRepoRelPath(repoRelPath) {
37291
37682
  return repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
@@ -37294,7 +37685,7 @@ function normalizeRepoRelPath(repoRelPath) {
37294
37685
  // src/git/changes/listing/enrich-commit-file-rows.ts
37295
37686
  async function enrichCommitFileRows(options) {
37296
37687
  await forEachWithGitYield(options.rows, async (row) => {
37297
- const pathInRepo = pathInRepoFromLauncher(row.pathRelLauncher, options.repoRelPath);
37688
+ const pathInRepo = pathInRepoFromWorkspace(row.workspaceRelPath, options.repoRelPath);
37298
37689
  row.patchContent = await unifiedDiffForFileInRange(
37299
37690
  options.repoGitCwd,
37300
37691
  options.range,
@@ -37313,102 +37704,6 @@ async function enrichCommitFileRows(options) {
37313
37704
  });
37314
37705
  }
37315
37706
 
37316
- // src/git/changes/parse/parse-name-status-entries.ts
37317
- function parseNameStatusEntries(lines) {
37318
- const entries = [];
37319
- for (const line of lines) {
37320
- if (!line.trim()) continue;
37321
- const tabParts = line.split(" ");
37322
- if (tabParts.length < 2) continue;
37323
- const status = tabParts[0].trim();
37324
- const code = status[0];
37325
- if (code === "A") {
37326
- entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "added" });
37327
- } else if (code === "D") {
37328
- entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "removed" });
37329
- } else if (code === "R" || code === "C") {
37330
- if (tabParts.length >= 3) {
37331
- entries.push({
37332
- pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]),
37333
- oldPathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 2]),
37334
- change: "moved"
37335
- });
37336
- }
37337
- } else if (code === "M" || code === "U" || code === "T") {
37338
- entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "modified" });
37339
- }
37340
- }
37341
- return entries;
37342
- }
37343
- function parseNameStatusLines(lines) {
37344
- const m = /* @__PURE__ */ new Map();
37345
- for (const entry of parseNameStatusEntries(lines)) {
37346
- m.set(entry.pathInRepo, entry.change);
37347
- }
37348
- return m;
37349
- }
37350
-
37351
- // src/git/changes/parse/parse-numstat-entries.ts
37352
- function parseNumstatCounts(parts) {
37353
- const [a, d] = parts;
37354
- return {
37355
- additions: a === "-" ? 0 : parseInt(String(a), 10) || 0,
37356
- deletions: d === "-" ? 0 : parseInt(String(d), 10) || 0
37357
- };
37358
- }
37359
- function parseNumstatLine(line) {
37360
- const parts = line.split(" ");
37361
- if (parts.length < 3) return null;
37362
- const rawPath = parts[parts.length - 1];
37363
- const { additions, deletions } = parseNumstatCounts(parts);
37364
- const expanded = expandGitRenameNumstatPath(rawPath);
37365
- const pathInRepo = normalizeGitDiffPath(expanded?.newPath ?? rawPath);
37366
- return {
37367
- pathInRepo,
37368
- additions,
37369
- deletions,
37370
- ...expanded ? { oldPathInRepo: expanded.oldPath } : {}
37371
- };
37372
- }
37373
- function parseNumstatEntries(lines) {
37374
- const m = /* @__PURE__ */ new Map();
37375
- for (const line of lines) {
37376
- if (!line.trim()) continue;
37377
- const parts = line.split(" ");
37378
- if (parts.length < 3) continue;
37379
- const rawPath = parts[parts.length - 1];
37380
- const expanded = expandGitRenameNumstatPath(rawPath);
37381
- const entry = parseNumstatLine(line);
37382
- if (!entry) continue;
37383
- const existing = m.get(entry.pathInRepo);
37384
- if (!existing || expanded) {
37385
- m.set(entry.pathInRepo, entry);
37386
- }
37387
- }
37388
- return m;
37389
- }
37390
- function parseNumstat(lines) {
37391
- const m = /* @__PURE__ */ new Map();
37392
- for (const [path48, entry] of parseNumstatEntries(lines)) {
37393
- m.set(path48, { additions: entry.additions, deletions: entry.deletions });
37394
- }
37395
- return m;
37396
- }
37397
-
37398
- // src/git/changes/parse/numstat-from-git-no-index.ts
37399
- async function numstatFromGitNoIndex(g, pathInRepo) {
37400
- const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
37401
- try {
37402
- const out = await g.raw(["diff", "--numstat", "--no-index", "--", devNull, pathInRepo]);
37403
- const first2 = String(out).split("\n").find((l) => l.trim()) ?? "";
37404
- const parsed = parseNumstatLine(first2);
37405
- if (!parsed) return null;
37406
- return { additions: parsed.additions, deletions: parsed.deletions };
37407
- } catch {
37408
- return null;
37409
- }
37410
- }
37411
-
37412
37707
  // src/git/changes/rows/collect-moved-source-paths.ts
37413
37708
  function collectMovedSourcePaths(nameEntries, numByPath) {
37414
37709
  const sources = /* @__PURE__ */ new Set();
@@ -37488,8 +37783,8 @@ function pickPreferredChangedFileRow(a, b) {
37488
37783
  const aHasPatch = Boolean(a.patchContent?.trim());
37489
37784
  const bHasPatch = Boolean(b.patchContent?.trim());
37490
37785
  if (aHasPatch !== bHasPatch) return aHasPatch ? a : b;
37491
- const aHasMoveMeta = Boolean(a.movedFromPathInRepo || a.movedFromPathRelLauncher);
37492
- const bHasMoveMeta = Boolean(b.movedFromPathInRepo || b.movedFromPathRelLauncher);
37786
+ const aHasMoveMeta = Boolean(a.movedFromPathInRepo || a.movedFromWorkspaceRelPath);
37787
+ const bHasMoveMeta = Boolean(b.movedFromPathInRepo || b.movedFromWorkspaceRelPath);
37493
37788
  if (aHasMoveMeta !== bHasMoveMeta) return aHasMoveMeta ? a : b;
37494
37789
  return a;
37495
37790
  }
@@ -37498,30 +37793,39 @@ function pickPreferredChangedFileRow(a, b) {
37498
37793
  function dedupeChangedFileRows(rows) {
37499
37794
  const byPath = /* @__PURE__ */ new Map();
37500
37795
  for (const row of rows) {
37501
- const key = normalizeGitDiffPath(row.pathRelLauncher);
37502
- const normalizedRow = key === row.pathRelLauncher ? row : { ...row, pathRelLauncher: key };
37796
+ const key = normalizeGitDiffPath(row.workspaceRelPath);
37797
+ const normalizedRow = key === row.workspaceRelPath ? row : { ...row, workspaceRelPath: key };
37503
37798
  const existing = byPath.get(key);
37504
37799
  if (!existing) {
37505
37800
  byPath.set(key, normalizedRow);
37506
37801
  continue;
37507
37802
  }
37508
37803
  const preferRow = pickPreferredChangedFileRow(existing, normalizedRow);
37509
- byPath.set(key, { ...preferRow, pathRelLauncher: key });
37804
+ byPath.set(key, { ...preferRow, workspaceRelPath: key });
37510
37805
  }
37511
37806
  return [...byPath.values()];
37512
37807
  }
37513
37808
 
37514
37809
  // src/git/changes/listing/list-changed-files-for-commit.ts
37515
- async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
37810
+ var COMMIT_FILES_CACHE_TTL_MS = 5 * 6e4;
37811
+ var commitFilesCache = /* @__PURE__ */ new Map();
37812
+ function commitFilesCacheKey(repoGitCwd, commitSha) {
37813
+ return `${path25.resolve(repoGitCwd)}:${commitSha}`;
37814
+ }
37815
+ async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha, options = {}) {
37816
+ const cacheKey = commitFilesCacheKey(repoGitCwd, commitSha);
37817
+ if (!options.includeFilePatches) {
37818
+ const now = Date.now();
37819
+ const cached2 = commitFilesCache.get(cacheKey);
37820
+ if (cached2 && cached2.expiresAt > now) return cached2.rows;
37821
+ }
37516
37822
  const g = cliSimpleGit(repoGitCwd);
37517
- const parent = await parentForCommitDiff(g, commitSha);
37518
- const range = `${parent}..${commitSha}`;
37823
+ const normRel = normalizeRepoRelPath(repoRelPath);
37519
37824
  const [nameStatusRaw, numstatRaw] = await Promise.all([
37520
- g.raw(["diff", ...RENAME_DIFF_ARGS, "--name-status", range]).catch(() => ""),
37521
- g.raw(["diff", ...RENAME_DIFF_ARGS, "--numstat", range]).catch(() => "")
37825
+ g.raw(["diff-tree", "--no-commit-id", "--name-status", "-r", ...RENAME_DIFF_ARGS, commitSha]).catch(() => ""),
37826
+ g.raw(["show", "--numstat", "--format=format:", commitSha]).catch(() => "")
37522
37827
  ]);
37523
37828
  const parsed = parseDiffOutput(String(nameStatusRaw), String(numstatRaw));
37524
- const normRel = normalizeRepoRelPath(repoRelPath);
37525
37829
  const paths = collectChangedPaths({
37526
37830
  nameEntries: parsed.nameEntries,
37527
37831
  numByPath: parsed.numByPath
@@ -37534,29 +37838,39 @@ async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
37534
37838
  movedSourcePaths: parsed.movedSourcePaths
37535
37839
  });
37536
37840
  rows = dedupeChangedFileRows(rows);
37537
- await enrichCommitFileRows({
37538
- rows,
37539
- repoGitCwd,
37540
- repoRelPath: normRel,
37541
- range,
37542
- commitSha,
37543
- parentSha: parent,
37544
- binaryByPath: parsed.binaryByPath
37545
- });
37841
+ if (options.includeFilePatches) {
37842
+ const parent = await parentForCommitDiff(g, commitSha);
37843
+ const range = `${parent}..${commitSha}`;
37844
+ await enrichCommitFileRows({
37845
+ rows,
37846
+ repoGitCwd,
37847
+ repoRelPath: normRel,
37848
+ range,
37849
+ commitSha,
37850
+ parentSha: parent,
37851
+ binaryByPath: parsed.binaryByPath
37852
+ });
37853
+ }
37546
37854
  const finalRows = dedupeChangedFileRows(rows);
37547
- finalRows.sort((a, b) => a.pathRelLauncher.localeCompare(b.pathRelLauncher));
37855
+ finalRows.sort((a, b) => a.workspaceRelPath.localeCompare(b.workspaceRelPath));
37856
+ if (!options.includeFilePatches) {
37857
+ commitFilesCache.set(cacheKey, {
37858
+ rows: finalRows,
37859
+ expiresAt: Date.now() + COMMIT_FILES_CACHE_TTL_MS
37860
+ });
37861
+ }
37548
37862
  return finalRows;
37549
37863
  }
37550
37864
 
37551
37865
  // src/git/changes/listing/apply-untracked-file-stats.ts
37552
- import * as fs18 from "node:fs";
37553
- import * as path21 from "node:path";
37866
+ import * as fs19 from "node:fs";
37867
+ import * as path26 from "node:path";
37554
37868
  function createUntrackedStatsApplier(options) {
37555
37869
  return async (row, pathInRepo) => {
37556
37870
  if (!options.untrackedSet.has(pathInRepo)) return;
37557
37871
  if (options.nameByPath.has(pathInRepo)) return;
37558
37872
  if (row.change === "moved") return;
37559
- const repoFilePath = path21.join(options.repoGitCwd, pathInRepo);
37873
+ const repoFilePath = path26.join(options.repoGitCwd, pathInRepo);
37560
37874
  const fromGit = await numstatFromGitNoIndex(options.g, pathInRepo);
37561
37875
  if (fromGit) {
37562
37876
  row.additions = fromGit.additions;
@@ -37564,7 +37878,7 @@ function createUntrackedStatsApplier(options) {
37564
37878
  return;
37565
37879
  }
37566
37880
  try {
37567
- const st = await fs18.promises.stat(repoFilePath);
37881
+ const st = await fs19.promises.stat(repoFilePath);
37568
37882
  row.additions = st.isFile() ? await countTextFileLines(repoFilePath) : 0;
37569
37883
  } catch {
37570
37884
  row.additions = 0;
@@ -37574,13 +37888,75 @@ function createUntrackedStatsApplier(options) {
37574
37888
  }
37575
37889
 
37576
37890
  // src/git/changes/listing/enrich-working-tree-file-rows.ts
37577
- import * as path22 from "node:path";
37891
+ import * as path27 from "node:path";
37578
37892
 
37579
- // src/git/changes/patch/hydrate-patch.ts
37580
- import * as fs19 from "node:fs";
37893
+ // src/git/changes/patch/hydrate/append-injected-context-lines.ts
37894
+ async function appendInjectedContextLines(out, inject, maybeYield, injectedTotal) {
37895
+ for (const t of inject) {
37896
+ out.push(` ${t}`);
37897
+ injectedTotal.value++;
37898
+ await maybeYield();
37899
+ }
37900
+ }
37901
+
37902
+ // src/git/changes/patch/hydrate/constants.ts
37581
37903
  var UNIFIED_HUNK_HEADER_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
37582
37904
  var MAX_HYDRATE_LINES_PER_GAP = 8e3;
37583
37905
  var MAX_HYDRATE_LINES_PER_FILE = 8e4;
37906
+ var HYDRATE_YIELD_EVERY_LINES = 512;
37907
+
37908
+ // src/git/changes/patch/hydrate/collect-gap-inject-lines.ts
37909
+ async function collectGapInjectLines(options) {
37910
+ if (options.injectedTotal >= MAX_HYDRATE_LINES_PER_FILE) return null;
37911
+ const remaining = MAX_HYDRATE_LINES_PER_FILE - options.injectedTotal;
37912
+ if (options.gapNewEnd >= options.gapNewStart && options.change !== "removed") {
37913
+ const nNew = options.gapNewEnd - options.gapNewStart + 1;
37914
+ if (options.gapOldEnd < options.gapOldStart || options.gapOldEnd - options.gapOldStart + 1 === nNew) {
37915
+ const cap = Math.min(nNew, MAX_HYDRATE_LINES_PER_GAP, remaining);
37916
+ const dl = await options.diskLines();
37917
+ if (dl && cap > 0) {
37918
+ return dl.slice(options.gapNewStart - 1, options.gapNewStart - 1 + cap);
37919
+ }
37920
+ }
37921
+ return null;
37922
+ }
37923
+ if (options.gapOldEnd >= options.gapOldStart && options.change === "removed") {
37924
+ const nOld = options.gapOldEnd - options.gapOldStart + 1;
37925
+ const cap = Math.min(nOld, MAX_HYDRATE_LINES_PER_GAP, remaining);
37926
+ const bl = await options.blobLines();
37927
+ if (bl && cap > 0) {
37928
+ return bl.slice(options.gapOldStart - 1, options.gapOldStart - 1 + cap);
37929
+ }
37930
+ }
37931
+ return null;
37932
+ }
37933
+
37934
+ // src/git/changes/patch/hydrate/consume-unified-hunk-body.ts
37935
+ async function consumeUnifiedHunkBody(options) {
37936
+ let i = options.startIndex;
37937
+ let oldConsumed = 0;
37938
+ let newConsumed = 0;
37939
+ while (i < options.all.length) {
37940
+ await options.maybeYield();
37941
+ const bl = options.all[i];
37942
+ if (UNIFIED_HUNK_HEADER_RE.test(bl)) break;
37943
+ options.out.push(bl);
37944
+ i++;
37945
+ if (bl.startsWith("\\")) continue;
37946
+ const ch = bl[0];
37947
+ if (ch === " ") {
37948
+ oldConsumed++;
37949
+ newConsumed++;
37950
+ } else if (ch === "-") {
37951
+ oldConsumed++;
37952
+ } else if (ch === "+") {
37953
+ newConsumed++;
37954
+ }
37955
+ }
37956
+ return { nextIndex: i, oldConsumed, newConsumed };
37957
+ }
37958
+
37959
+ // src/git/changes/patch/hydrate/read-git-blob-lines.ts
37584
37960
  async function readGitBlobLines(repoCwd, pathInRepo) {
37585
37961
  try {
37586
37962
  const rel = pathInRepo.replace(/\\/g, "/");
@@ -37590,35 +37966,66 @@ async function readGitBlobLines(repoCwd, pathInRepo) {
37590
37966
  return null;
37591
37967
  }
37592
37968
  }
37969
+
37970
+ // src/git/changes/patch/hydrate/read-worktree-file-lines.ts
37971
+ import * as fs20 from "node:fs";
37593
37972
  async function readWorktreeFileLines(filePath) {
37594
37973
  try {
37595
- const raw = await fs19.promises.readFile(filePath, "utf8");
37974
+ const raw = await fs20.promises.readFile(filePath, "utf8");
37596
37975
  return raw.split(/\r?\n/);
37597
37976
  } catch {
37598
37977
  return null;
37599
37978
  }
37600
37979
  }
37601
- async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, pathInRepo, change) {
37602
- if (!patch.trim() || patch.includes("Binary files")) return patch;
37603
- const all = patch.split("\n");
37604
- const out = [];
37605
- let prevOldEnd = 0;
37606
- let prevNewEnd = 0;
37607
- let injectedTotal = 0;
37608
- let i = 0;
37980
+
37981
+ // src/git/changes/patch/hydrate/create-hydrate-source-lines.ts
37982
+ function createHydrateSourceLines(options) {
37609
37983
  let blobCache;
37610
37984
  let diskCache;
37611
37985
  const blobLines = async () => {
37612
37986
  if (blobCache !== void 0) return blobCache;
37613
- blobCache = await readGitBlobLines(repoGitCwd, pathInRepo);
37987
+ blobCache = await readGitBlobLines(options.repoGitCwd, options.pathInRepo);
37614
37988
  return blobCache;
37615
37989
  };
37616
37990
  const diskLines = async () => {
37617
37991
  if (diskCache !== void 0) return diskCache;
37618
- diskCache = await readWorktreeFileLines(filePath);
37992
+ diskCache = await readWorktreeFileLines(options.filePath);
37619
37993
  return diskCache;
37620
37994
  };
37995
+ return { blobLines, diskLines };
37996
+ }
37997
+
37998
+ // src/git/changes/patch/hydrate/create-hydrate-yield.ts
37999
+ function createHydrateYield() {
38000
+ let processedLines = 0;
38001
+ return async () => {
38002
+ processedLines++;
38003
+ if (processedLines % HYDRATE_YIELD_EVERY_LINES === 0) {
38004
+ await yieldToEventLoop();
38005
+ }
38006
+ };
38007
+ }
38008
+
38009
+ // src/git/changes/patch/hydrate/update-hunk-line-ends.ts
38010
+ function updateHunkLineEnds(options) {
38011
+ const prevOldEnd = options.oldStart > 0 ? options.oldStart + options.oldConsumed - 1 : 0;
38012
+ const prevNewEnd = options.newStart > 0 ? options.newStart + options.newConsumed - 1 : 0;
38013
+ return { prevOldEnd, prevNewEnd };
38014
+ }
38015
+
38016
+ // src/git/changes/patch/hydrate/hydrate-unified-patch-with-file-context.ts
38017
+ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, pathInRepo, change) {
38018
+ if (!patch.trim() || patch.includes("Binary files")) return patch;
38019
+ const all = patch.split("\n");
38020
+ const out = [];
38021
+ let prevOldEnd = 0;
38022
+ let prevNewEnd = 0;
38023
+ const injectedTotal = { value: 0 };
38024
+ let i = 0;
38025
+ const maybeYield = createHydrateYield();
38026
+ const { blobLines, diskLines } = createHydrateSourceLines({ repoGitCwd, pathInRepo, filePath });
37621
38027
  while (i < all.length) {
38028
+ await maybeYield();
37622
38029
  const line = all[i];
37623
38030
  const hm = line.match(UNIFIED_HUNK_HEADER_RE);
37624
38031
  if (!hm) {
@@ -37628,66 +38035,31 @@ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, p
37628
38035
  }
37629
38036
  const oldStart = parseInt(hm[1], 10) || 0;
37630
38037
  const newStart = parseInt(hm[3], 10) || 0;
37631
- const gapOldStart = prevOldEnd + 1;
37632
- const gapOldEnd = oldStart - 1;
37633
- const gapNewStart = prevNewEnd + 1;
37634
- const gapNewEnd = newStart - 1;
37635
- if (injectedTotal < MAX_HYDRATE_LINES_PER_FILE) {
37636
- let inject = null;
37637
- if (gapNewEnd >= gapNewStart && change !== "removed") {
37638
- const nNew = gapNewEnd - gapNewStart + 1;
37639
- if (gapOldEnd < gapOldStart || gapOldEnd - gapOldStart + 1 === nNew) {
37640
- const cap = Math.min(nNew, MAX_HYDRATE_LINES_PER_GAP, MAX_HYDRATE_LINES_PER_FILE - injectedTotal);
37641
- const dl = await diskLines();
37642
- if (dl && cap > 0) {
37643
- inject = dl.slice(gapNewStart - 1, gapNewStart - 1 + cap);
37644
- }
37645
- }
37646
- } else if (gapOldEnd >= gapOldStart && change === "removed") {
37647
- const nOld = gapOldEnd - gapOldStart + 1;
37648
- const cap = Math.min(nOld, MAX_HYDRATE_LINES_PER_GAP, MAX_HYDRATE_LINES_PER_FILE - injectedTotal);
37649
- const bl = await blobLines();
37650
- if (bl && cap > 0) {
37651
- inject = bl.slice(gapOldStart - 1, gapOldStart - 1 + cap);
37652
- }
37653
- }
37654
- if (inject && inject.length > 0) {
37655
- for (const t of inject) {
37656
- out.push(` ${t}`);
37657
- injectedTotal++;
37658
- }
37659
- }
38038
+ const inject = await collectGapInjectLines({
38039
+ change,
38040
+ gapOldStart: prevOldEnd + 1,
38041
+ gapOldEnd: oldStart - 1,
38042
+ gapNewStart: prevNewEnd + 1,
38043
+ gapNewEnd: newStart - 1,
38044
+ injectedTotal: injectedTotal.value,
38045
+ blobLines,
38046
+ diskLines
38047
+ });
38048
+ if (inject && inject.length > 0) {
38049
+ await appendInjectedContextLines(out, inject, maybeYield, injectedTotal);
37660
38050
  }
37661
38051
  out.push(line);
37662
38052
  i++;
37663
- let oldConsumed = 0;
37664
- let newConsumed = 0;
37665
- while (i < all.length) {
37666
- const bl = all[i];
37667
- if (UNIFIED_HUNK_HEADER_RE.test(bl)) break;
37668
- out.push(bl);
37669
- i++;
37670
- if (bl.startsWith("\\")) continue;
37671
- const ch = bl[0];
37672
- if (ch === " ") {
37673
- oldConsumed++;
37674
- newConsumed++;
37675
- } else if (ch === "-") {
37676
- oldConsumed++;
37677
- } else if (ch === "+") {
37678
- newConsumed++;
37679
- }
37680
- }
37681
- if (oldStart > 0) {
37682
- prevOldEnd = oldStart + oldConsumed - 1;
37683
- } else {
37684
- prevOldEnd = 0;
37685
- }
37686
- if (newStart > 0) {
37687
- prevNewEnd = newStart + newConsumed - 1;
37688
- } else {
37689
- prevNewEnd = 0;
37690
- }
38053
+ const body = await consumeUnifiedHunkBody({ all, startIndex: i, out, maybeYield });
38054
+ i = body.nextIndex;
38055
+ const ends = updateHunkLineEnds({
38056
+ oldStart,
38057
+ newStart,
38058
+ oldConsumed: body.oldConsumed,
38059
+ newConsumed: body.newConsumed
38060
+ });
38061
+ prevOldEnd = ends.prevOldEnd;
38062
+ prevNewEnd = ends.prevNewEnd;
37691
38063
  }
37692
38064
  return truncatePatch(out.join("\n"));
37693
38065
  }
@@ -37695,8 +38067,8 @@ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, p
37695
38067
  // src/git/changes/listing/enrich-working-tree-file-rows.ts
37696
38068
  async function enrichWorkingTreeFileRows(options) {
37697
38069
  await forEachWithGitYield(options.rows, async (row) => {
37698
- const pathInRepo = pathInRepoFromLauncher(row.pathRelLauncher, options.repoRelPath);
37699
- const filePath = path22.join(options.repoGitCwd, pathInRepo);
38070
+ const pathInRepo = pathInRepoFromWorkspace(row.workspaceRelPath, options.repoRelPath);
38071
+ const filePath = path27.join(options.repoGitCwd, pathInRepo);
37700
38072
  const hydrateKind = row.change === "moved" ? "modified" : row.change;
37701
38073
  let patch = await unifiedDiffForFile(options.repoGitCwd, pathInRepo, row.change, row.movedFromPathInRepo);
37702
38074
  if (patch) {
@@ -37719,7 +38091,7 @@ async function enrichWorkingTreeFileRows(options) {
37719
38091
  }
37720
38092
 
37721
38093
  // src/git/changes/listing/list-changed-files-for-repo.ts
37722
- async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
38094
+ async function listChangedFilesForRepo(repoGitCwd, repoRelPath, options = {}) {
37723
38095
  const g = cliSimpleGit(repoGitCwd);
37724
38096
  const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
37725
38097
  g.raw(["diff", ...RENAME_DIFF_ARGS, "--name-status", "HEAD"]).catch(() => ""),
@@ -37749,23 +38121,101 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
37749
38121
  })
37750
38122
  });
37751
38123
  rows = dedupeChangedFileRows(rows);
37752
- await enrichWorkingTreeFileRows({
37753
- rows,
37754
- repoGitCwd,
37755
- repoRelPath,
37756
- binaryByPath: parsed.binaryByPath
37757
- });
38124
+ if (options.includeFilePatches) {
38125
+ await enrichWorkingTreeFileRows({
38126
+ rows,
38127
+ repoGitCwd,
38128
+ repoRelPath,
38129
+ binaryByPath: parsed.binaryByPath
38130
+ });
38131
+ }
37758
38132
  return dedupeChangedFileRows(rows);
37759
38133
  }
37760
38134
 
37761
- // src/git/changes/get-working-tree-change-repo-details.ts
38135
+ // src/git/changes/repo/norm-repo-rel-path.ts
37762
38136
  function normRepoRel(p) {
37763
38137
  const x = p.replace(/\\/g, "/").trim();
37764
38138
  return x === "" ? "." : x;
37765
38139
  }
38140
+
38141
+ // src/git/changes/repo/build-working-tree-change-repo-detail.ts
38142
+ async function loadRecentAndUnpushedCommits(repoDir, recentCommitsLimit) {
38143
+ const [unpushedCommits, recentCommitList] = await Promise.all([
38144
+ listUnpushedCommits(repoDir),
38145
+ listRecentCommitRefs(repoDir, recentCommitsLimit)
38146
+ ]);
38147
+ const unpushedSet = new Set(unpushedCommits.map((c) => c.sha));
38148
+ return {
38149
+ unpushedCommits,
38150
+ recentCommits: recentCommitList.commits.map((c) => ({
38151
+ ...c,
38152
+ needsPush: unpushedSet.has(c.sha)
38153
+ })),
38154
+ recentCommitsHasMore: recentCommitList.hasMore
38155
+ };
38156
+ }
38157
+ async function buildWorkingTreeChangeRepoDetail(options) {
38158
+ const t = path28.resolve(options.targetPath);
38159
+ if (!await isGitRepoDirectory(t)) return null;
38160
+ const g = cliSimpleGit(t);
38161
+ const isCommitView = options.basis.kind === "commit";
38162
+ const sessionWtRoot = options.sessionWorktreeRootPath ? path28.resolve(options.sessionWorktreeRootPath) : null;
38163
+ const legacyNested = options.legacyRepoNestedSessionLayout;
38164
+ await yieldToEventLoop2();
38165
+ const branchPromise = g.raw(["rev-parse", "--abbrev-ref", "HEAD"]).then((out) => out.trim() || "HEAD").catch(() => "HEAD");
38166
+ const remoteUrlPromise = getRemoteOriginUrl(t);
38167
+ const topLevelPromise = sessionWtRoot ? Promise.resolve(t) : g.raw(["rev-parse", "--show-toplevel"]).then((out) => out.trim()).catch(() => t);
38168
+ const [branch, remoteUrl, topOrT] = await Promise.all([
38169
+ branchPromise,
38170
+ remoteUrlPromise,
38171
+ topLevelPromise
38172
+ ]);
38173
+ const remoteDisplay = formatRemoteDisplayLabel(remoteUrl);
38174
+ let repoRelPath;
38175
+ if (sessionWtRoot) {
38176
+ const anchor = legacyNested ? path28.dirname(t) : t;
38177
+ const relNorm = path28.relative(sessionWtRoot, anchor);
38178
+ repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
38179
+ } else {
38180
+ const top = topOrT;
38181
+ const rel = path28.relative(options.bridgeRoot, path28.resolve(top)).replace(/\\/g, "/") || ".";
38182
+ repoRelPath = rel.startsWith("..") ? path28.basename(path28.resolve(top)) : rel;
38183
+ }
38184
+ const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
38185
+ if (options.repoFilterRelPath && norm !== options.repoFilterRelPath) return null;
38186
+ const repoDisplayName = formatRepoShortTitle(remoteUrl, norm === "." ? "." : norm);
38187
+ const relForList = norm === "." ? "." : norm;
38188
+ const commitSha = isCommitView ? options.basis.sha.trim() : null;
38189
+ const filesPromise = isCommitView && commitSha ? listChangedFilesForCommit(t, relForList, commitSha, {
38190
+ includeFilePatches: options.includeFilePatches
38191
+ }) : listChangedFilesForRepo(t, relForList, { includeFilePatches: options.includeFilePatches });
38192
+ const statusPromise = isCommitView ? Promise.resolve(false) : g.status().then((st) => (st.files?.length ?? 0) > 0);
38193
+ const commitsPromise = isCommitView ? Promise.resolve(null) : loadRecentAndUnpushedCommits(t, options.recentCommitsLimit);
38194
+ const [files, hasUncommittedChanges, commitData] = await Promise.all([
38195
+ filesPromise,
38196
+ statusPromise,
38197
+ commitsPromise
38198
+ ]);
38199
+ return {
38200
+ repoRelPath: norm,
38201
+ repoDisplayName,
38202
+ branch,
38203
+ remoteUrl,
38204
+ remoteDisplay,
38205
+ files,
38206
+ hasUncommittedChanges,
38207
+ unpushedCommits: commitData?.unpushedCommits ?? [],
38208
+ recentCommits: commitData?.recentCommits ?? [],
38209
+ recentCommitsHasMore: commitData?.recentCommitsHasMore ?? false,
38210
+ changesView: isCommitView ? "commit" : "working",
38211
+ changesCommitSha: commitSha
38212
+ };
38213
+ }
38214
+
38215
+ // src/git/changes/repo/get-working-tree-change-repo-details.ts
37766
38216
  async function getWorkingTreeChangeRepoDetails(options) {
37767
- const bridgeRoot = path23.resolve(getBridgeRoot());
37768
- const sessionWtRoot = options.sessionWorktreeRootPath ? path23.resolve(options.sessionWorktreeRootPath) : null;
38217
+ const bridgeRoot = path29.resolve(getBridgeRoot());
38218
+ const sessionWtRoot = options.sessionWorktreeRootPath ? path29.resolve(options.sessionWorktreeRootPath) : null;
37769
38219
  const legacyNested = options.legacyRepoNestedSessionLayout === true;
37770
38220
  const out = [];
37771
38221
  const filter = options.repoFilterRelPath != null ? normRepoRel(options.repoFilterRelPath) : null;
@@ -37778,152 +38228,167 @@ async function getWorkingTreeChangeRepoDetails(options) {
37778
38228
  }
37779
38229
  const basis = filter == null && basisInput.kind === "commit" ? { kind: "working" } : basisInput;
37780
38230
  for (let i = 0; i < options.commitTargetPaths.length; i++) {
37781
- if (i > 0) await yieldToEventLoop2();
37782
- const target = options.commitTargetPaths[i];
37783
- const t = path23.resolve(target);
37784
- if (!await isGitRepoDirectory(t)) continue;
37785
- const g = cliSimpleGit(t);
37786
- let branch = "HEAD";
37787
- try {
37788
- branch = (await g.raw(["rev-parse", "--abbrev-ref", "HEAD"])).trim() || "HEAD";
37789
- } catch {
37790
- branch = "HEAD";
37791
- }
37792
- const remoteUrl = await getRemoteOriginUrl(t);
37793
- const remoteDisplay = formatRemoteDisplayLabel(remoteUrl);
37794
- let repoRelPath;
37795
- if (sessionWtRoot) {
37796
- const anchor = legacyNested ? path23.dirname(t) : t;
37797
- const relNorm = path23.relative(sessionWtRoot, anchor);
37798
- repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
37799
- } else {
37800
- let top = t;
37801
- try {
37802
- top = (await g.raw(["rev-parse", "--show-toplevel"])).trim();
37803
- } catch {
37804
- top = t;
37805
- }
37806
- const rel = path23.relative(bridgeRoot, path23.resolve(top)).replace(/\\/g, "/") || ".";
37807
- repoRelPath = rel.startsWith("..") ? path23.basename(path23.resolve(top)) : rel;
37808
- }
37809
- const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
37810
- if (filter && norm !== filter) continue;
37811
- const repoDisplayName = formatRepoShortTitle(remoteUrl, norm === "." ? "." : norm);
37812
- const relForList = norm === "." ? "." : norm;
37813
- const files = basis.kind === "commit" ? await listChangedFilesForCommit(t, relForList, basis.sha.trim()) : await listChangedFilesForRepo(t, relForList);
37814
- const st = await g.status();
37815
- const hasUncommittedChanges = (st.files?.length ?? 0) > 0;
37816
- const [unpushedCommits, recentCommitList] = await Promise.all([
37817
- listUnpushedCommits(t),
37818
- listRecentCommits(t, options.recentCommitsLimit)
37819
- ]);
37820
- out.push({
37821
- repoRelPath: norm,
37822
- repoDisplayName,
37823
- branch,
37824
- remoteUrl,
37825
- remoteDisplay,
37826
- files,
37827
- hasUncommittedChanges,
37828
- unpushedCommits,
37829
- recentCommits: recentCommitList.commits,
37830
- recentCommitsHasMore: recentCommitList.hasMore,
37831
- changesView: basis.kind === "commit" ? "commit" : "working",
37832
- changesCommitSha: basis.kind === "commit" ? basis.sha.trim() : null
37833
- });
38231
+ await yieldToEventLoop2();
38232
+ const targetPath = options.commitTargetPaths[i];
38233
+ const detail = await buildWorkingTreeChangeRepoDetail({
38234
+ targetPath,
38235
+ bridgeRoot,
38236
+ sessionWorktreeRootPath: sessionWtRoot,
38237
+ legacyRepoNestedSessionLayout: legacyNested,
38238
+ repoFilterRelPath: filter,
38239
+ basis,
38240
+ recentCommitsLimit: options.recentCommitsLimit,
38241
+ includeFilePatches: options.includeFilePatches
38242
+ });
38243
+ if (!detail) continue;
38244
+ out.push(detail);
37834
38245
  if (filter) return out;
37835
38246
  }
37836
38247
  return out;
37837
38248
  }
37838
38249
 
37839
- // src/git/changes/status/working-tree-changed-path-count.ts
37840
- function workingTreeChangedPathCount(nameStatusLines, numstatLines, untrackedLines) {
37841
- const kindByPath = parseNameStatusLines(nameStatusLines);
37842
- const numByPath = parseNumstat(numstatLines);
37843
- const paths = /* @__PURE__ */ new Set([...kindByPath.keys(), ...numByPath.keys()]);
37844
- for (const p of untrackedLines.map((s) => s.trim()).filter(Boolean)) {
37845
- paths.add(p);
37846
- }
37847
- return paths.size;
37848
- }
37849
-
37850
- // src/git/changes/count-working-tree-changed-files.ts
37851
- async function countWorkingTreeChangedFilesForRepo(repoGitCwd) {
37852
- return runGitTask(async () => {
38250
+ // src/git/changes/repo/get-working-tree-change-file-patch.ts
38251
+ import * as path30 from "node:path";
38252
+ async function getWorkingTreeChangeFilePatch(options) {
38253
+ const repoGitCwd = path30.resolve(options.repoGitCwd);
38254
+ if (!await isGitRepoDirectory(repoGitCwd)) {
38255
+ throw new Error("Not a git repository");
38256
+ }
38257
+ const normRel = options.repoRelPath === "." || options.repoRelPath === "" ? "." : options.repoRelPath;
38258
+ const pathInRepo = pathInRepoFromWorkspace(options.workspaceRelPath, normRel);
38259
+ const movedFromPathInRepo = options.change === "moved" && options.movedFromWorkspaceRelPath ? pathInRepoFromWorkspace(options.movedFromWorkspaceRelPath, normRel) : void 0;
38260
+ if (options.basis.kind === "commit") {
38261
+ const commitSha = options.basis.sha.trim();
38262
+ if (!commitSha) throw new Error("commit sha is required for commit file patch");
37853
38263
  const g = cliSimpleGit(repoGitCwd);
37854
- const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
37855
- g.raw(["diff", "--name-status", "HEAD"]).catch(() => ""),
37856
- g.raw(["diff", "HEAD", "--numstat"]).catch(() => ""),
37857
- g.raw(["ls-files", "--others", "--exclude-standard"]).catch(() => "")
37858
- ]);
37859
- return workingTreeChangedPathCount(
37860
- String(nameStatusRaw).split("\n"),
37861
- String(numstatRaw).split("\n"),
37862
- String(untrackedRaw).split("\n")
38264
+ const parentSha = await parentForCommitDiff(g, commitSha);
38265
+ const range = `${parentSha}..${commitSha}`;
38266
+ const patchContent2 = await unifiedDiffForFileInRange(
38267
+ repoGitCwd,
38268
+ range,
38269
+ pathInRepo,
38270
+ options.change,
38271
+ movedFromPathInRepo
37863
38272
  );
38273
+ const isBinary = Boolean(patchContent2?.includes("Binary files"));
38274
+ const totalLines2 = await resolveCommitFileTotalLines({
38275
+ repoGitCwd,
38276
+ pathInRepo,
38277
+ change: options.change,
38278
+ commitSha,
38279
+ parentSha,
38280
+ isBinary
38281
+ });
38282
+ return { patchContent: patchContent2, totalLines: totalLines2 };
38283
+ }
38284
+ const filePath = path30.join(repoGitCwd, pathInRepo);
38285
+ const hydrateKind = options.change === "moved" ? "modified" : options.change;
38286
+ let patchContent = await unifiedDiffForFile(
38287
+ repoGitCwd,
38288
+ pathInRepo,
38289
+ options.change,
38290
+ movedFromPathInRepo
38291
+ );
38292
+ if (patchContent) {
38293
+ patchContent = await hydrateUnifiedPatchWithFileContext(
38294
+ patchContent,
38295
+ filePath,
38296
+ repoGitCwd,
38297
+ pathInRepo,
38298
+ hydrateKind
38299
+ );
38300
+ }
38301
+ const totalLines = await resolveWorkingTreeFileTotalLines({
38302
+ repoGitCwd,
38303
+ pathInRepo,
38304
+ change: options.change,
38305
+ isBinary: Boolean(patchContent?.includes("Binary files"))
37864
38306
  });
38307
+ return { patchContent, totalLines };
37865
38308
  }
37866
38309
 
37867
- // src/git/status/working-tree-status.ts
37868
- async function getRepoWorkingTreeStatus(repoDir) {
37869
- return runGitTask(async () => {
37870
- const uncommittedFileCount = await countWorkingTreeChangedFilesForRepo(repoDir);
37871
- const hasUncommittedChanges = uncommittedFileCount > 0;
37872
- const ahead = await commitsAheadOfRemoteTracking(repoDir);
37873
- return { hasUncommittedChanges, hasUnpushedCommits: ahead > 0, uncommittedFileCount };
38310
+ // src/worktrees/manager/git/get-session-working-tree-change-details.ts
38311
+ async function getSessionWorkingTreeChangeDetails(cache2, sessionId, discover, opts) {
38312
+ const targets = await resolveCommitTargetsAsync(sessionId, cache2, discover);
38313
+ return getWorkingTreeChangeRepoDetails({
38314
+ commitTargetPaths: targets,
38315
+ sessionWorktreeRootPath: cache2.getWorkingTreeRelRoot(sessionId),
38316
+ legacyRepoNestedSessionLayout: cache2.isLegacyNestedLayout(sessionId),
38317
+ repoFilterRelPath: opts?.repoRelPath?.trim() ? normRepoRel(opts.repoRelPath.trim()) : null,
38318
+ basis: opts?.basis,
38319
+ recentCommitsLimit: opts?.recentCommitsLimit,
38320
+ includeFilePatches: false
37874
38321
  });
37875
38322
  }
37876
- async function aggregateSessionPathsWorkingTreeStatus(paths) {
37877
- let hasUncommittedChanges = false;
37878
- let hasUnpushedCommits = false;
37879
- let uncommittedFileCount = 0;
37880
- await forEachWithGitYield(paths, async (p) => {
37881
- const s = await getRepoWorkingTreeStatus(p);
37882
- uncommittedFileCount += s.uncommittedFileCount;
37883
- if (s.hasUncommittedChanges) hasUncommittedChanges = true;
37884
- if (s.hasUnpushedCommits) hasUnpushedCommits = true;
37885
- });
37886
- return { hasUncommittedChanges, hasUnpushedCommits, uncommittedFileCount };
37887
- }
37888
- async function pushAheadOfUpstreamForPaths(paths) {
37889
- await forEachWithGitYield(paths, async (p) => {
37890
- const g = cliSimpleGit(p);
37891
- const ahead = await commitsAheadOfRemoteTracking(p);
37892
- if (ahead <= 0) return;
37893
- await g.push();
37894
- });
38323
+
38324
+ // src/worktrees/manager/git/parse-working-tree-change-kind.ts
38325
+ function parseWorkingTreeChangeKind(value) {
38326
+ if (value === "added" || value === "removed" || value === "modified" || value === "moved") return value;
38327
+ return null;
37895
38328
  }
37896
38329
 
37897
- // src/git/branches/commit-and-push.ts
37898
- async function gitCommitAllIfDirty(repoDir, message, options) {
37899
- const g = cliSimpleGit(repoDir);
37900
- const st = await g.status();
37901
- if (!st.files?.length) return;
37902
- const branch = options.branch.trim();
37903
- if (!branch) {
37904
- throw new Error("Branch name is required");
37905
- }
37906
- const branches = await g.branchLocal();
37907
- const localNames = new Set(branches.all.map((b) => b.replace(/^\*\s*/, "").trim()));
37908
- if (!localNames.has(branch)) {
37909
- await g.checkoutLocalBranch(branch);
38330
+ // src/worktrees/manager/git/get-session-working-tree-change-file-patch.ts
38331
+ import * as path32 from "node:path";
38332
+
38333
+ // src/git/changes/repo/resolve-checkout-repo-rel-path.ts
38334
+ import * as path31 from "node:path";
38335
+ async function resolveCheckoutRepoRelPath(options) {
38336
+ const t = path31.resolve(options.checkoutPath);
38337
+ const g = cliSimpleGit(t);
38338
+ const sessionWtRoot = options.sessionWorktreeRootPath ? path31.resolve(options.sessionWorktreeRootPath) : null;
38339
+ const legacyNested = options.legacyRepoNestedSessionLayout;
38340
+ let repoRelPath;
38341
+ if (sessionWtRoot) {
38342
+ const anchor = legacyNested ? path31.dirname(t) : t;
38343
+ const relNorm = path31.relative(sessionWtRoot, anchor);
38344
+ repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
37910
38345
  } else {
37911
- await g.checkout(branch);
38346
+ let top = t;
38347
+ try {
38348
+ await yieldToEventLoop2();
38349
+ top = (await g.raw(["rev-parse", "--show-toplevel"])).trim();
38350
+ } catch {
38351
+ top = t;
38352
+ }
38353
+ const rel = path31.relative(options.bridgeRoot, path31.resolve(top)).replace(/\\/g, "/") || ".";
38354
+ repoRelPath = rel.startsWith("..") ? path31.basename(path31.resolve(top)) : rel;
37912
38355
  }
37913
- await g.add(".");
37914
- await g.commit(message);
37915
- if (options.push) {
37916
- await g.push(["-u", "origin", branch]);
38356
+ return normRepoRel(repoRelPath === "" ? "." : repoRelPath);
38357
+ }
38358
+
38359
+ // src/worktrees/manager/git/get-session-working-tree-change-file-patch.ts
38360
+ async function getSessionWorkingTreeChangeFilePatch(cache2, sessionId, discover, opts) {
38361
+ const targets = await resolveCommitTargetsAsync(sessionId, cache2, discover);
38362
+ const bridgeRoot = path32.resolve(getBridgeRoot());
38363
+ const sessionWtRoot = cache2.getWorkingTreeRelRoot(sessionId) ? path32.resolve(cache2.getWorkingTreeRelRoot(sessionId)) : null;
38364
+ const legacyNested = cache2.isLegacyNestedLayout(sessionId);
38365
+ const filter = normRepoRel(opts.repoRelPath.trim());
38366
+ for (const targetPath of targets) {
38367
+ const checkoutPath = path32.resolve(targetPath);
38368
+ if (!await isGitRepoDirectory(checkoutPath)) continue;
38369
+ const repoRelPath = await resolveCheckoutRepoRelPath({
38370
+ checkoutPath,
38371
+ bridgeRoot,
38372
+ sessionWorktreeRootPath: sessionWtRoot,
38373
+ legacyRepoNestedSessionLayout: legacyNested
38374
+ });
38375
+ if (repoRelPath !== filter) continue;
38376
+ return getWorkingTreeChangeFilePatch({
38377
+ repoGitCwd: checkoutPath,
38378
+ repoRelPath: filter,
38379
+ workspaceRelPath: opts.workspaceRelPath.trim(),
38380
+ change: opts.change,
38381
+ movedFromWorkspaceRelPath: opts.movedFromWorkspaceRelPath?.trim() || null,
38382
+ basis: opts.basis
38383
+ });
37917
38384
  }
38385
+ throw new Error(`Repository not found: ${filter}`);
37918
38386
  }
37919
38387
 
37920
- // src/worktrees/commit-session-worktrees.ts
37921
- async function commitSessionWorktrees(options) {
37922
- const { paths, branch, message, push } = options;
38388
+ // src/worktrees/manager/git/push-session-upstream.ts
38389
+ async function pushSessionUpstream(cache2, sessionId, discover) {
37923
38390
  try {
37924
- for (const wt of paths) {
37925
- await gitCommitAllIfDirty(wt, message, { push, branch });
37926
- }
38391
+ await pushAheadOfUpstreamForPaths(await resolveCommitTargetsAsync(sessionId, cache2, discover));
37927
38392
  return { ok: true };
37928
38393
  } catch (e) {
37929
38394
  const err = e instanceof Error ? e.message : String(e);
@@ -37932,23 +38397,23 @@ async function commitSessionWorktrees(options) {
37932
38397
  }
37933
38398
 
37934
38399
  // src/worktrees/remove-session-worktrees.ts
37935
- import * as fs22 from "node:fs";
38400
+ import * as fs23 from "node:fs";
37936
38401
 
37937
38402
  // src/git/worktrees/worktree-remove.ts
37938
- import * as fs21 from "node:fs";
38403
+ import * as fs22 from "node:fs";
37939
38404
 
37940
38405
  // src/git/worktrees/resolve-main-repo-from-git-file.ts
37941
- import * as fs20 from "node:fs";
37942
- import * as path24 from "node:path";
38406
+ import * as fs21 from "node:fs";
38407
+ import * as path33 from "node:path";
37943
38408
  function resolveMainRepoFromWorktreeGitFile(wt) {
37944
- const gitDirFile = path24.join(wt, ".git");
37945
- if (!fs20.existsSync(gitDirFile) || !fs20.statSync(gitDirFile).isFile()) return "";
37946
- const first2 = fs20.readFileSync(gitDirFile, "utf8").trim();
38409
+ const gitDirFile = path33.join(wt, ".git");
38410
+ if (!fs21.existsSync(gitDirFile) || !fs21.statSync(gitDirFile).isFile()) return "";
38411
+ const first2 = fs21.readFileSync(gitDirFile, "utf8").trim();
37947
38412
  const m = first2.match(/^gitdir:\s*(.+)$/im);
37948
38413
  if (!m) return "";
37949
- const gitWorktreePath = path24.resolve(wt, m[1].trim());
37950
- const gitDir = path24.dirname(path24.dirname(gitWorktreePath));
37951
- return path24.dirname(gitDir);
38414
+ const gitWorktreePath = path33.resolve(wt, m[1].trim());
38415
+ const gitDir = path33.dirname(path33.dirname(gitWorktreePath));
38416
+ return path33.dirname(gitDir);
37952
38417
  }
37953
38418
 
37954
38419
  // src/git/worktrees/worktree-remove.ts
@@ -37957,7 +38422,7 @@ async function gitWorktreeRemoveForce(worktreePath) {
37957
38422
  if (mainRepo) {
37958
38423
  await cliSimpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
37959
38424
  } else {
37960
- fs21.rmSync(worktreePath, { recursive: true, force: true });
38425
+ fs22.rmSync(worktreePath, { recursive: true, force: true });
37961
38426
  }
37962
38427
  }
37963
38428
 
@@ -37970,13 +38435,20 @@ async function removeSessionWorktrees(paths, log2) {
37970
38435
  } catch (e) {
37971
38436
  log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
37972
38437
  try {
37973
- fs22.rmSync(wt, { recursive: true, force: true });
38438
+ fs23.rmSync(wt, { recursive: true, force: true });
37974
38439
  } catch {
37975
38440
  }
37976
38441
  }
37977
38442
  }
37978
38443
  }
37979
38444
 
38445
+ // src/worktrees/manager/git/remove-session-worktree-checkouts.ts
38446
+ async function removeSessionWorktreeCheckouts(cache2, sessionId, log2) {
38447
+ const paths = cache2.clearSession(sessionId);
38448
+ if (!paths?.length) return;
38449
+ await removeSessionWorktrees(paths, log2);
38450
+ }
38451
+
37980
38452
  // src/git/branches/rename-branch.ts
37981
38453
  async function gitRenameCurrentBranch(repoDir, newName) {
37982
38454
  const g = cliSimpleGit(repoDir);
@@ -37998,239 +38470,310 @@ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
37998
38470
  }
37999
38471
  }
38000
38472
 
38001
- // src/worktrees/worktree-layout-file.ts
38002
- import * as fs23 from "node:fs";
38003
- import * as path25 from "node:path";
38004
- import os7 from "node:os";
38005
- var LAYOUT_FILENAME = "worktree-launcher-layout.json";
38006
- function defaultWorktreeLayoutPath() {
38007
- return path25.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
38008
- }
38009
- function normalizeLoadedLayout(raw) {
38010
- if (raw && typeof raw === "object" && "launcherCwds" in raw) {
38011
- const j = raw;
38012
- if (Array.isArray(j.launcherCwds)) return { launcherCwds: j.launcherCwds };
38013
- }
38014
- return { launcherCwds: [] };
38015
- }
38016
- function loadWorktreeLayout() {
38017
- try {
38018
- const p = defaultWorktreeLayoutPath();
38019
- if (!fs23.existsSync(p)) return { launcherCwds: [] };
38020
- const raw = JSON.parse(fs23.readFileSync(p, "utf8"));
38021
- return normalizeLoadedLayout(raw);
38022
- } catch {
38023
- return { launcherCwds: [] };
38024
- }
38025
- }
38026
- function saveWorktreeLayout(layout) {
38027
- try {
38028
- const dir = path25.dirname(defaultWorktreeLayoutPath());
38029
- fs23.mkdirSync(dir, { recursive: true });
38030
- fs23.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
38031
- } catch {
38032
- }
38033
- }
38034
- function baseNameSafe(pathString) {
38035
- return path25.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
38036
- }
38037
- function getLauncherDirNameIfPresent(layout, bridgeRootPath2) {
38038
- const norm = path25.resolve(bridgeRootPath2);
38039
- const existing = layout.launcherCwds.find((e) => path25.resolve(e.absolutePath) === norm);
38040
- return existing?.dirName;
38473
+ // src/worktrees/manager/git/rename-session-worktree-branch.ts
38474
+ async function renameSessionWorktreeBranch(cache2, sessionId, newBranch, log2) {
38475
+ const paths = cache2.getRepoCheckoutPathsRef(sessionId);
38476
+ if (!paths?.length) return;
38477
+ await renameSessionWorktreeBranches(paths, newBranch, log2);
38041
38478
  }
38042
- function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
38043
- const existing = getLauncherDirNameIfPresent(layout, bridgeRootPath2);
38044
- if (existing) return existing;
38045
- const norm = path25.resolve(bridgeRootPath2);
38046
- const base = baseNameSafe(norm);
38047
- const used = new Set(layout.launcherCwds.map((e) => e.dirName));
38048
- let name = base;
38049
- let n = 2;
38050
- while (used.has(name)) {
38051
- name = `${base}-${n}`;
38052
- n += 1;
38053
- }
38054
- layout.launcherCwds.push({ absolutePath: norm, dirName: name });
38055
- saveWorktreeLayout(layout);
38056
- return name;
38479
+
38480
+ // src/worktrees/discovery/discover-session-worktree-on-disk.ts
38481
+ import * as fs27 from "node:fs";
38482
+ import * as path38 from "node:path";
38483
+
38484
+ // src/worktrees/discovery/collect-worktree-paths.ts
38485
+ import * as fs25 from "node:fs";
38486
+ import * as path35 from "node:path";
38487
+
38488
+ // src/worktrees/discovery/disk-walk-constants.ts
38489
+ var DISK_WALK_YIELD_EVERY = 64;
38490
+ var LEGACY_WALK_MAX_DEPTH = 12;
38491
+ var SKIP_DISK_WALK_DIR_NAMES = /* @__PURE__ */ new Set([
38492
+ "node_modules",
38493
+ "bower_components",
38494
+ "vendor",
38495
+ "Pods",
38496
+ "Carthage",
38497
+ "DerivedData",
38498
+ ".yarn",
38499
+ ".pnpm-store",
38500
+ "dist",
38501
+ "build",
38502
+ ".next",
38503
+ "coverage",
38504
+ ".turbo",
38505
+ ".cache",
38506
+ "target",
38507
+ "__pycache__"
38508
+ ]);
38509
+ function shouldSkipDiskWalkEntry(name) {
38510
+ return name.startsWith(".") || SKIP_DISK_WALK_DIR_NAMES.has(name);
38057
38511
  }
38058
38512
 
38059
- // src/worktrees/discover-session-worktree-on-disk.ts
38513
+ // src/worktrees/discovery/disk-walk-utils.ts
38060
38514
  import * as fs24 from "node:fs";
38061
- import * as path26 from "node:path";
38062
- function isGitDir(dirPath) {
38515
+ import * as path34 from "node:path";
38516
+ async function yieldDuringDiskWalk(state) {
38517
+ state.entries++;
38518
+ if (state.entries % DISK_WALK_YIELD_EVERY !== 0) return true;
38519
+ if (isCliImmediateShutdownRequested()) return false;
38520
+ await yieldToEventLoop();
38521
+ return !isCliImmediateShutdownRequested();
38522
+ }
38523
+ async function isGitDir(dirPath) {
38063
38524
  try {
38064
- return fs24.existsSync(path26.join(dirPath, ".git"));
38525
+ await fs24.promises.access(path34.join(dirPath, ".git"));
38526
+ return true;
38065
38527
  } catch {
38066
38528
  return false;
38067
38529
  }
38068
38530
  }
38069
- function collectGitRepoRootsUnderDirectory(rootPath) {
38531
+
38532
+ // src/worktrees/discovery/collect-worktree-paths.ts
38533
+ async function collectGitRepoRootsUnderDirectory(rootPath) {
38070
38534
  const out = [];
38071
- const walk = (dir) => {
38072
- if (isGitDir(dir)) {
38073
- out.push(path26.resolve(dir));
38535
+ const state = { entries: 0 };
38536
+ const walk = async (dir) => {
38537
+ if (!await yieldDuringDiskWalk(state)) return;
38538
+ if (await isGitDir(dir)) {
38539
+ out.push(path35.resolve(dir));
38074
38540
  return;
38075
38541
  }
38076
38542
  let entries;
38077
38543
  try {
38078
- entries = fs24.readdirSync(dir, { withFileTypes: true });
38544
+ entries = await fs25.promises.readdir(dir, { withFileTypes: true });
38079
38545
  } catch {
38080
38546
  return;
38081
38547
  }
38082
38548
  for (const e of entries) {
38083
- if (e.name.startsWith(".")) continue;
38084
- const full = path26.join(dir, e.name);
38549
+ if (shouldSkipDiskWalkEntry(e.name)) continue;
38550
+ const full = path35.join(dir, e.name);
38085
38551
  if (!e.isDirectory()) continue;
38086
- walk(full);
38552
+ await walk(full);
38087
38553
  }
38088
38554
  };
38089
- walk(path26.resolve(rootPath));
38090
- return [...new Set(out)];
38555
+ await walk(path35.resolve(rootPath));
38556
+ return { paths: [...new Set(out)], entriesVisited: state.entries };
38091
38557
  }
38092
- function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
38558
+ async function collectWorktreeRootsNamed(root, sessionId, maxDepth = LEGACY_WALK_MAX_DEPTH) {
38093
38559
  const out = [];
38094
- const walk = (dir, depth) => {
38560
+ const state = { entries: 0 };
38561
+ const walk = async (dir, depth) => {
38095
38562
  if (depth > maxDepth) return;
38563
+ if (!await yieldDuringDiskWalk(state)) return;
38096
38564
  let entries;
38097
38565
  try {
38098
- entries = fs24.readdirSync(dir, { withFileTypes: true });
38566
+ entries = await fs25.promises.readdir(dir, { withFileTypes: true });
38099
38567
  } catch {
38100
38568
  return;
38101
38569
  }
38102
38570
  for (const e of entries) {
38103
- if (e.name.startsWith(".")) continue;
38104
- const full = path26.join(dir, e.name);
38571
+ if (shouldSkipDiskWalkEntry(e.name)) continue;
38572
+ const full = path35.join(dir, e.name);
38105
38573
  if (!e.isDirectory()) continue;
38106
38574
  if (e.name === sessionId) {
38107
- if (isGitDir(full)) out.push(path26.resolve(full));
38108
- } else {
38109
- walk(full, depth + 1);
38575
+ if (await isGitDir(full)) out.push(path35.resolve(full));
38576
+ continue;
38110
38577
  }
38578
+ if (await isGitDir(full)) continue;
38579
+ await walk(full, depth + 1);
38111
38580
  }
38112
38581
  };
38113
- walk(root, 0);
38114
- return out;
38582
+ await walk(root, 0);
38583
+ return { paths: out, entriesVisited: state.entries };
38584
+ }
38585
+
38586
+ // src/worktrees/discovery/layout-keys-for-bridge-root.ts
38587
+ import * as path36 from "node:path";
38588
+ function layoutKeysForBridgeRoot(layout, bridgeRoot, keysOnDisk) {
38589
+ const bridge = path36.resolve(bridgeRoot);
38590
+ const preferred = getLauncherDirNameIfPresent(layout, bridgeRoot);
38591
+ const relevant = layout.launcherCwds.filter((e) => {
38592
+ const entry = path36.resolve(e.absolutePath);
38593
+ return bridge === entry || bridge.startsWith(entry + path36.sep) || entry.startsWith(bridge + path36.sep);
38594
+ }).sort((a, b) => path36.resolve(b.absolutePath).length - path36.resolve(a.absolutePath).length).map((e) => e.dirName).filter((name) => keysOnDisk.includes(name));
38595
+ const ordered = [];
38596
+ const seen = /* @__PURE__ */ new Set();
38597
+ const add = (k) => {
38598
+ if (seen.has(k) || !keysOnDisk.includes(k)) return;
38599
+ seen.add(k);
38600
+ ordered.push(k);
38601
+ };
38602
+ if (preferred) add(preferred);
38603
+ for (const k of relevant) add(k);
38604
+ if (relevant.length > 0) {
38605
+ return ordered;
38606
+ }
38607
+ for (const k of keysOnDisk) add(k);
38608
+ return ordered;
38115
38609
  }
38116
- function tryBindingFromSessionDirectory(sessionDir) {
38610
+
38611
+ // src/worktrees/discovery/try-binding-from-session-directory.ts
38612
+ import * as fs26 from "node:fs";
38613
+ import * as path37 from "node:path";
38614
+ async function tryBindingFromSessionDirectory(sessionDir) {
38117
38615
  let st;
38118
38616
  try {
38119
- st = fs24.statSync(sessionDir);
38617
+ st = await fs26.promises.stat(sessionDir);
38120
38618
  } catch {
38121
38619
  return null;
38122
38620
  }
38123
38621
  if (!st.isDirectory()) return null;
38124
- const worktreePaths = collectGitRepoRootsUnderDirectory(sessionDir);
38622
+ const { paths: worktreePaths } = await collectGitRepoRootsUnderDirectory(sessionDir);
38125
38623
  if (worktreePaths.length === 0) return null;
38126
- const abs = path26.resolve(sessionDir);
38624
+ const abs = path37.resolve(sessionDir);
38127
38625
  return {
38128
38626
  sessionParentPath: abs,
38129
38627
  workingTreeRelRoot: abs,
38130
38628
  repoCheckoutPaths: worktreePaths
38131
38629
  };
38132
38630
  }
38133
- function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
38631
+
38632
+ // src/worktrees/discovery/discover-session-worktree-on-disk.ts
38633
+ async function discoverSessionWorktreeOnDisk(options) {
38634
+ const { sessionId, worktreesRootPath, layout, bridgeRoot } = options;
38134
38635
  const sid = sessionId.trim();
38135
38636
  if (!sid) return null;
38136
- const hintR = path26.resolve(checkoutPath);
38137
- let best = null;
38138
- let cur = path26.dirname(hintR);
38139
- for (let i = 0; i < 40; i++) {
38140
- const paths = collectWorktreeRootsNamed(cur, sid, 24);
38141
- if (paths.some((p) => path26.resolve(p) === hintR)) {
38142
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path26.resolve(paths[0]);
38143
- best = {
38144
- sessionParentPath: path26.resolve(isolated),
38145
- workingTreeRelRoot: path26.resolve(cur),
38146
- repoCheckoutPaths: paths.map((p) => path26.resolve(p))
38147
- };
38148
- }
38149
- const next = path26.dirname(cur);
38150
- if (next === cur) break;
38151
- cur = next;
38637
+ try {
38638
+ await fs27.promises.access(worktreesRootPath);
38639
+ } catch {
38640
+ return null;
38152
38641
  }
38153
- return best;
38154
- }
38155
- function discoverSessionWorktreeOnDisk(options) {
38156
- const { sessionId, worktreesRootPath, layout, bridgeRoot } = options;
38157
- if (!sessionId.trim() || !fs24.existsSync(worktreesRootPath)) return null;
38158
38642
  const preferredKey = getLauncherDirNameIfPresent(layout, bridgeRoot);
38159
- const keys = [];
38160
- if (preferredKey) keys.push(preferredKey);
38643
+ const keysOnDisk = [];
38644
+ if (preferredKey) keysOnDisk.push(preferredKey);
38161
38645
  try {
38162
- for (const name of fs24.readdirSync(worktreesRootPath)) {
38646
+ for (const name of await fs27.promises.readdir(worktreesRootPath)) {
38163
38647
  if (name.startsWith(".")) continue;
38164
- const p = path26.join(worktreesRootPath, name);
38165
- if (!fs24.statSync(p).isDirectory()) continue;
38166
- if (name !== preferredKey) keys.push(name);
38648
+ const p = path38.join(worktreesRootPath, name);
38649
+ let st;
38650
+ try {
38651
+ st = await fs27.promises.stat(p);
38652
+ } catch {
38653
+ continue;
38654
+ }
38655
+ if (!st.isDirectory()) continue;
38656
+ if (!keysOnDisk.includes(name)) keysOnDisk.push(name);
38167
38657
  }
38168
38658
  } catch {
38169
38659
  return null;
38170
38660
  }
38661
+ const keys = layoutKeysForBridgeRoot(layout, bridgeRoot, keysOnDisk);
38171
38662
  for (const key of keys) {
38172
- const layoutRoot = path26.join(worktreesRootPath, key);
38173
- if (!fs24.existsSync(layoutRoot) || !fs24.statSync(layoutRoot).isDirectory()) continue;
38174
- const sessionDir = path26.join(layoutRoot, sessionId);
38175
- const nested = tryBindingFromSessionDirectory(sessionDir);
38663
+ if (isCliImmediateShutdownRequested()) return null;
38664
+ await yieldToEventLoop();
38665
+ const layoutRoot = path38.join(worktreesRootPath, key);
38666
+ let layoutSt;
38667
+ try {
38668
+ layoutSt = await fs27.promises.stat(layoutRoot);
38669
+ } catch {
38670
+ continue;
38671
+ }
38672
+ if (!layoutSt.isDirectory()) continue;
38673
+ const sessionDir = path38.join(layoutRoot, sid);
38674
+ const nested = await tryBindingFromSessionDirectory(sessionDir);
38176
38675
  if (nested) return nested;
38177
- const legacyPaths = collectWorktreeRootsNamed(layoutRoot, sessionId, 24);
38178
- if (legacyPaths.length > 0) {
38179
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path26.resolve(legacyPaths[0]);
38676
+ const legacy = await collectWorktreeRootsNamed(layoutRoot, sid);
38677
+ if (legacy.paths.length > 0) {
38678
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacy.paths) ?? path38.resolve(legacy.paths[0]);
38180
38679
  return {
38181
- sessionParentPath: path26.resolve(isolated),
38182
- workingTreeRelRoot: path26.resolve(layoutRoot),
38183
- repoCheckoutPaths: legacyPaths.map((p) => path26.resolve(p))
38680
+ sessionParentPath: path38.resolve(isolated),
38681
+ workingTreeRelRoot: path38.resolve(layoutRoot),
38682
+ repoCheckoutPaths: legacy.paths.map((p) => path38.resolve(p))
38184
38683
  };
38185
38684
  }
38186
38685
  }
38187
38686
  return null;
38188
38687
  }
38189
- function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPathOrHint, sessionId) {
38688
+
38689
+ // src/worktrees/discovery/discover-session-worktrees-under-session-worktree-root.ts
38690
+ import * as fs28 from "node:fs";
38691
+ import * as path40 from "node:path";
38692
+
38693
+ // src/worktrees/discovery/discover-legacy-binding-ascending-from-checkout.ts
38694
+ import * as path39 from "node:path";
38695
+ async function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
38696
+ const sid = sessionId.trim();
38697
+ if (!sid) return null;
38698
+ const hintR = path39.resolve(checkoutPath);
38699
+ let best = null;
38700
+ let cur = path39.dirname(hintR);
38701
+ for (let i = 0; i < 40; i++) {
38702
+ if (isCliImmediateShutdownRequested()) return best;
38703
+ await yieldToEventLoop();
38704
+ const paths = await collectWorktreeRootsNamed(cur, sid);
38705
+ if (paths.paths.some((p) => path39.resolve(p) === hintR)) {
38706
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths.paths) ?? path39.resolve(paths.paths[0]);
38707
+ best = {
38708
+ sessionParentPath: path39.resolve(isolated),
38709
+ workingTreeRelRoot: path39.resolve(cur),
38710
+ repoCheckoutPaths: paths.paths.map((p) => path39.resolve(p))
38711
+ };
38712
+ }
38713
+ const next = path39.dirname(cur);
38714
+ if (next === cur) break;
38715
+ cur = next;
38716
+ }
38717
+ return best;
38718
+ }
38719
+
38720
+ // src/worktrees/discovery/discover-session-worktrees-under-session-worktree-root.ts
38721
+ async function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPathOrHint, sessionId) {
38190
38722
  const sid = sessionId.trim();
38191
38723
  if (!sid) return null;
38192
- const hint = path26.resolve(sessionWorktreeRootPathOrHint);
38193
- const underHint = tryBindingFromSessionDirectory(path26.join(hint, sid));
38724
+ const hint = path40.resolve(sessionWorktreeRootPathOrHint);
38725
+ const underHint = await tryBindingFromSessionDirectory(path40.join(hint, sid));
38194
38726
  if (underHint) return underHint;
38195
- const direct = tryBindingFromSessionDirectory(hint);
38727
+ const direct = await tryBindingFromSessionDirectory(hint);
38196
38728
  if (direct) {
38197
- if (path26.basename(hint) === sid && isGitDir(hint)) {
38198
- const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
38729
+ if (path40.basename(hint) === sid && await isGitDir(hint)) {
38730
+ const legacyFromCheckout = await discoverLegacyBindingAscendingFromCheckout(sid, hint);
38199
38731
  if (legacyFromCheckout && legacyFromCheckout.repoCheckoutPaths.length > direct.repoCheckoutPaths.length) {
38200
38732
  return legacyFromCheckout;
38201
38733
  }
38202
38734
  }
38203
38735
  return direct;
38204
38736
  }
38205
- if (path26.basename(hint) === sid && isGitDir(hint)) {
38206
- const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
38737
+ if (path40.basename(hint) === sid && await isGitDir(hint)) {
38738
+ const legacyFromCheckout = await discoverLegacyBindingAscendingFromCheckout(sid, hint);
38207
38739
  if (legacyFromCheckout) return legacyFromCheckout;
38208
38740
  }
38209
38741
  let st;
38210
38742
  try {
38211
- st = fs24.statSync(hint);
38743
+ st = await fs28.promises.stat(hint);
38212
38744
  } catch {
38213
38745
  return null;
38214
38746
  }
38215
38747
  if (!st.isDirectory()) return null;
38216
- const legacyPaths = collectWorktreeRootsNamed(hint, sid, 24);
38217
- if (legacyPaths.length === 0) return null;
38218
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path26.resolve(legacyPaths[0]);
38748
+ const legacyPaths = await collectWorktreeRootsNamed(hint, sid);
38749
+ if (legacyPaths.paths.length === 0) return null;
38750
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths.paths) ?? path40.resolve(legacyPaths.paths[0]);
38219
38751
  return {
38220
- sessionParentPath: path26.resolve(isolated),
38752
+ sessionParentPath: path40.resolve(isolated),
38221
38753
  workingTreeRelRoot: hint,
38222
- repoCheckoutPaths: legacyPaths.map((p) => path26.resolve(p))
38754
+ repoCheckoutPaths: legacyPaths.paths.map((p) => path40.resolve(p))
38223
38755
  };
38224
38756
  }
38225
38757
 
38226
38758
  // src/worktrees/manager/discover-session-binding.ts
38227
- function discoverSessionBinding(params) {
38228
- return discoverSessionWorktreeOnDisk({
38229
- sessionId: params.sessionId,
38759
+ var discoverInflightBySessionId = /* @__PURE__ */ new Map();
38760
+ function discoverSessionBindingAsync(params) {
38761
+ const sid = params.sessionId.trim();
38762
+ if (!sid) return Promise.resolve(null);
38763
+ const existing = discoverInflightBySessionId.get(sid);
38764
+ if (existing) return existing;
38765
+ const promise2 = discoverSessionWorktreeOnDisk({
38766
+ sessionId: sid,
38230
38767
  worktreesRootPath: params.worktreesRootPath,
38231
38768
  layout: params.layout,
38232
38769
  bridgeRoot: getBridgeRoot()
38770
+ }).finally(() => {
38771
+ if (discoverInflightBySessionId.get(sid) === promise2) {
38772
+ discoverInflightBySessionId.delete(sid);
38773
+ }
38233
38774
  });
38775
+ discoverInflightBySessionId.set(sid, promise2);
38776
+ return promise2;
38234
38777
  }
38235
38778
 
38236
38779
  // src/worktrees/manager/resolve-isolated-session-parent-path.ts
@@ -38243,12 +38786,17 @@ function resolveIsolatedSessionParentPath(sessionId, cache2, ensureRepoCheckoutP
38243
38786
  if (!paths?.length) return null;
38244
38787
  return resolveIsolatedSessionParentPathFromCheckouts(paths);
38245
38788
  }
38246
- function ensureRepoCheckoutPathsForSession(sessionId, cache2, discover) {
38789
+ function ensureRepoCheckoutPathsForSession(sessionId, cache2) {
38790
+ if (!sessionId?.trim()) return void 0;
38791
+ const cached2 = cache2.getRepoCheckoutPaths(sessionId.trim());
38792
+ return cached2?.length ? [...cached2] : void 0;
38793
+ }
38794
+ async function ensureRepoCheckoutPathsForSessionAsync(sessionId, cache2, discover) {
38247
38795
  if (!sessionId?.trim()) return void 0;
38248
38796
  const sid = sessionId.trim();
38249
38797
  const cached2 = cache2.getRepoCheckoutPaths(sid);
38250
38798
  if (cached2?.length) return cached2;
38251
- const disc = discover(sid);
38799
+ const disc = await discover(sid);
38252
38800
  if (disc?.repoCheckoutPaths.length) {
38253
38801
  cache2.remember(sid, disc);
38254
38802
  return [...disc.repoCheckoutPaths];
@@ -38256,18 +38804,6 @@ function ensureRepoCheckoutPathsForSession(sessionId, cache2, discover) {
38256
38804
  return void 0;
38257
38805
  }
38258
38806
 
38259
- // src/worktrees/manager/resolve-commit-targets.ts
38260
- function resolveCommitTargets(sessionId, cache2, discover) {
38261
- const paths = cache2.getRepoCheckoutPathsRef(sessionId);
38262
- if (paths?.length) return paths;
38263
- const disc = discover(sessionId);
38264
- if (disc?.repoCheckoutPaths.length) {
38265
- cache2.remember(sessionId, disc);
38266
- return disc.repoCheckoutPaths;
38267
- }
38268
- return [getBridgeRoot()];
38269
- }
38270
-
38271
38807
  // src/worktrees/manager/parse-session-parent.ts
38272
38808
  function parseSessionParent(v) {
38273
38809
  if (v === "bridge_root" || v === "worktrees_root") return v;
@@ -38276,8 +38812,8 @@ function parseSessionParent(v) {
38276
38812
  }
38277
38813
 
38278
38814
  // src/worktrees/prepare-new-session-worktrees.ts
38279
- import * as fs25 from "node:fs";
38280
- import * as path27 from "node:path";
38815
+ import * as fs29 from "node:fs";
38816
+ import * as path41 from "node:path";
38281
38817
 
38282
38818
  // src/git/worktrees/worktree-add.ts
38283
38819
  async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch, baseRef = "HEAD") {
@@ -38299,10 +38835,10 @@ function resolveBaseRefForRepo(relNorm, baseBranches) {
38299
38835
  }
38300
38836
  async function prepareNewSessionWorktrees(options) {
38301
38837
  const { worktreesRootPath, bridgeRoot, sessionId, layout, log: log2, worktreeBaseBranches } = options;
38302
- const bridgeResolved = path27.resolve(bridgeRoot);
38838
+ const bridgeResolved = path41.resolve(bridgeRoot);
38303
38839
  const cwdKey = allocateDirNameForLauncherCwd(layout, bridgeResolved);
38304
- const bridgeKeyDir = path27.join(worktreesRootPath, cwdKey);
38305
- const sessionDir = path27.join(bridgeKeyDir, sessionId);
38840
+ const bridgeKeyDir = path41.join(worktreesRootPath, cwdKey);
38841
+ const sessionDir = path41.join(bridgeKeyDir, sessionId);
38306
38842
  const repos = await discoverGitReposUnderRoot(bridgeResolved);
38307
38843
  if (repos.length === 0) {
38308
38844
  log2("[worktrees] No Git repositories under bridge root; skipping worktree creation.");
@@ -38310,14 +38846,14 @@ async function prepareNewSessionWorktrees(options) {
38310
38846
  }
38311
38847
  const branch = `session-${sessionId}`;
38312
38848
  const worktreePaths = [];
38313
- fs25.mkdirSync(sessionDir, { recursive: true });
38849
+ fs29.mkdirSync(sessionDir, { recursive: true });
38314
38850
  for (const repo of repos) {
38315
- let rel = path27.relative(bridgeResolved, repo.absolutePath);
38316
- if (rel.startsWith("..") || path27.isAbsolute(rel)) continue;
38851
+ let rel = path41.relative(bridgeResolved, repo.absolutePath);
38852
+ if (rel.startsWith("..") || path41.isAbsolute(rel)) continue;
38317
38853
  const relNorm = normalizeRepoRelPath2(rel === "" ? "." : rel);
38318
- const wtPath = relNorm === "." ? sessionDir : path27.join(sessionDir, relNorm);
38854
+ const wtPath = relNorm === "." ? sessionDir : path41.join(sessionDir, relNorm);
38319
38855
  if (relNorm !== ".") {
38320
- fs25.mkdirSync(path27.dirname(wtPath), { recursive: true });
38856
+ fs29.mkdirSync(path41.dirname(wtPath), { recursive: true });
38321
38857
  }
38322
38858
  const baseRef = resolveBaseRefForRepo(relNorm, worktreeBaseBranches);
38323
38859
  try {
@@ -38358,10 +38894,10 @@ async function prepareAndRememberSessionWorktrees(params) {
38358
38894
  }
38359
38895
 
38360
38896
  // src/worktrees/manager/resolve-existing-session-parent-path.ts
38361
- function resolveExistingSessionParentPath(sessionId, cache2, discover) {
38897
+ async function resolveExistingSessionParentPath(sessionId, cache2, discover) {
38362
38898
  const cached2 = cache2.getSessionParentPath(sessionId);
38363
38899
  if (cached2) return cached2;
38364
- const disc = discover();
38900
+ const disc = await discover();
38365
38901
  if (disc) {
38366
38902
  cache2.remember(sessionId, disc);
38367
38903
  return cache2.getSessionParentPath(sessionId);
@@ -38370,9 +38906,9 @@ function resolveExistingSessionParentPath(sessionId, cache2, discover) {
38370
38906
  }
38371
38907
 
38372
38908
  // src/worktrees/manager/resolve-explicit-session-parent-path.ts
38373
- import * as path28 from "node:path";
38374
- function resolveExplicitSessionParentPath(params) {
38375
- const resolved = path28.resolve(params.parentPathRaw);
38909
+ import * as path42 from "node:path";
38910
+ async function resolveExplicitSessionParentPath(params) {
38911
+ const resolved = path42.resolve(params.parentPathRaw);
38376
38912
  if (parseSessionParent(params.sessionParent) !== "worktrees_root") {
38377
38913
  return resolved;
38378
38914
  }
@@ -38380,15 +38916,16 @@ function resolveExplicitSessionParentPath(params) {
38380
38916
  params.cache.remember(params.sessionId, binding);
38381
38917
  return params.cache.getSessionParentPath(params.sessionId) ?? resolved;
38382
38918
  };
38383
- const diskFirst = params.discover();
38919
+ const diskFirst = await params.discover();
38384
38920
  if (diskFirst) return rememberAndReturn(diskFirst);
38385
- const fromRoot = discoverSessionWorktreesUnderSessionWorktreeRoot(resolved, params.sessionId);
38921
+ const fromRoot = await discoverSessionWorktreesUnderSessionWorktreeRoot(resolved, params.sessionId);
38386
38922
  if (fromRoot) return rememberAndReturn(fromRoot);
38387
38923
  let cur = resolved;
38388
38924
  for (let i = 0; i < 16; i++) {
38389
- const tryRoot = discoverSessionWorktreesUnderSessionWorktreeRoot(cur, params.sessionId);
38925
+ await yieldToEventLoop();
38926
+ const tryRoot = await discoverSessionWorktreesUnderSessionWorktreeRoot(cur, params.sessionId);
38390
38927
  if (tryRoot) return rememberAndReturn(tryRoot);
38391
- const next = path28.dirname(cur);
38928
+ const next = path42.dirname(cur);
38392
38929
  if (next === cur) break;
38393
38930
  cur = next;
38394
38931
  }
@@ -38439,173 +38976,117 @@ async function resolveSessionParentPathForPrompt(params) {
38439
38976
  });
38440
38977
  }
38441
38978
 
38442
- // src/worktrees/manager/session-worktree-cache.ts
38443
- import * as path29 from "node:path";
38444
- var SessionWorktreeCache = class {
38445
- sessionRepoCheckoutPaths = /* @__PURE__ */ new Map();
38446
- sessionParentPathBySession = /* @__PURE__ */ new Map();
38447
- sessionWorkingTreeRelRootBySession = /* @__PURE__ */ new Map();
38448
- remember(sessionId, binding) {
38449
- const paths = binding.repoCheckoutPaths.map((p) => path29.resolve(p));
38450
- this.sessionRepoCheckoutPaths.set(sessionId, paths);
38451
- this.sessionParentPathBySession.set(sessionId, path29.resolve(binding.sessionParentPath));
38452
- this.sessionWorkingTreeRelRootBySession.set(sessionId, path29.resolve(binding.workingTreeRelRoot));
38453
- }
38454
- clearSession(sessionId) {
38455
- const paths = this.sessionRepoCheckoutPaths.get(sessionId);
38456
- this.sessionRepoCheckoutPaths.delete(sessionId);
38457
- this.sessionParentPathBySession.delete(sessionId);
38458
- this.sessionWorkingTreeRelRootBySession.delete(sessionId);
38459
- return paths;
38460
- }
38461
- getSessionParentPath(sessionId) {
38462
- return this.sessionParentPathBySession.get(sessionId);
38463
- }
38464
- getWorkingTreeRelRoot(sessionId) {
38465
- return this.sessionWorkingTreeRelRootBySession.get(sessionId) ?? null;
38466
- }
38467
- hasSession(sessionId) {
38468
- return this.sessionParentPathBySession.has(sessionId);
38469
- }
38470
- getRepoCheckoutPaths(sessionId) {
38471
- const paths = this.sessionRepoCheckoutPaths.get(sessionId);
38472
- return paths?.length ? [...paths] : void 0;
38473
- }
38474
- getRepoCheckoutPathsRef(sessionId) {
38475
- const paths = this.sessionRepoCheckoutPaths.get(sessionId);
38476
- return paths?.length ? paths : void 0;
38477
- }
38478
- isLegacyNestedLayout(sessionId) {
38479
- const parent = this.sessionParentPathBySession.get(sessionId);
38480
- const relRoot = this.sessionWorkingTreeRelRootBySession.get(sessionId);
38481
- if (!parent || !relRoot) return false;
38482
- return path29.resolve(parent) !== path29.resolve(relRoot);
38483
- }
38484
- };
38979
+ // src/worktrees/manager/session-worktree-paths.ts
38980
+ function createSessionWorktreePathsApi(ctx) {
38981
+ const discoverAsync = (sessionId) => discoverSessionBindingAsync({
38982
+ sessionId,
38983
+ worktreesRootPath: ctx.worktreesRootPath,
38984
+ layout: ctx.layout
38985
+ });
38986
+ return {
38987
+ discoverAsync,
38988
+ getIsolatedSessionParentPathForSession(sessionId) {
38989
+ return resolveIsolatedSessionParentPath(
38990
+ sessionId,
38991
+ ctx.cache,
38992
+ (sid) => ensureRepoCheckoutPathsForSession(sid, ctx.cache)
38993
+ );
38994
+ },
38995
+ resolveSessionParentPathForPrompt(sessionId, opts) {
38996
+ return resolveSessionParentPathForPrompt({
38997
+ sessionId,
38998
+ cache: ctx.cache,
38999
+ worktreesRootPath: ctx.worktreesRootPath,
39000
+ layout: ctx.layout,
39001
+ log: ctx.log,
39002
+ discover: (sid) => discoverAsync(sid),
39003
+ opts
39004
+ });
39005
+ },
39006
+ usesWorktreeSession(sessionId) {
39007
+ if (!sessionId) return false;
39008
+ return ctx.cache.hasSession(sessionId);
39009
+ },
39010
+ getRepoCheckoutPathsForSession(sessionId) {
39011
+ if (!sessionId) return void 0;
39012
+ return ctx.cache.getRepoCheckoutPaths(sessionId);
39013
+ },
39014
+ ensureRepoCheckoutPathsForSession(sessionId) {
39015
+ return ensureRepoCheckoutPathsForSession(sessionId, ctx.cache);
39016
+ },
39017
+ async ensureRepoCheckoutPathsForSessionAsync(sessionId) {
39018
+ return ensureRepoCheckoutPathsForSessionAsync(sessionId, ctx.cache, (sid) => discoverAsync(sid));
39019
+ },
39020
+ getSessionWorktreeRootForSession(sessionId) {
39021
+ return resolveIsolatedSessionParentPath(
39022
+ sessionId,
39023
+ ctx.cache,
39024
+ (sid) => ensureRepoCheckoutPathsForSession(sid, ctx.cache)
39025
+ );
39026
+ }
39027
+ };
39028
+ }
39029
+
39030
+ // src/worktrees/manager/session-worktree-git.ts
39031
+ function createSessionWorktreeGitApi(ctx) {
39032
+ const paths = createSessionWorktreePathsApi(ctx);
39033
+ return {
39034
+ async renameSessionBranch(sessionId, newBranch) {
39035
+ await renameSessionWorktreeBranch(ctx.cache, sessionId, newBranch, ctx.log);
39036
+ },
39037
+ async removeSessionWorktrees(sessionId) {
39038
+ await removeSessionWorktreeCheckouts(ctx.cache, sessionId, ctx.log);
39039
+ },
39040
+ async commitSession(params) {
39041
+ return commitSessionWorktree(ctx.cache, params);
39042
+ },
39043
+ async getSessionWorkingTreeStatus(sessionId) {
39044
+ return getSessionWorkingTreeStatus(ctx.cache, sessionId, (sid) => paths.discoverAsync(sid));
39045
+ },
39046
+ async getSessionWorkingTreeChangeDetails(sessionId, opts) {
39047
+ return getSessionWorkingTreeChangeDetails(ctx.cache, sessionId, (sid) => paths.discoverAsync(sid), opts);
39048
+ },
39049
+ async getSessionWorkingTreeChangeFilePatch(sessionId, opts) {
39050
+ return getSessionWorkingTreeChangeFilePatch(ctx.cache, sessionId, (sid) => paths.discoverAsync(sid), opts);
39051
+ },
39052
+ async pushSessionUpstream(sessionId) {
39053
+ return pushSessionUpstream(ctx.cache, sessionId, (sid) => paths.discoverAsync(sid));
39054
+ }
39055
+ };
39056
+ }
38485
39057
 
38486
- // src/worktrees/manager/manager.ts
39058
+ // src/worktrees/manager/session-worktree-manager.ts
38487
39059
  var SessionWorktreeManager = class {
38488
- worktreesRootPath;
38489
- log;
38490
- cache = new SessionWorktreeCache();
38491
- layout;
38492
39060
  constructor(options) {
38493
- this.worktreesRootPath = options.worktreesRootPath;
38494
- this.log = options.log;
38495
- this.layout = loadWorktreeLayout();
38496
- }
38497
- discover(sessionId) {
38498
- return discoverSessionBinding({
38499
- sessionId,
38500
- worktreesRootPath: this.worktreesRootPath,
38501
- layout: this.layout
38502
- });
38503
- }
38504
- getIsolatedSessionParentPathForSession(sessionId) {
38505
- return resolveIsolatedSessionParentPath(
38506
- sessionId,
38507
- this.cache,
38508
- (sid) => this.ensureRepoCheckoutPathsForSession(sid)
38509
- );
38510
- }
38511
- resolveSessionParentPathForPrompt(sessionId, opts) {
38512
- return resolveSessionParentPathForPrompt({
38513
- sessionId,
38514
- cache: this.cache,
38515
- worktreesRootPath: this.worktreesRootPath,
38516
- layout: this.layout,
38517
- log: this.log,
38518
- discover: (sid) => this.discover(sid),
38519
- opts
38520
- });
38521
- }
38522
- async renameSessionBranch(sessionId, newBranch) {
38523
- const paths = this.cache.getRepoCheckoutPathsRef(sessionId);
38524
- if (!paths?.length) return;
38525
- await renameSessionWorktreeBranches(paths, newBranch, this.log);
38526
- }
38527
- usesWorktreeSession(sessionId) {
38528
- if (!sessionId) return false;
38529
- return this.cache.hasSession(sessionId);
38530
- }
38531
- getRepoCheckoutPathsForSession(sessionId) {
38532
- if (!sessionId) return void 0;
38533
- return this.cache.getRepoCheckoutPaths(sessionId);
38534
- }
38535
- ensureRepoCheckoutPathsForSession(sessionId) {
38536
- return ensureRepoCheckoutPathsForSession(sessionId, this.cache, (sid) => this.discover(sid));
38537
- }
38538
- getSessionWorktreeRootForSession(sessionId) {
38539
- return this.getIsolatedSessionParentPathForSession(sessionId);
38540
- }
38541
- async removeSessionWorktrees(sessionId) {
38542
- const paths = this.cache.clearSession(sessionId);
38543
- if (!paths?.length) return;
38544
- await removeSessionWorktrees(paths, this.log);
38545
- }
38546
- async commitSession(params) {
38547
- const paths = this.cache.getRepoCheckoutPathsRef(params.sessionId);
38548
- const targets = paths?.length ? paths : [getBridgeRoot()];
38549
- return commitSessionWorktrees({
38550
- paths: targets,
38551
- branch: params.branch,
38552
- message: params.message,
38553
- push: params.push
38554
- });
38555
- }
38556
- async getSessionWorkingTreeStatus(sessionId) {
38557
- return aggregateSessionPathsWorkingTreeStatus(
38558
- resolveCommitTargets(sessionId, this.cache, (sid) => this.discover(sid))
38559
- );
38560
- }
38561
- async getSessionWorkingTreeChangeDetails(sessionId, opts) {
38562
- const targets = resolveCommitTargets(sessionId, this.cache, (sid) => this.discover(sid));
38563
- return getWorkingTreeChangeRepoDetails({
38564
- commitTargetPaths: targets,
38565
- sessionWorktreeRootPath: this.cache.getWorkingTreeRelRoot(sessionId),
38566
- legacyRepoNestedSessionLayout: this.cache.isLegacyNestedLayout(sessionId),
38567
- repoFilterRelPath: opts?.repoRelPath?.trim() ? opts.repoRelPath.trim() : null,
38568
- basis: opts?.basis,
38569
- recentCommitsLimit: opts?.recentCommitsLimit
38570
- });
38571
- }
38572
- async pushSessionUpstream(sessionId) {
38573
- try {
38574
- await pushAheadOfUpstreamForPaths(
38575
- resolveCommitTargets(sessionId, this.cache, (sid) => this.discover(sid))
38576
- );
38577
- return { ok: true };
38578
- } catch (e) {
38579
- const err = e instanceof Error ? e.message : String(e);
38580
- return { ok: false, error: err };
38581
- }
39061
+ const ctx = createSessionWorktreeManagerContext(options);
39062
+ Object.assign(this, createSessionWorktreePathsApi(ctx), createSessionWorktreeGitApi(ctx));
38582
39063
  }
38583
39064
  };
38584
39065
 
38585
39066
  // src/worktrees/manager/default-worktrees-root-path.ts
38586
- import * as path30 from "node:path";
39067
+ import * as path43 from "node:path";
38587
39068
  import os8 from "node:os";
38588
39069
  function defaultWorktreesRootPath() {
38589
- return path30.join(os8.homedir(), ".buildautomaton", "worktrees");
39070
+ return path43.join(os8.homedir(), ".buildautomaton", "worktrees");
38590
39071
  }
38591
39072
 
38592
39073
  // src/files/watch-file-index.ts
38593
39074
  import { watch } from "node:fs";
38594
- import path35 from "node:path";
39075
+ import path48 from "node:path";
38595
39076
 
38596
39077
  // src/files/index/paths.ts
38597
- import path31 from "node:path";
39078
+ import path44 from "node:path";
38598
39079
  import crypto2 from "node:crypto";
38599
39080
  function getCwdHashForFileIndex(resolvedCwd) {
38600
- return crypto2.createHash("sha256").update(path31.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
39081
+ return crypto2.createHash("sha256").update(path44.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
38601
39082
  }
38602
39083
 
38603
39084
  // src/files/index/build-file-index.ts
38604
- import path33 from "node:path";
39085
+ import path46 from "node:path";
38605
39086
 
38606
39087
  // src/files/index/walk-workspace-tree.ts
38607
- import fs26 from "node:fs";
38608
- import path32 from "node:path";
39088
+ import fs30 from "node:fs";
39089
+ import path45 from "node:path";
38609
39090
  var DEPENDENCY_INSTALL_DIR_NAMES = /* @__PURE__ */ new Set([
38610
39091
  "node_modules",
38611
39092
  "bower_components",
@@ -38622,7 +39103,7 @@ function shouldSkipWorkspaceWalkEntry(name) {
38622
39103
  async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
38623
39104
  let names;
38624
39105
  try {
38625
- names = await fs26.promises.readdir(dir);
39106
+ names = await fs30.promises.readdir(dir);
38626
39107
  } catch {
38627
39108
  return;
38628
39109
  }
@@ -38634,18 +39115,18 @@ async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
38634
39115
  if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
38635
39116
  }
38636
39117
  state.n++;
38637
- const full = path32.join(dir, name);
39118
+ const full = path45.join(dir, name);
38638
39119
  let stat3;
38639
39120
  try {
38640
- stat3 = await fs26.promises.stat(full);
39121
+ stat3 = await fs30.promises.stat(full);
38641
39122
  } catch {
38642
39123
  continue;
38643
39124
  }
38644
- const relative6 = path32.relative(baseDir, full).replace(/\\/g, "/");
39125
+ const relative7 = path45.relative(baseDir, full).replace(/\\/g, "/");
38645
39126
  if (stat3.isDirectory()) {
38646
39127
  await walkWorkspaceTreeAsync(full, baseDir, onFile, state);
38647
39128
  } else if (stat3.isFile()) {
38648
- onFile(relative6);
39129
+ onFile(relative7);
38649
39130
  }
38650
39131
  }
38651
39132
  }
@@ -38654,7 +39135,7 @@ function createWalkYieldState() {
38654
39135
  }
38655
39136
 
38656
39137
  // src/files/index/file-index-sqlite-lock.ts
38657
- import fs27 from "node:fs";
39138
+ import fs31 from "node:fs";
38658
39139
  function isSqliteCorruptError(e) {
38659
39140
  const msg = e instanceof Error ? e.message : String(e);
38660
39141
  return msg.includes("malformed") || msg.includes("database disk image is malformed") || msg.includes("corrupt");
@@ -38668,7 +39149,7 @@ function withFileIndexSqliteLock(fn) {
38668
39149
  if (!isSqliteCorruptError(e)) throw e;
38669
39150
  closeAllCliSqliteConnections();
38670
39151
  try {
38671
- fs27.unlinkSync(getCliSqlitePath());
39152
+ fs31.unlinkSync(getCliSqlitePath());
38672
39153
  } catch {
38673
39154
  }
38674
39155
  chain = Promise.resolve();
@@ -38743,7 +39224,7 @@ async function collectWorkspacePathsAsync(resolved) {
38743
39224
  }
38744
39225
  async function buildFileIndexAsync(cwd) {
38745
39226
  return withFileIndexSqliteLock(async () => {
38746
- const resolved = path33.resolve(cwd);
39227
+ const resolved = path46.resolve(cwd);
38747
39228
  await yieldToEventLoop();
38748
39229
  assertNotShutdown();
38749
39230
  const paths = await collectWorkspacePathsAsync(resolved);
@@ -38755,7 +39236,7 @@ async function buildFileIndexAsync(cwd) {
38755
39236
  }
38756
39237
 
38757
39238
  // src/files/index/ensure-file-index.ts
38758
- import path34 from "node:path";
39239
+ import path47 from "node:path";
38759
39240
 
38760
39241
  // src/files/index/search-file-index.ts
38761
39242
  function escapeLikePattern(fragment) {
@@ -38807,7 +39288,7 @@ async function searchBridgeFilePathsAsync(resolvedCwd, query, limit = 100) {
38807
39288
 
38808
39289
  // src/files/index/ensure-file-index.ts
38809
39290
  async function ensureFileIndexAsync(cwd) {
38810
- const resolved = path34.resolve(cwd);
39291
+ const resolved = path47.resolve(cwd);
38811
39292
  if (await bridgeFileIndexIsPopulated(resolved)) {
38812
39293
  return { fromCache: true, pathCount: await bridgeFileIndexPathCount(resolved) };
38813
39294
  }
@@ -38851,7 +39332,7 @@ function createFsWatcher(resolved, schedule) {
38851
39332
  }
38852
39333
  }
38853
39334
  function startFileIndexWatcher(cwd = getBridgeRoot()) {
38854
- const resolved = path35.resolve(cwd);
39335
+ const resolved = path48.resolve(cwd);
38855
39336
  void buildFileIndexAsync(resolved).catch((e) => {
38856
39337
  if (e instanceof CliSqliteInterrupted) return;
38857
39338
  console.error("[file-index] Initial index build failed:", e);
@@ -38881,7 +39362,7 @@ function startFileIndexWatcher(cwd = getBridgeRoot()) {
38881
39362
  }
38882
39363
 
38883
39364
  // src/connection/create-bridge-connection.ts
38884
- import * as path46 from "node:path";
39365
+ import * as path60 from "node:path";
38885
39366
 
38886
39367
  // src/dev-servers/manager/dev-server-manager.ts
38887
39368
  import { rm as rm2 } from "node:fs/promises";
@@ -38903,15 +39384,15 @@ function sendDevServerStatus(getWs, serverId, status, options) {
38903
39384
 
38904
39385
  // src/dev-servers/process/terminate-child-process.ts
38905
39386
  async function sigtermAndWaitForExit(proc, graceMs, log2, shortId) {
38906
- const exited = new Promise((resolve22) => {
38907
- proc.once("exit", () => resolve22());
39387
+ const exited = new Promise((resolve34) => {
39388
+ proc.once("exit", () => resolve34());
38908
39389
  });
38909
39390
  log2(`[dev-server] Sending SIGTERM to ${shortId} (pid=${proc.pid ?? "?"}).`);
38910
39391
  try {
38911
39392
  proc.kill("SIGTERM");
38912
39393
  } catch {
38913
39394
  }
38914
- await Promise.race([exited, new Promise((resolve22) => setTimeout(resolve22, graceMs))]);
39395
+ await Promise.race([exited, new Promise((resolve34) => setTimeout(resolve34, graceMs))]);
38915
39396
  }
38916
39397
  function forceKillChild(proc, log2, shortId, graceMs) {
38917
39398
  log2(
@@ -38925,7 +39406,7 @@ function forceKillChild(proc, log2, shortId, graceMs) {
38925
39406
  }
38926
39407
 
38927
39408
  // src/dev-servers/process/wire-dev-server-child-process.ts
38928
- import fs28 from "node:fs";
39409
+ import fs32 from "node:fs";
38929
39410
 
38930
39411
  // src/dev-servers/manager/forward-pipe.ts
38931
39412
  function forwardChildPipe(childReadable, terminal, onData) {
@@ -38961,7 +39442,7 @@ function wireDevServerChildProcess(d) {
38961
39442
  d.setPollInterval(void 0);
38962
39443
  return;
38963
39444
  }
38964
- fs28.readFile(d.mergedLogPath, (err, buf) => {
39445
+ fs32.readFile(d.mergedLogPath, (err, buf) => {
38965
39446
  if (err || (d.getSpawnGeneration() ?? 0) !== d.scheduledGen) return;
38966
39447
  if (buf.length <= d.mergedReadPos.value) return;
38967
39448
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
@@ -38999,7 +39480,7 @@ ${errTail}` : ""}`);
38999
39480
  d.sendStatus(code === 0 || code == null ? "stopped" : "error", detail, tails);
39000
39481
  };
39001
39482
  if (mergedPath) {
39002
- fs28.readFile(mergedPath, (err, buf) => {
39483
+ fs32.readFile(mergedPath, (err, buf) => {
39003
39484
  if (!err && buf.length > d.mergedReadPos.value) {
39004
39485
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
39005
39486
  if (chunk.length > 0) {
@@ -39101,13 +39582,13 @@ function parseDevServerDefs(servers) {
39101
39582
  }
39102
39583
 
39103
39584
  // src/dev-servers/manager/shell-spawn/utils.ts
39104
- import fs29 from "node:fs";
39585
+ import fs33 from "node:fs";
39105
39586
  function isSpawnEbadf(e) {
39106
39587
  return typeof e === "object" && e !== null && "code" in e && e.code === "EBADF";
39107
39588
  }
39108
39589
  function rmDirQuiet(dir) {
39109
39590
  try {
39110
- fs29.rmSync(dir, { recursive: true, force: true });
39591
+ fs33.rmSync(dir, { recursive: true, force: true });
39111
39592
  } catch {
39112
39593
  }
39113
39594
  }
@@ -39115,7 +39596,7 @@ var cachedDevNullReadFd;
39115
39596
  function devNullReadFd() {
39116
39597
  if (cachedDevNullReadFd === void 0) {
39117
39598
  const devPath = process.platform === "win32" ? "nul" : "/dev/null";
39118
- cachedDevNullReadFd = fs29.openSync(devPath, "r");
39599
+ cachedDevNullReadFd = fs33.openSync(devPath, "r");
39119
39600
  }
39120
39601
  return cachedDevNullReadFd;
39121
39602
  }
@@ -39189,15 +39670,15 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
39189
39670
 
39190
39671
  // src/dev-servers/manager/shell-spawn/try-spawn-merged-log-file.ts
39191
39672
  import { spawn as spawn7 } from "node:child_process";
39192
- import fs30 from "node:fs";
39673
+ import fs34 from "node:fs";
39193
39674
  import { tmpdir } from "node:os";
39194
- import path36 from "node:path";
39675
+ import path49 from "node:path";
39195
39676
  function trySpawnMergedLogFile(command, env, cwd, signal) {
39196
- const tmpRoot = fs30.mkdtempSync(path36.join(tmpdir(), "ba-devsrv-log-"));
39197
- const logPath = path36.join(tmpRoot, "combined.log");
39677
+ const tmpRoot = fs34.mkdtempSync(path49.join(tmpdir(), "ba-devsrv-log-"));
39678
+ const logPath = path49.join(tmpRoot, "combined.log");
39198
39679
  let logFd;
39199
39680
  try {
39200
- logFd = fs30.openSync(logPath, "a");
39681
+ logFd = fs34.openSync(logPath, "a");
39201
39682
  } catch {
39202
39683
  rmDirQuiet(tmpRoot);
39203
39684
  return null;
@@ -39216,7 +39697,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
39216
39697
  } else {
39217
39698
  proc = spawn7("/bin/sh", ["-c", command], { env, cwd, stdio, ...signal ? { signal } : {} });
39218
39699
  }
39219
- fs30.closeSync(logFd);
39700
+ fs34.closeSync(logFd);
39220
39701
  return {
39221
39702
  proc,
39222
39703
  pipedStdoutStderr: true,
@@ -39225,7 +39706,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
39225
39706
  };
39226
39707
  } catch (e) {
39227
39708
  try {
39228
- fs30.closeSync(logFd);
39709
+ fs34.closeSync(logFd);
39229
39710
  } catch {
39230
39711
  }
39231
39712
  rmDirQuiet(tmpRoot);
@@ -39236,22 +39717,22 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
39236
39717
 
39237
39718
  // src/dev-servers/manager/shell-spawn/try-spawn-shell-script-log-redirect.ts
39238
39719
  import { spawn as spawn8 } from "node:child_process";
39239
- import fs31 from "node:fs";
39720
+ import fs35 from "node:fs";
39240
39721
  import { tmpdir as tmpdir2 } from "node:os";
39241
- import path37 from "node:path";
39722
+ import path50 from "node:path";
39242
39723
  function shSingleQuote(s) {
39243
39724
  return `'${s.replace(/'/g, `'\\''`)}'`;
39244
39725
  }
39245
39726
  function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
39246
- const tmpRoot = fs31.mkdtempSync(path37.join(tmpdir2(), "ba-devsrv-sh-"));
39247
- const logPath = path37.join(tmpRoot, "combined.log");
39248
- const innerPath = path37.join(tmpRoot, "_cmd.sh");
39249
- const runnerPath = path37.join(tmpRoot, "_run.sh");
39727
+ const tmpRoot = fs35.mkdtempSync(path50.join(tmpdir2(), "ba-devsrv-sh-"));
39728
+ const logPath = path50.join(tmpRoot, "combined.log");
39729
+ const innerPath = path50.join(tmpRoot, "_cmd.sh");
39730
+ const runnerPath = path50.join(tmpRoot, "_run.sh");
39250
39731
  try {
39251
- fs31.writeFileSync(innerPath, `#!/bin/sh
39732
+ fs35.writeFileSync(innerPath, `#!/bin/sh
39252
39733
  ${command}
39253
39734
  `);
39254
- fs31.writeFileSync(
39735
+ fs35.writeFileSync(
39255
39736
  runnerPath,
39256
39737
  `#!/bin/sh
39257
39738
  cd ${shSingleQuote(cwd)}
@@ -39277,13 +39758,13 @@ cd ${shSingleQuote(cwd)}
39277
39758
  }
39278
39759
  }
39279
39760
  function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
39280
- const tmpRoot = fs31.mkdtempSync(path37.join(tmpdir2(), "ba-devsrv-sh-"));
39281
- const logPath = path37.join(tmpRoot, "combined.log");
39282
- const runnerPath = path37.join(tmpRoot, "_run.bat");
39761
+ const tmpRoot = fs35.mkdtempSync(path50.join(tmpdir2(), "ba-devsrv-sh-"));
39762
+ const logPath = path50.join(tmpRoot, "combined.log");
39763
+ const runnerPath = path50.join(tmpRoot, "_run.bat");
39283
39764
  const q = (p) => `"${p.replace(/"/g, '""')}"`;
39284
39765
  const com = process.env.ComSpec || "cmd.exe";
39285
39766
  try {
39286
- fs31.writeFileSync(
39767
+ fs35.writeFileSync(
39287
39768
  runnerPath,
39288
39769
  `@ECHO OFF\r
39289
39770
  CD /D ${q(cwd)}\r
@@ -39792,7 +40273,7 @@ async function proxyToLocal(request) {
39792
40273
  };
39793
40274
  const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
39794
40275
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
39795
- const once = await new Promise((resolve22) => {
40276
+ const once = await new Promise((resolve34) => {
39796
40277
  const req = mod.request(opts, (res) => {
39797
40278
  const chunks = [];
39798
40279
  res.on("data", (c) => chunks.push(c));
@@ -39803,7 +40284,7 @@ async function proxyToLocal(request) {
39803
40284
  if (typeof v === "string") headers[k] = v;
39804
40285
  else if (Array.isArray(v) && v[0]) headers[k] = v[0];
39805
40286
  }
39806
- resolve22({
40287
+ resolve34({
39807
40288
  id: request.id,
39808
40289
  statusCode: res.statusCode ?? 0,
39809
40290
  headers,
@@ -39812,7 +40293,7 @@ async function proxyToLocal(request) {
39812
40293
  });
39813
40294
  });
39814
40295
  req.on("error", (err) => {
39815
- resolve22({
40296
+ resolve34({
39816
40297
  id: request.id,
39817
40298
  statusCode: 0,
39818
40299
  headers: {},
@@ -40219,61 +40700,84 @@ function createOnBridgeIdentified(opts) {
40219
40700
  };
40220
40701
  }
40221
40702
 
40222
- // src/skills/discover-local-agent-skills.ts
40223
- import fs32 from "node:fs";
40224
- import path38 from "node:path";
40703
+ // src/skills/discovery/skill-discovery-roots.ts
40225
40704
  var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
40226
- function discoverLocalSkills(cwd) {
40705
+ async function yieldSkillDiscoveryWork(entryCount) {
40706
+ if (entryCount > 0 && entryCount % INDEX_WORK_YIELD_EVERY === 0) {
40707
+ await yieldToEventLoop();
40708
+ }
40709
+ }
40710
+
40711
+ // src/skills/discovery/discover-local-skills.ts
40712
+ import fs36 from "node:fs";
40713
+ import path51 from "node:path";
40714
+ function collectSkillFromDir(rel, base, name, seenKeys, out) {
40715
+ const dir = path51.join(base, name);
40716
+ try {
40717
+ if (!fs36.statSync(dir).isDirectory()) return;
40718
+ } catch {
40719
+ return;
40720
+ }
40721
+ const skillMd = path51.join(dir, "SKILL.md");
40722
+ if (!fs36.existsSync(skillMd)) return;
40723
+ const key = `${rel}/${name}`;
40724
+ if (seenKeys.has(key)) return;
40725
+ seenKeys.add(key);
40726
+ out.push({ skillKey: name, path: `${rel}/${name}`.replace(/\\/g, "/") });
40727
+ }
40728
+ async function discoverLocalSkillsAsync(cwd) {
40227
40729
  const out = [];
40228
40730
  const seenKeys = /* @__PURE__ */ new Set();
40731
+ let work = 0;
40229
40732
  for (const rel of SKILL_DISCOVERY_ROOTS) {
40230
- const base = path38.join(cwd, rel);
40231
- if (!fs32.existsSync(base) || !fs32.statSync(base).isDirectory()) continue;
40733
+ await yieldSkillDiscoveryWork(++work);
40734
+ const base = path51.join(cwd, rel);
40735
+ if (!fs36.existsSync(base) || !fs36.statSync(base).isDirectory()) continue;
40232
40736
  let entries = [];
40233
40737
  try {
40234
- entries = fs32.readdirSync(base);
40738
+ entries = fs36.readdirSync(base);
40235
40739
  } catch {
40236
40740
  continue;
40237
40741
  }
40238
- for (const name of entries) {
40239
- const dir = path38.join(base, name);
40240
- try {
40241
- if (!fs32.statSync(dir).isDirectory()) continue;
40242
- } catch {
40243
- continue;
40244
- }
40245
- const skillMd = path38.join(dir, "SKILL.md");
40246
- if (!fs32.existsSync(skillMd)) continue;
40247
- const key = `${rel}/${name}`;
40248
- if (seenKeys.has(key)) continue;
40249
- seenKeys.add(key);
40250
- out.push({ skillKey: name, path: `${rel}/${name}`.replace(/\\/g, "/") });
40742
+ for (let i = 0; i < entries.length; i++) {
40743
+ await yieldSkillDiscoveryWork(++work);
40744
+ collectSkillFromDir(rel, base, entries[i], seenKeys, out);
40251
40745
  }
40252
40746
  }
40253
40747
  return out;
40254
40748
  }
40255
- function discoverSkillLayoutRoots(cwd) {
40749
+
40750
+ // src/skills/discovery/discover-skill-layout-roots.ts
40751
+ import fs37 from "node:fs";
40752
+ import path52 from "node:path";
40753
+ function collectLayoutSkill(rel, base, name, skills2) {
40754
+ const dir = path52.join(base, name);
40755
+ try {
40756
+ if (!fs37.statSync(dir).isDirectory()) return;
40757
+ } catch {
40758
+ return;
40759
+ }
40760
+ if (!fs37.existsSync(path52.join(dir, "SKILL.md"))) return;
40761
+ const relPath = `${rel}/${name}`.replace(/\\/g, "/");
40762
+ skills2.push({ name, relPath });
40763
+ }
40764
+ async function discoverSkillLayoutRootsAsync(cwd) {
40256
40765
  const roots = [];
40766
+ let work = 0;
40257
40767
  for (const rel of SKILL_DISCOVERY_ROOTS) {
40258
- const base = path38.join(cwd, rel);
40259
- if (!fs32.existsSync(base) || !fs32.statSync(base).isDirectory()) continue;
40768
+ await yieldSkillDiscoveryWork(++work);
40769
+ const base = path52.join(cwd, rel);
40770
+ if (!fs37.existsSync(base) || !fs37.statSync(base).isDirectory()) continue;
40260
40771
  let entries = [];
40261
40772
  try {
40262
- entries = fs32.readdirSync(base);
40773
+ entries = fs37.readdirSync(base);
40263
40774
  } catch {
40264
40775
  continue;
40265
40776
  }
40266
40777
  const skills2 = [];
40267
- for (const name of entries) {
40268
- const dir = path38.join(base, name);
40269
- try {
40270
- if (!fs32.statSync(dir).isDirectory()) continue;
40271
- } catch {
40272
- continue;
40273
- }
40274
- if (!fs32.existsSync(path38.join(dir, "SKILL.md"))) continue;
40275
- const relPath = `${rel}/${name}`.replace(/\\/g, "/");
40276
- skills2.push({ name, relPath });
40778
+ for (let i = 0; i < entries.length; i++) {
40779
+ await yieldSkillDiscoveryWork(++work);
40780
+ collectLayoutSkill(rel, base, entries[i], skills2);
40277
40781
  }
40278
40782
  if (skills2.length > 0) {
40279
40783
  roots.push({ path: rel.replace(/\\/g, "/"), skills: skills2 });
@@ -40315,16 +40819,18 @@ async function detectLocalAgentTypes() {
40315
40819
  function createSendLocalSkillsReport(getWs, logFn) {
40316
40820
  return () => {
40317
40821
  setImmediate(() => {
40318
- try {
40319
- const socket = getWs();
40320
- if (!socket || socket.readyState !== wrapper_default.OPEN) return;
40321
- const skills2 = discoverLocalSkills(getBridgeRoot());
40322
- sendWsMessage(socket, { type: "local_skills", skills: skills2 });
40323
- } catch (e) {
40324
- logFn(
40325
- `[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
40326
- );
40327
- }
40822
+ void (async () => {
40823
+ try {
40824
+ const socket = getWs();
40825
+ if (!socket || socket.readyState !== wrapper_default.OPEN) return;
40826
+ const skills2 = await discoverLocalSkillsAsync(getBridgeRoot());
40827
+ sendWsMessage(socket, { type: "local_skills", skills: skills2 });
40828
+ } catch (e) {
40829
+ logFn(
40830
+ `[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
40831
+ );
40832
+ }
40833
+ })();
40328
40834
  });
40329
40835
  };
40330
40836
  }
@@ -40627,7 +41133,7 @@ function syncRunningTurnQueueKeys(turns, queueKey) {
40627
41133
  }
40628
41134
 
40629
41135
  // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
40630
- import fs33 from "node:fs";
41136
+ import fs38 from "node:fs";
40631
41137
  async function runLocalRevertBeforeQueuedPrompt(next, deps) {
40632
41138
  if (next.serverState !== "requeued_with_revert") return true;
40633
41139
  const sid = next.sessionId;
@@ -40636,7 +41142,7 @@ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
40636
41142
  const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
40637
41143
  const file2 = snapshotFilePath(agentBase, tid);
40638
41144
  try {
40639
- await fs33.promises.access(file2, fs33.constants.F_OK);
41145
+ await fs38.promises.access(file2, fs38.constants.F_OK);
40640
41146
  } catch {
40641
41147
  deps.log(
40642
41148
  `[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
@@ -40751,7 +41257,7 @@ function createBridgePromptSenders(deps, getWs) {
40751
41257
  sendWsMessage(s, wire);
40752
41258
  return true;
40753
41259
  };
40754
- const sendResult2 = (result) => {
41260
+ const sendResult = (result) => {
40755
41261
  const skipEncryptForChangeSummaryFollowUp = result.type === "prompt_result" && result.followUpCatalogPromptId === BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID;
40756
41262
  const encryptedFields = result.type === "prompt_result" && !skipEncryptForChangeSummaryFollowUp ? ["output", "error"] : [];
40757
41263
  sendBridgeMessage(result, encryptedFields);
@@ -40784,7 +41290,7 @@ function createBridgePromptSenders(deps, getWs) {
40784
41290
  ]) : payload;
40785
41291
  sendWsMessage(s, wire);
40786
41292
  };
40787
- return { sendBridgeMessage, sendResult: sendResult2, sendSessionUpdate };
41293
+ return { sendBridgeMessage, sendResult, sendSessionUpdate };
40788
41294
  }
40789
41295
 
40790
41296
  // src/agents/acp/from-bridge/handle-bridge-prompt/parse-bridge-attachments.ts
@@ -40888,7 +41394,7 @@ async function readGitBranch(cwd) {
40888
41394
  async function runBridgePromptPreamble(params) {
40889
41395
  const { getWs, log: log2, sessionWorktreeManager, sessionId, runId, effectiveCwd } = params;
40890
41396
  const s = getWs();
40891
- const repoCheckoutPaths = sessionWorktreeManager.ensureRepoCheckoutPathsForSession(sessionId);
41397
+ const repoCheckoutPaths = await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sessionId);
40892
41398
  const repoRoots = await resolveSnapshotRepoRoots({
40893
41399
  worktreePaths: repoCheckoutPaths,
40894
41400
  fallbackCwd: effectiveCwd,
@@ -40924,9 +41430,9 @@ function parseChangeSummarySnapshots(raw) {
40924
41430
  for (const item of raw) {
40925
41431
  if (!item || typeof item !== "object") continue;
40926
41432
  const o = item;
40927
- const path48 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
40928
- if (!path48) continue;
40929
- const row = { path: path48 };
41433
+ const path62 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
41434
+ if (!path62) continue;
41435
+ const row = { path: path62 };
40930
41436
  if (typeof o.patchContent === "string") row.patchContent = o.patchContent;
40931
41437
  if (typeof o.oldText === "string") row.oldText = o.oldText;
40932
41438
  if (typeof o.newText === "string") row.newText = o.newText;
@@ -40960,7 +41466,7 @@ async function runPreambleAndPrompt(params) {
40960
41466
  agentId,
40961
41467
  agentConfig,
40962
41468
  resolvedCwd,
40963
- senders: { sendResult: sendResult2, sendSessionUpdate }
41469
+ senders: { sendResult, sendSessionUpdate }
40964
41470
  } = params;
40965
41471
  const effectiveCwd = resolveSessionParentPathForAgentProcess(resolvedCwd);
40966
41472
  await runBridgePromptPreamble({
@@ -40998,7 +41504,7 @@ async function runPreambleAndPrompt(params) {
40998
41504
  agentId,
40999
41505
  agentConfig,
41000
41506
  sessionParentPath: effectiveCwd,
41001
- sendResult: sendResult2,
41507
+ sendResult,
41002
41508
  sendSessionUpdate,
41003
41509
  followUpCatalogPromptId,
41004
41510
  sessionChangeSummaryFilePaths: pathsForUpload,
@@ -41136,8 +41642,8 @@ function randomSecret() {
41136
41642
  }
41137
41643
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
41138
41644
  }
41139
- async function requestPreviewApi(port, secret, method, path48, body) {
41140
- const url2 = `http://127.0.0.1:${port}${path48}`;
41645
+ async function requestPreviewApi(port, secret, method, path62, body) {
41646
+ const url2 = `http://127.0.0.1:${port}${path62}`;
41141
41647
  const headers = {
41142
41648
  [PREVIEW_SECRET_HEADER]: secret,
41143
41649
  "Content-Type": "application/json"
@@ -41149,7 +41655,7 @@ async function requestPreviewApi(port, secret, method, path48, body) {
41149
41655
  });
41150
41656
  const data = await res.json().catch(() => ({}));
41151
41657
  if (!res.ok) {
41152
- throw new Error(data?.error ?? `Preview API ${method} ${path48}: ${res.status}`);
41658
+ throw new Error(data?.error ?? `Preview API ${method} ${path62}: ${res.status}`);
41153
41659
  }
41154
41660
  return data;
41155
41661
  }
@@ -41314,14 +41820,14 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
41314
41820
  };
41315
41821
 
41316
41822
  // src/files/list-dir/index.ts
41317
- import fs35 from "node:fs";
41823
+ import fs40 from "node:fs";
41318
41824
 
41319
41825
  // src/files/ensure-under-cwd.ts
41320
- import path39 from "node:path";
41826
+ import path53 from "node:path";
41321
41827
  function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
41322
- const normalized = path39.normalize(relativePath).replace(/^(\.\/)+/, "");
41323
- const resolved = path39.resolve(cwd, normalized);
41324
- if (!resolved.startsWith(cwd + path39.sep) && resolved !== cwd) {
41828
+ const normalized = path53.normalize(relativePath).replace(/^(\.\/)+/, "");
41829
+ const resolved = path53.resolve(cwd, normalized);
41830
+ if (!resolved.startsWith(cwd + path53.sep) && resolved !== cwd) {
41325
41831
  return null;
41326
41832
  }
41327
41833
  return resolved;
@@ -41331,15 +41837,15 @@ function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
41331
41837
  var LIST_DIR_YIELD_EVERY = 256;
41332
41838
 
41333
41839
  // src/files/list-dir/map-dir-entry.ts
41334
- import path40 from "node:path";
41335
- import fs34 from "node:fs";
41840
+ import path54 from "node:path";
41841
+ import fs39 from "node:fs";
41336
41842
  async function mapDirEntry(d, relativePath, resolved) {
41337
- const entryPath = path40.join(relativePath || ".", d.name).replace(/\\/g, "/");
41338
- const fullPath = path40.join(resolved, d.name);
41843
+ const entryPath = path54.join(relativePath || ".", d.name).replace(/\\/g, "/");
41844
+ const fullPath = path54.join(resolved, d.name);
41339
41845
  let isDir = d.isDirectory();
41340
41846
  if (d.isSymbolicLink()) {
41341
41847
  try {
41342
- const targetStat = await fs34.promises.stat(fullPath);
41848
+ const targetStat = await fs39.promises.stat(fullPath);
41343
41849
  isDir = targetStat.isDirectory();
41344
41850
  } catch {
41345
41851
  isDir = false;
@@ -41369,7 +41875,7 @@ async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
41369
41875
  return { error: "Path is outside working directory" };
41370
41876
  }
41371
41877
  try {
41372
- const names = await fs35.promises.readdir(resolved, { withFileTypes: true });
41878
+ const names = await fs40.promises.readdir(resolved, { withFileTypes: true });
41373
41879
  const entries = [];
41374
41880
  for (let i = 0; i < names.length; i++) {
41375
41881
  if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
@@ -41389,18 +41895,18 @@ var LINE_CHUNK_SIZE = 64 * 1024;
41389
41895
  var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
41390
41896
 
41391
41897
  // src/files/read-file/resolve-file-path.ts
41392
- import fs36 from "node:fs";
41898
+ import fs41 from "node:fs";
41393
41899
  async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
41394
41900
  const resolved = ensureUnderCwd(relativePath, sessionParentPath);
41395
41901
  if (!resolved) return { error: "Path is outside working directory" };
41396
41902
  let real;
41397
41903
  try {
41398
- real = await fs36.promises.realpath(resolved);
41904
+ real = await fs41.promises.realpath(resolved);
41399
41905
  } catch {
41400
41906
  real = resolved;
41401
41907
  }
41402
41908
  try {
41403
- const stat3 = await fs36.promises.stat(real);
41909
+ const stat3 = await fs41.promises.stat(real);
41404
41910
  if (!stat3.isFile()) return { error: "Not a file" };
41405
41911
  return real;
41406
41912
  } catch (err) {
@@ -41409,11 +41915,11 @@ async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeR
41409
41915
  }
41410
41916
 
41411
41917
  // src/files/read-file/read-file-range-async.ts
41412
- import fs37 from "node:fs";
41918
+ import fs42 from "node:fs";
41413
41919
  import { StringDecoder } from "node:string_decoder";
41414
41920
  async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
41415
- const fileSize = (await fs37.promises.stat(filePath)).size;
41416
- const fd = await fs37.promises.open(filePath, "r");
41921
+ const fileSize = (await fs42.promises.stat(filePath)).size;
41922
+ const fd = await fs42.promises.open(filePath, "r");
41417
41923
  const bufSize = 64 * 1024;
41418
41924
  const buf = Buffer.alloc(bufSize);
41419
41925
  const decoder = new StringDecoder("utf8");
@@ -41575,11 +42081,11 @@ async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, li
41575
42081
  }
41576
42082
 
41577
42083
  // src/files/read-file/read-file-buffer-full-async.ts
41578
- import fs38 from "node:fs";
42084
+ import fs43 from "node:fs";
41579
42085
  var READ_CHUNK_BYTES = 256 * 1024;
41580
42086
  async function readFileBufferFullAsync(filePath) {
41581
- const stat3 = await fs38.promises.stat(filePath);
41582
- const fd = await fs38.promises.open(filePath, "r");
42087
+ const stat3 = await fs43.promises.stat(filePath);
42088
+ const fd = await fs43.promises.open(filePath, "r");
41583
42089
  const chunks = [];
41584
42090
  let position = 0;
41585
42091
  let bytesSinceYield = 0;
@@ -41675,10 +42181,10 @@ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineC
41675
42181
  }
41676
42182
 
41677
42183
  // src/files/resolve-file-browser-session-parent.ts
41678
- function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
42184
+ async function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
41679
42185
  const sid = sessionId?.trim();
41680
42186
  if (sid) {
41681
- sessionWorktreeManager.ensureRepoCheckoutPathsForSession(sid);
42187
+ await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sid);
41682
42188
  const worktreeRoot = sessionWorktreeManager.getSessionWorktreeRootForSession(sid);
41683
42189
  if (worktreeRoot) return worktreeRoot;
41684
42190
  }
@@ -41686,14 +42192,14 @@ function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
41686
42192
  }
41687
42193
 
41688
42194
  // src/files/handle-file-browser-search.ts
41689
- import path41 from "node:path";
42195
+ import path55 from "node:path";
41690
42196
  var SEARCH_LIMIT = 100;
41691
42197
  function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
41692
42198
  void (async () => {
41693
42199
  await yieldToEventLoop();
41694
42200
  const q = typeof msg.q === "string" ? msg.q : "";
41695
- const sessionParentPath = path41.resolve(
41696
- sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
42201
+ const sessionParentPath = path55.resolve(
42202
+ sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
41697
42203
  );
41698
42204
  if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
41699
42205
  const payload2 = {
@@ -41731,7 +42237,7 @@ function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
41731
42237
  void (async () => {
41732
42238
  const reqPath = msg.path.replace(/^\/+/, "") || ".";
41733
42239
  const op = msg.op === "read" ? "read" : "list";
41734
- const sessionParentPath = sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
42240
+ const sessionParentPath = sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
41735
42241
  if (op === "list") {
41736
42242
  const result = await listDirAsync(reqPath, sessionParentPath);
41737
42243
  if ("error" in result) {
@@ -41802,37 +42308,56 @@ function handleFileBrowserSearchMessage(msg, { getWs, e2ee, sessionWorktreeManag
41802
42308
 
41803
42309
  // src/routing/handlers/skill-layout-request.ts
41804
42310
  function handleSkillLayoutRequest(msg, deps) {
41805
- const socket = deps.getWs();
41806
42311
  const id = typeof msg.id === "string" ? msg.id : "";
41807
- const roots = discoverSkillLayoutRoots(getBridgeRoot());
41808
- if (socket) {
41809
- sendWsMessage(socket, { type: "skill_layout_response", id, roots });
41810
- }
42312
+ void (async () => {
42313
+ const socket = deps.getWs();
42314
+ const roots = await discoverSkillLayoutRootsAsync(getBridgeRoot());
42315
+ if (socket) {
42316
+ sendWsMessage(socket, { type: "skill_layout_response", id, roots });
42317
+ }
42318
+ })();
41811
42319
  }
41812
42320
 
41813
- // src/skills/install-remote-skills.ts
41814
- import fs39 from "node:fs";
41815
- import path42 from "node:path";
41816
- function installRemoteSkills(cwd, targetDir, items) {
42321
+ // src/skills/install/is-valid-install-item.ts
42322
+ function isValidRemoteSkillInstallItem(item) {
42323
+ if (item === null || typeof item !== "object") return false;
42324
+ const o = item;
42325
+ return typeof o.sourceId === "string" && typeof o.skillName === "string" && typeof o.versionHash === "string" && Array.isArray(o.files);
42326
+ }
42327
+
42328
+ // src/skills/install/install-remote-skills-async.ts
42329
+ import fs44 from "node:fs";
42330
+ import path56 from "node:path";
42331
+
42332
+ // src/skills/install/constants.ts
42333
+ var INSTALL_SKILLS_YIELD_EVERY = 16;
42334
+
42335
+ // src/skills/install/install-remote-skills-async.ts
42336
+ async function writeInstallFileAsync(skillDir, f, filesWritten) {
42337
+ if (typeof f.path !== "string" || !f.text && !f.base64) return;
42338
+ if (++filesWritten.count % INSTALL_SKILLS_YIELD_EVERY === 0) {
42339
+ await yieldToEventLoop();
42340
+ }
42341
+ const dest = path56.join(skillDir, f.path);
42342
+ await fs44.promises.mkdir(path56.dirname(dest), { recursive: true });
42343
+ if (f.text !== void 0) {
42344
+ await fs44.promises.writeFile(dest, f.text, "utf8");
42345
+ } else if (f.base64) {
42346
+ await fs44.promises.writeFile(dest, Buffer.from(f.base64, "base64"));
42347
+ }
42348
+ }
42349
+ async function installRemoteSkillsAsync(cwd, targetDir, items) {
41817
42350
  const installed2 = [];
41818
42351
  if (!Array.isArray(items)) {
41819
42352
  return { success: false, error: "Invalid items" };
41820
42353
  }
42354
+ const filesWritten = { count: 0 };
41821
42355
  try {
41822
42356
  for (const item of items) {
41823
- if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
41824
- continue;
41825
- }
41826
- const skillDir = path42.join(cwd, targetDir, item.skillName);
42357
+ if (!isValidRemoteSkillInstallItem(item)) continue;
42358
+ const skillDir = path56.join(cwd, targetDir, item.skillName);
41827
42359
  for (const f of item.files) {
41828
- if (typeof f.path !== "string" || !f.text && !f.base64) continue;
41829
- const dest = path42.join(skillDir, f.path);
41830
- fs39.mkdirSync(path42.dirname(dest), { recursive: true });
41831
- if (f.text !== void 0) {
41832
- fs39.writeFileSync(dest, f.text, "utf8");
41833
- } else if (f.base64) {
41834
- fs39.writeFileSync(dest, Buffer.from(f.base64, "base64"));
41835
- }
42360
+ await writeInstallFileAsync(skillDir, f, filesWritten);
41836
42361
  }
41837
42362
  installed2.push({
41838
42363
  sourceId: item.sourceId,
@@ -41848,118 +42373,241 @@ function installRemoteSkills(cwd, targetDir, items) {
41848
42373
 
41849
42374
  // src/routing/handlers/install-skills.ts
41850
42375
  var handleInstallSkillsMessage = (msg, deps) => {
41851
- const socket = deps.getWs();
41852
42376
  const id = typeof msg.id === "string" ? msg.id : "";
41853
42377
  const targetDir = typeof msg.targetDir === "string" && msg.targetDir.trim() ? msg.targetDir.trim() : ".agents/skills";
41854
42378
  const rawItems = msg.items;
41855
42379
  const cwd = getBridgeRoot();
41856
- const result = installRemoteSkills(cwd, targetDir, rawItems);
41857
- if (!result.success) {
41858
- const err = result.error ?? "Invalid items";
41859
- deps.log(`[Bridge service] Install skills failed: ${err}`);
42380
+ void (async () => {
42381
+ const socket = deps.getWs();
42382
+ const result = await installRemoteSkillsAsync(cwd, targetDir, rawItems);
42383
+ if (!result.success) {
42384
+ const err = result.error ?? "Invalid items";
42385
+ deps.log(`[Bridge service] Install skills failed: ${err}`);
42386
+ if (socket) {
42387
+ sendWsMessage(socket, { type: "install_skills_result", id, success: false, error: err });
42388
+ }
42389
+ return;
42390
+ }
41860
42391
  if (socket) {
41861
- sendWsMessage(socket, { type: "install_skills_result", id, success: false, error: err });
42392
+ sendWsMessage(socket, { type: "install_skills_result", id, success: true, installed: result.installed });
41862
42393
  }
41863
- return;
41864
- }
41865
- if (socket) {
41866
- sendWsMessage(socket, { type: "install_skills_result", id, success: true, installed: result.installed });
41867
- }
42394
+ })();
41868
42395
  };
41869
42396
 
41870
42397
  // src/routing/handlers/refresh-local-skills.ts
41871
42398
  var handleRefreshLocalSkills = (_msg, deps) => {
41872
- deps.sendLocalSkillsReport?.();
42399
+ setImmediate(() => {
42400
+ deps.sendLocalSkillsReport?.();
42401
+ });
41873
42402
  };
41874
42403
 
41875
- // src/routing/handlers/session-git-request.ts
41876
- function sendResult(ws, id, payload, e2ee, encryptedFields = []) {
42404
+ // src/routing/handlers/git/handle-session-git-commit.ts
42405
+ async function handleSessionGitCommitAction(deps, sessionId, msg, reply) {
42406
+ const branch = typeof msg.branch === "string" ? msg.branch : "";
42407
+ const message = typeof msg.message === "string" ? msg.message : "";
42408
+ const pushAfterCommit = msg.pushAfterCommit === true;
42409
+ if (!branch.trim() || !message.trim()) {
42410
+ reply({ ok: false, error: "branch and message are required for commit" });
42411
+ return;
42412
+ }
42413
+ const commitRes = await deps.sessionWorktreeManager.commitSession({
42414
+ sessionId,
42415
+ branch: branch.trim(),
42416
+ message: message.trim(),
42417
+ push: pushAfterCommit
42418
+ });
42419
+ if (!commitRes.ok) {
42420
+ reply({ ok: false, error: commitRes.error ?? "Commit failed" });
42421
+ return;
42422
+ }
42423
+ const st = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
42424
+ reply({
42425
+ ok: true,
42426
+ hasUncommittedChanges: st.hasUncommittedChanges,
42427
+ hasUnpushedCommits: st.hasUnpushedCommits,
42428
+ uncommittedFileCount: st.uncommittedFileCount
42429
+ });
42430
+ }
42431
+
42432
+ // src/routing/handlers/git/session-git-changes-params.ts
42433
+ function readString(value) {
42434
+ return typeof value === "string" ? value.trim() : "";
42435
+ }
42436
+ function readChangesObject(msg) {
42437
+ const changes = msg.changes;
42438
+ if (!changes || typeof changes !== "object" || Array.isArray(changes)) return null;
42439
+ return changes;
42440
+ }
42441
+ function parseSessionGitChangesParams(msg) {
42442
+ const nested = readChangesObject(msg);
42443
+ const file2 = nested?.file && typeof nested.file === "object" && !Array.isArray(nested.file) ? nested.file : null;
42444
+ const repoRelPath = readString(nested?.repoRelPath) || readString(msg.changesRepoRelPath);
42445
+ const viewRaw = nested?.view ?? msg.changesView;
42446
+ const view = viewRaw === "commit" ? "commit" : "working";
42447
+ const commitSha = readString(nested?.commitSha) || readString(msg.changesCommitSha);
42448
+ const recentCommitsLimit = nested?.recentCommitsLimit ?? msg.changesRecentCommitsLimit;
42449
+ return {
42450
+ repoRelPath,
42451
+ view,
42452
+ commitSha,
42453
+ recentCommitsLimit: typeof recentCommitsLimit === "number" || typeof recentCommitsLimit === "string" ? recentCommitsLimit : void 0,
42454
+ fileWorkspaceRelPath: readString(file2?.workspaceRelPath),
42455
+ fileChange: readString(file2?.change),
42456
+ fileMovedFromWorkspaceRelPath: readString(file2?.movedFromWorkspaceRelPath)
42457
+ };
42458
+ }
42459
+
42460
+ // src/routing/handlers/git/handle-session-git-get-change-file-patch.ts
42461
+ async function handleSessionGitGetChangeFilePatchAction(deps, msg, reply) {
42462
+ const changes = parseSessionGitChangesParams(msg);
42463
+ const { repoRelPath: repoRel, view, commitSha, fileWorkspaceRelPath, fileChange, fileMovedFromWorkspaceRelPath } = changes;
42464
+ const change = parseWorkingTreeChangeKind(fileChange);
42465
+ if (!repoRel || !fileWorkspaceRelPath || !change) {
42466
+ reply({
42467
+ ok: false,
42468
+ error: "changes.repoRelPath, changes.file.workspaceRelPath, and changes.file.change are required"
42469
+ });
42470
+ return;
42471
+ }
42472
+ if (view === "commit" && !commitSha) {
42473
+ reply({ ok: false, error: "changes.commitSha is required for commit file patch" });
42474
+ return;
42475
+ }
42476
+ const patch = await deps.sessionWorktreeManager.getSessionWorkingTreeChangeFilePatch(msg.sessionId, {
42477
+ repoRelPath: repoRel,
42478
+ workspaceRelPath: fileWorkspaceRelPath,
42479
+ change,
42480
+ movedFromWorkspaceRelPath: fileMovedFromWorkspaceRelPath || null,
42481
+ basis: view === "commit" ? { kind: "commit", sha: commitSha } : { kind: "working" }
42482
+ });
42483
+ reply(
42484
+ {
42485
+ ok: true,
42486
+ patchContent: patch.patchContent ?? null,
42487
+ totalLines: patch.totalLines
42488
+ },
42489
+ ["patchContent"]
42490
+ );
42491
+ }
42492
+
42493
+ // src/routing/handlers/git/coalesce-list-changes.ts
42494
+ var listChangesInflight = /* @__PURE__ */ new Map();
42495
+ function normalizeListChangesRepoRel(repoRelPath) {
42496
+ if (!repoRelPath?.trim()) return "";
42497
+ return normRepoRel(repoRelPath.trim());
42498
+ }
42499
+ function listChangesInflightKey(sessionId, opts) {
42500
+ const basis = opts?.basis;
42501
+ return [
42502
+ sessionId,
42503
+ normalizeListChangesRepoRel(opts?.repoRelPath),
42504
+ basis?.kind === "commit" ? `commit:${basis.sha}` : "working",
42505
+ String(opts?.recentCommitsLimit ?? "")
42506
+ ].join("|");
42507
+ }
42508
+ function getSessionWorkingTreeChangeDetailsCoalesced(sessionWorktreeManager, sessionId, opts) {
42509
+ const key = listChangesInflightKey(sessionId, opts);
42510
+ const existing = listChangesInflight.get(key);
42511
+ if (existing) return existing;
42512
+ const promise2 = sessionWorktreeManager.getSessionWorkingTreeChangeDetails(sessionId, opts).finally(() => {
42513
+ if (listChangesInflight.get(key) === promise2) listChangesInflight.delete(key);
42514
+ });
42515
+ listChangesInflight.set(key, promise2);
42516
+ return promise2;
42517
+ }
42518
+
42519
+ // src/routing/handlers/git/handle-session-git-list-changes.ts
42520
+ async function handleSessionGitListChangesAction(deps, msg, reply) {
42521
+ const changes = parseSessionGitChangesParams(msg);
42522
+ const { repoRelPath: repoRel, view, commitSha, recentCommitsLimit } = changes;
42523
+ if (view === "commit") {
42524
+ if (!repoRel || !commitSha) {
42525
+ reply({
42526
+ ok: false,
42527
+ error: "changes.repoRelPath and changes.commitSha are required for commit view"
42528
+ });
42529
+ return;
42530
+ }
42531
+ }
42532
+ const opts = repoRel && view === "commit" && commitSha ? {
42533
+ repoRelPath: repoRel,
42534
+ basis: { kind: "commit", sha: commitSha },
42535
+ recentCommitsLimit
42536
+ } : repoRel ? { repoRelPath: repoRel, basis: { kind: "working" }, recentCommitsLimit } : recentCommitsLimit !== void 0 ? { recentCommitsLimit } : void 0;
42537
+ const repos = await getSessionWorkingTreeChangeDetailsCoalesced(
42538
+ deps.sessionWorktreeManager,
42539
+ msg.sessionId,
42540
+ opts
42541
+ );
42542
+ reply({ ok: true, repos }, ["repos"]);
42543
+ }
42544
+
42545
+ // src/routing/handlers/git/handle-session-git-push.ts
42546
+ async function handleSessionGitPushAction(deps, sessionId, reply) {
42547
+ const pushRes = await deps.sessionWorktreeManager.pushSessionUpstream(sessionId);
42548
+ if (!pushRes.ok) {
42549
+ reply({ ok: false, error: pushRes.error ?? "Push failed" });
42550
+ return;
42551
+ }
42552
+ const st = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
42553
+ reply({
42554
+ ok: true,
42555
+ hasUncommittedChanges: st.hasUncommittedChanges,
42556
+ hasUnpushedCommits: st.hasUnpushedCommits,
42557
+ uncommittedFileCount: st.uncommittedFileCount
42558
+ });
42559
+ }
42560
+
42561
+ // src/routing/handlers/git/handle-session-git-status.ts
42562
+ async function handleSessionGitStatusAction(deps, sessionId, reply) {
42563
+ const r = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
42564
+ reply({
42565
+ ok: true,
42566
+ hasUncommittedChanges: r.hasUncommittedChanges,
42567
+ hasUnpushedCommits: r.hasUnpushedCommits,
42568
+ uncommittedFileCount: r.uncommittedFileCount
42569
+ });
42570
+ }
42571
+
42572
+ // src/routing/handlers/git/send-session-git-result.ts
42573
+ function sendSessionGitResult(ws, id, payload, e2ee, encryptedFields = []) {
41877
42574
  if (!ws) return;
41878
42575
  const message = { type: "session_git_result", id, ...payload };
41879
- sendWsMessage(ws, e2ee && encryptedFields.length > 0 ? e2ee.encryptFields(message, encryptedFields) : message);
42576
+ let wire = message;
42577
+ if (e2ee && encryptedFields.length > 0) {
42578
+ wire = e2ee.encryptFields(message, encryptedFields);
42579
+ }
42580
+ sendWsMessage(ws, wire);
41880
42581
  }
42582
+
42583
+ // src/routing/handlers/git/session-git-request.ts
41881
42584
  var handleSessionGitRequestMessage = (msg, deps) => {
41882
42585
  if (typeof msg.id !== "string") return;
41883
42586
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
41884
42587
  const action = msg.action;
41885
- if (!sessionId || action !== "status" && action !== "push" && action !== "commit" && action !== "list_changes")
42588
+ if (!sessionId || action !== "status" && action !== "push" && action !== "commit" && action !== "list_changes" && action !== "file_diff")
41886
42589
  return;
41887
42590
  void (async () => {
41888
42591
  const ws = deps.getWs();
41889
- const reply = (payload, encryptedFields = []) => sendResult(ws, msg.id, payload, deps.e2ee, encryptedFields);
42592
+ const reply = (payload, encryptedFields = []) => sendSessionGitResult(ws, msg.id, payload, deps.e2ee, encryptedFields);
41890
42593
  try {
41891
42594
  if (action === "status") {
41892
- const r = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
41893
- reply({
41894
- ok: true,
41895
- hasUncommittedChanges: r.hasUncommittedChanges,
41896
- hasUnpushedCommits: r.hasUnpushedCommits,
41897
- uncommittedFileCount: r.uncommittedFileCount
41898
- });
42595
+ await handleSessionGitStatusAction(deps, sessionId, reply);
41899
42596
  return;
41900
42597
  }
41901
42598
  if (action === "list_changes") {
41902
- const repoRel = typeof msg.changesRepoRelPath === "string" ? msg.changesRepoRelPath.trim() : "";
41903
- const view = msg.changesView === "commit" ? "commit" : "working";
41904
- const commitSha = typeof msg.changesCommitSha === "string" ? msg.changesCommitSha.trim() : "";
41905
- if (view === "commit") {
41906
- if (!repoRel || !commitSha) {
41907
- reply({ ok: false, error: "changesRepoRelPath and changesCommitSha are required for commit view" });
41908
- return;
41909
- }
41910
- }
41911
- const recentCommitsLimit = typeof msg.changesRecentCommitsLimit === "number" || typeof msg.changesRecentCommitsLimit === "string" ? msg.changesRecentCommitsLimit : void 0;
41912
- const opts = repoRel && view === "commit" && commitSha ? {
41913
- repoRelPath: repoRel,
41914
- basis: { kind: "commit", sha: commitSha },
41915
- recentCommitsLimit
41916
- } : repoRel ? { repoRelPath: repoRel, basis: { kind: "working" }, recentCommitsLimit } : recentCommitsLimit !== void 0 ? { recentCommitsLimit } : void 0;
41917
- const repos = await deps.sessionWorktreeManager.getSessionWorkingTreeChangeDetails(sessionId, opts);
41918
- reply({
41919
- ok: true,
41920
- repos
41921
- }, ["repos"]);
42599
+ await handleSessionGitListChangesAction(deps, { ...msg, sessionId }, reply);
41922
42600
  return;
41923
42601
  }
41924
- if (action === "push") {
41925
- const pushRes = await deps.sessionWorktreeManager.pushSessionUpstream(sessionId);
41926
- if (!pushRes.ok) {
41927
- reply({ ok: false, error: pushRes.error ?? "Push failed" });
41928
- return;
41929
- }
41930
- const st2 = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
41931
- reply({
41932
- ok: true,
41933
- hasUncommittedChanges: st2.hasUncommittedChanges,
41934
- hasUnpushedCommits: st2.hasUnpushedCommits,
41935
- uncommittedFileCount: st2.uncommittedFileCount
41936
- });
41937
- return;
41938
- }
41939
- const branch = typeof msg.branch === "string" ? msg.branch : "";
41940
- const message = typeof msg.message === "string" ? msg.message : "";
41941
- const pushAfterCommit = msg.pushAfterCommit === true;
41942
- if (!branch.trim() || !message.trim()) {
41943
- reply({ ok: false, error: "branch and message are required for commit" });
42602
+ if (action === "file_diff") {
42603
+ await handleSessionGitGetChangeFilePatchAction(deps, { ...msg, sessionId }, reply);
41944
42604
  return;
41945
42605
  }
41946
- const commitRes = await deps.sessionWorktreeManager.commitSession({
41947
- sessionId,
41948
- branch: branch.trim(),
41949
- message: message.trim(),
41950
- push: pushAfterCommit
41951
- });
41952
- if (!commitRes.ok) {
41953
- reply({ ok: false, error: commitRes.error ?? "Commit failed" });
42606
+ if (action === "push") {
42607
+ await handleSessionGitPushAction(deps, sessionId, reply);
41954
42608
  return;
41955
42609
  }
41956
- const st = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
41957
- reply({
41958
- ok: true,
41959
- hasUncommittedChanges: st.hasUncommittedChanges,
41960
- hasUnpushedCommits: st.hasUnpushedCommits,
41961
- uncommittedFileCount: st.uncommittedFileCount
41962
- });
42610
+ await handleSessionGitCommitAction(deps, sessionId, msg, reply);
41963
42611
  } catch (e) {
41964
42612
  reply({ ok: false, error: e instanceof Error ? e.message : String(e) });
41965
42613
  }
@@ -41989,7 +42637,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
41989
42637
  };
41990
42638
 
41991
42639
  // src/routing/handlers/revert-turn-snapshot.ts
41992
- import * as fs40 from "node:fs";
42640
+ import * as fs45 from "node:fs";
41993
42641
  var handleRevertTurnSnapshotMessage = (msg, deps) => {
41994
42642
  const id = typeof msg.id === "string" ? msg.id : "";
41995
42643
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
@@ -42002,7 +42650,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
42002
42650
  const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
42003
42651
  const file2 = snapshotFilePath(agentBase, turnId);
42004
42652
  try {
42005
- await fs40.promises.access(file2, fs40.constants.F_OK);
42653
+ await fs45.promises.access(file2, fs45.constants.F_OK);
42006
42654
  } catch {
42007
42655
  sendWsMessage(s, {
42008
42656
  type: "revert_turn_snapshot_result",
@@ -42040,11 +42688,13 @@ var handleDevServerControl = (msg, deps) => {
42040
42688
  // src/routing/handlers/dev-servers-config.ts
42041
42689
  var handleDevServersConfig = (msg, deps) => {
42042
42690
  const devServers = msg.devServers;
42043
- deps.devServerManager?.applyConfig(devServers ?? []);
42691
+ setImmediate(() => {
42692
+ deps.devServerManager?.applyConfig(devServers ?? []);
42693
+ });
42044
42694
  };
42045
42695
 
42046
42696
  // src/git/bridge-git-context.ts
42047
- import * as path43 from "node:path";
42697
+ import * as path57 from "node:path";
42048
42698
 
42049
42699
  // src/git/branches/get-current-branch.ts
42050
42700
  async function getCurrentBranch(repoPath) {
@@ -42094,12 +42744,12 @@ async function listRepoBranchRefs(repoPath) {
42094
42744
  // src/git/bridge-git-context.ts
42095
42745
  function folderNameForRelPath(relPath, bridgeRoot) {
42096
42746
  if (relPath === "." || relPath === "") {
42097
- return path43.basename(path43.resolve(bridgeRoot)) || "repo";
42747
+ return path57.basename(path57.resolve(bridgeRoot)) || "repo";
42098
42748
  }
42099
- return path43.basename(relPath) || relPath;
42749
+ return path57.basename(relPath) || relPath;
42100
42750
  }
42101
42751
  async function discoverGitReposForBridgeContext(bridgeRoot) {
42102
- const root = path43.resolve(bridgeRoot);
42752
+ const root = path57.resolve(bridgeRoot);
42103
42753
  if (await isGitRepoDirectory(root)) {
42104
42754
  const remoteUrl = await getRemoteOriginUrl(root);
42105
42755
  return [{ absolutePath: root, remoteUrl }];
@@ -42107,17 +42757,19 @@ async function discoverGitReposForBridgeContext(bridgeRoot) {
42107
42757
  const [deep, shallow] = await Promise.all([discoverGitReposUnderRoot(root), discoverGitRepos(root)]);
42108
42758
  const byPath = /* @__PURE__ */ new Map();
42109
42759
  for (const repo of [...deep, ...shallow]) {
42110
- byPath.set(path43.resolve(repo.absolutePath), repo);
42760
+ byPath.set(path57.resolve(repo.absolutePath), repo);
42111
42761
  }
42112
42762
  return [...byPath.values()];
42113
42763
  }
42114
42764
  async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
42115
- const bridgeResolved = path43.resolve(bridgeRoot);
42765
+ const bridgeResolved = path57.resolve(bridgeRoot);
42116
42766
  const repos = await discoverGitReposForBridgeContext(bridgeResolved);
42117
42767
  const rows = [];
42118
- for (const repo of repos) {
42119
- let rel = path43.relative(bridgeResolved, repo.absolutePath);
42120
- if (rel.startsWith("..") || path43.isAbsolute(rel)) continue;
42768
+ for (let i = 0; i < repos.length; i++) {
42769
+ if (i > 0) await yieldToEventLoop();
42770
+ const repo = repos[i];
42771
+ let rel = path57.relative(bridgeResolved, repo.absolutePath);
42772
+ if (rel.startsWith("..") || path57.isAbsolute(rel)) continue;
42121
42773
  const relPath = rel === "" ? "." : rel.replace(/\\/g, "/");
42122
42774
  const currentBranch = await getCurrentBranch(repo.absolutePath);
42123
42775
  const remoteUrl = repo.remoteUrl.trim() || null;
@@ -42132,11 +42784,11 @@ async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
42132
42784
  return rows;
42133
42785
  }
42134
42786
  async function listRepoBranchesForBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
42135
- const bridgeResolved = path43.resolve(bridgeRoot);
42787
+ const bridgeResolved = path57.resolve(bridgeRoot);
42136
42788
  const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
42137
- const repoPath = rel === "" ? bridgeResolved : path43.join(bridgeResolved, rel);
42138
- const resolved = path43.resolve(repoPath);
42139
- if (!resolved.startsWith(bridgeResolved + path43.sep) && resolved !== bridgeResolved) {
42789
+ const repoPath = rel === "" ? bridgeResolved : path57.join(bridgeResolved, rel);
42790
+ const resolved = path57.resolve(repoPath);
42791
+ if (!resolved.startsWith(bridgeResolved + path57.sep) && resolved !== bridgeResolved) {
42140
42792
  return [];
42141
42793
  }
42142
42794
  return listRepoBranchRefs(resolved);
@@ -42641,10 +43293,10 @@ function listCliAgentCapabilityCacheForWorkspace(db, workspaceId) {
42641
43293
  }
42642
43294
 
42643
43295
  // src/agents/capabilities/warmup-agent-capabilities-on-connect.ts
42644
- import * as path45 from "node:path";
43296
+ import * as path59 from "node:path";
42645
43297
 
42646
43298
  // src/agents/capabilities/probe-one-agent-type-for-capabilities.ts
42647
- import * as path44 from "node:path";
43299
+ import * as path58 from "node:path";
42648
43300
  async function probeOneAgentTypeForCapabilities(params) {
42649
43301
  const { agentType, cwd, workspaceId, log: log2, reportAgentCapabilities, bridgeReport = true, shouldContinue } = params;
42650
43302
  const canContinue = () => shouldContinue?.() !== false;
@@ -42682,7 +43334,7 @@ async function probeOneAgentTypeForCapabilities(params) {
42682
43334
  if (!canContinue()) return false;
42683
43335
  handle = await resolved.createClient({
42684
43336
  command: resolved.command,
42685
- cwd: path44.resolve(cwd),
43337
+ cwd: path58.resolve(cwd),
42686
43338
  backendAgentType: agentType,
42687
43339
  sessionMode: "agent",
42688
43340
  persistedAcpSessionId: null,
@@ -42760,7 +43412,7 @@ async function warmupAgentCapabilitiesOnConnect(params) {
42760
43412
  const { workspaceId, log: log2, getWs } = params;
42761
43413
  const isCurrent = beginAgentCapabilityWarmupRun();
42762
43414
  if (!isCurrent()) return;
42763
- const cwd = path45.resolve(getBridgeRoot());
43415
+ const cwd = path59.resolve(getBridgeRoot());
42764
43416
  async function sendBatchFromCache() {
42765
43417
  if (!isCurrent()) return;
42766
43418
  const socket = getWs();
@@ -43106,18 +43758,18 @@ import * as http from "node:http";
43106
43758
 
43107
43759
  // src/mcp/bridge-access/read-json-body.ts
43108
43760
  function readJsonBody(req) {
43109
- return new Promise((resolve22, reject) => {
43761
+ return new Promise((resolve34, reject) => {
43110
43762
  const chunks = [];
43111
43763
  req.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
43112
43764
  req.on("end", () => {
43113
43765
  try {
43114
43766
  const raw = Buffer.concat(chunks).toString("utf8").trim();
43115
43767
  if (!raw) {
43116
- resolve22({});
43768
+ resolve34({});
43117
43769
  return;
43118
43770
  }
43119
43771
  const parsed = JSON.parse(raw);
43120
- resolve22(parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {});
43772
+ resolve34(parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {});
43121
43773
  } catch (e) {
43122
43774
  reject(e);
43123
43775
  }
@@ -43175,7 +43827,7 @@ function createBridgeAccessRequestHandler(registry2) {
43175
43827
  // src/mcp/bridge-access/start-server.ts
43176
43828
  function startBridgeAccessServer(registry2) {
43177
43829
  const server = http.createServer(createBridgeAccessRequestHandler(registry2));
43178
- return new Promise((resolve22, reject) => {
43830
+ return new Promise((resolve34, reject) => {
43179
43831
  server.once("error", reject);
43180
43832
  server.listen(0, "127.0.0.1", () => {
43181
43833
  const addr = server.address();
@@ -43183,7 +43835,7 @@ function startBridgeAccessServer(registry2) {
43183
43835
  reject(new Error("Bridge access server did not bind"));
43184
43836
  return;
43185
43837
  }
43186
- resolve22({
43838
+ resolve34({
43187
43839
  port: addr.port,
43188
43840
  close: () => new Promise((closeResolve, closeReject) => {
43189
43841
  server.close((err) => err ? closeReject(err) : closeResolve());
@@ -43323,8 +43975,8 @@ async function createBridgeConnection(options) {
43323
43975
  getCloudAccessToken: () => tokens.accessToken
43324
43976
  };
43325
43977
  const identifyReportedPaths = {
43326
- bridgeRootPath: path46.resolve(getBridgeRoot()),
43327
- worktreesRootPath: path46.resolve(worktreesRootPath)
43978
+ bridgeRootPath: path60.resolve(getBridgeRoot()),
43979
+ worktreesRootPath: path60.resolve(worktreesRootPath)
43328
43980
  };
43329
43981
  const { connect } = createMainBridgeWebSocketLifecycle({
43330
43982
  state,
@@ -43579,9 +44231,9 @@ async function runCliAction(program2, opts) {
43579
44231
  const firehoseServerUrl = opts.firehoseUrl ?? opts.proxyUrl ?? process.env.BUILDAUTOMATON_FIREHOSE_URL ?? process.env.BUILDAUTOMATON_PROXY_URL ?? DEFAULT_FIREHOSE_URL;
43580
44232
  const bridgeRootOpt = (opts.bridgeRoot && typeof opts.bridgeRoot === "string" && opts.bridgeRoot.trim() ? opts.bridgeRoot.trim() : null) ?? (opts.cwd && typeof opts.cwd === "string" && opts.cwd.trim() ? opts.cwd.trim() : null);
43581
44233
  if (bridgeRootOpt) {
43582
- const resolvedBridgeRoot = path47.resolve(process.cwd(), bridgeRootOpt);
44234
+ const resolvedBridgeRoot = path61.resolve(process.cwd(), bridgeRootOpt);
43583
44235
  try {
43584
- const st = fs41.statSync(resolvedBridgeRoot);
44236
+ const st = fs46.statSync(resolvedBridgeRoot);
43585
44237
  if (!st.isDirectory()) {
43586
44238
  console.error(`Bridge root is not a directory: ${resolvedBridgeRoot}`);
43587
44239
  process.exit(1);
@@ -43601,7 +44253,7 @@ async function runCliAction(program2, opts) {
43601
44253
  );
43602
44254
  let worktreesRootPath;
43603
44255
  if (opts.worktreesRoot && opts.worktreesRoot.trim()) {
43604
- worktreesRootPath = path47.resolve(opts.worktreesRoot.trim());
44256
+ worktreesRootPath = path61.resolve(opts.worktreesRoot.trim());
43605
44257
  }
43606
44258
  const e2eCertificates = opts.e2eeCertificatesDir?.trim() ? await loadOrCreateE2eCertificates(opts.e2eeCertificatesDir.trim()) : void 0;
43607
44259
  if (e2eCertificates) {