@buildautomaton/cli 0.1.46 → 0.1.47

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/index.js CHANGED
@@ -4175,8 +4175,8 @@ var init_parseUtil = __esm({
4175
4175
  init_errors();
4176
4176
  init_en();
4177
4177
  makeIssue = (params) => {
4178
- const { data, path: path44, errorMaps, issueData } = params;
4179
- const fullPath = [...path44, ...issueData.path || []];
4178
+ const { data, path: path46, errorMaps, issueData } = params;
4179
+ const fullPath = [...path46, ...issueData.path || []];
4180
4180
  const fullIssue = {
4181
4181
  ...issueData,
4182
4182
  path: fullPath
@@ -4484,11 +4484,11 @@ var init_types = __esm({
4484
4484
  init_parseUtil();
4485
4485
  init_util();
4486
4486
  ParseInputLazyPath = class {
4487
- constructor(parent, value, path44, key) {
4487
+ constructor(parent, value, path46, key) {
4488
4488
  this._cachedPath = [];
4489
4489
  this.parent = parent;
4490
4490
  this.data = value;
4491
- this._path = path44;
4491
+ this._path = path46;
4492
4492
  this._key = key;
4493
4493
  }
4494
4494
  get path() {
@@ -8103,15 +8103,15 @@ function assignProp(target, prop, value) {
8103
8103
  configurable: true
8104
8104
  });
8105
8105
  }
8106
- function getElementAtPath(obj, path44) {
8107
- if (!path44)
8106
+ function getElementAtPath(obj, path46) {
8107
+ if (!path46)
8108
8108
  return obj;
8109
- return path44.reduce((acc, key) => acc?.[key], obj);
8109
+ return path46.reduce((acc, key) => acc?.[key], obj);
8110
8110
  }
8111
8111
  function promiseAllObject(promisesObj) {
8112
8112
  const keys = Object.keys(promisesObj);
8113
- const promises5 = keys.map((key) => promisesObj[key]);
8114
- return Promise.all(promises5).then((results) => {
8113
+ const promises6 = keys.map((key) => promisesObj[key]);
8114
+ return Promise.all(promises6).then((results) => {
8115
8115
  const resolvedObj = {};
8116
8116
  for (let i = 0; i < keys.length; i++) {
8117
8117
  resolvedObj[keys[i]] = results[i];
@@ -8355,11 +8355,11 @@ function aborted(x, startIndex = 0) {
8355
8355
  }
8356
8356
  return false;
8357
8357
  }
8358
- function prefixIssues(path44, issues) {
8358
+ function prefixIssues(path46, issues) {
8359
8359
  return issues.map((iss) => {
8360
8360
  var _a2;
8361
8361
  (_a2 = iss).path ?? (_a2.path = []);
8362
- iss.path.unshift(path44);
8362
+ iss.path.unshift(path46);
8363
8363
  return iss;
8364
8364
  });
8365
8365
  }
@@ -8548,7 +8548,7 @@ function treeifyError(error40, _mapper) {
8548
8548
  return issue2.message;
8549
8549
  };
8550
8550
  const result = { errors: [] };
8551
- const processError = (error41, path44 = []) => {
8551
+ const processError = (error41, path46 = []) => {
8552
8552
  var _a2, _b;
8553
8553
  for (const issue2 of error41.issues) {
8554
8554
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -8558,7 +8558,7 @@ function treeifyError(error40, _mapper) {
8558
8558
  } else if (issue2.code === "invalid_element") {
8559
8559
  processError({ issues: issue2.issues }, issue2.path);
8560
8560
  } else {
8561
- const fullpath = [...path44, ...issue2.path];
8561
+ const fullpath = [...path46, ...issue2.path];
8562
8562
  if (fullpath.length === 0) {
8563
8563
  result.errors.push(mapper(issue2));
8564
8564
  continue;
@@ -8588,9 +8588,9 @@ function treeifyError(error40, _mapper) {
8588
8588
  processError(error40);
8589
8589
  return result;
8590
8590
  }
8591
- function toDotPath(path44) {
8591
+ function toDotPath(path46) {
8592
8592
  const segs = [];
8593
- for (const seg of path44) {
8593
+ for (const seg of path46) {
8594
8594
  if (typeof seg === "number")
8595
8595
  segs.push(`[${seg}]`);
8596
8596
  else if (typeof seg === "symbol")
@@ -22073,10 +22073,10 @@ var require_src2 = __commonJS({
22073
22073
  var fs_1 = __require("fs");
22074
22074
  var debug_1 = __importDefault(require_src());
22075
22075
  var log2 = debug_1.default("@kwsites/file-exists");
22076
- function check2(path44, isFile, isDirectory) {
22077
- log2(`checking %s`, path44);
22076
+ function check2(path46, isFile, isDirectory) {
22077
+ log2(`checking %s`, path46);
22078
22078
  try {
22079
- const stat2 = fs_1.statSync(path44);
22079
+ const stat2 = fs_1.statSync(path46);
22080
22080
  if (stat2.isFile() && isFile) {
22081
22081
  log2(`[OK] path represents a file`);
22082
22082
  return true;
@@ -22096,8 +22096,8 @@ var require_src2 = __commonJS({
22096
22096
  throw e;
22097
22097
  }
22098
22098
  }
22099
- function exists2(path44, type = exports.READABLE) {
22100
- return check2(path44, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
22099
+ function exists2(path46, type = exports.READABLE) {
22100
+ return check2(path46, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
22101
22101
  }
22102
22102
  exports.exists = exists2;
22103
22103
  exports.FILE = 1;
@@ -22940,9 +22940,9 @@ function parseChangeSummaryJson(raw, allowedPaths, options) {
22940
22940
  const rawPath = typeof o.path === "string" ? o.path.trim() : "";
22941
22941
  const summary = typeof o.summary === "string" ? o.summary.trim() : "";
22942
22942
  if (!rawPath || !summary) continue;
22943
- const path44 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
22944
- if (!path44) continue;
22945
- rows.push({ path: path44, summary: clampSummaryToAtMostTwoLines(summary) });
22943
+ const path46 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
22944
+ if (!path46) continue;
22945
+ rows.push({ path: path46, summary: clampSummaryToAtMostTwoLines(summary) });
22946
22946
  }
22947
22947
  return rows;
22948
22948
  }
@@ -24241,8 +24241,8 @@ function randomSecret() {
24241
24241
  }
24242
24242
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
24243
24243
  }
24244
- async function requestPreviewApi(port, secret, method, path44, body) {
24245
- const url2 = `http://127.0.0.1:${port}${path44}`;
24244
+ async function requestPreviewApi(port, secret, method, path46, body) {
24245
+ const url2 = `http://127.0.0.1:${port}${path46}`;
24246
24246
  const headers = {
24247
24247
  [PREVIEW_SECRET_HEADER]: secret,
24248
24248
  "Content-Type": "application/json"
@@ -24254,7 +24254,7 @@ async function requestPreviewApi(port, secret, method, path44, body) {
24254
24254
  });
24255
24255
  const data = await res.json().catch(() => ({}));
24256
24256
  if (!res.ok) {
24257
- throw new Error(data?.error ?? `Preview API ${method} ${path44}: ${res.status}`);
24257
+ throw new Error(data?.error ?? `Preview API ${method} ${path46}: ${res.status}`);
24258
24258
  }
24259
24259
  return data;
24260
24260
  }
@@ -24469,7 +24469,7 @@ function installBridgeProcessResilience() {
24469
24469
  }
24470
24470
 
24471
24471
  // src/cli-version.ts
24472
- var CLI_VERSION = "0.1.46".length > 0 ? "0.1.46" : "0.0.0-dev";
24472
+ var CLI_VERSION = "0.1.47".length > 0 ? "0.1.47" : "0.0.0-dev";
24473
24473
 
24474
24474
  // src/connection/heartbeat/constants.ts
24475
24475
  var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -27995,8 +27995,8 @@ function pathspec(...paths) {
27995
27995
  cache.set(key, paths);
27996
27996
  return key;
27997
27997
  }
27998
- function isPathSpec(path44) {
27999
- return path44 instanceof String && cache.has(path44);
27998
+ function isPathSpec(path46) {
27999
+ return path46 instanceof String && cache.has(path46);
28000
28000
  }
28001
28001
  function toPaths(pathSpec) {
28002
28002
  return cache.get(pathSpec) || [];
@@ -28085,8 +28085,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
28085
28085
  function forEachLineWithContent(input, callback) {
28086
28086
  return toLinesWithContent(input, true).map((line) => callback(line));
28087
28087
  }
28088
- function folderExists(path44) {
28089
- return (0, import_file_exists.exists)(path44, import_file_exists.FOLDER);
28088
+ function folderExists(path46) {
28089
+ return (0, import_file_exists.exists)(path46, import_file_exists.FOLDER);
28090
28090
  }
28091
28091
  function append(target, item) {
28092
28092
  if (Array.isArray(target)) {
@@ -28490,8 +28490,8 @@ function checkIsRepoRootTask() {
28490
28490
  commands,
28491
28491
  format: "utf-8",
28492
28492
  onError,
28493
- parser(path44) {
28494
- return /^\.(git)?$/.test(path44.trim());
28493
+ parser(path46) {
28494
+ return /^\.(git)?$/.test(path46.trim());
28495
28495
  }
28496
28496
  };
28497
28497
  }
@@ -28925,11 +28925,11 @@ function parseGrep(grep) {
28925
28925
  const paths = /* @__PURE__ */ new Set();
28926
28926
  const results = {};
28927
28927
  forEachLineWithContent(grep, (input) => {
28928
- const [path44, line, preview] = input.split(NULL);
28929
- paths.add(path44);
28930
- (results[path44] = results[path44] || []).push({
28928
+ const [path46, line, preview] = input.split(NULL);
28929
+ paths.add(path46);
28930
+ (results[path46] = results[path46] || []).push({
28931
28931
  line: asNumber(line),
28932
- path: path44,
28932
+ path: path46,
28933
28933
  preview
28934
28934
  });
28935
28935
  });
@@ -29694,14 +29694,14 @@ var init_hash_object = __esm2({
29694
29694
  init_task();
29695
29695
  }
29696
29696
  });
29697
- function parseInit(bare, path44, text) {
29697
+ function parseInit(bare, path46, text) {
29698
29698
  const response = String(text).trim();
29699
29699
  let result;
29700
29700
  if (result = initResponseRegex.exec(response)) {
29701
- return new InitSummary(bare, path44, false, result[1]);
29701
+ return new InitSummary(bare, path46, false, result[1]);
29702
29702
  }
29703
29703
  if (result = reInitResponseRegex.exec(response)) {
29704
- return new InitSummary(bare, path44, true, result[1]);
29704
+ return new InitSummary(bare, path46, true, result[1]);
29705
29705
  }
29706
29706
  let gitDir = "";
29707
29707
  const tokens = response.split(" ");
@@ -29712,7 +29712,7 @@ function parseInit(bare, path44, text) {
29712
29712
  break;
29713
29713
  }
29714
29714
  }
29715
- return new InitSummary(bare, path44, /^re/i.test(response), gitDir);
29715
+ return new InitSummary(bare, path46, /^re/i.test(response), gitDir);
29716
29716
  }
29717
29717
  var InitSummary;
29718
29718
  var initResponseRegex;
@@ -29721,9 +29721,9 @@ var init_InitSummary = __esm2({
29721
29721
  "src/lib/responses/InitSummary.ts"() {
29722
29722
  "use strict";
29723
29723
  InitSummary = class {
29724
- constructor(bare, path44, existing, gitDir) {
29724
+ constructor(bare, path46, existing, gitDir) {
29725
29725
  this.bare = bare;
29726
- this.path = path44;
29726
+ this.path = path46;
29727
29727
  this.existing = existing;
29728
29728
  this.gitDir = gitDir;
29729
29729
  }
@@ -29735,7 +29735,7 @@ var init_InitSummary = __esm2({
29735
29735
  function hasBareCommand(command) {
29736
29736
  return command.includes(bareCommand);
29737
29737
  }
29738
- function initTask(bare = false, path44, customArgs) {
29738
+ function initTask(bare = false, path46, customArgs) {
29739
29739
  const commands = ["init", ...customArgs];
29740
29740
  if (bare && !hasBareCommand(commands)) {
29741
29741
  commands.splice(1, 0, bareCommand);
@@ -29744,7 +29744,7 @@ function initTask(bare = false, path44, customArgs) {
29744
29744
  commands,
29745
29745
  format: "utf-8",
29746
29746
  parser(text) {
29747
- return parseInit(commands.includes("--bare"), path44, text);
29747
+ return parseInit(commands.includes("--bare"), path46, text);
29748
29748
  }
29749
29749
  };
29750
29750
  }
@@ -30560,12 +30560,12 @@ var init_FileStatusSummary = __esm2({
30560
30560
  "use strict";
30561
30561
  fromPathRegex = /^(.+)\0(.+)$/;
30562
30562
  FileStatusSummary = class {
30563
- constructor(path44, index, working_dir) {
30564
- this.path = path44;
30563
+ constructor(path46, index, working_dir) {
30564
+ this.path = path46;
30565
30565
  this.index = index;
30566
30566
  this.working_dir = working_dir;
30567
30567
  if (index === "R" || working_dir === "R") {
30568
- const detail = fromPathRegex.exec(path44) || [null, path44, path44];
30568
+ const detail = fromPathRegex.exec(path46) || [null, path46, path46];
30569
30569
  this.from = detail[2] || "";
30570
30570
  this.path = detail[1] || "";
30571
30571
  }
@@ -30596,14 +30596,14 @@ function splitLine(result, lineStr) {
30596
30596
  default:
30597
30597
  return;
30598
30598
  }
30599
- function data(index, workingDir, path44) {
30599
+ function data(index, workingDir, path46) {
30600
30600
  const raw = `${index}${workingDir}`;
30601
30601
  const handler = parsers6.get(raw);
30602
30602
  if (handler) {
30603
- handler(result, path44);
30603
+ handler(result, path46);
30604
30604
  }
30605
30605
  if (raw !== "##" && raw !== "!!") {
30606
- result.files.push(new FileStatusSummary(path44, index, workingDir));
30606
+ result.files.push(new FileStatusSummary(path46, index, workingDir));
30607
30607
  }
30608
30608
  }
30609
30609
  }
@@ -30912,9 +30912,9 @@ var init_simple_git_api = __esm2({
30912
30912
  next
30913
30913
  );
30914
30914
  }
30915
- hashObject(path44, write) {
30915
+ hashObject(path46, write) {
30916
30916
  return this._runTask(
30917
- hashObjectTask(path44, write === true),
30917
+ hashObjectTask(path46, write === true),
30918
30918
  trailingFunctionArgument(arguments)
30919
30919
  );
30920
30920
  }
@@ -31267,8 +31267,8 @@ var init_branch = __esm2({
31267
31267
  }
31268
31268
  });
31269
31269
  function toPath(input) {
31270
- const path44 = input.trim().replace(/^["']|["']$/g, "");
31271
- return path44 && normalize3(path44);
31270
+ const path46 = input.trim().replace(/^["']|["']$/g, "");
31271
+ return path46 && normalize3(path46);
31272
31272
  }
31273
31273
  var parseCheckIgnore;
31274
31274
  var init_CheckIgnore = __esm2({
@@ -31582,8 +31582,8 @@ __export2(sub_module_exports, {
31582
31582
  subModuleTask: () => subModuleTask,
31583
31583
  updateSubModuleTask: () => updateSubModuleTask
31584
31584
  });
31585
- function addSubModuleTask(repo, path44) {
31586
- return subModuleTask(["add", repo, path44]);
31585
+ function addSubModuleTask(repo, path46) {
31586
+ return subModuleTask(["add", repo, path46]);
31587
31587
  }
31588
31588
  function initSubModuleTask(customArgs) {
31589
31589
  return subModuleTask(["init", ...customArgs]);
@@ -31916,8 +31916,8 @@ var require_git = __commonJS2({
31916
31916
  }
31917
31917
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
31918
31918
  };
31919
- Git2.prototype.submoduleAdd = function(repo, path44, then) {
31920
- return this._runTask(addSubModuleTask2(repo, path44), trailingFunctionArgument2(arguments));
31919
+ Git2.prototype.submoduleAdd = function(repo, path46, then) {
31920
+ return this._runTask(addSubModuleTask2(repo, path46), trailingFunctionArgument2(arguments));
31921
31921
  };
31922
31922
  Git2.prototype.submoduleUpdate = function(args, then) {
31923
31923
  return this._runTask(
@@ -32880,9 +32880,9 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
32880
32880
  // src/agents/acp/put-summarize-change-summaries.ts
32881
32881
  async function putEncryptedChangeSummaryRows(params) {
32882
32882
  const base = params.apiBaseUrl.replace(/\/+$/, "");
32883
- const entries = params.rows.map(({ path: path44, summary }) => {
32883
+ const entries = params.rows.map(({ path: path46, summary }) => {
32884
32884
  const enc = params.e2ee.encryptFields({ summary }, ["summary"]);
32885
- return { path: path44, summary: JSON.stringify(enc) };
32885
+ return { path: path46, summary: JSON.stringify(enc) };
32886
32886
  });
32887
32887
  const res = await fetch(
32888
32888
  `${base}/internal/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
@@ -33502,7 +33502,7 @@ async function createAcpManager(options) {
33502
33502
  // src/git/changes/types.ts
33503
33503
  var MAX_PATCH_CHARS = 35e4;
33504
33504
 
33505
- // src/git/changes/lib/repo-format.ts
33505
+ // src/git/changes/paths/repo-format.ts
33506
33506
  function posixJoinDirFile(dir, file2) {
33507
33507
  const d = dir === "." || dir === "" ? "" : dir.replace(/\\/g, "/").replace(/\/+$/, "");
33508
33508
  const f = file2.replace(/\\/g, "/").replace(/^\/+/, "");
@@ -33557,7 +33557,7 @@ function formatRemoteDisplayLabel(remoteUrl) {
33557
33557
  }
33558
33558
 
33559
33559
  // src/git/changes/get-working-tree-change-repo-details.ts
33560
- import * as path20 from "node:path";
33560
+ import * as path22 from "node:path";
33561
33561
 
33562
33562
  // src/git/commits/resolve-remote-tracking.ts
33563
33563
  async function tryConfigGet(g, key) {
@@ -33727,9 +33727,232 @@ async function listRecentCommits(repoDir, limitInput) {
33727
33727
  }
33728
33728
  }
33729
33729
 
33730
- // src/git/changes/lib/parse-git-status.ts
33731
- function parseNameStatusLines(lines) {
33732
- const m = /* @__PURE__ */ new Map();
33730
+ // src/git/changes/parse/normalize-git-diff-path.ts
33731
+ function normalizeGitDiffPath(path46) {
33732
+ return path46.replace(/\\/g, "/").replace(/\/ +/g, "/").trim();
33733
+ }
33734
+
33735
+ // src/git/changes/parse/expand-git-rename-numstat-path.ts
33736
+ function expandGitRenameNumstatPath(rawPath) {
33737
+ const open2 = rawPath.indexOf("{");
33738
+ const arrow = rawPath.indexOf("=>");
33739
+ const close = rawPath.lastIndexOf("}");
33740
+ if (open2 === -1 || arrow === -1 || close === -1 || open2 > arrow || arrow > close) {
33741
+ return null;
33742
+ }
33743
+ const prefix = rawPath.slice(0, open2);
33744
+ const suffix = rawPath.slice(close + 1);
33745
+ const oldPart = rawPath.slice(open2 + 1, arrow).trim();
33746
+ const newPart = rawPath.slice(arrow + 2, close).trim();
33747
+ return {
33748
+ oldPath: normalizeGitDiffPath(`${prefix}${oldPart}${suffix}`),
33749
+ newPath: normalizeGitDiffPath(`${prefix}${newPart}${suffix}`)
33750
+ };
33751
+ }
33752
+ function isGitRenameNumstatPath(rawPath) {
33753
+ return expandGitRenameNumstatPath(rawPath) != null;
33754
+ }
33755
+
33756
+ // src/git/changes/rows/build-changed-file-row.ts
33757
+ function buildChangedFileRow(options) {
33758
+ const pathInRepo = normalizeGitDiffPath(options.pathInRepo);
33759
+ const relLauncher = posixJoinDirFile(options.repoRelPath, pathInRepo);
33760
+ const additions = options.numEntry?.additions ?? 0;
33761
+ const deletions = options.numEntry?.deletions ?? 0;
33762
+ let change = options.nameEntry?.change ?? "modified";
33763
+ let movedFromPathRelLauncher;
33764
+ let movedFromPathInRepo;
33765
+ const oldPathInRepo = options.nameEntry?.oldPathInRepo ?? options.numEntry?.oldPathInRepo;
33766
+ if (change === "moved" && oldPathInRepo) {
33767
+ movedFromPathInRepo = normalizeGitDiffPath(oldPathInRepo);
33768
+ movedFromPathRelLauncher = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
33769
+ } else if (oldPathInRepo && change === "modified") {
33770
+ change = "moved";
33771
+ movedFromPathInRepo = normalizeGitDiffPath(oldPathInRepo);
33772
+ movedFromPathRelLauncher = posixJoinDirFile(options.repoRelPath, movedFromPathInRepo);
33773
+ }
33774
+ if (options.untracked && !options.nameEntry) {
33775
+ change = "added";
33776
+ } else if (!options.nameEntry && options.numEntry && change !== "moved") {
33777
+ if (additions > 0 && deletions === 0) change = "added";
33778
+ else if (deletions > 0 && additions === 0) change = "removed";
33779
+ else change = "modified";
33780
+ }
33781
+ return {
33782
+ pathRelLauncher: relLauncher,
33783
+ additions,
33784
+ deletions,
33785
+ change,
33786
+ ...movedFromPathRelLauncher ? { movedFromPathRelLauncher } : {},
33787
+ ...movedFromPathInRepo ? { movedFromPathInRepo } : {}
33788
+ };
33789
+ }
33790
+
33791
+ // src/git/changes/listing/build-changed-file-rows.ts
33792
+ async function buildChangedFileRowsFromPaths(options) {
33793
+ const rows = [];
33794
+ await forEachWithGitYield([...options.paths], async (pathInRepo) => {
33795
+ if (isGitRenameNumstatPath(pathInRepo)) return;
33796
+ if (options.movedSourcePaths.has(pathInRepo)) return;
33797
+ const nameEntry = options.nameByPath.get(pathInRepo);
33798
+ const numEntry = options.numByPath.get(pathInRepo);
33799
+ const row = buildChangedFileRow({
33800
+ pathInRepo,
33801
+ repoRelPath: options.repoRelPath,
33802
+ nameEntry,
33803
+ numEntry,
33804
+ untracked: options.untrackedSet?.has(pathInRepo)
33805
+ });
33806
+ if (options.applyUntrackedStats) {
33807
+ await options.applyUntrackedStats(row, pathInRepo);
33808
+ }
33809
+ rows.push(row);
33810
+ });
33811
+ return rows;
33812
+ }
33813
+
33814
+ // src/git/changes/patch/patch-truncate.ts
33815
+ function truncatePatch(s) {
33816
+ if (s.length <= MAX_PATCH_CHARS) return s;
33817
+ return `${s.slice(0, MAX_PATCH_CHARS)}
33818
+
33819
+ \u2026 (diff truncated)`;
33820
+ }
33821
+
33822
+ // src/git/changes/patch/unified-diff-for-file.ts
33823
+ function patchTextFromGitDiffOutput(raw) {
33824
+ if (raw.trim() === "") return void 0;
33825
+ return normalizePatchContent(raw);
33826
+ }
33827
+ async function unifiedDiffForFile(repoCwd, pathInRepo, change, movedFromPathInRepo) {
33828
+ const g = cliSimpleGit(repoCwd);
33829
+ const renameArgs = ["-M", "--find-renames"];
33830
+ const args = change === "moved" && movedFromPathInRepo ? ["diff", "--no-color", ...renameArgs, "HEAD", "--", movedFromPathInRepo, pathInRepo] : change === "removed" ? ["diff", "--no-color", ...renameArgs, "HEAD", "--", pathInRepo] : ["diff", "--no-color", ...renameArgs, "HEAD", "--", pathInRepo];
33831
+ const raw = await g.raw([...args]).catch(() => "");
33832
+ const patch = patchTextFromGitDiffOutput(String(raw));
33833
+ return patch ? truncatePatch(patch) : void 0;
33834
+ }
33835
+ async function unifiedDiffForFileInRange(repoCwd, range, pathInRepo, change, movedFromPathInRepo) {
33836
+ const g = cliSimpleGit(repoCwd);
33837
+ const renameArgs = ["-M", "--find-renames"];
33838
+ const args = change === "moved" && movedFromPathInRepo ? ["diff", "--no-color", ...renameArgs, "-U20000", range, "--", movedFromPathInRepo, pathInRepo] : ["diff", "--no-color", ...renameArgs, "-U20000", range, "--", pathInRepo];
33839
+ const raw = await g.raw([...args]).catch(() => "");
33840
+ const patch = patchTextFromGitDiffOutput(String(raw));
33841
+ return patch ? truncatePatch(patch) : void 0;
33842
+ }
33843
+
33844
+ // src/git/changes/total-lines/resolve-changed-file-total-lines.ts
33845
+ import * as fs16 from "node:fs";
33846
+ import * as path19 from "node:path";
33847
+
33848
+ // src/git/changes/lines/count-lines.ts
33849
+ import { createReadStream } from "node:fs";
33850
+ import * as readline2 from "node:readline";
33851
+ function countLinesInText(text) {
33852
+ return splitTextIntoDiffLines(text).length;
33853
+ }
33854
+ async function countTextFileLines(filePath) {
33855
+ let bytes = 0;
33856
+ const maxBytes = 512e3;
33857
+ let lines = 0;
33858
+ const stream = createReadStream(filePath, { encoding: "utf8" });
33859
+ const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
33860
+ for await (const _line of rl) {
33861
+ lines += 1;
33862
+ bytes += Buffer.byteLength(String(_line), "utf8") + 1;
33863
+ if (bytes > maxBytes) {
33864
+ rl.close();
33865
+ stream.destroy();
33866
+ return lines;
33867
+ }
33868
+ }
33869
+ return lines;
33870
+ }
33871
+
33872
+ // src/git/changes/total-lines/resolve-changed-file-total-lines.ts
33873
+ var MAX_BYTES = 512e3;
33874
+ async function countLinesInGitRef(repoGitCwd, ref, pathInRepo) {
33875
+ const spec = `${ref}:${pathInRepo}`;
33876
+ const g = cliSimpleGit(repoGitCwd);
33877
+ try {
33878
+ await g.raw(["cat-file", "-e", spec]);
33879
+ } catch {
33880
+ return null;
33881
+ }
33882
+ try {
33883
+ const raw = String(await g.raw(["show", spec]));
33884
+ const capped = raw.length > MAX_BYTES ? raw.slice(0, MAX_BYTES) : raw;
33885
+ return countLinesInText(capped);
33886
+ } catch {
33887
+ return null;
33888
+ }
33889
+ }
33890
+ async function resolveWorkingTreeFileTotalLines(options) {
33891
+ if (options.isBinary) return null;
33892
+ const filePath = path19.join(options.repoGitCwd, options.pathInRepo);
33893
+ if (options.change === "removed") {
33894
+ return countLinesInGitRef(options.repoGitCwd, "HEAD", options.pathInRepo);
33895
+ }
33896
+ if (options.change === "moved" || options.change === "modified") {
33897
+ try {
33898
+ const st = await fs16.promises.stat(filePath);
33899
+ if (!st.isFile()) return null;
33900
+ return await countTextFileLines(filePath);
33901
+ } catch {
33902
+ return null;
33903
+ }
33904
+ }
33905
+ return null;
33906
+ }
33907
+ async function resolveCommitFileTotalLines(options) {
33908
+ if (options.isBinary) return null;
33909
+ if (options.change === "removed") {
33910
+ return countLinesInGitRef(options.repoGitCwd, options.parentSha, options.pathInRepo);
33911
+ }
33912
+ if (options.change === "moved" || options.change === "modified") {
33913
+ return countLinesInGitRef(options.repoGitCwd, options.commitSha, options.pathInRepo);
33914
+ }
33915
+ return null;
33916
+ }
33917
+
33918
+ // src/git/changes/listing/path-in-repo-from-launcher.ts
33919
+ function pathInRepoFromLauncher(pathRelLauncher, repoRelPath) {
33920
+ const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
33921
+ if (normRel === ".") return pathRelLauncher;
33922
+ if (pathRelLauncher.startsWith(`${normRel}/`)) {
33923
+ return pathRelLauncher.slice(normRel.length + 1);
33924
+ }
33925
+ return pathRelLauncher;
33926
+ }
33927
+ function normalizeRepoRelPath(repoRelPath) {
33928
+ return repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
33929
+ }
33930
+
33931
+ // src/git/changes/listing/enrich-commit-file-rows.ts
33932
+ async function enrichCommitFileRows(options) {
33933
+ await forEachWithGitYield(options.rows, async (row) => {
33934
+ const pathInRepo = pathInRepoFromLauncher(row.pathRelLauncher, options.repoRelPath);
33935
+ row.patchContent = await unifiedDiffForFileInRange(
33936
+ options.repoGitCwd,
33937
+ options.range,
33938
+ pathInRepo,
33939
+ row.change,
33940
+ row.movedFromPathInRepo
33941
+ );
33942
+ row.totalLines = await resolveCommitFileTotalLines({
33943
+ repoGitCwd: options.repoGitCwd,
33944
+ pathInRepo,
33945
+ change: row.change,
33946
+ commitSha: options.commitSha,
33947
+ parentSha: options.parentSha,
33948
+ isBinary: options.binaryByPath.has(pathInRepo)
33949
+ });
33950
+ });
33951
+ }
33952
+
33953
+ // src/git/changes/parse/parse-name-status-entries.ts
33954
+ function parseNameStatusEntries(lines) {
33955
+ const entries = [];
33733
33956
  for (const line of lines) {
33734
33957
  if (!line.trim()) continue;
33735
33958
  const tabParts = line.split(" ");
@@ -33737,58 +33960,143 @@ function parseNameStatusLines(lines) {
33737
33960
  const status = tabParts[0].trim();
33738
33961
  const code = status[0];
33739
33962
  if (code === "A") {
33740
- m.set(tabParts[tabParts.length - 1], "added");
33963
+ entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "added" });
33741
33964
  } else if (code === "D") {
33742
- m.set(tabParts[tabParts.length - 1], "removed");
33965
+ entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "removed" });
33743
33966
  } else if (code === "R" || code === "C") {
33744
- if (tabParts.length >= 3) m.set(tabParts[tabParts.length - 1], "modified");
33967
+ if (tabParts.length >= 3) {
33968
+ entries.push({
33969
+ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]),
33970
+ oldPathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 2]),
33971
+ change: "moved"
33972
+ });
33973
+ }
33745
33974
  } else if (code === "M" || code === "U" || code === "T") {
33746
- m.set(tabParts[tabParts.length - 1], "modified");
33975
+ entries.push({ pathInRepo: normalizeGitDiffPath(tabParts[tabParts.length - 1]), change: "modified" });
33747
33976
  }
33748
33977
  }
33978
+ return entries;
33979
+ }
33980
+ function parseNameStatusLines(lines) {
33981
+ const m = /* @__PURE__ */ new Map();
33982
+ for (const entry of parseNameStatusEntries(lines)) {
33983
+ m.set(entry.pathInRepo, entry.change);
33984
+ }
33749
33985
  return m;
33750
33986
  }
33751
- function parseNumstatFirstLine(line) {
33987
+
33988
+ // src/git/changes/parse/parse-numstat-entries.ts
33989
+ function parseNumstatCounts(parts) {
33990
+ const [a, d] = parts;
33991
+ return {
33992
+ additions: a === "-" ? 0 : parseInt(String(a), 10) || 0,
33993
+ deletions: d === "-" ? 0 : parseInt(String(d), 10) || 0
33994
+ };
33995
+ }
33996
+ function parseNumstatLine(line) {
33752
33997
  const parts = line.split(" ");
33753
33998
  if (parts.length < 3) return null;
33754
- const [a, d] = parts;
33755
- const additions = a === "-" ? 0 : parseInt(String(a), 10) || 0;
33756
- const deletions = d === "-" ? 0 : parseInt(String(d), 10) || 0;
33757
- return { additions, deletions };
33999
+ const rawPath = parts[parts.length - 1];
34000
+ const { additions, deletions } = parseNumstatCounts(parts);
34001
+ const expanded = expandGitRenameNumstatPath(rawPath);
34002
+ const pathInRepo = normalizeGitDiffPath(expanded?.newPath ?? rawPath);
34003
+ return {
34004
+ pathInRepo,
34005
+ additions,
34006
+ deletions,
34007
+ ...expanded ? { oldPathInRepo: expanded.oldPath } : {}
34008
+ };
33758
34009
  }
33759
- function parseNumstat(lines) {
34010
+ function parseNumstatEntries(lines) {
33760
34011
  const m = /* @__PURE__ */ new Map();
33761
34012
  for (const line of lines) {
33762
34013
  if (!line.trim()) continue;
33763
34014
  const parts = line.split(" ");
33764
34015
  if (parts.length < 3) continue;
33765
- const [a, d, p] = parts;
33766
- const additions = a === "-" ? 0 : parseInt(String(a), 10) || 0;
33767
- const deletions = d === "-" ? 0 : parseInt(String(d), 10) || 0;
33768
- m.set(p, { additions, deletions });
34016
+ const rawPath = parts[parts.length - 1];
34017
+ const expanded = expandGitRenameNumstatPath(rawPath);
34018
+ const entry = parseNumstatLine(line);
34019
+ if (!entry) continue;
34020
+ const existing = m.get(entry.pathInRepo);
34021
+ if (!existing || expanded) {
34022
+ m.set(entry.pathInRepo, entry);
34023
+ }
33769
34024
  }
33770
34025
  return m;
33771
34026
  }
34027
+ function parseNumstat(lines) {
34028
+ const m = /* @__PURE__ */ new Map();
34029
+ for (const [path46, entry] of parseNumstatEntries(lines)) {
34030
+ m.set(path46, { additions: entry.additions, deletions: entry.deletions });
34031
+ }
34032
+ return m;
34033
+ }
34034
+
34035
+ // src/git/changes/parse/numstat-from-git-no-index.ts
33772
34036
  async function numstatFromGitNoIndex(g, pathInRepo) {
33773
34037
  const devNull = process.platform === "win32" ? "NUL" : "/dev/null";
33774
34038
  try {
33775
34039
  const out = await g.raw(["diff", "--numstat", "--no-index", "--", devNull, pathInRepo]);
33776
34040
  const first2 = String(out).split("\n").find((l) => l.trim()) ?? "";
33777
- return parseNumstatFirstLine(first2);
34041
+ const parsed = parseNumstatLine(first2);
34042
+ if (!parsed) return null;
34043
+ return { additions: parsed.additions, deletions: parsed.deletions };
33778
34044
  } catch {
33779
34045
  return null;
33780
34046
  }
33781
34047
  }
33782
34048
 
33783
- // src/git/changes/lib/patch-truncate.ts
33784
- function truncatePatch(s) {
33785
- if (s.length <= MAX_PATCH_CHARS) return s;
33786
- return `${s.slice(0, MAX_PATCH_CHARS)}
34049
+ // src/git/changes/rows/collect-moved-source-paths.ts
34050
+ function collectMovedSourcePaths(nameEntries, numByPath) {
34051
+ const sources = /* @__PURE__ */ new Set();
34052
+ for (const entry of nameEntries) {
34053
+ if (entry.change === "moved" && entry.oldPathInRepo) sources.add(entry.oldPathInRepo);
34054
+ }
34055
+ for (const entry of numByPath.values()) {
34056
+ if (entry.oldPathInRepo) sources.add(entry.oldPathInRepo);
34057
+ }
34058
+ return sources;
34059
+ }
33787
34060
 
33788
- \u2026 (diff truncated)`;
34061
+ // src/git/changes/numstat/is-binary-numstat-line.ts
34062
+ function isBinaryNumstatLine(line) {
34063
+ const parts = line.split(" ");
34064
+ if (parts.length < 3) return false;
34065
+ return parts[0] === "-" || parts[1] === "-";
33789
34066
  }
33790
34067
 
33791
- // src/git/commits/list-changed-files-for-commit.ts
34068
+ // src/git/changes/listing/collect-binary-paths-from-numstat.ts
34069
+ function collectBinaryPathsFromNumstat(numstatRaw) {
34070
+ const binaryByPath = /* @__PURE__ */ new Set();
34071
+ for (const line of numstatRaw.split("\n")) {
34072
+ if (!line.trim()) continue;
34073
+ const parts = line.split(" ");
34074
+ if (parts.length < 3) continue;
34075
+ const rawPath = parts[parts.length - 1];
34076
+ const expanded = expandGitRenameNumstatPath(rawPath);
34077
+ if (isBinaryNumstatLine(line)) binaryByPath.add(expanded?.newPath ?? rawPath);
34078
+ }
34079
+ return binaryByPath;
34080
+ }
34081
+
34082
+ // src/git/changes/listing/parse-diff-output.ts
34083
+ function parseDiffOutput(nameStatusRaw, numstatRaw) {
34084
+ const nameEntries = parseNameStatusEntries(nameStatusRaw.split("\n"));
34085
+ const nameByPath = new Map(nameEntries.map((entry) => [entry.pathInRepo, entry]));
34086
+ const numByPath = parseNumstatEntries(numstatRaw.split("\n"));
34087
+ const movedSourcePaths = collectMovedSourcePaths(nameEntries, numByPath);
34088
+ const binaryByPath = collectBinaryPathsFromNumstat(numstatRaw);
34089
+ return { nameEntries, nameByPath, numByPath, movedSourcePaths, binaryByPath };
34090
+ }
34091
+ function collectChangedPaths(options) {
34092
+ const paths = /* @__PURE__ */ new Set();
34093
+ for (const entry of options.nameEntries) paths.add(entry.pathInRepo);
34094
+ for (const pathInRepo of options.numByPath.keys()) paths.add(pathInRepo);
34095
+ for (const pathInRepo of options.untracked ?? []) paths.add(pathInRepo);
34096
+ return paths;
34097
+ }
34098
+
34099
+ // src/git/changes/listing/parent-for-commit-diff.ts
33792
34100
  var EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
33793
34101
  async function parentForCommitDiff(g, sha) {
33794
34102
  try {
@@ -33801,76 +34109,112 @@ async function parentForCommitDiff(g, sha) {
33801
34109
  }
33802
34110
  }
33803
34111
  }
34112
+
34113
+ // src/git/changes/listing/rename-diff-args.ts
34114
+ var RENAME_DIFF_ARGS = ["-M", "--find-renames"];
34115
+
34116
+ // src/git/changes/rows/pick-preferred-changed-file-row.ts
34117
+ function rowRank(row) {
34118
+ if (row.change === "moved") return 3;
34119
+ if (row.change === "modified") return 2;
34120
+ if (row.change === "added") return 1;
34121
+ return 0;
34122
+ }
34123
+ function pickPreferredChangedFileRow(a, b) {
34124
+ if (rowRank(a) !== rowRank(b)) return rowRank(a) > rowRank(b) ? a : b;
34125
+ const aHasPatch = Boolean(a.patchContent?.trim());
34126
+ const bHasPatch = Boolean(b.patchContent?.trim());
34127
+ if (aHasPatch !== bHasPatch) return aHasPatch ? a : b;
34128
+ const aHasMoveMeta = Boolean(a.movedFromPathInRepo || a.movedFromPathRelLauncher);
34129
+ const bHasMoveMeta = Boolean(b.movedFromPathInRepo || b.movedFromPathRelLauncher);
34130
+ if (aHasMoveMeta !== bHasMoveMeta) return aHasMoveMeta ? a : b;
34131
+ return a;
34132
+ }
34133
+
34134
+ // src/git/changes/rows/dedupe-changed-file-rows.ts
34135
+ function dedupeChangedFileRows(rows) {
34136
+ const byPath = /* @__PURE__ */ new Map();
34137
+ for (const row of rows) {
34138
+ const key = normalizeGitDiffPath(row.pathRelLauncher);
34139
+ const normalizedRow = key === row.pathRelLauncher ? row : { ...row, pathRelLauncher: key };
34140
+ const existing = byPath.get(key);
34141
+ if (!existing) {
34142
+ byPath.set(key, normalizedRow);
34143
+ continue;
34144
+ }
34145
+ const preferRow = pickPreferredChangedFileRow(existing, normalizedRow);
34146
+ byPath.set(key, { ...preferRow, pathRelLauncher: key });
34147
+ }
34148
+ return [...byPath.values()];
34149
+ }
34150
+
34151
+ // src/git/changes/listing/list-changed-files-for-commit.ts
33804
34152
  async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
33805
34153
  const g = cliSimpleGit(repoGitCwd);
33806
34154
  const parent = await parentForCommitDiff(g, commitSha);
33807
34155
  const range = `${parent}..${commitSha}`;
33808
34156
  const [nameStatusRaw, numstatRaw] = await Promise.all([
33809
- g.raw(["diff", "--name-status", range]).catch(() => ""),
33810
- g.raw(["diff", "--numstat", range]).catch(() => "")
34157
+ g.raw(["diff", ...RENAME_DIFF_ARGS, "--name-status", range]).catch(() => ""),
34158
+ g.raw(["diff", ...RENAME_DIFF_ARGS, "--numstat", range]).catch(() => "")
33811
34159
  ]);
33812
- const kindByPath = parseNameStatusLines(String(nameStatusRaw).split("\n"));
33813
- const numByPath = parseNumstat(String(numstatRaw).split("\n"));
33814
- const paths = new Set([...kindByPath.keys(), ...numByPath.keys()].filter(Boolean));
33815
- const rows = [];
33816
- const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
33817
- await forEachWithGitYield([...paths], async (pathInRepo) => {
33818
- const relLauncher = posixJoinDirFile(normRel, pathInRepo.replace(/\\/g, "/"));
33819
- const nums = numByPath.get(pathInRepo);
33820
- let additions = nums?.additions ?? 0;
33821
- let deletions = nums?.deletions ?? 0;
33822
- let change = kindByPath.get(pathInRepo) ?? "modified";
33823
- if (!kindByPath.has(pathInRepo) && nums) {
33824
- if (additions > 0 && deletions === 0) change = "added";
33825
- else if (deletions > 0 && additions === 0) change = "removed";
33826
- else change = "modified";
33827
- }
33828
- rows.push({ pathRelLauncher: relLauncher, additions, deletions, change });
33829
- });
33830
- await forEachWithGitYield(rows, async (row) => {
33831
- let pathInRepo;
33832
- if (normRel === ".") {
33833
- pathInRepo = row.pathRelLauncher;
33834
- } else if (row.pathRelLauncher.startsWith(`${normRel}/`)) {
33835
- pathInRepo = row.pathRelLauncher.slice(normRel.length + 1);
33836
- } else {
33837
- pathInRepo = row.pathRelLauncher;
33838
- }
33839
- const raw = await g.raw(["diff", "-U20000", range, "--", pathInRepo]).catch(() => "");
33840
- const t = String(raw).trim();
33841
- row.patchContent = t ? truncatePatch(t) : void 0;
34160
+ const parsed = parseDiffOutput(String(nameStatusRaw), String(numstatRaw));
34161
+ const normRel = normalizeRepoRelPath(repoRelPath);
34162
+ const paths = collectChangedPaths({
34163
+ nameEntries: parsed.nameEntries,
34164
+ numByPath: parsed.numByPath
33842
34165
  });
33843
- rows.sort((a, b) => a.pathRelLauncher.localeCompare(b.pathRelLauncher));
33844
- return rows;
33845
- }
33846
-
33847
- // src/git/changes/list-changed-files-for-repo.ts
34166
+ let rows = await buildChangedFileRowsFromPaths({
34167
+ paths,
34168
+ repoRelPath: normRel,
34169
+ nameByPath: parsed.nameByPath,
34170
+ numByPath: parsed.numByPath,
34171
+ movedSourcePaths: parsed.movedSourcePaths
34172
+ });
34173
+ rows = dedupeChangedFileRows(rows);
34174
+ await enrichCommitFileRows({
34175
+ rows,
34176
+ repoGitCwd,
34177
+ repoRelPath: normRel,
34178
+ range,
34179
+ commitSha,
34180
+ parentSha: parent,
34181
+ binaryByPath: parsed.binaryByPath
34182
+ });
34183
+ const finalRows = dedupeChangedFileRows(rows);
34184
+ finalRows.sort((a, b) => a.pathRelLauncher.localeCompare(b.pathRelLauncher));
34185
+ return finalRows;
34186
+ }
34187
+
34188
+ // src/git/changes/listing/apply-untracked-file-stats.ts
33848
34189
  import * as fs17 from "node:fs";
33849
- import * as path19 from "node:path";
33850
-
33851
- // src/git/changes/lib/count-lines.ts
33852
- import { createReadStream } from "node:fs";
33853
- import * as readline2 from "node:readline";
33854
- async function countTextFileLines(filePath) {
33855
- let bytes = 0;
33856
- const maxBytes = 512e3;
33857
- let lines = 0;
33858
- const stream = createReadStream(filePath, { encoding: "utf8" });
33859
- const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
33860
- for await (const _line of rl) {
33861
- lines += 1;
33862
- bytes += Buffer.byteLength(String(_line), "utf8") + 1;
33863
- if (bytes > maxBytes) {
33864
- rl.close();
33865
- stream.destroy();
33866
- return lines;
34190
+ import * as path20 from "node:path";
34191
+ function createUntrackedStatsApplier(options) {
34192
+ return async (row, pathInRepo) => {
34193
+ if (!options.untrackedSet.has(pathInRepo)) return;
34194
+ if (options.nameByPath.has(pathInRepo)) return;
34195
+ if (row.change === "moved") return;
34196
+ const repoFilePath = path20.join(options.repoGitCwd, pathInRepo);
34197
+ const fromGit = await numstatFromGitNoIndex(options.g, pathInRepo);
34198
+ if (fromGit) {
34199
+ row.additions = fromGit.additions;
34200
+ row.deletions = fromGit.deletions;
34201
+ return;
33867
34202
  }
33868
- }
33869
- return lines;
34203
+ try {
34204
+ const st = await fs17.promises.stat(repoFilePath);
34205
+ row.additions = st.isFile() ? await countTextFileLines(repoFilePath) : 0;
34206
+ } catch {
34207
+ row.additions = 0;
34208
+ }
34209
+ row.deletions = 0;
34210
+ };
33870
34211
  }
33871
34212
 
33872
- // src/git/changes/hydrate-patch.ts
33873
- import * as fs16 from "node:fs";
34213
+ // src/git/changes/listing/enrich-working-tree-file-rows.ts
34214
+ import * as path21 from "node:path";
34215
+
34216
+ // src/git/changes/patch/hydrate-patch.ts
34217
+ import * as fs18 from "node:fs";
33874
34218
  var UNIFIED_HUNK_HEADER_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
33875
34219
  var MAX_HYDRATE_LINES_PER_GAP = 8e3;
33876
34220
  var MAX_HYDRATE_LINES_PER_FILE = 8e4;
@@ -33885,7 +34229,7 @@ async function readGitBlobLines(repoCwd, pathInRepo) {
33885
34229
  }
33886
34230
  async function readWorktreeFileLines(filePath) {
33887
34231
  try {
33888
- const raw = await fs16.promises.readFile(filePath, "utf8");
34232
+ const raw = await fs18.promises.readFile(filePath, "utf8");
33889
34233
  return raw.split(/\r?\n/);
33890
34234
  } catch {
33891
34235
  return null;
@@ -33985,82 +34329,70 @@ async function hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, p
33985
34329
  return truncatePatch(out.join("\n"));
33986
34330
  }
33987
34331
 
33988
- // src/git/changes/unified-diff-for-file.ts
33989
- function patchTextFromGitDiffOutput(raw) {
33990
- if (raw.trim() === "") return void 0;
33991
- return normalizePatchContent(raw);
33992
- }
33993
- async function unifiedDiffForFile(repoCwd, pathInRepo, change) {
33994
- const g = cliSimpleGit(repoCwd);
33995
- const args = change === "added" ? ["diff", "--no-color", "HEAD", "--", pathInRepo] : change === "removed" ? ["diff", "--no-color", "HEAD", "--", pathInRepo] : ["diff", "--no-color", "HEAD", "--", pathInRepo];
33996
- const raw = await g.raw([...args]).catch(() => "");
33997
- const patch = patchTextFromGitDiffOutput(String(raw));
33998
- return patch ? truncatePatch(patch) : void 0;
34332
+ // src/git/changes/listing/enrich-working-tree-file-rows.ts
34333
+ async function enrichWorkingTreeFileRows(options) {
34334
+ await forEachWithGitYield(options.rows, async (row) => {
34335
+ const pathInRepo = pathInRepoFromLauncher(row.pathRelLauncher, options.repoRelPath);
34336
+ const filePath = path21.join(options.repoGitCwd, pathInRepo);
34337
+ const hydrateKind = row.change === "moved" ? "modified" : row.change;
34338
+ let patch = await unifiedDiffForFile(options.repoGitCwd, pathInRepo, row.change, row.movedFromPathInRepo);
34339
+ if (patch) {
34340
+ patch = await hydrateUnifiedPatchWithFileContext(
34341
+ patch,
34342
+ filePath,
34343
+ options.repoGitCwd,
34344
+ pathInRepo,
34345
+ hydrateKind
34346
+ );
34347
+ }
34348
+ row.patchContent = patch;
34349
+ row.totalLines = await resolveWorkingTreeFileTotalLines({
34350
+ repoGitCwd: options.repoGitCwd,
34351
+ pathInRepo,
34352
+ change: row.change,
34353
+ isBinary: options.binaryByPath.has(pathInRepo)
34354
+ });
34355
+ });
33999
34356
  }
34000
34357
 
34001
- // src/git/changes/list-changed-files-for-repo.ts
34358
+ // src/git/changes/listing/list-changed-files-for-repo.ts
34002
34359
  async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
34003
34360
  const g = cliSimpleGit(repoGitCwd);
34004
34361
  const [nameStatusRaw, numstatRaw, untrackedRaw] = await Promise.all([
34005
- g.raw(["diff", "--name-status", "HEAD"]).catch(() => ""),
34006
- g.raw(["diff", "HEAD", "--numstat"]).catch(() => ""),
34362
+ g.raw(["diff", ...RENAME_DIFF_ARGS, "--name-status", "HEAD"]).catch(() => ""),
34363
+ g.raw(["diff", ...RENAME_DIFF_ARGS, "HEAD", "--numstat"]).catch(() => ""),
34007
34364
  g.raw(["ls-files", "--others", "--exclude-standard"]).catch(() => "")
34008
34365
  ]);
34009
- const kindByPath = parseNameStatusLines(String(nameStatusRaw).split("\n"));
34010
- const numByPath = parseNumstat(String(numstatRaw).split("\n"));
34011
- const paths = /* @__PURE__ */ new Set([...kindByPath.keys(), ...numByPath.keys()]);
34366
+ const parsed = parseDiffOutput(String(nameStatusRaw), String(numstatRaw));
34012
34367
  const untracked = String(untrackedRaw).split("\n").map((s) => s.trim()).filter(Boolean);
34013
- for (const p of untracked) paths.add(p);
34014
- const rows = [];
34015
- await forEachWithGitYield([...paths], async (pathInRepo) => {
34016
- const relLauncher = posixJoinDirFile(repoRelPath, pathInRepo.replace(/\\/g, "/"));
34017
- const repoFilePath = path19.join(repoGitCwd, pathInRepo);
34018
- const nums = numByPath.get(pathInRepo);
34019
- let additions = nums?.additions ?? 0;
34020
- let deletions = nums?.deletions ?? 0;
34021
- let change = kindByPath.get(pathInRepo) ?? "modified";
34022
- if (untracked.includes(pathInRepo) && !kindByPath.has(pathInRepo)) {
34023
- change = "added";
34024
- const fromGit = await numstatFromGitNoIndex(g, pathInRepo);
34025
- if (fromGit) {
34026
- additions = fromGit.additions;
34027
- deletions = fromGit.deletions;
34028
- } else {
34029
- try {
34030
- const st = await fs17.promises.stat(repoFilePath);
34031
- if (st.isFile()) additions = await countTextFileLines(repoFilePath);
34032
- else additions = 0;
34033
- } catch {
34034
- additions = 0;
34035
- }
34036
- deletions = 0;
34037
- }
34038
- }
34039
- if (!kindByPath.has(pathInRepo) && nums) {
34040
- if (additions > 0 && deletions === 0) change = "added";
34041
- else if (deletions > 0 && additions === 0) change = "removed";
34042
- else change = "modified";
34043
- }
34044
- rows.push({ pathRelLauncher: relLauncher, additions, deletions, change });
34368
+ const untrackedSet = new Set(untracked);
34369
+ const paths = collectChangedPaths({
34370
+ nameEntries: parsed.nameEntries,
34371
+ numByPath: parsed.numByPath,
34372
+ untracked
34045
34373
  });
34046
- const normRel = repoRelPath === "." || repoRelPath === "" ? "." : repoRelPath;
34047
- await forEachWithGitYield(rows, async (row) => {
34048
- let pathInRepo;
34049
- if (normRel === ".") {
34050
- pathInRepo = row.pathRelLauncher;
34051
- } else if (row.pathRelLauncher.startsWith(`${normRel}/`)) {
34052
- pathInRepo = row.pathRelLauncher.slice(normRel.length + 1);
34053
- } else {
34054
- pathInRepo = row.pathRelLauncher;
34055
- }
34056
- const filePath = path19.join(repoGitCwd, pathInRepo);
34057
- let patch = await unifiedDiffForFile(repoGitCwd, pathInRepo, row.change);
34058
- if (patch) {
34059
- patch = await hydrateUnifiedPatchWithFileContext(patch, filePath, repoGitCwd, pathInRepo, row.change);
34060
- }
34061
- row.patchContent = patch;
34374
+ let rows = await buildChangedFileRowsFromPaths({
34375
+ paths,
34376
+ repoRelPath,
34377
+ nameByPath: parsed.nameByPath,
34378
+ numByPath: parsed.numByPath,
34379
+ movedSourcePaths: parsed.movedSourcePaths,
34380
+ untrackedSet,
34381
+ applyUntrackedStats: createUntrackedStatsApplier({
34382
+ g,
34383
+ repoGitCwd,
34384
+ untrackedSet,
34385
+ nameByPath: parsed.nameByPath
34386
+ })
34062
34387
  });
34063
- return rows;
34388
+ rows = dedupeChangedFileRows(rows);
34389
+ await enrichWorkingTreeFileRows({
34390
+ rows,
34391
+ repoGitCwd,
34392
+ repoRelPath,
34393
+ binaryByPath: parsed.binaryByPath
34394
+ });
34395
+ return dedupeChangedFileRows(rows);
34064
34396
  }
34065
34397
 
34066
34398
  // src/git/changes/get-working-tree-change-repo-details.ts
@@ -34069,8 +34401,8 @@ function normRepoRel(p) {
34069
34401
  return x === "" ? "." : x;
34070
34402
  }
34071
34403
  async function getWorkingTreeChangeRepoDetails(options) {
34072
- const bridgeRoot = path20.resolve(getBridgeRoot());
34073
- const sessionWtRoot = options.sessionWorktreeRootPath ? path20.resolve(options.sessionWorktreeRootPath) : null;
34404
+ const bridgeRoot = path22.resolve(getBridgeRoot());
34405
+ const sessionWtRoot = options.sessionWorktreeRootPath ? path22.resolve(options.sessionWorktreeRootPath) : null;
34074
34406
  const legacyNested = options.legacyRepoNestedSessionLayout === true;
34075
34407
  const out = [];
34076
34408
  const filter = options.repoFilterRelPath != null ? normRepoRel(options.repoFilterRelPath) : null;
@@ -34085,7 +34417,7 @@ async function getWorkingTreeChangeRepoDetails(options) {
34085
34417
  for (let i = 0; i < options.commitTargetPaths.length; i++) {
34086
34418
  if (i > 0) await yieldToEventLoop2();
34087
34419
  const target = options.commitTargetPaths[i];
34088
- const t = path20.resolve(target);
34420
+ const t = path22.resolve(target);
34089
34421
  if (!await isGitRepoDirectory(t)) continue;
34090
34422
  const g = cliSimpleGit(t);
34091
34423
  let branch = "HEAD";
@@ -34098,8 +34430,8 @@ async function getWorkingTreeChangeRepoDetails(options) {
34098
34430
  const remoteDisplay = formatRemoteDisplayLabel(remoteUrl);
34099
34431
  let repoRelPath;
34100
34432
  if (sessionWtRoot) {
34101
- const anchor = legacyNested ? path20.dirname(t) : t;
34102
- const relNorm = path20.relative(sessionWtRoot, anchor);
34433
+ const anchor = legacyNested ? path22.dirname(t) : t;
34434
+ const relNorm = path22.relative(sessionWtRoot, anchor);
34103
34435
  repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
34104
34436
  } else {
34105
34437
  let top = t;
@@ -34108,8 +34440,8 @@ async function getWorkingTreeChangeRepoDetails(options) {
34108
34440
  } catch {
34109
34441
  top = t;
34110
34442
  }
34111
- const rel = path20.relative(bridgeRoot, path20.resolve(top)).replace(/\\/g, "/") || ".";
34112
- repoRelPath = rel.startsWith("..") ? path20.basename(path20.resolve(top)) : rel;
34443
+ const rel = path22.relative(bridgeRoot, path22.resolve(top)).replace(/\\/g, "/") || ".";
34444
+ repoRelPath = rel.startsWith("..") ? path22.basename(path22.resolve(top)) : rel;
34113
34445
  }
34114
34446
  const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
34115
34447
  if (filter && norm !== filter) continue;
@@ -34141,7 +34473,7 @@ async function getWorkingTreeChangeRepoDetails(options) {
34141
34473
  return out;
34142
34474
  }
34143
34475
 
34144
- // src/git/changes/lib/working-tree-changed-path-count.ts
34476
+ // src/git/changes/status/working-tree-changed-path-count.ts
34145
34477
  function workingTreeChangedPathCount(nameStatusLines, numstatLines, untrackedLines) {
34146
34478
  const kindByPath = parseNameStatusLines(nameStatusLines);
34147
34479
  const numByPath = parseNumstat(numstatLines);
@@ -34237,23 +34569,23 @@ async function commitSessionWorktrees(options) {
34237
34569
  }
34238
34570
 
34239
34571
  // src/worktrees/remove-session-worktrees.ts
34240
- import * as fs20 from "node:fs";
34572
+ import * as fs21 from "node:fs";
34241
34573
 
34242
34574
  // src/git/worktrees/worktree-remove.ts
34243
- import * as fs19 from "node:fs";
34575
+ import * as fs20 from "node:fs";
34244
34576
 
34245
34577
  // src/git/worktrees/resolve-main-repo-from-git-file.ts
34246
- import * as fs18 from "node:fs";
34247
- import * as path21 from "node:path";
34578
+ import * as fs19 from "node:fs";
34579
+ import * as path23 from "node:path";
34248
34580
  function resolveMainRepoFromWorktreeGitFile(wt) {
34249
- const gitDirFile = path21.join(wt, ".git");
34250
- if (!fs18.existsSync(gitDirFile) || !fs18.statSync(gitDirFile).isFile()) return "";
34251
- const first2 = fs18.readFileSync(gitDirFile, "utf8").trim();
34581
+ const gitDirFile = path23.join(wt, ".git");
34582
+ if (!fs19.existsSync(gitDirFile) || !fs19.statSync(gitDirFile).isFile()) return "";
34583
+ const first2 = fs19.readFileSync(gitDirFile, "utf8").trim();
34252
34584
  const m = first2.match(/^gitdir:\s*(.+)$/im);
34253
34585
  if (!m) return "";
34254
- const gitWorktreePath = path21.resolve(wt, m[1].trim());
34255
- const gitDir = path21.dirname(path21.dirname(gitWorktreePath));
34256
- return path21.dirname(gitDir);
34586
+ const gitWorktreePath = path23.resolve(wt, m[1].trim());
34587
+ const gitDir = path23.dirname(path23.dirname(gitWorktreePath));
34588
+ return path23.dirname(gitDir);
34257
34589
  }
34258
34590
 
34259
34591
  // src/git/worktrees/worktree-remove.ts
@@ -34262,7 +34594,7 @@ async function gitWorktreeRemoveForce(worktreePath) {
34262
34594
  if (mainRepo) {
34263
34595
  await cliSimpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
34264
34596
  } else {
34265
- fs19.rmSync(worktreePath, { recursive: true, force: true });
34597
+ fs20.rmSync(worktreePath, { recursive: true, force: true });
34266
34598
  }
34267
34599
  }
34268
34600
 
@@ -34275,7 +34607,7 @@ async function removeSessionWorktrees(paths, log2) {
34275
34607
  } catch (e) {
34276
34608
  log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
34277
34609
  try {
34278
- fs20.rmSync(wt, { recursive: true, force: true });
34610
+ fs21.rmSync(wt, { recursive: true, force: true });
34279
34611
  } catch {
34280
34612
  }
34281
34613
  }
@@ -34304,12 +34636,12 @@ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
34304
34636
  }
34305
34637
 
34306
34638
  // src/worktrees/worktree-layout-file.ts
34307
- import * as fs21 from "node:fs";
34308
- import * as path22 from "node:path";
34639
+ import * as fs22 from "node:fs";
34640
+ import * as path24 from "node:path";
34309
34641
  import os7 from "node:os";
34310
34642
  var LAYOUT_FILENAME = "worktree-launcher-layout.json";
34311
34643
  function defaultWorktreeLayoutPath() {
34312
- return path22.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
34644
+ return path24.join(os7.homedir(), ".buildautomaton", LAYOUT_FILENAME);
34313
34645
  }
34314
34646
  function normalizeLoadedLayout(raw) {
34315
34647
  if (raw && typeof raw === "object" && "launcherCwds" in raw) {
@@ -34321,8 +34653,8 @@ function normalizeLoadedLayout(raw) {
34321
34653
  function loadWorktreeLayout() {
34322
34654
  try {
34323
34655
  const p = defaultWorktreeLayoutPath();
34324
- if (!fs21.existsSync(p)) return { launcherCwds: [] };
34325
- const raw = JSON.parse(fs21.readFileSync(p, "utf8"));
34656
+ if (!fs22.existsSync(p)) return { launcherCwds: [] };
34657
+ const raw = JSON.parse(fs22.readFileSync(p, "utf8"));
34326
34658
  return normalizeLoadedLayout(raw);
34327
34659
  } catch {
34328
34660
  return { launcherCwds: [] };
@@ -34330,24 +34662,24 @@ function loadWorktreeLayout() {
34330
34662
  }
34331
34663
  function saveWorktreeLayout(layout) {
34332
34664
  try {
34333
- const dir = path22.dirname(defaultWorktreeLayoutPath());
34334
- fs21.mkdirSync(dir, { recursive: true });
34335
- fs21.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
34665
+ const dir = path24.dirname(defaultWorktreeLayoutPath());
34666
+ fs22.mkdirSync(dir, { recursive: true });
34667
+ fs22.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
34336
34668
  } catch {
34337
34669
  }
34338
34670
  }
34339
34671
  function baseNameSafe(pathString) {
34340
- return path22.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
34672
+ return path24.basename(pathString).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
34341
34673
  }
34342
34674
  function getLauncherDirNameIfPresent(layout, bridgeRootPath2) {
34343
- const norm = path22.resolve(bridgeRootPath2);
34344
- const existing = layout.launcherCwds.find((e) => path22.resolve(e.absolutePath) === norm);
34675
+ const norm = path24.resolve(bridgeRootPath2);
34676
+ const existing = layout.launcherCwds.find((e) => path24.resolve(e.absolutePath) === norm);
34345
34677
  return existing?.dirName;
34346
34678
  }
34347
34679
  function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
34348
34680
  const existing = getLauncherDirNameIfPresent(layout, bridgeRootPath2);
34349
34681
  if (existing) return existing;
34350
- const norm = path22.resolve(bridgeRootPath2);
34682
+ const norm = path24.resolve(bridgeRootPath2);
34351
34683
  const base = baseNameSafe(norm);
34352
34684
  const used = new Set(layout.launcherCwds.map((e) => e.dirName));
34353
34685
  let name = base;
@@ -34362,11 +34694,11 @@ function allocateDirNameForLauncherCwd(layout, bridgeRootPath2) {
34362
34694
  }
34363
34695
 
34364
34696
  // src/worktrees/discover-session-worktree-on-disk.ts
34365
- import * as fs22 from "node:fs";
34366
- import * as path23 from "node:path";
34697
+ import * as fs23 from "node:fs";
34698
+ import * as path25 from "node:path";
34367
34699
  function isGitDir(dirPath) {
34368
34700
  try {
34369
- return fs22.existsSync(path23.join(dirPath, ".git"));
34701
+ return fs23.existsSync(path25.join(dirPath, ".git"));
34370
34702
  } catch {
34371
34703
  return false;
34372
34704
  }
@@ -34375,23 +34707,23 @@ function collectGitRepoRootsUnderDirectory(rootPath) {
34375
34707
  const out = [];
34376
34708
  const walk = (dir) => {
34377
34709
  if (isGitDir(dir)) {
34378
- out.push(path23.resolve(dir));
34710
+ out.push(path25.resolve(dir));
34379
34711
  return;
34380
34712
  }
34381
34713
  let entries;
34382
34714
  try {
34383
- entries = fs22.readdirSync(dir, { withFileTypes: true });
34715
+ entries = fs23.readdirSync(dir, { withFileTypes: true });
34384
34716
  } catch {
34385
34717
  return;
34386
34718
  }
34387
34719
  for (const e of entries) {
34388
34720
  if (e.name.startsWith(".")) continue;
34389
- const full = path23.join(dir, e.name);
34721
+ const full = path25.join(dir, e.name);
34390
34722
  if (!e.isDirectory()) continue;
34391
34723
  walk(full);
34392
34724
  }
34393
34725
  };
34394
- walk(path23.resolve(rootPath));
34726
+ walk(path25.resolve(rootPath));
34395
34727
  return [...new Set(out)];
34396
34728
  }
34397
34729
  function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
@@ -34400,16 +34732,16 @@ function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
34400
34732
  if (depth > maxDepth) return;
34401
34733
  let entries;
34402
34734
  try {
34403
- entries = fs22.readdirSync(dir, { withFileTypes: true });
34735
+ entries = fs23.readdirSync(dir, { withFileTypes: true });
34404
34736
  } catch {
34405
34737
  return;
34406
34738
  }
34407
34739
  for (const e of entries) {
34408
34740
  if (e.name.startsWith(".")) continue;
34409
- const full = path23.join(dir, e.name);
34741
+ const full = path25.join(dir, e.name);
34410
34742
  if (!e.isDirectory()) continue;
34411
34743
  if (e.name === sessionId) {
34412
- if (isGitDir(full)) out.push(path23.resolve(full));
34744
+ if (isGitDir(full)) out.push(path25.resolve(full));
34413
34745
  } else {
34414
34746
  walk(full, depth + 1);
34415
34747
  }
@@ -34421,14 +34753,14 @@ function collectWorktreeRootsNamed(root, sessionId, maxDepth) {
34421
34753
  function tryBindingFromSessionDirectory(sessionDir) {
34422
34754
  let st;
34423
34755
  try {
34424
- st = fs22.statSync(sessionDir);
34756
+ st = fs23.statSync(sessionDir);
34425
34757
  } catch {
34426
34758
  return null;
34427
34759
  }
34428
34760
  if (!st.isDirectory()) return null;
34429
34761
  const worktreePaths = collectGitRepoRootsUnderDirectory(sessionDir);
34430
34762
  if (worktreePaths.length === 0) return null;
34431
- const abs = path23.resolve(sessionDir);
34763
+ const abs = path25.resolve(sessionDir);
34432
34764
  return {
34433
34765
  sessionParentPath: abs,
34434
34766
  workingTreeRelRoot: abs,
@@ -34438,20 +34770,20 @@ function tryBindingFromSessionDirectory(sessionDir) {
34438
34770
  function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
34439
34771
  const sid = sessionId.trim();
34440
34772
  if (!sid) return null;
34441
- const hintR = path23.resolve(checkoutPath);
34773
+ const hintR = path25.resolve(checkoutPath);
34442
34774
  let best = null;
34443
- let cur = path23.dirname(hintR);
34775
+ let cur = path25.dirname(hintR);
34444
34776
  for (let i = 0; i < 40; i++) {
34445
34777
  const paths = collectWorktreeRootsNamed(cur, sid, 24);
34446
- if (paths.some((p) => path23.resolve(p) === hintR)) {
34447
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path23.resolve(paths[0]);
34778
+ if (paths.some((p) => path25.resolve(p) === hintR)) {
34779
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(paths) ?? path25.resolve(paths[0]);
34448
34780
  best = {
34449
- sessionParentPath: path23.resolve(isolated),
34450
- workingTreeRelRoot: path23.resolve(cur),
34451
- repoCheckoutPaths: paths.map((p) => path23.resolve(p))
34781
+ sessionParentPath: path25.resolve(isolated),
34782
+ workingTreeRelRoot: path25.resolve(cur),
34783
+ repoCheckoutPaths: paths.map((p) => path25.resolve(p))
34452
34784
  };
34453
34785
  }
34454
- const next = path23.dirname(cur);
34786
+ const next = path25.dirname(cur);
34455
34787
  if (next === cur) break;
34456
34788
  cur = next;
34457
34789
  }
@@ -34459,33 +34791,33 @@ function discoverLegacyBindingAscendingFromCheckout(sessionId, checkoutPath) {
34459
34791
  }
34460
34792
  function discoverSessionWorktreeOnDisk(options) {
34461
34793
  const { sessionId, worktreesRootPath, layout, bridgeRoot } = options;
34462
- if (!sessionId.trim() || !fs22.existsSync(worktreesRootPath)) return null;
34794
+ if (!sessionId.trim() || !fs23.existsSync(worktreesRootPath)) return null;
34463
34795
  const preferredKey = getLauncherDirNameIfPresent(layout, bridgeRoot);
34464
34796
  const keys = [];
34465
34797
  if (preferredKey) keys.push(preferredKey);
34466
34798
  try {
34467
- for (const name of fs22.readdirSync(worktreesRootPath)) {
34799
+ for (const name of fs23.readdirSync(worktreesRootPath)) {
34468
34800
  if (name.startsWith(".")) continue;
34469
- const p = path23.join(worktreesRootPath, name);
34470
- if (!fs22.statSync(p).isDirectory()) continue;
34801
+ const p = path25.join(worktreesRootPath, name);
34802
+ if (!fs23.statSync(p).isDirectory()) continue;
34471
34803
  if (name !== preferredKey) keys.push(name);
34472
34804
  }
34473
34805
  } catch {
34474
34806
  return null;
34475
34807
  }
34476
34808
  for (const key of keys) {
34477
- const layoutRoot = path23.join(worktreesRootPath, key);
34478
- if (!fs22.existsSync(layoutRoot) || !fs22.statSync(layoutRoot).isDirectory()) continue;
34479
- const sessionDir = path23.join(layoutRoot, sessionId);
34809
+ const layoutRoot = path25.join(worktreesRootPath, key);
34810
+ if (!fs23.existsSync(layoutRoot) || !fs23.statSync(layoutRoot).isDirectory()) continue;
34811
+ const sessionDir = path25.join(layoutRoot, sessionId);
34480
34812
  const nested = tryBindingFromSessionDirectory(sessionDir);
34481
34813
  if (nested) return nested;
34482
34814
  const legacyPaths = collectWorktreeRootsNamed(layoutRoot, sessionId, 24);
34483
34815
  if (legacyPaths.length > 0) {
34484
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path23.resolve(legacyPaths[0]);
34816
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path25.resolve(legacyPaths[0]);
34485
34817
  return {
34486
- sessionParentPath: path23.resolve(isolated),
34487
- workingTreeRelRoot: path23.resolve(layoutRoot),
34488
- repoCheckoutPaths: legacyPaths.map((p) => path23.resolve(p))
34818
+ sessionParentPath: path25.resolve(isolated),
34819
+ workingTreeRelRoot: path25.resolve(layoutRoot),
34820
+ repoCheckoutPaths: legacyPaths.map((p) => path25.resolve(p))
34489
34821
  };
34490
34822
  }
34491
34823
  }
@@ -34494,12 +34826,12 @@ function discoverSessionWorktreeOnDisk(options) {
34494
34826
  function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPathOrHint, sessionId) {
34495
34827
  const sid = sessionId.trim();
34496
34828
  if (!sid) return null;
34497
- const hint = path23.resolve(sessionWorktreeRootPathOrHint);
34498
- const underHint = tryBindingFromSessionDirectory(path23.join(hint, sid));
34829
+ const hint = path25.resolve(sessionWorktreeRootPathOrHint);
34830
+ const underHint = tryBindingFromSessionDirectory(path25.join(hint, sid));
34499
34831
  if (underHint) return underHint;
34500
34832
  const direct = tryBindingFromSessionDirectory(hint);
34501
34833
  if (direct) {
34502
- if (path23.basename(hint) === sid && isGitDir(hint)) {
34834
+ if (path25.basename(hint) === sid && isGitDir(hint)) {
34503
34835
  const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
34504
34836
  if (legacyFromCheckout && legacyFromCheckout.repoCheckoutPaths.length > direct.repoCheckoutPaths.length) {
34505
34837
  return legacyFromCheckout;
@@ -34507,24 +34839,24 @@ function discoverSessionWorktreesUnderSessionWorktreeRoot(sessionWorktreeRootPat
34507
34839
  }
34508
34840
  return direct;
34509
34841
  }
34510
- if (path23.basename(hint) === sid && isGitDir(hint)) {
34842
+ if (path25.basename(hint) === sid && isGitDir(hint)) {
34511
34843
  const legacyFromCheckout = discoverLegacyBindingAscendingFromCheckout(sid, hint);
34512
34844
  if (legacyFromCheckout) return legacyFromCheckout;
34513
34845
  }
34514
34846
  let st;
34515
34847
  try {
34516
- st = fs22.statSync(hint);
34848
+ st = fs23.statSync(hint);
34517
34849
  } catch {
34518
34850
  return null;
34519
34851
  }
34520
34852
  if (!st.isDirectory()) return null;
34521
34853
  const legacyPaths = collectWorktreeRootsNamed(hint, sid, 24);
34522
34854
  if (legacyPaths.length === 0) return null;
34523
- const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path23.resolve(legacyPaths[0]);
34855
+ const isolated = resolveIsolatedSessionParentPathFromCheckouts(legacyPaths) ?? path25.resolve(legacyPaths[0]);
34524
34856
  return {
34525
- sessionParentPath: path23.resolve(isolated),
34857
+ sessionParentPath: path25.resolve(isolated),
34526
34858
  workingTreeRelRoot: hint,
34527
- repoCheckoutPaths: legacyPaths.map((p) => path23.resolve(p))
34859
+ repoCheckoutPaths: legacyPaths.map((p) => path25.resolve(p))
34528
34860
  };
34529
34861
  }
34530
34862
 
@@ -34581,8 +34913,8 @@ function parseSessionParent(v) {
34581
34913
  }
34582
34914
 
34583
34915
  // src/worktrees/prepare-new-session-worktrees.ts
34584
- import * as fs23 from "node:fs";
34585
- import * as path24 from "node:path";
34916
+ import * as fs24 from "node:fs";
34917
+ import * as path26 from "node:path";
34586
34918
 
34587
34919
  // src/git/worktrees/worktree-add.ts
34588
34920
  async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch, baseRef = "HEAD") {
@@ -34592,7 +34924,7 @@ async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch, baseRef
34592
34924
  }
34593
34925
 
34594
34926
  // src/worktrees/prepare-new-session-worktrees.ts
34595
- function normalizeRepoRelPath(rel) {
34927
+ function normalizeRepoRelPath2(rel) {
34596
34928
  return rel === "" ? "." : rel.replace(/\\/g, "/");
34597
34929
  }
34598
34930
  function resolveBaseRefForRepo(relNorm, baseBranches) {
@@ -34604,10 +34936,10 @@ function resolveBaseRefForRepo(relNorm, baseBranches) {
34604
34936
  }
34605
34937
  async function prepareNewSessionWorktrees(options) {
34606
34938
  const { worktreesRootPath, bridgeRoot, sessionId, layout, log: log2, worktreeBaseBranches } = options;
34607
- const bridgeResolved = path24.resolve(bridgeRoot);
34939
+ const bridgeResolved = path26.resolve(bridgeRoot);
34608
34940
  const cwdKey = allocateDirNameForLauncherCwd(layout, bridgeResolved);
34609
- const bridgeKeyDir = path24.join(worktreesRootPath, cwdKey);
34610
- const sessionDir = path24.join(bridgeKeyDir, sessionId);
34941
+ const bridgeKeyDir = path26.join(worktreesRootPath, cwdKey);
34942
+ const sessionDir = path26.join(bridgeKeyDir, sessionId);
34611
34943
  const repos = await discoverGitReposUnderRoot(bridgeResolved);
34612
34944
  if (repos.length === 0) {
34613
34945
  log2("[worktrees] No Git repositories under bridge root; skipping worktree creation.");
@@ -34615,14 +34947,14 @@ async function prepareNewSessionWorktrees(options) {
34615
34947
  }
34616
34948
  const branch = `session-${sessionId}`;
34617
34949
  const worktreePaths = [];
34618
- fs23.mkdirSync(sessionDir, { recursive: true });
34950
+ fs24.mkdirSync(sessionDir, { recursive: true });
34619
34951
  for (const repo of repos) {
34620
- let rel = path24.relative(bridgeResolved, repo.absolutePath);
34621
- if (rel.startsWith("..") || path24.isAbsolute(rel)) continue;
34622
- const relNorm = normalizeRepoRelPath(rel === "" ? "." : rel);
34623
- const wtPath = relNorm === "." ? sessionDir : path24.join(sessionDir, relNorm);
34952
+ let rel = path26.relative(bridgeResolved, repo.absolutePath);
34953
+ if (rel.startsWith("..") || path26.isAbsolute(rel)) continue;
34954
+ const relNorm = normalizeRepoRelPath2(rel === "" ? "." : rel);
34955
+ const wtPath = relNorm === "." ? sessionDir : path26.join(sessionDir, relNorm);
34624
34956
  if (relNorm !== ".") {
34625
- fs23.mkdirSync(path24.dirname(wtPath), { recursive: true });
34957
+ fs24.mkdirSync(path26.dirname(wtPath), { recursive: true });
34626
34958
  }
34627
34959
  const baseRef = resolveBaseRefForRepo(relNorm, worktreeBaseBranches);
34628
34960
  try {
@@ -34675,9 +35007,9 @@ function resolveExistingSessionParentPath(sessionId, cache2, discover) {
34675
35007
  }
34676
35008
 
34677
35009
  // src/worktrees/manager/resolve-explicit-session-parent-path.ts
34678
- import * as path25 from "node:path";
35010
+ import * as path27 from "node:path";
34679
35011
  function resolveExplicitSessionParentPath(params) {
34680
- const resolved = path25.resolve(params.parentPathRaw);
35012
+ const resolved = path27.resolve(params.parentPathRaw);
34681
35013
  if (parseSessionParent(params.sessionParent) !== "worktrees_root") {
34682
35014
  return resolved;
34683
35015
  }
@@ -34693,7 +35025,7 @@ function resolveExplicitSessionParentPath(params) {
34693
35025
  for (let i = 0; i < 16; i++) {
34694
35026
  const tryRoot = discoverSessionWorktreesUnderSessionWorktreeRoot(cur, params.sessionId);
34695
35027
  if (tryRoot) return rememberAndReturn(tryRoot);
34696
- const next = path25.dirname(cur);
35028
+ const next = path27.dirname(cur);
34697
35029
  if (next === cur) break;
34698
35030
  cur = next;
34699
35031
  }
@@ -34745,16 +35077,16 @@ async function resolveSessionParentPathForPrompt(params) {
34745
35077
  }
34746
35078
 
34747
35079
  // src/worktrees/manager/session-worktree-cache.ts
34748
- import * as path26 from "node:path";
35080
+ import * as path28 from "node:path";
34749
35081
  var SessionWorktreeCache = class {
34750
35082
  sessionRepoCheckoutPaths = /* @__PURE__ */ new Map();
34751
35083
  sessionParentPathBySession = /* @__PURE__ */ new Map();
34752
35084
  sessionWorkingTreeRelRootBySession = /* @__PURE__ */ new Map();
34753
35085
  remember(sessionId, binding) {
34754
- const paths = binding.repoCheckoutPaths.map((p) => path26.resolve(p));
35086
+ const paths = binding.repoCheckoutPaths.map((p) => path28.resolve(p));
34755
35087
  this.sessionRepoCheckoutPaths.set(sessionId, paths);
34756
- this.sessionParentPathBySession.set(sessionId, path26.resolve(binding.sessionParentPath));
34757
- this.sessionWorkingTreeRelRootBySession.set(sessionId, path26.resolve(binding.workingTreeRelRoot));
35088
+ this.sessionParentPathBySession.set(sessionId, path28.resolve(binding.sessionParentPath));
35089
+ this.sessionWorkingTreeRelRootBySession.set(sessionId, path28.resolve(binding.workingTreeRelRoot));
34758
35090
  }
34759
35091
  clearSession(sessionId) {
34760
35092
  const paths = this.sessionRepoCheckoutPaths.get(sessionId);
@@ -34784,7 +35116,7 @@ var SessionWorktreeCache = class {
34784
35116
  const parent = this.sessionParentPathBySession.get(sessionId);
34785
35117
  const relRoot = this.sessionWorkingTreeRelRootBySession.get(sessionId);
34786
35118
  if (!parent || !relRoot) return false;
34787
- return path26.resolve(parent) !== path26.resolve(relRoot);
35119
+ return path28.resolve(parent) !== path28.resolve(relRoot);
34788
35120
  }
34789
35121
  };
34790
35122
 
@@ -34888,29 +35220,29 @@ var SessionWorktreeManager = class {
34888
35220
  };
34889
35221
 
34890
35222
  // src/worktrees/manager/default-worktrees-root-path.ts
34891
- import * as path27 from "node:path";
35223
+ import * as path29 from "node:path";
34892
35224
  import os8 from "node:os";
34893
35225
  function defaultWorktreesRootPath() {
34894
- return path27.join(os8.homedir(), ".buildautomaton", "worktrees");
35226
+ return path29.join(os8.homedir(), ".buildautomaton", "worktrees");
34895
35227
  }
34896
35228
 
34897
35229
  // src/files/watch-file-index.ts
34898
35230
  import { watch } from "node:fs";
34899
- import path32 from "node:path";
35231
+ import path34 from "node:path";
34900
35232
 
34901
35233
  // src/files/index/paths.ts
34902
- import path28 from "node:path";
35234
+ import path30 from "node:path";
34903
35235
  import crypto2 from "node:crypto";
34904
35236
  function getCwdHashForFileIndex(resolvedCwd) {
34905
- return crypto2.createHash("sha256").update(path28.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
35237
+ return crypto2.createHash("sha256").update(path30.resolve(resolvedCwd)).digest("hex").slice(0, INDEX_HASH_LEN);
34906
35238
  }
34907
35239
 
34908
35240
  // src/files/index/build-file-index.ts
34909
- import path30 from "node:path";
35241
+ import path32 from "node:path";
34910
35242
 
34911
35243
  // src/files/index/walk-workspace-tree.ts
34912
- import fs24 from "node:fs";
34913
- import path29 from "node:path";
35244
+ import fs25 from "node:fs";
35245
+ import path31 from "node:path";
34914
35246
  var DEPENDENCY_INSTALL_DIR_NAMES = /* @__PURE__ */ new Set([
34915
35247
  "node_modules",
34916
35248
  "bower_components",
@@ -34927,7 +35259,7 @@ function shouldSkipWorkspaceWalkEntry(name) {
34927
35259
  async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
34928
35260
  let names;
34929
35261
  try {
34930
- names = await fs24.promises.readdir(dir);
35262
+ names = await fs25.promises.readdir(dir);
34931
35263
  } catch {
34932
35264
  return;
34933
35265
  }
@@ -34939,14 +35271,14 @@ async function walkWorkspaceTreeAsync(dir, baseDir, onFile, state) {
34939
35271
  if (isCliImmediateShutdownRequested()) throw new CliSqliteInterrupted();
34940
35272
  }
34941
35273
  state.n++;
34942
- const full = path29.join(dir, name);
35274
+ const full = path31.join(dir, name);
34943
35275
  let stat2;
34944
35276
  try {
34945
- stat2 = await fs24.promises.stat(full);
35277
+ stat2 = await fs25.promises.stat(full);
34946
35278
  } catch {
34947
35279
  continue;
34948
35280
  }
34949
- const relative6 = path29.relative(baseDir, full).replace(/\\/g, "/");
35281
+ const relative6 = path31.relative(baseDir, full).replace(/\\/g, "/");
34950
35282
  if (stat2.isDirectory()) {
34951
35283
  await walkWorkspaceTreeAsync(full, baseDir, onFile, state);
34952
35284
  } else if (stat2.isFile()) {
@@ -34959,7 +35291,7 @@ function createWalkYieldState() {
34959
35291
  }
34960
35292
 
34961
35293
  // src/files/index/file-index-sqlite-lock.ts
34962
- import fs25 from "node:fs";
35294
+ import fs26 from "node:fs";
34963
35295
  function isSqliteCorruptError(e) {
34964
35296
  const msg = e instanceof Error ? e.message : String(e);
34965
35297
  return msg.includes("malformed") || msg.includes("database disk image is malformed") || msg.includes("corrupt");
@@ -34973,7 +35305,7 @@ function withFileIndexSqliteLock(fn) {
34973
35305
  if (!isSqliteCorruptError(e)) throw e;
34974
35306
  closeAllCliSqliteConnections();
34975
35307
  try {
34976
- fs25.unlinkSync(getCliSqlitePath());
35308
+ fs26.unlinkSync(getCliSqlitePath());
34977
35309
  } catch {
34978
35310
  }
34979
35311
  chain = Promise.resolve();
@@ -35048,7 +35380,7 @@ async function collectWorkspacePathsAsync(resolved) {
35048
35380
  }
35049
35381
  async function buildFileIndexAsync(cwd) {
35050
35382
  return withFileIndexSqliteLock(async () => {
35051
- const resolved = path30.resolve(cwd);
35383
+ const resolved = path32.resolve(cwd);
35052
35384
  await yieldToEventLoop();
35053
35385
  assertNotShutdown();
35054
35386
  const paths = await collectWorkspacePathsAsync(resolved);
@@ -35060,7 +35392,7 @@ async function buildFileIndexAsync(cwd) {
35060
35392
  }
35061
35393
 
35062
35394
  // src/files/index/ensure-file-index.ts
35063
- import path31 from "node:path";
35395
+ import path33 from "node:path";
35064
35396
 
35065
35397
  // src/files/index/search-file-index.ts
35066
35398
  function escapeLikePattern(fragment) {
@@ -35112,7 +35444,7 @@ async function searchBridgeFilePathsAsync(resolvedCwd, query, limit = 100) {
35112
35444
 
35113
35445
  // src/files/index/ensure-file-index.ts
35114
35446
  async function ensureFileIndexAsync(cwd) {
35115
- const resolved = path31.resolve(cwd);
35447
+ const resolved = path33.resolve(cwd);
35116
35448
  if (await bridgeFileIndexIsPopulated(resolved)) {
35117
35449
  return { fromCache: true, pathCount: await bridgeFileIndexPathCount(resolved) };
35118
35450
  }
@@ -35156,7 +35488,7 @@ function createFsWatcher(resolved, schedule) {
35156
35488
  }
35157
35489
  }
35158
35490
  function startFileIndexWatcher(cwd = getBridgeRoot()) {
35159
- const resolved = path32.resolve(cwd);
35491
+ const resolved = path34.resolve(cwd);
35160
35492
  void buildFileIndexAsync(resolved).catch((e) => {
35161
35493
  if (e instanceof CliSqliteInterrupted) return;
35162
35494
  console.error("[file-index] Initial index build failed:", e);
@@ -35186,7 +35518,7 @@ function startFileIndexWatcher(cwd = getBridgeRoot()) {
35186
35518
  }
35187
35519
 
35188
35520
  // src/connection/create-bridge-connection.ts
35189
- import * as path43 from "node:path";
35521
+ import * as path45 from "node:path";
35190
35522
 
35191
35523
  // src/dev-servers/manager/dev-server-manager.ts
35192
35524
  import { rm as rm2 } from "node:fs/promises";
@@ -35230,7 +35562,7 @@ function forceKillChild(proc, log2, shortId, graceMs) {
35230
35562
  }
35231
35563
 
35232
35564
  // src/dev-servers/process/wire-dev-server-child-process.ts
35233
- import fs26 from "node:fs";
35565
+ import fs27 from "node:fs";
35234
35566
 
35235
35567
  // src/dev-servers/manager/forward-pipe.ts
35236
35568
  function forwardChildPipe(childReadable, terminal, onData) {
@@ -35266,7 +35598,7 @@ function wireDevServerChildProcess(d) {
35266
35598
  d.setPollInterval(void 0);
35267
35599
  return;
35268
35600
  }
35269
- fs26.readFile(d.mergedLogPath, (err, buf) => {
35601
+ fs27.readFile(d.mergedLogPath, (err, buf) => {
35270
35602
  if (err || (d.getSpawnGeneration() ?? 0) !== d.scheduledGen) return;
35271
35603
  if (buf.length <= d.mergedReadPos.value) return;
35272
35604
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
@@ -35304,7 +35636,7 @@ ${errTail}` : ""}`);
35304
35636
  d.sendStatus(code === 0 || code == null ? "stopped" : "error", detail, tails);
35305
35637
  };
35306
35638
  if (mergedPath) {
35307
- fs26.readFile(mergedPath, (err, buf) => {
35639
+ fs27.readFile(mergedPath, (err, buf) => {
35308
35640
  if (!err && buf.length > d.mergedReadPos.value) {
35309
35641
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
35310
35642
  if (chunk.length > 0) {
@@ -35406,13 +35738,13 @@ function parseDevServerDefs(servers) {
35406
35738
  }
35407
35739
 
35408
35740
  // src/dev-servers/manager/shell-spawn/utils.ts
35409
- import fs27 from "node:fs";
35741
+ import fs28 from "node:fs";
35410
35742
  function isSpawnEbadf(e) {
35411
35743
  return typeof e === "object" && e !== null && "code" in e && e.code === "EBADF";
35412
35744
  }
35413
35745
  function rmDirQuiet(dir) {
35414
35746
  try {
35415
- fs27.rmSync(dir, { recursive: true, force: true });
35747
+ fs28.rmSync(dir, { recursive: true, force: true });
35416
35748
  } catch {
35417
35749
  }
35418
35750
  }
@@ -35420,7 +35752,7 @@ var cachedDevNullReadFd;
35420
35752
  function devNullReadFd() {
35421
35753
  if (cachedDevNullReadFd === void 0) {
35422
35754
  const devPath = process.platform === "win32" ? "nul" : "/dev/null";
35423
- cachedDevNullReadFd = fs27.openSync(devPath, "r");
35755
+ cachedDevNullReadFd = fs28.openSync(devPath, "r");
35424
35756
  }
35425
35757
  return cachedDevNullReadFd;
35426
35758
  }
@@ -35494,15 +35826,15 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
35494
35826
 
35495
35827
  // src/dev-servers/manager/shell-spawn/try-spawn-merged-log-file.ts
35496
35828
  import { spawn as spawn8 } from "node:child_process";
35497
- import fs28 from "node:fs";
35829
+ import fs29 from "node:fs";
35498
35830
  import { tmpdir } from "node:os";
35499
- import path33 from "node:path";
35831
+ import path35 from "node:path";
35500
35832
  function trySpawnMergedLogFile(command, env, cwd, signal) {
35501
- const tmpRoot = fs28.mkdtempSync(path33.join(tmpdir(), "ba-devsrv-log-"));
35502
- const logPath = path33.join(tmpRoot, "combined.log");
35833
+ const tmpRoot = fs29.mkdtempSync(path35.join(tmpdir(), "ba-devsrv-log-"));
35834
+ const logPath = path35.join(tmpRoot, "combined.log");
35503
35835
  let logFd;
35504
35836
  try {
35505
- logFd = fs28.openSync(logPath, "a");
35837
+ logFd = fs29.openSync(logPath, "a");
35506
35838
  } catch {
35507
35839
  rmDirQuiet(tmpRoot);
35508
35840
  return null;
@@ -35521,7 +35853,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
35521
35853
  } else {
35522
35854
  proc = spawn8("/bin/sh", ["-c", command], { env, cwd, stdio, ...signal ? { signal } : {} });
35523
35855
  }
35524
- fs28.closeSync(logFd);
35856
+ fs29.closeSync(logFd);
35525
35857
  return {
35526
35858
  proc,
35527
35859
  pipedStdoutStderr: true,
@@ -35530,7 +35862,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
35530
35862
  };
35531
35863
  } catch (e) {
35532
35864
  try {
35533
- fs28.closeSync(logFd);
35865
+ fs29.closeSync(logFd);
35534
35866
  } catch {
35535
35867
  }
35536
35868
  rmDirQuiet(tmpRoot);
@@ -35541,22 +35873,22 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
35541
35873
 
35542
35874
  // src/dev-servers/manager/shell-spawn/try-spawn-shell-script-log-redirect.ts
35543
35875
  import { spawn as spawn9 } from "node:child_process";
35544
- import fs29 from "node:fs";
35876
+ import fs30 from "node:fs";
35545
35877
  import { tmpdir as tmpdir2 } from "node:os";
35546
- import path34 from "node:path";
35878
+ import path36 from "node:path";
35547
35879
  function shSingleQuote(s) {
35548
35880
  return `'${s.replace(/'/g, `'\\''`)}'`;
35549
35881
  }
35550
35882
  function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
35551
- const tmpRoot = fs29.mkdtempSync(path34.join(tmpdir2(), "ba-devsrv-sh-"));
35552
- const logPath = path34.join(tmpRoot, "combined.log");
35553
- const innerPath = path34.join(tmpRoot, "_cmd.sh");
35554
- const runnerPath = path34.join(tmpRoot, "_run.sh");
35883
+ const tmpRoot = fs30.mkdtempSync(path36.join(tmpdir2(), "ba-devsrv-sh-"));
35884
+ const logPath = path36.join(tmpRoot, "combined.log");
35885
+ const innerPath = path36.join(tmpRoot, "_cmd.sh");
35886
+ const runnerPath = path36.join(tmpRoot, "_run.sh");
35555
35887
  try {
35556
- fs29.writeFileSync(innerPath, `#!/bin/sh
35888
+ fs30.writeFileSync(innerPath, `#!/bin/sh
35557
35889
  ${command}
35558
35890
  `);
35559
- fs29.writeFileSync(
35891
+ fs30.writeFileSync(
35560
35892
  runnerPath,
35561
35893
  `#!/bin/sh
35562
35894
  cd ${shSingleQuote(cwd)}
@@ -35582,13 +35914,13 @@ cd ${shSingleQuote(cwd)}
35582
35914
  }
35583
35915
  }
35584
35916
  function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
35585
- const tmpRoot = fs29.mkdtempSync(path34.join(tmpdir2(), "ba-devsrv-sh-"));
35586
- const logPath = path34.join(tmpRoot, "combined.log");
35587
- const runnerPath = path34.join(tmpRoot, "_run.bat");
35917
+ const tmpRoot = fs30.mkdtempSync(path36.join(tmpdir2(), "ba-devsrv-sh-"));
35918
+ const logPath = path36.join(tmpRoot, "combined.log");
35919
+ const runnerPath = path36.join(tmpRoot, "_run.bat");
35588
35920
  const q = (p) => `"${p.replace(/"/g, '""')}"`;
35589
35921
  const com = process.env.ComSpec || "cmd.exe";
35590
35922
  try {
35591
- fs29.writeFileSync(
35923
+ fs30.writeFileSync(
35592
35924
  runnerPath,
35593
35925
  `@ECHO OFF\r
35594
35926
  CD /D ${q(cwd)}\r
@@ -36357,30 +36689,30 @@ function createOnBridgeIdentified(opts) {
36357
36689
  }
36358
36690
 
36359
36691
  // src/skills/discover-local-agent-skills.ts
36360
- import fs30 from "node:fs";
36361
- import path35 from "node:path";
36692
+ import fs31 from "node:fs";
36693
+ import path37 from "node:path";
36362
36694
  var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
36363
36695
  function discoverLocalSkills(cwd) {
36364
36696
  const out = [];
36365
36697
  const seenKeys = /* @__PURE__ */ new Set();
36366
36698
  for (const rel of SKILL_DISCOVERY_ROOTS) {
36367
- const base = path35.join(cwd, rel);
36368
- if (!fs30.existsSync(base) || !fs30.statSync(base).isDirectory()) continue;
36699
+ const base = path37.join(cwd, rel);
36700
+ if (!fs31.existsSync(base) || !fs31.statSync(base).isDirectory()) continue;
36369
36701
  let entries = [];
36370
36702
  try {
36371
- entries = fs30.readdirSync(base);
36703
+ entries = fs31.readdirSync(base);
36372
36704
  } catch {
36373
36705
  continue;
36374
36706
  }
36375
36707
  for (const name of entries) {
36376
- const dir = path35.join(base, name);
36708
+ const dir = path37.join(base, name);
36377
36709
  try {
36378
- if (!fs30.statSync(dir).isDirectory()) continue;
36710
+ if (!fs31.statSync(dir).isDirectory()) continue;
36379
36711
  } catch {
36380
36712
  continue;
36381
36713
  }
36382
- const skillMd = path35.join(dir, "SKILL.md");
36383
- if (!fs30.existsSync(skillMd)) continue;
36714
+ const skillMd = path37.join(dir, "SKILL.md");
36715
+ if (!fs31.existsSync(skillMd)) continue;
36384
36716
  const key = `${rel}/${name}`;
36385
36717
  if (seenKeys.has(key)) continue;
36386
36718
  seenKeys.add(key);
@@ -36392,23 +36724,23 @@ function discoverLocalSkills(cwd) {
36392
36724
  function discoverSkillLayoutRoots(cwd) {
36393
36725
  const roots = [];
36394
36726
  for (const rel of SKILL_DISCOVERY_ROOTS) {
36395
- const base = path35.join(cwd, rel);
36396
- if (!fs30.existsSync(base) || !fs30.statSync(base).isDirectory()) continue;
36727
+ const base = path37.join(cwd, rel);
36728
+ if (!fs31.existsSync(base) || !fs31.statSync(base).isDirectory()) continue;
36397
36729
  let entries = [];
36398
36730
  try {
36399
- entries = fs30.readdirSync(base);
36731
+ entries = fs31.readdirSync(base);
36400
36732
  } catch {
36401
36733
  continue;
36402
36734
  }
36403
36735
  const skills2 = [];
36404
36736
  for (const name of entries) {
36405
- const dir = path35.join(base, name);
36737
+ const dir = path37.join(base, name);
36406
36738
  try {
36407
- if (!fs30.statSync(dir).isDirectory()) continue;
36739
+ if (!fs31.statSync(dir).isDirectory()) continue;
36408
36740
  } catch {
36409
36741
  continue;
36410
36742
  }
36411
- if (!fs30.existsSync(path35.join(dir, "SKILL.md"))) continue;
36743
+ if (!fs31.existsSync(path37.join(dir, "SKILL.md"))) continue;
36412
36744
  const relPath = `${rel}/${name}`.replace(/\\/g, "/");
36413
36745
  skills2.push({ name, relPath });
36414
36746
  }
@@ -36764,7 +37096,7 @@ function syncRunningTurnQueueKeys(turns, queueKey) {
36764
37096
  }
36765
37097
 
36766
37098
  // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
36767
- import fs31 from "node:fs";
37099
+ import fs32 from "node:fs";
36768
37100
  async function runLocalRevertBeforeQueuedPrompt(next, deps) {
36769
37101
  if (next.serverState !== "requeued_with_revert") return true;
36770
37102
  const sid = next.sessionId;
@@ -36773,7 +37105,7 @@ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
36773
37105
  const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
36774
37106
  const file2 = snapshotFilePath(agentBase, tid);
36775
37107
  try {
36776
- await fs31.promises.access(file2, fs31.constants.F_OK);
37108
+ await fs32.promises.access(file2, fs32.constants.F_OK);
36777
37109
  } catch {
36778
37110
  deps.log(
36779
37111
  `[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
@@ -37061,9 +37393,9 @@ function parseChangeSummarySnapshots(raw) {
37061
37393
  for (const item of raw) {
37062
37394
  if (!item || typeof item !== "object") continue;
37063
37395
  const o = item;
37064
- const path44 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
37065
- if (!path44) continue;
37066
- const row = { path: path44 };
37396
+ const path46 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
37397
+ if (!path46) continue;
37398
+ const row = { path: path46 };
37067
37399
  if (typeof o.patchContent === "string") row.patchContent = o.patchContent;
37068
37400
  if (typeof o.oldText === "string") row.oldText = o.oldText;
37069
37401
  if (typeof o.newText === "string") row.newText = o.newText;
@@ -37277,14 +37609,14 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
37277
37609
  };
37278
37610
 
37279
37611
  // src/files/list-dir/index.ts
37280
- import fs33 from "node:fs";
37612
+ import fs34 from "node:fs";
37281
37613
 
37282
37614
  // src/files/ensure-under-cwd.ts
37283
- import path36 from "node:path";
37615
+ import path38 from "node:path";
37284
37616
  function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
37285
- const normalized = path36.normalize(relativePath).replace(/^(\.\/)+/, "");
37286
- const resolved = path36.resolve(cwd, normalized);
37287
- if (!resolved.startsWith(cwd + path36.sep) && resolved !== cwd) {
37617
+ const normalized = path38.normalize(relativePath).replace(/^(\.\/)+/, "");
37618
+ const resolved = path38.resolve(cwd, normalized);
37619
+ if (!resolved.startsWith(cwd + path38.sep) && resolved !== cwd) {
37288
37620
  return null;
37289
37621
  }
37290
37622
  return resolved;
@@ -37294,15 +37626,15 @@ function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
37294
37626
  var LIST_DIR_YIELD_EVERY = 256;
37295
37627
 
37296
37628
  // src/files/list-dir/map-dir-entry.ts
37297
- import path37 from "node:path";
37298
- import fs32 from "node:fs";
37629
+ import path39 from "node:path";
37630
+ import fs33 from "node:fs";
37299
37631
  async function mapDirEntry(d, relativePath, resolved) {
37300
- const entryPath = path37.join(relativePath || ".", d.name).replace(/\\/g, "/");
37301
- const fullPath = path37.join(resolved, d.name);
37632
+ const entryPath = path39.join(relativePath || ".", d.name).replace(/\\/g, "/");
37633
+ const fullPath = path39.join(resolved, d.name);
37302
37634
  let isDir = d.isDirectory();
37303
37635
  if (d.isSymbolicLink()) {
37304
37636
  try {
37305
- const targetStat = await fs32.promises.stat(fullPath);
37637
+ const targetStat = await fs33.promises.stat(fullPath);
37306
37638
  isDir = targetStat.isDirectory();
37307
37639
  } catch {
37308
37640
  isDir = false;
@@ -37332,7 +37664,7 @@ async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
37332
37664
  return { error: "Path is outside working directory" };
37333
37665
  }
37334
37666
  try {
37335
- const names = await fs33.promises.readdir(resolved, { withFileTypes: true });
37667
+ const names = await fs34.promises.readdir(resolved, { withFileTypes: true });
37336
37668
  const entries = [];
37337
37669
  for (let i = 0; i < names.length; i++) {
37338
37670
  if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
@@ -37352,18 +37684,18 @@ var LINE_CHUNK_SIZE = 64 * 1024;
37352
37684
  var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
37353
37685
 
37354
37686
  // src/files/read-file/resolve-file-path.ts
37355
- import fs34 from "node:fs";
37687
+ import fs35 from "node:fs";
37356
37688
  async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
37357
37689
  const resolved = ensureUnderCwd(relativePath, sessionParentPath);
37358
37690
  if (!resolved) return { error: "Path is outside working directory" };
37359
37691
  let real;
37360
37692
  try {
37361
- real = await fs34.promises.realpath(resolved);
37693
+ real = await fs35.promises.realpath(resolved);
37362
37694
  } catch {
37363
37695
  real = resolved;
37364
37696
  }
37365
37697
  try {
37366
- const stat2 = await fs34.promises.stat(real);
37698
+ const stat2 = await fs35.promises.stat(real);
37367
37699
  if (!stat2.isFile()) return { error: "Not a file" };
37368
37700
  return real;
37369
37701
  } catch (err) {
@@ -37372,11 +37704,11 @@ async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeR
37372
37704
  }
37373
37705
 
37374
37706
  // src/files/read-file/read-file-range-async.ts
37375
- import fs35 from "node:fs";
37707
+ import fs36 from "node:fs";
37376
37708
  import { StringDecoder } from "node:string_decoder";
37377
37709
  async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
37378
- const fileSize = (await fs35.promises.stat(filePath)).size;
37379
- const fd = await fs35.promises.open(filePath, "r");
37710
+ const fileSize = (await fs36.promises.stat(filePath)).size;
37711
+ const fd = await fs36.promises.open(filePath, "r");
37380
37712
  const bufSize = 64 * 1024;
37381
37713
  const buf = Buffer.alloc(bufSize);
37382
37714
  const decoder = new StringDecoder("utf8");
@@ -37538,11 +37870,11 @@ async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, li
37538
37870
  }
37539
37871
 
37540
37872
  // src/files/read-file/read-file-buffer-full-async.ts
37541
- import fs36 from "node:fs";
37873
+ import fs37 from "node:fs";
37542
37874
  var READ_CHUNK_BYTES = 256 * 1024;
37543
37875
  async function readFileBufferFullAsync(filePath) {
37544
- const stat2 = await fs36.promises.stat(filePath);
37545
- const fd = await fs36.promises.open(filePath, "r");
37876
+ const stat2 = await fs37.promises.stat(filePath);
37877
+ const fd = await fs37.promises.open(filePath, "r");
37546
37878
  const chunks = [];
37547
37879
  let position = 0;
37548
37880
  let bytesSinceYield = 0;
@@ -37649,13 +37981,13 @@ function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
37649
37981
  }
37650
37982
 
37651
37983
  // src/files/handle-file-browser-search.ts
37652
- import path38 from "node:path";
37984
+ import path40 from "node:path";
37653
37985
  var SEARCH_LIMIT = 100;
37654
37986
  function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
37655
37987
  void (async () => {
37656
37988
  await yieldToEventLoop();
37657
37989
  const q = typeof msg.q === "string" ? msg.q : "";
37658
- const sessionParentPath = path38.resolve(
37990
+ const sessionParentPath = path40.resolve(
37659
37991
  sessionWorktreeManager != null ? resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
37660
37992
  );
37661
37993
  if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
@@ -37774,8 +38106,8 @@ function handleSkillLayoutRequest(msg, deps) {
37774
38106
  }
37775
38107
 
37776
38108
  // src/skills/install-remote-skills.ts
37777
- import fs37 from "node:fs";
37778
- import path39 from "node:path";
38109
+ import fs38 from "node:fs";
38110
+ import path41 from "node:path";
37779
38111
  function installRemoteSkills(cwd, targetDir, items) {
37780
38112
  const installed2 = [];
37781
38113
  if (!Array.isArray(items)) {
@@ -37786,15 +38118,15 @@ function installRemoteSkills(cwd, targetDir, items) {
37786
38118
  if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
37787
38119
  continue;
37788
38120
  }
37789
- const skillDir = path39.join(cwd, targetDir, item.skillName);
38121
+ const skillDir = path41.join(cwd, targetDir, item.skillName);
37790
38122
  for (const f of item.files) {
37791
38123
  if (typeof f.path !== "string" || !f.text && !f.base64) continue;
37792
- const dest = path39.join(skillDir, f.path);
37793
- fs37.mkdirSync(path39.dirname(dest), { recursive: true });
38124
+ const dest = path41.join(skillDir, f.path);
38125
+ fs38.mkdirSync(path41.dirname(dest), { recursive: true });
37794
38126
  if (f.text !== void 0) {
37795
- fs37.writeFileSync(dest, f.text, "utf8");
38127
+ fs38.writeFileSync(dest, f.text, "utf8");
37796
38128
  } else if (f.base64) {
37797
- fs37.writeFileSync(dest, Buffer.from(f.base64, "base64"));
38129
+ fs38.writeFileSync(dest, Buffer.from(f.base64, "base64"));
37798
38130
  }
37799
38131
  }
37800
38132
  installed2.push({
@@ -37952,7 +38284,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
37952
38284
  };
37953
38285
 
37954
38286
  // src/routing/handlers/revert-turn-snapshot.ts
37955
- import * as fs38 from "node:fs";
38287
+ import * as fs39 from "node:fs";
37956
38288
  var handleRevertTurnSnapshotMessage = (msg, deps) => {
37957
38289
  const id = typeof msg.id === "string" ? msg.id : "";
37958
38290
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
@@ -37965,7 +38297,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
37965
38297
  const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
37966
38298
  const file2 = snapshotFilePath(agentBase, turnId);
37967
38299
  try {
37968
- await fs38.promises.access(file2, fs38.constants.F_OK);
38300
+ await fs39.promises.access(file2, fs39.constants.F_OK);
37969
38301
  } catch {
37970
38302
  sendWsMessage(s, {
37971
38303
  type: "revert_turn_snapshot_result",
@@ -38007,7 +38339,7 @@ var handleDevServersConfig = (msg, deps) => {
38007
38339
  };
38008
38340
 
38009
38341
  // src/git/bridge-git-context.ts
38010
- import * as path40 from "node:path";
38342
+ import * as path42 from "node:path";
38011
38343
 
38012
38344
  // src/git/branches/get-current-branch.ts
38013
38345
  async function getCurrentBranch(repoPath) {
@@ -38057,12 +38389,12 @@ async function listRepoBranchRefs(repoPath) {
38057
38389
  // src/git/bridge-git-context.ts
38058
38390
  function folderNameForRelPath(relPath, bridgeRoot) {
38059
38391
  if (relPath === "." || relPath === "") {
38060
- return path40.basename(path40.resolve(bridgeRoot)) || "repo";
38392
+ return path42.basename(path42.resolve(bridgeRoot)) || "repo";
38061
38393
  }
38062
- return path40.basename(relPath) || relPath;
38394
+ return path42.basename(relPath) || relPath;
38063
38395
  }
38064
38396
  async function discoverGitReposForBridgeContext(bridgeRoot) {
38065
- const root = path40.resolve(bridgeRoot);
38397
+ const root = path42.resolve(bridgeRoot);
38066
38398
  if (await isGitRepoDirectory(root)) {
38067
38399
  const remoteUrl = await getRemoteOriginUrl(root);
38068
38400
  return [{ absolutePath: root, remoteUrl }];
@@ -38070,17 +38402,17 @@ async function discoverGitReposForBridgeContext(bridgeRoot) {
38070
38402
  const [deep, shallow] = await Promise.all([discoverGitReposUnderRoot(root), discoverGitRepos(root)]);
38071
38403
  const byPath = /* @__PURE__ */ new Map();
38072
38404
  for (const repo of [...deep, ...shallow]) {
38073
- byPath.set(path40.resolve(repo.absolutePath), repo);
38405
+ byPath.set(path42.resolve(repo.absolutePath), repo);
38074
38406
  }
38075
38407
  return [...byPath.values()];
38076
38408
  }
38077
38409
  async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
38078
- const bridgeResolved = path40.resolve(bridgeRoot);
38410
+ const bridgeResolved = path42.resolve(bridgeRoot);
38079
38411
  const repos = await discoverGitReposForBridgeContext(bridgeResolved);
38080
38412
  const rows = [];
38081
38413
  for (const repo of repos) {
38082
- let rel = path40.relative(bridgeResolved, repo.absolutePath);
38083
- if (rel.startsWith("..") || path40.isAbsolute(rel)) continue;
38414
+ let rel = path42.relative(bridgeResolved, repo.absolutePath);
38415
+ if (rel.startsWith("..") || path42.isAbsolute(rel)) continue;
38084
38416
  const relPath = rel === "" ? "." : rel.replace(/\\/g, "/");
38085
38417
  const currentBranch = await getCurrentBranch(repo.absolutePath);
38086
38418
  const remoteUrl = repo.remoteUrl.trim() || null;
@@ -38095,11 +38427,11 @@ async function getBridgeGitContext(bridgeRoot = getBridgeRoot()) {
38095
38427
  return rows;
38096
38428
  }
38097
38429
  async function listRepoBranchesForBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
38098
- const bridgeResolved = path40.resolve(bridgeRoot);
38430
+ const bridgeResolved = path42.resolve(bridgeRoot);
38099
38431
  const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
38100
- const repoPath = rel === "" ? bridgeResolved : path40.join(bridgeResolved, rel);
38101
- const resolved = path40.resolve(repoPath);
38102
- if (!resolved.startsWith(bridgeResolved + path40.sep) && resolved !== bridgeResolved) {
38432
+ const repoPath = rel === "" ? bridgeResolved : path42.join(bridgeResolved, rel);
38433
+ const resolved = path42.resolve(repoPath);
38434
+ if (!resolved.startsWith(bridgeResolved + path42.sep) && resolved !== bridgeResolved) {
38103
38435
  return [];
38104
38436
  }
38105
38437
  return listRepoBranchRefs(resolved);
@@ -38683,10 +39015,10 @@ function listCliAgentCapabilityCacheForWorkspace(db, workspaceId) {
38683
39015
  }
38684
39016
 
38685
39017
  // src/agents/capabilities/warmup-agent-capabilities-on-connect.ts
38686
- import * as path42 from "node:path";
39018
+ import * as path44 from "node:path";
38687
39019
 
38688
39020
  // src/agents/capabilities/probe-one-agent-type-for-capabilities.ts
38689
- import * as path41 from "node:path";
39021
+ import * as path43 from "node:path";
38690
39022
  async function probeOneAgentTypeForCapabilities(params) {
38691
39023
  const { agentType, cwd, workspaceId, log: log2, reportAgentCapabilities, bridgeReport = true, shouldContinue } = params;
38692
39024
  const canContinue = () => shouldContinue?.() !== false;
@@ -38724,7 +39056,7 @@ async function probeOneAgentTypeForCapabilities(params) {
38724
39056
  if (!canContinue()) return false;
38725
39057
  handle = await resolved.createClient({
38726
39058
  command: resolved.command,
38727
- cwd: path41.resolve(cwd),
39059
+ cwd: path43.resolve(cwd),
38728
39060
  backendAgentType: agentType,
38729
39061
  sessionMode: "agent",
38730
39062
  persistedAcpSessionId: null,
@@ -38802,7 +39134,7 @@ async function warmupAgentCapabilitiesOnConnect(params) {
38802
39134
  const { workspaceId, log: log2, getWs } = params;
38803
39135
  const isCurrent = beginAgentCapabilityWarmupRun();
38804
39136
  if (!isCurrent()) return;
38805
- const cwd = path42.resolve(getBridgeRoot());
39137
+ const cwd = path44.resolve(getBridgeRoot());
38806
39138
  async function sendBatchFromCache() {
38807
39139
  if (!isCurrent()) return;
38808
39140
  const socket = getWs();
@@ -39207,8 +39539,8 @@ async function createBridgeConnection(options) {
39207
39539
  getCloudAccessToken: () => tokens.accessToken
39208
39540
  };
39209
39541
  const identifyReportedPaths = {
39210
- bridgeRootPath: path43.resolve(getBridgeRoot()),
39211
- worktreesRootPath: path43.resolve(worktreesRootPath)
39542
+ bridgeRootPath: path45.resolve(getBridgeRoot()),
39543
+ worktreesRootPath: path45.resolve(worktreesRootPath)
39212
39544
  };
39213
39545
  const { connect } = createMainBridgeWebSocketLifecycle({
39214
39546
  state,