@lzehrung/codegraph 2.2.2 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +2 -2
  2. package/codegraph-skill/codegraph/SKILL.md +16 -8
  3. package/dist/agent/followUps.d.ts +1 -0
  4. package/dist/agent/followUps.d.ts.map +1 -1
  5. package/dist/agent/followUps.js +11 -0
  6. package/dist/agent/followUps.js.map +1 -1
  7. package/dist/agent/orient.d.ts.map +1 -1
  8. package/dist/agent/orient.js +3 -3
  9. package/dist/agent/orient.js.map +1 -1
  10. package/dist/bin/cli.js +1764 -1247
  11. package/dist/cli/affected.js +2 -2
  12. package/dist/cli/affected.js.map +1 -1
  13. package/dist/cli/chunk.d.ts.map +1 -1
  14. package/dist/cli/chunk.js +16 -6
  15. package/dist/cli/chunk.js.map +1 -1
  16. package/dist/cli/commandCatalog.d.ts +1 -1
  17. package/dist/cli/commandCatalog.d.ts.map +1 -1
  18. package/dist/cli/commandCatalog.js +15 -3
  19. package/dist/cli/commandCatalog.js.map +1 -1
  20. package/dist/cli/compileCache.d.ts +2 -2
  21. package/dist/cli/compileCache.js +2 -2
  22. package/dist/cli/context.d.ts.map +1 -1
  23. package/dist/cli/context.js +1 -0
  24. package/dist/cli/context.js.map +1 -1
  25. package/dist/cli/doctor.d.ts.map +1 -1
  26. package/dist/cli/doctor.js +6 -3
  27. package/dist/cli/doctor.js.map +1 -1
  28. package/dist/cli/explain.d.ts.map +1 -1
  29. package/dist/cli/explain.js +3 -0
  30. package/dist/cli/explain.js.map +1 -1
  31. package/dist/cli/graph.d.ts.map +1 -1
  32. package/dist/cli/graph.js +1 -1
  33. package/dist/cli/graph.js.map +1 -1
  34. package/dist/cli/graphQueries.d.ts.map +1 -1
  35. package/dist/cli/graphQueries.js +22 -14
  36. package/dist/cli/graphQueries.js.map +1 -1
  37. package/dist/cli/grep.js +2 -2
  38. package/dist/cli/grep.js.map +1 -1
  39. package/dist/cli/help.d.ts +6 -5
  40. package/dist/cli/help.d.ts.map +1 -1
  41. package/dist/cli/help.js +21 -10
  42. package/dist/cli/help.js.map +1 -1
  43. package/dist/cli/lifecycle.js +1 -1
  44. package/dist/cli/lifecycle.js.map +1 -1
  45. package/dist/cli/navigation.d.ts.map +1 -1
  46. package/dist/cli/navigation.js +19 -6
  47. package/dist/cli/navigation.js.map +1 -1
  48. package/dist/cli/options.d.ts +8 -0
  49. package/dist/cli/options.d.ts.map +1 -1
  50. package/dist/cli/options.js +43 -11
  51. package/dist/cli/options.js.map +1 -1
  52. package/dist/cli/progress.d.ts +1 -0
  53. package/dist/cli/progress.d.ts.map +1 -1
  54. package/dist/cli/progress.js +103 -30
  55. package/dist/cli/progress.js.map +1 -1
  56. package/dist/cli/rootPolicy.d.ts.map +1 -1
  57. package/dist/cli/rootPolicy.js +2 -4
  58. package/dist/cli/rootPolicy.js.map +1 -1
  59. package/dist/cli/server.d.ts.map +1 -1
  60. package/dist/cli/server.js +3 -2
  61. package/dist/cli/server.js.map +1 -1
  62. package/dist/cli/sql.d.ts.map +1 -1
  63. package/dist/cli/sql.js +2 -1
  64. package/dist/cli/sql.js.map +1 -1
  65. package/dist/cli/typeHierarchy.d.ts.map +1 -1
  66. package/dist/cli/typeHierarchy.js +12 -3
  67. package/dist/cli/typeHierarchy.js.map +1 -1
  68. package/dist/cli.d.ts.map +1 -1
  69. package/dist/cli.js +5 -1
  70. package/dist/cli.js.map +1 -1
  71. package/dist/graphs.d.ts +0 -1
  72. package/dist/graphs.d.ts.map +1 -1
  73. package/dist/graphs.js +0 -1
  74. package/dist/graphs.js.map +1 -1
  75. package/dist/indexer/build-cache/location.d.ts +4 -2
  76. package/dist/indexer/build-cache/location.d.ts.map +1 -1
  77. package/dist/indexer/build-cache/location.js +63 -15
  78. package/dist/indexer/build-cache/location.js.map +1 -1
  79. package/dist/indexer/build-cache/project-snapshot.js +26 -25
  80. package/dist/indexer/build-cache/project-snapshot.js.map +1 -1
  81. package/dist/indexer.d.ts +3 -6
  82. package/dist/indexer.d.ts.map +1 -1
  83. package/dist/indexer.js +2 -9
  84. package/dist/indexer.js.map +1 -1
  85. package/dist/lifecycle/gitignore.d.ts +1 -1
  86. package/dist/lifecycle/gitignore.d.ts.map +1 -1
  87. package/dist/lifecycle/gitignore.js +7 -12
  88. package/dist/lifecycle/gitignore.js.map +1 -1
  89. package/dist/lifecycle/manifest.d.ts.map +1 -1
  90. package/dist/lifecycle/manifest.js +10 -4
  91. package/dist/lifecycle/manifest.js.map +1 -1
  92. package/dist/mcp/handlers.js +1 -1
  93. package/dist/mcp/handlers.js.map +1 -1
  94. package/dist/mcp/protocol.d.ts.map +1 -1
  95. package/dist/mcp/protocol.js +79 -1
  96. package/dist/mcp/protocol.js.map +1 -1
  97. package/dist/mcp/tools.js +1 -1
  98. package/dist/mcp/tools.js.map +1 -1
  99. package/dist/util/git.d.ts +66 -1
  100. package/dist/util/git.d.ts.map +1 -1
  101. package/dist/util/git.js +173 -1
  102. package/dist/util/git.js.map +1 -1
  103. package/dist/util/projectFiles.d.ts +7 -6
  104. package/dist/util/projectFiles.d.ts.map +1 -1
  105. package/dist/util/projectFiles.js +268 -59
  106. package/dist/util/projectFiles.js.map +1 -1
  107. package/docs/mcp.md +13 -11
  108. package/package.json +3 -3
package/dist/bin/cli.js CHANGED
@@ -432,6 +432,13 @@ function graphCommandSchema(positionals) {
432
432
  "--symbols-detailed-scope"
433
433
  ], positionals);
434
434
  }
435
+ function getCliCommandUsage(command) {
436
+ const usage = CLI_COMMAND_SCHEMAS.get(command)?.positionals.usage;
437
+ if (!usage) {
438
+ throw new Error(`No validation usage is defined for ${command}.`);
439
+ }
440
+ return usage;
441
+ }
435
442
  function allowedFlagsForSchema(schema) {
436
443
  return /* @__PURE__ */ new Set(["--help", "-h", "--version", "-v", "--debug", ...schema.flags ?? []]);
437
444
  }
@@ -692,7 +699,8 @@ var init_options = __esm({
692
699
  [
693
700
  "affected",
694
701
  commandSchema([...SHARED_BUILD_FLAGS, "--json", "--quiet", "--stdin"], [...SHARED_BUILD_OPTIONS, "--base", "--depth", "--filter", "--head"], {
695
- kind: "any"
702
+ kind: "any",
703
+ usage: "Usage: codegraph affected <file...> [--stdin] [--base <ref> --head <ref>] [--root <path>] [--depth <n>] [--filter <glob>] [--json | --quiet]"
696
704
  })
697
705
  ],
698
706
  [
@@ -716,7 +724,7 @@ var init_options = __esm({
716
724
  commandSchema([...JSON_OUTPUT_FLAGS, "--text"], ["--language", "--max-tokens", "--min-tokens"], {
717
725
  kind: "max",
718
726
  max: 1,
719
- usage: "Usage: codegraph chunk <file-path> [options]"
727
+ usage: "Usage: codegraph chunk <file-path> [--language <language>] [--min-tokens <n>] [--max-tokens <n>] [--text] [--json | --pretty]"
720
728
  })
721
729
  ],
722
730
  [
@@ -727,10 +735,10 @@ var init_options = __esm({
727
735
  ],
728
736
  [
729
737
  "deps",
730
- commandSchema([...SHARED_BUILD_FLAGS, ...JSON_OUTPUT_FLAGS], [...SHARED_BUILD_OPTIONS, "--depth"], {
738
+ commandSchema([...SHARED_BUILD_FLAGS, ...JSON_OUTPUT_FLAGS, "--all"], [...SHARED_BUILD_OPTIONS, "--depth"], {
731
739
  kind: "max",
732
740
  max: 1,
733
- usage: "Usage: codegraph deps <file> [--root <path>] [--depth <n>] [--json | --pretty]"
741
+ usage: "Usage: codegraph deps <file|file::symbol|symbol:...> [--root <path>] [--depth <n> | --all] [--json | --pretty]"
734
742
  })
735
743
  ],
736
744
  [
@@ -813,7 +821,7 @@ var init_options = __esm({
813
821
  commandSchema([...SHARED_BUILD_FLAGS, ...JSON_OUTPUT_FLAGS], SHARED_BUILD_OPTIONS, {
814
822
  kind: "max",
815
823
  max: 3,
816
- usage: "Usage: codegraph goto <file>[:line[:column]] [line] [column] [--root <path>] [--json | --pretty]"
824
+ usage: "Usage: codegraph goto <file|file::symbol|symbol:...> [--root <path>] [--json | --pretty]\n codegraph goto <file>[:line[:column]] [line] [column] [--root <path>] [--json | --pretty]"
817
825
  })
818
826
  ],
819
827
  ["graph", graphCommandSchema({ kind: "any" })],
@@ -832,7 +840,16 @@ var init_options = __esm({
832
840
  [
833
841
  "grep",
834
842
  commandSchema(["--ignore-case", "--json", "--pretty", "-i", "--no-gitignore"], ["--glob", "--ignore-glob", "--include-glob", "--max-hits", "--pattern", "--query", "--regex", "--root"], {
835
- kind: "any"
843
+ kind: "max",
844
+ max: 1,
845
+ // `handleGrepCommand` takes the pattern from --pattern, then --regex, then the
846
+ // positional, and --query is a separate syntax-tree mode. Present all three forms
847
+ // so a parse failure does not imply the positional regex is mandatory.
848
+ usage: [
849
+ "Usage: codegraph grep <regex> [--root <dir>] [--glob|--include-glob|--ignore-glob <glob>] [--ignore-case|-i] [--no-gitignore] [--max-hits <n>] [--json | --pretty]",
850
+ " codegraph grep --pattern|--regex <expr> [--root <dir>] [--glob|--include-glob|--ignore-glob <glob>] [--ignore-case|-i] [--no-gitignore] [--max-hits <n>] [--json | --pretty]",
851
+ " codegraph grep --query <tree-sitter-query> [--root <dir>] [--json | --pretty]"
852
+ ].join("\n")
836
853
  })
837
854
  ],
838
855
  [
@@ -872,7 +889,7 @@ var init_options = __esm({
872
889
  ], {
873
890
  kind: "max",
874
891
  max: 1,
875
- usage: "Usage: codegraph impact [project-root] [--provider git|github|raw] [options]"
892
+ usage: "Usage: codegraph impact [project-root] [--provider git|github|raw] [--depth <n>] [options] [--json | --pretty]"
876
893
  })
877
894
  ],
878
895
  [
@@ -947,7 +964,7 @@ var init_options = __esm({
947
964
  ], {
948
965
  kind: "max",
949
966
  max: 1,
950
- usage: "Usage: codegraph server <start|status|stop> [--root <path>]"
967
+ usage: "Usage: codegraph server <start|status|stop> [--root <path>] [--json | --pretty]"
951
968
  })
952
969
  ],
953
970
  [
@@ -974,10 +991,10 @@ var init_options = __esm({
974
991
  ],
975
992
  [
976
993
  "rdeps",
977
- commandSchema([...SHARED_BUILD_FLAGS, ...JSON_OUTPUT_FLAGS], [...SHARED_BUILD_OPTIONS, "--depth"], {
994
+ commandSchema([...SHARED_BUILD_FLAGS, ...JSON_OUTPUT_FLAGS, "--all"], [...SHARED_BUILD_OPTIONS, "--depth"], {
978
995
  kind: "max",
979
996
  max: 1,
980
- usage: "Usage: codegraph rdeps <file> [--root <path>] [--depth <n>] [--json | --pretty]"
997
+ usage: "Usage: codegraph rdeps <file|file::symbol|symbol:...> [--root <path>] [--depth <n> | --all] [--json | --pretty]"
981
998
  })
982
999
  ],
983
1000
  [
@@ -985,7 +1002,7 @@ var init_options = __esm({
985
1002
  commandSchema([...SHARED_BUILD_FLAGS, ...JSON_OUTPUT_FLAGS], [...SHARED_BUILD_OPTIONS, "--col", "--column", "--file", "--line"], {
986
1003
  kind: "max",
987
1004
  max: 3,
988
- usage: "Usage: codegraph refs <file>[:line[:column]] [line] [column] [--root <path>] [--json | --pretty]"
1005
+ usage: "Usage: codegraph refs <file|file::symbol|symbol:...> [--root <path>] [--json | --pretty]\n codegraph refs <file>[:line[:column]] [line] [column] [--root <path>] [--json | --pretty]\n codegraph refs --file <file> [--line <line> --col <column>] [--root <path>] [--json | --pretty]"
989
1006
  })
990
1007
  ],
991
1008
  [
@@ -1095,7 +1112,7 @@ var init_options = __esm({
1095
1112
  commandSchema(["--json", "--pretty"], ["--db", "--query", "--sqlite"], {
1096
1113
  kind: "max",
1097
1114
  max: 2,
1098
- usage: 'Usage: codegraph sql <sqlite-path> "SELECT ..." [--json | --pretty] OR codegraph sql --db <sqlite path> --query "SELECT ..." [--json | --pretty]'
1115
+ usage: 'Usage: codegraph sql <sqlite-path> "SELECT ..." [--json | --pretty] OR codegraph sql --db <sqlite-path> --query "SELECT ..." [--json | --pretty]'
1099
1116
  })
1100
1117
  ],
1101
1118
  [
@@ -1148,16 +1165,14 @@ function resolveCliProgressPresentation(input) {
1148
1165
  if (input.stderrIsTTY) {
1149
1166
  return input.terminalSupportsControlSequences ? "interactive" : "log";
1150
1167
  }
1151
- if (input.policy === "always")
1152
- return "log";
1153
- return "off";
1168
+ return "log";
1154
1169
  }
1155
1170
  function createCliProgressDisplay(options) {
1156
1171
  if (options.presentation === "log")
1157
- return createLogProgressDisplay(options.write);
1158
- return createInteractiveProgressDisplay(options.write);
1172
+ return createLogProgressDisplay(options.write, options.delayMs);
1173
+ return createInteractiveProgressDisplay(options.write, options.delayMs);
1159
1174
  }
1160
- function createInteractiveProgressDisplay(write) {
1175
+ function createInteractiveProgressDisplay(write, delayMs = 0) {
1161
1176
  let active = false;
1162
1177
  let rendered = false;
1163
1178
  let frameIndex = 0;
@@ -1165,6 +1180,7 @@ function createInteractiveProgressDisplay(write) {
1165
1180
  let total = 0;
1166
1181
  let mode = "build";
1167
1182
  let interval;
1183
+ let delay;
1168
1184
  const clear = () => {
1169
1185
  if (!rendered)
1170
1186
  return;
@@ -1177,39 +1193,55 @@ function createInteractiveProgressDisplay(write) {
1177
1193
  const action = progressAction(mode);
1178
1194
  const frame = SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length];
1179
1195
  frameIndex += 1;
1180
- const count = total > 0 ? ` ${current}/${total} files` : "";
1196
+ const count = formatProgressCount(current, total);
1181
1197
  write(`${CLEAR_LINE}${action} project index... ${frame}${count}`);
1182
1198
  rendered = true;
1183
1199
  };
1184
1200
  const stopInterval = () => {
1185
- if (interval === void 0)
1186
- return;
1187
- clearInterval(interval);
1188
- interval = void 0;
1201
+ if (interval !== void 0) {
1202
+ clearInterval(interval);
1203
+ interval = void 0;
1204
+ }
1205
+ if (delay !== void 0) {
1206
+ clearTimeout(delay);
1207
+ delay = void 0;
1208
+ }
1189
1209
  };
1190
1210
  const start = (update) => {
1191
1211
  stopInterval();
1192
1212
  clear();
1193
1213
  active = true;
1214
+ rendered = false;
1194
1215
  frameIndex = 0;
1195
1216
  current = update.current;
1196
1217
  total = update.total;
1197
1218
  mode = update.mode ?? "build";
1198
- render();
1199
- interval = setInterval(render, REFRESH_INTERVAL_MS);
1200
- interval.unref();
1219
+ const beginRendering = () => {
1220
+ if (!active)
1221
+ return;
1222
+ render();
1223
+ interval = setInterval(render, REFRESH_INTERVAL_MS);
1224
+ interval.unref();
1225
+ };
1226
+ if (delayMs) {
1227
+ delay = setTimeout(beginRendering, delayMs);
1228
+ delay.unref();
1229
+ } else {
1230
+ beginRendering();
1231
+ }
1201
1232
  };
1202
1233
  const complete = (update) => {
1203
1234
  if (!active)
1204
1235
  return;
1205
1236
  active = false;
1206
1237
  stopInterval();
1238
+ if (!rendered)
1239
+ return;
1207
1240
  clear();
1208
1241
  const verb = progressCompleteVerb(mode);
1209
- const fileCount = update.total;
1210
- const files = fileCount === 1 ? "file" : "files";
1242
+ const fileCount = formatFileCount(update.total, "words");
1211
1243
  const elapsed = update.elapsedMs === void 0 ? "" : ` in ${formatDuration(update.elapsedMs)}`;
1212
- write(`${verb} project index: ${fileCount} ${files}${elapsed}.
1244
+ write(`${verb} project index: ${fileCount}${elapsed}.
1213
1245
  `);
1214
1246
  };
1215
1247
  return {
@@ -1239,37 +1271,92 @@ function createInteractiveProgressDisplay(write) {
1239
1271
  }
1240
1272
  };
1241
1273
  }
1242
- function createLogProgressDisplay(write) {
1274
+ function formatFileCount(count, zeroFormat = "numeric") {
1275
+ if (!count && zeroFormat === "words")
1276
+ return "no files";
1277
+ return `${count} file${count === 1 ? "" : "s"}`;
1278
+ }
1279
+ function formatProgressCount(current, total) {
1280
+ if (total > 0)
1281
+ return ` ${current}/${formatFileCount(total)}`;
1282
+ return ` ${formatFileCount(current)} processed`;
1283
+ }
1284
+ function createLogProgressDisplay(write, delayMs = 0) {
1243
1285
  let active = false;
1286
+ let rendered = false;
1287
+ let current = 0;
1288
+ let total = 0;
1244
1289
  let mode = "build";
1290
+ let delay;
1291
+ let heartbeat;
1292
+ const stopTimers = () => {
1293
+ if (delay !== void 0) {
1294
+ clearTimeout(delay);
1295
+ delay = void 0;
1296
+ }
1297
+ if (heartbeat !== void 0) {
1298
+ clearInterval(heartbeat);
1299
+ heartbeat = void 0;
1300
+ }
1301
+ };
1302
+ const renderStart = () => {
1303
+ if (!active || rendered)
1304
+ return;
1305
+ rendered = true;
1306
+ write(`[Progress] ${progressAction(mode)} project index.
1307
+ `);
1308
+ heartbeat = setInterval(() => {
1309
+ if (!active)
1310
+ return;
1311
+ write(`[Progress] ${progressAction(mode)} project index:${formatProgressCount(current, total)}.
1312
+ `);
1313
+ }, 1e3);
1314
+ heartbeat.unref();
1315
+ };
1316
+ const start = (update) => {
1317
+ stopTimers();
1318
+ active = true;
1319
+ rendered = false;
1320
+ current = update.current;
1321
+ total = update.total;
1322
+ mode = update.mode ?? "build";
1323
+ if (delayMs) {
1324
+ delay = setTimeout(renderStart, delayMs);
1325
+ delay.unref();
1326
+ } else {
1327
+ renderStart();
1328
+ }
1329
+ };
1245
1330
  return {
1246
1331
  update: (update) => {
1247
1332
  if (update.phase === "start") {
1248
- active = true;
1249
- mode = update.mode ?? "build";
1250
- const action = progressAction(mode);
1251
- write(`[Progress] ${action} project index.
1252
- `);
1333
+ start(update);
1253
1334
  return;
1254
1335
  }
1255
1336
  if (update.phase === "complete") {
1256
1337
  if (!active)
1257
1338
  return;
1258
1339
  active = false;
1340
+ stopTimers();
1341
+ if (!rendered)
1342
+ return;
1259
1343
  const verb = progressCompleteVerb(mode);
1260
1344
  const elapsed = update.elapsedMs === void 0 ? "" : ` in ${formatDuration(update.elapsedMs)}`;
1261
- write(`[Progress] ${verb} project index: ${update.total} files${elapsed}.
1345
+ write(`[Progress] ${verb} project index: ${formatFileCount(update.total, "words")}${elapsed}.
1262
1346
  `);
1263
1347
  return;
1264
1348
  }
1265
1349
  if (!active) {
1266
1350
  if (update.phase === "update")
1267
1351
  return;
1268
- active = true;
1352
+ start(update);
1269
1353
  }
1270
- const isComplete = update.current >= update.total;
1271
- if (update.current === 1 || isComplete || update.current % 100 === 0) {
1272
- write(`[Progress] ${update.current}/${update.total} files processed.
1354
+ current = update.current;
1355
+ total = update.total;
1356
+ mode = update.mode ?? mode;
1357
+ const isComplete = update.total > 0 && update.current >= update.total;
1358
+ if (rendered && (update.current === 1 || isComplete || update.current % 100 === 0)) {
1359
+ write(`[Progress] ${formatFileCount(update.current)} processed.
1273
1360
  `);
1274
1361
  }
1275
1362
  },
@@ -1277,6 +1364,7 @@ function createLogProgressDisplay(write) {
1277
1364
  },
1278
1365
  dispose: () => {
1279
1366
  active = false;
1367
+ stopTimers();
1280
1368
  }
1281
1369
  };
1282
1370
  }
@@ -1295,9 +1383,10 @@ function progressCompleteVerb(mode) {
1295
1383
  return "Checked";
1296
1384
  }
1297
1385
  function formatDuration(elapsedMs2) {
1298
- if (elapsedMs2 < 1e3)
1299
- return `${Math.max(0, Math.round(elapsedMs2))}ms`;
1300
- return `${(elapsedMs2 / 1e3).toFixed(1)}s`;
1386
+ const durationMs = Math.max(0, elapsedMs2);
1387
+ if (durationMs < 1e3)
1388
+ return `${Math.round(durationMs)}ms`;
1389
+ return `${(durationMs / 1e3).toFixed(1)}s`;
1301
1390
  }
1302
1391
  var REFRESH_INTERVAL_MS, CLEAR_LINE, SPINNER_FRAMES;
1303
1392
  var init_progress = __esm({
@@ -1519,7 +1608,8 @@ function createCliProgressHandler(policy) {
1519
1608
  context.progressDisplay?.dispose();
1520
1609
  const display = createCliProgressDisplay({
1521
1610
  presentation,
1522
- write: context.runtime.stderr
1611
+ write: context.runtime.stderr,
1612
+ ...policy === "auto" ? { delayMs: 1e3 } : {}
1523
1613
  });
1524
1614
  context.progressDisplay = display;
1525
1615
  return display.update;
@@ -4671,9 +4761,9 @@ var require_path = __commonJS({
4671
4761
  "use strict";
4672
4762
  Object.defineProperty(exports, "__esModule", { value: true });
4673
4763
  exports.convertPosixPathToPattern = exports.convertWindowsPathToPattern = exports.convertPathToPattern = exports.escapePosixPath = exports.escapeWindowsPath = exports.escape = exports.removeLeadingDotSegment = exports.makeAbsolute = exports.unixify = void 0;
4674
- var os11 = __require("os");
4764
+ var os12 = __require("os");
4675
4765
  var path99 = __require("path");
4676
- var IS_WINDOWS_PLATFORM = os11.platform() === "win32";
4766
+ var IS_WINDOWS_PLATFORM = os12.platform() === "win32";
4677
4767
  var LEADING_DOT_SEGMENT_CHARACTERS_COUNT = 2;
4678
4768
  var POSIX_UNESCAPED_GLOB_SYMBOLS_RE = /(\\?)([()*?[\]{|}]|^!|[!+@](?=\()|\\(?![!()*+?@[\]{|}]))/g;
4679
4769
  var WINDOWS_UNESCAPED_GLOB_SYMBOLS_RE = /(\\?)([()[\]{}]|^!|[!+@](?=\())/g;
@@ -10191,8 +10281,8 @@ var require_settings4 = __commonJS({
10191
10281
  Object.defineProperty(exports, "__esModule", { value: true });
10192
10282
  exports.DEFAULT_FILE_SYSTEM_ADAPTER = void 0;
10193
10283
  var fs49 = __require("fs");
10194
- var os11 = __require("os");
10195
- var CPU_COUNT = Math.max(os11.cpus().length, 1);
10284
+ var os12 = __require("os");
10285
+ var CPU_COUNT = Math.max(os12.cpus().length, 1);
10196
10286
  exports.DEFAULT_FILE_SYSTEM_ADAPTER = {
10197
10287
  lstat: fs49.lstat,
10198
10288
  lstatSync: fs49.lstatSync,
@@ -13403,1029 +13493,1256 @@ var init_concurrency = __esm({
13403
13493
  }
13404
13494
  });
13405
13495
 
13406
- // dist/util/projectFiles.js
13407
- var projectFiles_exports = {};
13408
- __export(projectFiles_exports, {
13409
- DEFAULT_PROJECT_FILE_IGNORES: () => DEFAULT_PROJECT_FILE_IGNORES,
13410
- DEFAULT_PROJECT_MANIFESTS: () => DEFAULT_PROJECT_MANIFESTS,
13411
- DEFAULT_PROJECT_PATTERNS: () => DEFAULT_PROJECT_PATTERNS,
13412
- createDiscoveredFileMatcher: () => createDiscoveredFileMatcher,
13413
- discoverProjectFiles: () => discoverProjectFiles,
13414
- filterRealPathsWithinRoot: () => filterRealPathsWithinRoot,
13415
- isRelativePathInside: () => isRelativePathInside,
13416
- listProjectFiles: () => listProjectFiles,
13417
- matchesDiscoveryGlob: () => matchesDiscoveryGlob,
13418
- translateGlobRootIgnoreGlobsForScanRoot: () => translateGlobRootIgnoreGlobsForScanRoot
13496
+ // dist/util/git.js
13497
+ var git_exports = {};
13498
+ __export(git_exports, {
13499
+ DEFAULT_GIT_TIMEOUT_MS: () => DEFAULT_GIT_TIMEOUT_MS,
13500
+ assertSafeRevision: () => assertSafeRevision,
13501
+ clearGitDiscoveryCacheForTests: () => clearGitDiscoveryCacheForTests,
13502
+ clearGitRepositoryCheckCacheForTests: () => clearGitRepositoryCheckCacheForTests,
13503
+ decodeGitPath: () => decodeGitPath,
13504
+ getGitBlobHashes: () => getGitBlobHashes,
13505
+ getGitHead: () => getGitHead,
13506
+ getUnifiedDiff: () => getUnifiedDiff,
13507
+ gitDiffArgs: () => gitDiffArgs,
13508
+ isGitIndexSentinel: () => isGitIndexSentinel,
13509
+ isGitPathIgnored: () => isGitPathIgnored,
13510
+ isGitPathTracked: () => isGitPathTracked,
13511
+ isGitProjectRootIgnored: () => isGitProjectRootIgnored,
13512
+ isGitRepo: () => isGitRepo,
13513
+ isGitWorktreeSentinel: () => isGitWorktreeSentinel,
13514
+ listChangedFiles: () => listChangedFiles,
13515
+ listGitExcludeFiles: () => listGitExcludeFiles,
13516
+ listGitSubmoduleDirectories: () => listGitSubmoduleDirectories,
13517
+ listTrackedFiles: () => listTrackedFiles,
13518
+ listUntrackedFiles: () => listUntrackedFiles,
13519
+ runGit: () => runGit,
13520
+ setGitExecutableForTests: () => setGitExecutableForTests
13419
13521
  });
13420
- import fs6 from "node:fs";
13522
+ import { spawn } from "node:child_process";
13523
+ import { StringDecoder } from "node:string_decoder";
13421
13524
  import fsp4 from "node:fs/promises";
13525
+ import os2 from "node:os";
13422
13526
  import path8 from "node:path";
13423
- function isPresent(value) {
13424
- return value !== void 0;
13425
- }
13426
- function normalizeGlobPattern(globPattern) {
13427
- return globPattern.trim().replace(/\\/g, "/");
13428
- }
13429
- function isIgnoreGlobReopenedByIncludes(ignoreGlob, includeGlobs) {
13430
- const literalSegments = ignoreGlob.split("/").filter((segment) => segment && !segment.includes("*"));
13431
- if (!literalSegments.length)
13432
- return false;
13433
- return includeGlobs.some((includeGlob) => {
13434
- const includeSegments = includeGlob.split("/");
13435
- return literalSegments.every((segment) => includeSegments.includes(segment));
13436
- });
13437
- }
13438
- function isLocationIndependentGlob(globPattern) {
13439
- return globPattern.startsWith("**/");
13440
- }
13441
- function translateGlobRootIgnoreGlobsForScanRoot(scanRoot, globRoot, ignoreGlobs) {
13442
- const relativeScanRoot = normalizePath(path8.relative(globRoot, scanRoot));
13443
- if (!relativeScanRoot || !isRelativePathInside(relativeScanRoot)) {
13444
- return ignoreGlobs.map(normalizeGlobPattern).filter(Boolean);
13527
+ function decodeGitPath(rawPath) {
13528
+ if (!rawPath.startsWith('"') || !rawPath.endsWith('"')) {
13529
+ return rawPath;
13445
13530
  }
13446
- const rootPrefix = `${relativeScanRoot}/`;
13447
- return ignoreGlobs.map(normalizeGlobPattern).filter(Boolean).map((globPattern) => {
13448
- const rootRelativePattern = globPattern.startsWith("/") ? globPattern.slice(1) : globPattern;
13449
- if (isLocationIndependentGlob(rootRelativePattern))
13450
- return rootRelativePattern;
13451
- if (rootRelativePattern === relativeScanRoot || rootRelativePattern === `${relativeScanRoot}/**`)
13452
- return "**";
13453
- if (rootRelativePattern.startsWith(rootPrefix))
13454
- return rootRelativePattern.slice(rootPrefix.length) || "**";
13455
- return void 0;
13456
- }).filter(isPresent);
13457
- }
13458
- function stripGitignoreTrailingSpaces(line) {
13459
- let end = line.length;
13460
- while (end > 0 && line[end - 1] === " ") {
13461
- let slashCount = 0;
13462
- for (let i = end - 2; i >= 0 && line[i] === "\\"; i -= 1) {
13463
- slashCount += 1;
13531
+ const inner = rawPath.slice(1, -1);
13532
+ const bytes = [];
13533
+ for (let index = 0; index < inner.length; ) {
13534
+ const char = inner[index];
13535
+ if (char !== "\\") {
13536
+ const codePoint = inner.codePointAt(index);
13537
+ bytes.push(...Buffer.from(String.fromCodePoint(codePoint), "utf8"));
13538
+ index += codePoint > 65535 ? 2 : 1;
13539
+ continue;
13464
13540
  }
13465
- if (slashCount % 2 === 1) {
13466
- break;
13541
+ const octal = inner.slice(index + 1, index + 4).match(/^[0-7]{1,3}/);
13542
+ if (octal) {
13543
+ bytes.push(parseInt(octal[0], 8) & 255);
13544
+ index += 1 + octal[0].length;
13545
+ continue;
13467
13546
  }
13468
- end -= 1;
13469
- }
13470
- return line.slice(0, end);
13471
- }
13472
- function parseGitignoreRule(baseDir, rawLine) {
13473
- const trimmedLine = stripGitignoreTrailingSpaces(rawLine);
13474
- if (!trimmedLine)
13475
- return null;
13476
- if (trimmedLine.startsWith("#"))
13477
- return null;
13478
- const negated = trimmedLine.startsWith("!");
13479
- let pattern = negated ? trimmedLine.slice(1) : trimmedLine;
13480
- if (!pattern || pattern === "/")
13481
- return null;
13482
- const dirOnly = pattern.endsWith("/");
13483
- if (dirOnly) {
13484
- pattern = pattern.slice(0, -1);
13485
- }
13486
- if (pattern.startsWith("/")) {
13487
- pattern = pattern.slice(1);
13488
- }
13489
- pattern = pattern.replace(/^\\([#!])/, "$1").replace(/\\/g, "/");
13490
- if (!pattern)
13491
- return null;
13492
- const anchored = trimmedLine.startsWith("/") || negated && trimmedLine.startsWith("!/");
13493
- const baseMatcherPattern = anchored || pattern.includes("/") ? pattern : `**/${pattern}`;
13494
- const matcherPattern = dirOnly ? `${baseMatcherPattern}/**` : [baseMatcherPattern, `${baseMatcherPattern}/**`];
13495
- const matches = (0, import_picomatch.default)(matcherPattern, { dot: true });
13496
- return {
13497
- baseDir: normalizePath(baseDir),
13498
- negated,
13499
- dirOnly,
13500
- matches
13501
- };
13502
- }
13503
- async function loadGitignoreRules(projectRoot) {
13504
- const gitignoreFiles = await (0, import_fast_glob2.default)(["**/.gitignore"], {
13505
- cwd: projectRoot,
13506
- absolute: true,
13507
- dot: true,
13508
- followSymbolicLinks: false,
13509
- ignore: DEFAULT_PROJECT_FILE_IGNORES
13510
- });
13511
- gitignoreFiles.sort((left, right) => normalizePath(left).localeCompare(normalizePath(right)));
13512
- const rules = [];
13513
- for (const gitignoreFile of gitignoreFiles) {
13514
- if (isIgnoredByGitignore(gitignoreFile, rules)) {
13547
+ const next = inner[index + 1];
13548
+ if (next === "\\" || next === '"') {
13549
+ bytes.push(next.charCodeAt(0));
13550
+ index += 2;
13515
13551
  continue;
13516
13552
  }
13517
- try {
13518
- const baseDir = path8.dirname(gitignoreFile);
13519
- const raw = await fsp4.readFile(gitignoreFile, "utf8");
13520
- rules.push(...raw.split(/\r?\n/).map((line) => parseGitignoreRule(baseDir, line)).filter((rule) => !!rule));
13521
- } catch {
13553
+ const singleByteEscape = GIT_QUOTED_PATH_SINGLE_BYTE_ESCAPES[next ?? ""];
13554
+ if (singleByteEscape !== void 0) {
13555
+ bytes.push(singleByteEscape);
13556
+ index += 2;
13522
13557
  continue;
13523
13558
  }
13559
+ bytes.push(92);
13560
+ index += 1;
13524
13561
  }
13525
- return rules;
13562
+ return Buffer.from(bytes).toString("utf8");
13526
13563
  }
13527
- async function loadGitignoreRulesForRootAliases(projectRoot) {
13528
- const roots = [projectRoot];
13529
- const realRoot = await fsp4.realpath(projectRoot);
13530
- if (normalizePath(realRoot) !== normalizePath(projectRoot)) {
13531
- roots.push(realRoot);
13532
- }
13533
- const rules = [];
13534
- for (const root of roots) {
13535
- rules.push(...await loadGitignoreRules(root));
13564
+ function setGitExecutableForTests(executable) {
13565
+ gitExecutableForTests = executable;
13566
+ }
13567
+ function clearGitRepositoryCheckCacheForTests() {
13568
+ gitRepositoryChecks.clear();
13569
+ }
13570
+ function clearGitDiscoveryCacheForTests() {
13571
+ gitDiscoveryRootIgnoredChecks.clear();
13572
+ gitExcludeFileChecks.clear();
13573
+ }
13574
+ function resolveGitTimeoutMs(timeoutMs) {
13575
+ if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0) {
13576
+ return Math.floor(timeoutMs);
13536
13577
  }
13537
- return rules;
13578
+ return DEFAULT_GIT_TIMEOUT_MS;
13538
13579
  }
13539
- function isIgnoredByGitignore(absolutePath, rules) {
13540
- let ignored = false;
13541
- for (const rule of rules) {
13542
- const relativePath3 = path8.relative(rule.baseDir, absolutePath);
13543
- if (!isRelativePathInside(relativePath3)) {
13544
- continue;
13545
- }
13546
- const normalizedRelativePath2 = normalizePath(relativePath3);
13547
- if (rule.dirOnly && !normalizedRelativePath2.includes("/")) {
13548
- continue;
13549
- }
13550
- if (rule.matches(normalizedRelativePath2)) {
13551
- ignored = !rule.negated;
13580
+ function appendBoundedTail(current, chunk, maxBytes) {
13581
+ function utf8Tail(value, byteLimit) {
13582
+ let start = value.length;
13583
+ let bytes = 0;
13584
+ while (start) {
13585
+ const last = value.charCodeAt(start - 1);
13586
+ const preceding = start > 1 ? value.charCodeAt(start - 2) : void 0;
13587
+ const isSurrogatePair = last >= 56320 && last <= 57343 && preceding !== void 0 && preceding >= 55296 && preceding <= 56319;
13588
+ const codeUnits = isSurrogatePair ? 2 : 1;
13589
+ let codePointBytes = 3;
13590
+ if (isSurrogatePair) {
13591
+ codePointBytes = 4;
13592
+ } else if (last <= 127) {
13593
+ codePointBytes = 1;
13594
+ } else if (last <= 2047) {
13595
+ codePointBytes = 2;
13596
+ }
13597
+ if (bytes + codePointBytes > byteLimit)
13598
+ break;
13599
+ bytes += codePointBytes;
13600
+ start -= codeUnits;
13552
13601
  }
13602
+ return value.slice(start);
13553
13603
  }
13554
- return ignored;
13604
+ if (maxBytes <= 0)
13605
+ return "";
13606
+ const retainedChunk = utf8Tail(chunk, maxBytes);
13607
+ const retainedChunkBytes = Buffer.byteLength(retainedChunk, "utf8");
13608
+ if (retainedChunkBytes >= maxBytes)
13609
+ return retainedChunk;
13610
+ return utf8Tail(current, maxBytes - retainedChunkBytes) + retainedChunk;
13555
13611
  }
13556
- async function ensureDirectoryReadable(directoryPath, label) {
13557
- const resolvedPath = path8.resolve(directoryPath);
13558
- let stats;
13612
+ function killGitChild(child) {
13613
+ if (child.killed || child.exitCode !== null)
13614
+ return;
13559
13615
  try {
13560
- stats = await fsp4.stat(resolvedPath);
13561
- } catch (error51) {
13562
- throw new Error(`${label} does not exist or is not readable: ${resolvedPath} (${stringifyUnknown(error51)})`);
13563
- }
13564
- if (!stats.isDirectory()) {
13565
- throw new Error(`${label} is not a directory: ${resolvedPath}`);
13616
+ child.kill("SIGTERM");
13617
+ } catch {
13566
13618
  }
13567
- return resolvedPath;
13619
+ setTimeout(() => {
13620
+ if (child.killed || child.exitCode !== null)
13621
+ return;
13622
+ try {
13623
+ child.kill("SIGKILL");
13624
+ } catch {
13625
+ }
13626
+ }, 1e3).unref?.();
13568
13627
  }
13569
- async function listProjectFiles(projectRoot, patterns = DEFAULT_PROJECT_PATTERNS, options) {
13570
- const root = await ensureDirectoryReadable(projectRoot, "Project root");
13571
- const globRoot = options?.globRoot ? await ensureDirectoryReadable(options.globRoot, "Glob root") : root;
13572
- const includeGlobs = (options?.includeGlobs ?? []).map(normalizeGlobPattern).filter(Boolean);
13573
- const includeMatchers = includeGlobs.map((globPattern) => (0, import_picomatch.default)(globPattern, { dot: true }));
13574
- const userIgnoreGlobs = (options?.ignoreGlobs ?? []).map(normalizeGlobPattern).filter(Boolean);
13575
- const userIgnoreMatchers = userIgnoreGlobs.map((globPattern) => (0, import_picomatch.default)(globPattern, { dot: true }));
13576
- const defaultIgnoreMatchers = DEFAULT_PROJECT_FILE_IGNORES.map((globPattern) => (0, import_picomatch.default)(globPattern, { dot: true }));
13577
- const patternMatchers = patterns.map((pattern) => (0, import_picomatch.default)(normalizeGlobPattern(pattern), { dot: true }));
13578
- const translatedUserIgnoreGlobs = translateGlobRootIgnoreGlobsForScanRoot(root, globRoot, userIgnoreGlobs);
13579
- const fastGlobIgnoreGlobs = [...DEFAULT_PROJECT_FILE_IGNORES, ...translatedUserIgnoreGlobs];
13580
- const symlinkProbeIgnoreGlobs = includeGlobs.length ? [
13581
- ...translatedUserIgnoreGlobs,
13582
- ...DEFAULT_PROJECT_FILE_IGNORES.filter((ignoreGlob) => !isIgnoreGlobReopenedByIncludes(ignoreGlob, includeGlobs))
13583
- ] : fastGlobIgnoreGlobs;
13584
- try {
13585
- const useGitignore = options?.useGitignore ?? true;
13586
- const realRoot = await fsp4.realpath(root);
13587
- const gitignoreRules = !useGitignore ? [] : await loadGitignoreRulesForRootAliases(options?.gitignoreRoot ? await ensureDirectoryReadable(options.gitignoreRoot, "Gitignore root") : root);
13588
- const files = await (0, import_fast_glob2.default)(patterns, {
13589
- cwd: root,
13590
- absolute: true,
13591
- dot: true,
13592
- followSymbolicLinks: false,
13593
- ignore: fastGlobIgnoreGlobs
13628
+ async function runGit(projectRoot, args, options) {
13629
+ const timeoutMs = resolveGitTimeoutMs(options?.timeoutMs);
13630
+ const maxBuffer = options?.maxBuffer ?? 64 * 1024 * 1024;
13631
+ const executable = gitExecutableForTests ?? "git";
13632
+ const signal = options?.signal;
13633
+ if (signal?.aborted) {
13634
+ throw createGitError(projectRoot, args, new Error("aborted before start"));
13635
+ }
13636
+ return await new Promise((resolve, reject) => {
13637
+ const child = spawn(executable, args, {
13638
+ cwd: projectRoot,
13639
+ env: process.env,
13640
+ stdio: [options?.input !== void 0 ? "pipe" : "ignore", "pipe", "pipe"],
13641
+ windowsHide: true
13594
13642
  });
13595
- const includedOverrideFiles = includeGlobs.length ? await (0, import_fast_glob2.default)(translateGlobRootIgnoreGlobsForScanRoot(root, globRoot, includeGlobs), {
13596
- cwd: root,
13597
- absolute: true,
13598
- dot: true,
13599
- followSymbolicLinks: false,
13600
- ignore: translatedUserIgnoreGlobs
13601
- }) : [];
13602
- const symlinkOptions = {
13603
- globRoot,
13604
- ...options?.knownSymlinkDirectories !== void 0 ? { knownSymlinkDirectories: options.knownSymlinkDirectories } : {},
13605
- ...options?.onSymlinkDirectoriesDiscovered ? { onSymlinkDirectoriesDiscovered: options.onSymlinkDirectoriesDiscovered } : {}
13643
+ options?.onSpawn?.(child.pid === void 0 ? {} : { pid: child.pid });
13644
+ let stdout = "";
13645
+ let stderr = "";
13646
+ let settled = false;
13647
+ let timedOut = false;
13648
+ let aborted2 = false;
13649
+ let totalBytes = 0;
13650
+ let stderrBytes = 0;
13651
+ let stderrOverflowed = false;
13652
+ const settle = (fn) => {
13653
+ if (settled)
13654
+ return;
13655
+ settled = true;
13656
+ clearTimeout(timer);
13657
+ if (signal)
13658
+ signal.removeEventListener("abort", onAbort);
13659
+ fn();
13606
13660
  };
13607
- const safeSymlinkDirectories = await resolveSafeSymlinkDirectories(root, realRoot, symlinkProbeIgnoreGlobs, symlinkOptions);
13608
- const linkedFiles = await listEntriesFromSafeSymlinkDirectories(root, realRoot, patterns, fastGlobIgnoreGlobs, {
13609
- ...symlinkOptions,
13610
- filterIgnoreGlobs: [...DEFAULT_PROJECT_FILE_IGNORES, ...userIgnoreGlobs],
13611
- resolvedSafeSymlinkDirectories: safeSymlinkDirectories
13612
- });
13613
- const linkedOverrideFiles = includeGlobs.length === 0 ? [] : await listEntriesFromSafeSymlinkDirectories(root, realRoot, patterns, translatedUserIgnoreGlobs, {
13614
- globRoot,
13615
- filterIgnoreGlobs: userIgnoreGlobs,
13616
- resolvedSafeSymlinkDirectories: safeSymlinkDirectories
13661
+ const timer = setTimeout(() => {
13662
+ timedOut = true;
13663
+ killGitChild(child);
13664
+ }, timeoutMs);
13665
+ timer.unref?.();
13666
+ const onAbort = () => {
13667
+ aborted2 = true;
13668
+ killGitChild(child);
13669
+ };
13670
+ if (signal)
13671
+ signal.addEventListener("abort", onAbort, { once: true });
13672
+ const stdoutStream = child.stdout;
13673
+ const stderrStream = child.stderr;
13674
+ if (!stdoutStream || !stderrStream) {
13675
+ killGitChild(child);
13676
+ settle(() => reject(createGitError(projectRoot, args, new Error("git child missing stdio pipes"))));
13677
+ return;
13678
+ }
13679
+ const stdoutDecoder = new StringDecoder("utf8");
13680
+ const stderrDecoder = new StringDecoder("utf8");
13681
+ stdoutStream.on("data", (chunk) => {
13682
+ const chunkBytes = typeof chunk === "string" ? Buffer.byteLength(chunk, "utf8") : chunk.length;
13683
+ totalBytes += chunkBytes;
13684
+ if (totalBytes > maxBuffer) {
13685
+ killGitChild(child);
13686
+ settle(() => reject(createGitError(projectRoot, args, new Error(`stdout exceeded maxBuffer (${maxBuffer} bytes)`))));
13687
+ return;
13688
+ }
13689
+ stdout += typeof chunk === "string" ? chunk : stdoutDecoder.write(chunk);
13617
13690
  });
13618
- const rootSafeFiles = await filterRealPathsWithinRootEntries([...files, ...includedOverrideFiles, ...linkedFiles, ...linkedOverrideFiles], realRoot);
13619
- const seen = /* @__PURE__ */ new Set();
13620
- return rootSafeFiles.map(({ path: filePath, realPath }) => ({ filePath: normalizePath(filePath), realPath })).filter(({ filePath, realPath }) => {
13621
- if (seen.has(filePath))
13622
- return false;
13623
- seen.add(filePath);
13624
- const rootRelative = normalizePath(path8.relative(root, filePath));
13625
- if (!isRelativePathInside(rootRelative))
13626
- return false;
13627
- if (!patternMatchers.some((matcher) => matcher(rootRelative)))
13628
- return false;
13629
- const matchesInclude = !includeMatchers.length || includeMatchers.some((matcher) => matchesDiscoveryGlob(filePath, globRoot, matcher));
13630
- if (!matchesInclude)
13631
- return false;
13632
- if (userIgnoreMatchers.some((matcher) => matchesDiscoveryGlob(filePath, globRoot, matcher))) {
13633
- return false;
13691
+ stderrStream.on("data", (chunk) => {
13692
+ if (stderrOverflowed)
13693
+ return;
13694
+ const chunkBytes = typeof chunk === "string" ? Buffer.byteLength(chunk, "utf8") : chunk.length;
13695
+ stderrBytes += chunkBytes;
13696
+ const decoded = typeof chunk === "string" ? chunk : stderrDecoder.write(chunk);
13697
+ stderr = appendBoundedTail(stderr, decoded, maxBuffer);
13698
+ if (stderrBytes > maxBuffer) {
13699
+ stderrOverflowed = true;
13700
+ killGitChild(child);
13701
+ settle(() => reject(createGitError(projectRoot, args, new Error(`stderr exceeded maxBuffer (${maxBuffer} bytes): ${stderr.trim()}`))));
13634
13702
  }
13635
- const ignoredByDefault = defaultIgnoreMatchers.some((matcher) => matcher(rootRelative));
13636
- if (ignoredByDefault && !(includeMatchers.length > 0 && matchesInclude)) {
13637
- return false;
13703
+ });
13704
+ child.on("error", (error51) => {
13705
+ settle(() => reject(createGitError(projectRoot, args, error51)));
13706
+ });
13707
+ child.on("close", (code, signalName) => {
13708
+ stdout += stdoutDecoder.end();
13709
+ stderr = appendBoundedTail(stderr, stderrDecoder.end(), maxBuffer);
13710
+ settle(() => {
13711
+ if (timedOut) {
13712
+ reject(createGitError(projectRoot, args, new Error(`timed out after ${timeoutMs}ms${stderr ? `: ${stderr.trim()}` : ""}`)));
13713
+ return;
13714
+ }
13715
+ if (aborted2) {
13716
+ reject(createGitError(projectRoot, args, new Error(`aborted${stderr ? `: ${stderr.trim()}` : ""}`)));
13717
+ return;
13718
+ }
13719
+ if (code !== 0) {
13720
+ reject(new Error(`git ${args.join(" ")} failed (${code ?? signalName ?? "unknown"}): ${stderr || stdout || "unknown error"}`));
13721
+ return;
13722
+ }
13723
+ resolve({ stdout, stderr });
13724
+ });
13725
+ });
13726
+ if (options?.input !== void 0) {
13727
+ if (!child.stdin) {
13728
+ killGitChild(child);
13729
+ settle(() => reject(createGitError(projectRoot, args, new Error("git child missing stdin pipe"))));
13730
+ return;
13638
13731
  }
13639
- return !isIgnoredByGitignore(filePath, gitignoreRules) && !isIgnoredByGitignore(realPath, gitignoreRules);
13640
- }).map(({ filePath }) => filePath);
13641
- } catch (error51) {
13642
- logWithLevel(options?.logLevel, "debug", `listProjectFiles failed for ${root}: ${stringifyUnknown(error51)}`);
13643
- throw new Error(`Failed to list files in ${root}: ${stringifyUnknown(error51)}`);
13732
+ child.stdin.write(options.input);
13733
+ child.stdin.end();
13734
+ }
13735
+ });
13736
+ }
13737
+ async function runGitCollectStdout(projectRoot, args) {
13738
+ const { stdout } = await runGit(projectRoot, args);
13739
+ return stdout;
13740
+ }
13741
+ function isGitWorktreeSentinel(value) {
13742
+ return value.toUpperCase() === "WORKTREE";
13743
+ }
13744
+ function isGitIndexSentinel(value) {
13745
+ const normalized = value.toUpperCase();
13746
+ return normalized === "INDEX" || normalized === "STAGED";
13747
+ }
13748
+ function assertSafeRevision(value, label) {
13749
+ if (UNSAFE_REVISION_CHARACTERS.test(value)) {
13750
+ throw new Error(`Invalid ${label}: revisions must not contain NUL or newline characters.`);
13751
+ }
13752
+ const trimmed = value.trim();
13753
+ if (!trimmed) {
13754
+ throw new Error(`Invalid ${label}: revision must not be empty.`);
13755
+ }
13756
+ if (trimmed.startsWith("-")) {
13757
+ throw new Error(`Invalid ${label} "${trimmed}": revisions must not start with "-".`);
13644
13758
  }
13759
+ return trimmed;
13645
13760
  }
13646
- function createDiscoveredFileMatcher(root, globRoot, patterns, options) {
13647
- const patternMatchers = patterns.map((pattern) => (0, import_picomatch.default)(normalizeGlobPattern(pattern), { dot: true }));
13648
- const defaultIgnoreMatchers = DEFAULT_PROJECT_FILE_IGNORES.map((pattern) => (0, import_picomatch.default)(normalizeGlobPattern(pattern), { dot: true }));
13649
- const includeMatchers = (options?.includeGlobs ?? []).map(normalizeGlobPattern).filter(Boolean).map((pattern) => (0, import_picomatch.default)(pattern, { dot: true }));
13650
- const userIgnoreMatchers = (options?.ignoreGlobs ?? []).map(normalizeGlobPattern).filter(Boolean).map((pattern) => (0, import_picomatch.default)(pattern, { dot: true }));
13651
- return (absolutePath) => {
13652
- const rootRelative = path8.relative(root, absolutePath);
13653
- if (!isRelativePathInside(rootRelative))
13654
- return false;
13655
- const normalizedRootRelative = normalizePath(rootRelative);
13656
- if (!patternMatchers.some((matcher) => matcher(normalizedRootRelative)))
13657
- return false;
13658
- const matchesInclude = !includeMatchers.length || includeMatchers.some((matcher) => matchesDiscoveryGlob(absolutePath, globRoot, matcher));
13659
- if (!matchesInclude)
13660
- return false;
13661
- if (defaultIgnoreMatchers.some((matcher) => matcher(normalizedRootRelative))) {
13662
- if (!(includeMatchers.length > 0 && matchesInclude))
13663
- return false;
13664
- }
13665
- return !userIgnoreMatchers.some((matcher) => matchesDiscoveryGlob(absolutePath, globRoot, matcher));
13666
- };
13761
+ function gitDiffArgs(base, head, extraArgs = []) {
13762
+ const safeBase = assertSafeRevision(base, "base");
13763
+ const diffSafetyArgs = ["--no-ext-diff", "--no-textconv"];
13764
+ const renameArgs = ["--find-renames"];
13765
+ if (isGitWorktreeSentinel(head)) {
13766
+ return ["diff", ...diffSafetyArgs, ...renameArgs, ...extraArgs, "--end-of-options", safeBase];
13767
+ }
13768
+ if (isGitIndexSentinel(head)) {
13769
+ return ["diff", "--cached", ...diffSafetyArgs, ...renameArgs, ...extraArgs, "--end-of-options", safeBase];
13770
+ }
13771
+ const safeHead = assertSafeRevision(head, "head");
13772
+ return ["diff", ...diffSafetyArgs, ...renameArgs, ...extraArgs, "--end-of-options", `${safeBase}..${safeHead}`];
13667
13773
  }
13668
- async function isSafeSymlinkDirectory(root, linkPath, realRoot) {
13774
+ async function getGitHead(projectRoot) {
13669
13775
  try {
13670
- if (!isRelativePathInside(path8.relative(root, linkPath)))
13671
- return false;
13672
- const [linkStats, realPath, targetStats] = await Promise.all([
13673
- fsp4.lstat(linkPath),
13674
- fsp4.realpath(linkPath),
13675
- fsp4.stat(linkPath)
13676
- ]);
13677
- if (!linkStats.isSymbolicLink())
13678
- return false;
13679
- if (!targetStats.isDirectory())
13680
- return false;
13681
- if (!isFilePathWithinRoot(realRoot, realPath))
13682
- return false;
13683
- return normalizePath(realPath) !== normalizePath(realRoot);
13776
+ const { stdout } = await runGit(projectRoot, ["rev-parse", "HEAD"]);
13777
+ const hash2 = stdout.trim();
13778
+ return hash2 || null;
13684
13779
  } catch {
13685
- return false;
13780
+ return null;
13686
13781
  }
13687
13782
  }
13688
- async function resolveSafeSymlinkDirectories(root, realRoot, ignore, options) {
13689
- if (options.knownSymlinkDirectories !== void 0) {
13690
- const verified = await mapLimitSemaphore(Array.from(new Set(options.knownSymlinkDirectories)), REALPATH_FILTER_CONCURRENCY, async (linkPath) => await isSafeSymlinkDirectory(root, linkPath, realRoot) ? linkPath : null);
13691
- const resolved = verified.filter((entry) => entry !== null);
13692
- options.onSymlinkDirectoriesDiscovered?.(resolved);
13693
- return resolved;
13694
- }
13695
- const entries = await (0, import_fast_glob2.default)(["**/*"], {
13696
- cwd: root,
13697
- absolute: true,
13698
- dot: true,
13699
- onlyFiles: false,
13700
- followSymbolicLinks: false,
13701
- objectMode: true,
13702
- ignore
13703
- });
13704
- const candidates = await mapLimitSemaphore(entries.filter((entry) => entry.dirent.isSymbolicLink()).map((entry) => entry.path), REALPATH_FILTER_CONCURRENCY, async (linkPath) => await isSafeSymlinkDirectory(root, linkPath, realRoot) ? linkPath : null);
13705
- const discovered = candidates.filter((entry) => entry !== null);
13706
- options.onSymlinkDirectoriesDiscovered?.(discovered);
13707
- return discovered;
13783
+ async function isGitRepo(projectRoot) {
13784
+ const resolvedRoot = path8.resolve(projectRoot);
13785
+ const cached2 = gitRepositoryChecks.get(resolvedRoot);
13786
+ if (cached2)
13787
+ return await cached2;
13788
+ const check2 = (async () => {
13789
+ try {
13790
+ const { stdout } = await runGit(resolvedRoot, ["rev-parse", "--is-inside-work-tree"]);
13791
+ return stdout.trim() === "true";
13792
+ } catch {
13793
+ return false;
13794
+ }
13795
+ })();
13796
+ gitRepositoryChecks.set(resolvedRoot, check2);
13797
+ return await check2;
13708
13798
  }
13709
- async function listEntriesFromSafeSymlinkDirectories(root, realRoot, patterns, ignore, options = {}) {
13710
- const globRoot = options.globRoot ?? root;
13711
- const filterIgnoreGlobs = options.filterIgnoreGlobs ?? ignore;
13712
- const rootRelativeIgnoreMatchers = filterIgnoreGlobs.map(normalizeGlobPattern).filter(Boolean).map((globPattern) => (0, import_picomatch.default)(globPattern, { dot: true }));
13713
- const locationIndependentIgnores = ignore.map(normalizeGlobPattern).filter(isLocationIndependentGlob);
13714
- const safeSymlinkDirectories = options.resolvedSafeSymlinkDirectories ?? await resolveSafeSymlinkDirectories(root, realRoot, ignore, options);
13715
- if (!safeSymlinkDirectories.length)
13799
+ async function isGitPathTracked(projectRoot, file2) {
13800
+ return await runGitPathPredicate(projectRoot, ["ls-files", "--error-unmatch", "--", normalizePath(file2)]);
13801
+ }
13802
+ async function isGitPathIgnored(projectRoot, file2) {
13803
+ return await runGitPathPredicate(projectRoot, ["check-ignore", "--quiet", "--no-index", "--", normalizePath(file2)]);
13804
+ }
13805
+ async function isGitProjectRootIgnored(projectRoot) {
13806
+ const resolvedRoot = path8.resolve(projectRoot);
13807
+ const cached2 = gitDiscoveryRootIgnoredChecks.get(resolvedRoot);
13808
+ if (cached2)
13809
+ return await cached2;
13810
+ const check2 = isGitPathIgnored(resolvedRoot, resolvedRoot);
13811
+ gitDiscoveryRootIgnoredChecks.set(resolvedRoot, check2);
13812
+ return await check2;
13813
+ }
13814
+ async function runGitPathPredicate(projectRoot, args) {
13815
+ try {
13816
+ await runGit(projectRoot, args);
13817
+ return true;
13818
+ } catch (error51) {
13819
+ const message = error51 instanceof Error ? error51.message : String(error51);
13820
+ if (/\bfailed \(1\):/.test(message))
13821
+ return false;
13822
+ if (error51 instanceof Error && message.startsWith(`git ${args.join(" ")} failed in ${projectRoot}:`)) {
13823
+ throw error51;
13824
+ }
13825
+ throw createGitError(projectRoot, args, error51);
13826
+ }
13827
+ }
13828
+ async function getGitBlobHashes(projectRoot, files, opts) {
13829
+ const gitAvailable = opts?.gitAvailable ?? true;
13830
+ if (!gitAvailable)
13831
+ return /* @__PURE__ */ new Map();
13832
+ const relFileSet = new Set(files.map((file2) => normalizePath(path8.relative(projectRoot, file2))).filter((rel) => rel && !rel.startsWith("..") && !path8.isAbsolute(rel) && rel !== "."));
13833
+ if (!relFileSet.size)
13834
+ return /* @__PURE__ */ new Map();
13835
+ try {
13836
+ const { stdout: trackedStdout } = await runGit(projectRoot, ["ls-files", "-z"], {
13837
+ maxBuffer: 64 * 1024 * 1024
13838
+ });
13839
+ const trackedRel = trackedStdout.split("\0").filter((rel) => rel && relFileSet.has(rel));
13840
+ if (!trackedRel.length)
13841
+ return /* @__PURE__ */ new Map();
13842
+ const hashes = await hashGitPaths(projectRoot, trackedRel);
13843
+ if (hashes.length !== trackedRel.length) {
13844
+ logWithLevel(opts?.logLevel, "warn", `Warning: git hash-object returned ${hashes.length} hash(es) for ${trackedRel.length} requested file(s); discarding Git signatures for this build.`);
13845
+ return /* @__PURE__ */ new Map();
13846
+ }
13847
+ const out = /* @__PURE__ */ new Map();
13848
+ for (let i = 0; i < trackedRel.length; i += 1) {
13849
+ const rel = trackedRel[i];
13850
+ const hash2 = hashes[i];
13851
+ if (!hash2)
13852
+ continue;
13853
+ const abs = normalizePath(path8.resolve(projectRoot, rel));
13854
+ out.set(abs, hash2);
13855
+ }
13856
+ return out;
13857
+ } catch (error51) {
13858
+ logWithLevel(opts?.logLevel, "warn", "Warning: Failed to read Git blob hashes; falling back to content hashing.", error51);
13859
+ return /* @__PURE__ */ new Map();
13860
+ }
13861
+ }
13862
+ async function hashGitPaths(projectRoot, trackedRel) {
13863
+ const batches = [];
13864
+ let currentBatch = [];
13865
+ let currentBatchBytes = 0;
13866
+ for (const rel of trackedRel) {
13867
+ const absolutePath = path8.resolve(projectRoot, rel);
13868
+ const pathBytes = Buffer.byteLength(absolutePath, "utf8") + 1;
13869
+ const wouldExceedBatchLimit = currentBatch.length && currentBatchBytes + pathBytes > MAX_GIT_HASH_OBJECT_ARGUMENT_BYTES;
13870
+ if (wouldExceedBatchLimit) {
13871
+ batches.push(currentBatch);
13872
+ currentBatch = [];
13873
+ currentBatchBytes = 0;
13874
+ }
13875
+ currentBatch.push(absolutePath);
13876
+ currentBatchBytes += pathBytes;
13877
+ }
13878
+ if (currentBatch.length)
13879
+ batches.push(currentBatch);
13880
+ const hashes = [];
13881
+ for (const batch of batches) {
13882
+ const { stdout } = await runGit(projectRoot, ["hash-object", "--", ...batch]);
13883
+ hashes.push(...stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
13884
+ }
13885
+ return hashes;
13886
+ }
13887
+ async function listChangedFiles(projectRoot, opts) {
13888
+ let args = [
13889
+ "diff",
13890
+ "--no-ext-diff",
13891
+ "--no-textconv",
13892
+ "--find-renames",
13893
+ "--name-only",
13894
+ "-z",
13895
+ "--diff-filter=ACDMRTUXB"
13896
+ ];
13897
+ if (opts.base) {
13898
+ const head = opts.head ?? "HEAD";
13899
+ args = gitDiffArgs(opts.base, head, ["--name-only", "-z", "--diff-filter=ACDMRTUXB"]);
13900
+ } else if (opts.changedSince) {
13901
+ args.push("--end-of-options", assertSafeRevision(opts.changedSince, "changedSince"));
13902
+ } else {
13716
13903
  return [];
13717
- const filesByPath = /* @__PURE__ */ new Map();
13718
- const filesByDirectory = await mapLimitSemaphore(Array.from(safeSymlinkDirectories), REALPATH_FILTER_CONCURRENCY, async (directory) => (await (0, import_fast_glob2.default)(patterns, {
13719
- cwd: directory,
13720
- absolute: true,
13721
- dot: true,
13722
- followSymbolicLinks: false,
13723
- ignore: locationIndependentIgnores,
13724
- ...options.onlyFiles !== void 0 ? { onlyFiles: options.onlyFiles } : {},
13725
- ...options.markDirectories !== void 0 ? { markDirectories: options.markDirectories } : {}
13726
- })).filter((filePath) => {
13727
- const cleanPath = filePath.endsWith("/") ? filePath.slice(0, -1) : filePath;
13728
- return !rootRelativeIgnoreMatchers.some((matcher) => matchesDiscoveryGlob(cleanPath, globRoot, matcher));
13729
- }));
13730
- for (const files of filesByDirectory) {
13731
- for (const file2 of files) {
13732
- filesByPath.set(normalizePath(file2), file2);
13904
+ }
13905
+ args.push("--");
13906
+ try {
13907
+ const stdout = await runGitCollectStdout(projectRoot, args);
13908
+ const relFiles = stdout.split("\0").filter(Boolean);
13909
+ const out = [];
13910
+ for (const rel of relFiles) {
13911
+ const abs = normalizePath(path8.resolve(projectRoot, rel));
13912
+ if (abs)
13913
+ out.push(abs);
13733
13914
  }
13915
+ return Array.from(new Set(out));
13916
+ } catch (error51) {
13917
+ throw createGitError(projectRoot, args, error51);
13734
13918
  }
13735
- return [...filesByPath.values()];
13736
13919
  }
13737
- async function filterRealPathsWithinRootEntries(paths, realRoot) {
13738
- const filtered = await mapLimitSemaphore(paths, REALPATH_FILTER_CONCURRENCY, async (filePath) => {
13739
- try {
13740
- const realPath = await fsp4.realpath(filePath);
13741
- return isFilePathWithinRoot(realRoot, realPath) ? { path: filePath, realPath } : null;
13742
- } catch {
13743
- return null;
13920
+ async function listTrackedFiles(projectRoot, opts) {
13921
+ if (!(opts?.gitAvailable ?? true))
13922
+ return [];
13923
+ const args = ["ls-files", "-z"];
13924
+ if (opts?.recurseSubmodules)
13925
+ args.push("--recurse-submodules");
13926
+ try {
13927
+ const { stdout } = await runGit(projectRoot, args, { maxBuffer: 64 * 1024 * 1024 });
13928
+ const out = [];
13929
+ for (const rel of stdout.split("\0").filter(Boolean)) {
13930
+ const abs = normalizePath(path8.resolve(projectRoot, rel));
13931
+ if (abs)
13932
+ out.push(abs);
13744
13933
  }
13745
- });
13746
- return filtered.filter((entry) => entry !== null);
13934
+ return Array.from(new Set(out));
13935
+ } catch (error51) {
13936
+ throw createGitError(projectRoot, args, error51);
13937
+ }
13747
13938
  }
13748
- async function filterRealPathsWithinRoot(paths, realRoot) {
13749
- const entries = await filterRealPathsWithinRootEntries(paths, realRoot);
13750
- return entries.map((entry) => entry.path);
13939
+ async function listGitSubmoduleDirectories(projectRoot, opts) {
13940
+ if (!(opts?.gitAvailable ?? true))
13941
+ return [];
13942
+ const args = ["ls-files", "--stage", "-z"];
13943
+ try {
13944
+ const { stdout } = await runGit(projectRoot, args, { maxBuffer: 64 * 1024 * 1024 });
13945
+ const direct = [];
13946
+ for (const record2 of stdout.split("\0").filter(Boolean)) {
13947
+ if (!record2.startsWith("160000 "))
13948
+ continue;
13949
+ const tabIndex = record2.indexOf(" ");
13950
+ if (tabIndex < 0)
13951
+ continue;
13952
+ const rel = record2.slice(tabIndex + 1);
13953
+ if (rel)
13954
+ direct.push(normalizePath(path8.resolve(projectRoot, rel)));
13955
+ }
13956
+ if (!opts?.recurse || !direct.length)
13957
+ return Array.from(new Set(direct));
13958
+ const nested = await Promise.all(direct.map(async (directory) => await listGitSubmoduleDirectories(directory, opts)));
13959
+ return Array.from(/* @__PURE__ */ new Set([...direct, ...nested.flat()]));
13960
+ } catch (error51) {
13961
+ throw createGitError(projectRoot, args, error51);
13962
+ }
13751
13963
  }
13752
- function toProjectGlob(pattern) {
13753
- return pattern.startsWith("**/") ? pattern : `**/${pattern}`;
13964
+ async function listGitExcludeFiles(projectRoot, opts) {
13965
+ if (!(opts?.gitAvailable ?? true))
13966
+ return [];
13967
+ const resolvedRoot = path8.resolve(projectRoot);
13968
+ const cached2 = gitExcludeFileChecks.get(resolvedRoot);
13969
+ if (cached2)
13970
+ return await cached2;
13971
+ const check2 = listGitExcludeFilesUncached(resolvedRoot);
13972
+ gitExcludeFileChecks.set(resolvedRoot, check2);
13973
+ return await check2;
13754
13974
  }
13755
- async function buildProjectFileInfo(def, filePath) {
13756
- const normalizedPath = normalizePath(filePath);
13757
- const projectRoot = normalizePath(path8.dirname(filePath));
13758
- let name = null;
13759
- if (def.parseName && def.kind === "file") {
13975
+ async function listGitExcludeFilesUncached(projectRoot) {
13976
+ let baseDir;
13977
+ try {
13978
+ const { stdout } = await runGit(projectRoot, ["rev-parse", "--show-toplevel"]);
13979
+ baseDir = normalizePath(stdout.trim());
13980
+ } catch {
13981
+ return [];
13982
+ }
13983
+ if (!baseDir)
13984
+ return [];
13985
+ const candidates = [];
13986
+ try {
13987
+ const { stdout } = await runGit(projectRoot, ["rev-parse", "--git-path", "info/exclude"]);
13988
+ const resolved = stdout.trim();
13989
+ if (resolved)
13990
+ candidates.push(normalizePath(path8.resolve(projectRoot, resolved)));
13991
+ } catch {
13992
+ }
13993
+ try {
13994
+ const { stdout } = await runGit(projectRoot, ["config", "--get", "core.excludesFile"]);
13995
+ const configured = stdout.trim();
13996
+ if (configured) {
13997
+ const expanded = configured.startsWith("~") ? path8.join(os2.homedir(), configured.slice(1)) : path8.resolve(projectRoot, configured);
13998
+ candidates.push(normalizePath(expanded));
13999
+ }
14000
+ } catch {
14001
+ }
14002
+ const sources = [];
14003
+ for (const file2 of Array.from(new Set(candidates))) {
13760
14004
  try {
13761
- const raw = await fsp4.readFile(filePath, "utf8");
13762
- name = def.parseName(raw, filePath);
14005
+ if ((await fsp4.stat(file2)).isFile())
14006
+ sources.push({ file: file2, baseDir });
13763
14007
  } catch {
13764
- name = null;
13765
14008
  }
13766
14009
  }
13767
- if (!name && def.nameFromPath) {
13768
- if (def.nameFromPath === "file") {
13769
- name = trimToNull(path8.basename(filePath, path8.extname(filePath)));
13770
- } else {
13771
- name = trimToNull(path8.basename(projectRoot));
14010
+ return sources;
14011
+ }
14012
+ async function listUntrackedFiles(projectRoot, opts) {
14013
+ const gitAvailable = opts?.gitAvailable ?? true;
14014
+ if (!gitAvailable)
14015
+ return [];
14016
+ const args = ["ls-files", "--others", "-z"];
14017
+ if (opts?.respectGitignore ?? true)
14018
+ args.push("--exclude-standard");
14019
+ try {
14020
+ const stdout = await runGitCollectStdout(projectRoot, args);
14021
+ const relFiles = stdout.toString().split("\0").filter(Boolean);
14022
+ const out = [];
14023
+ for (const rel of relFiles) {
14024
+ const abs = normalizePath(path8.resolve(projectRoot, rel));
14025
+ if (abs)
14026
+ out.push(abs);
13772
14027
  }
14028
+ return Array.from(new Set(out));
14029
+ } catch (error51) {
14030
+ throw createGitError(projectRoot, args, error51);
13773
14031
  }
13774
- return {
13775
- path: normalizedPath,
13776
- kind: def.kind,
13777
- type: def.type,
13778
- role: def.role,
13779
- projectRoot,
13780
- ...name ? { name } : {}
13781
- };
13782
14032
  }
13783
- async function discoverProjectFiles(projectRoot, options) {
13784
- const root = await ensureDirectoryReadable(projectRoot, "Project root");
14033
+ async function getUnifiedDiff(projectRoot, opts) {
14034
+ let args = [
14035
+ "diff",
14036
+ "--no-ext-diff",
14037
+ "--no-textconv",
14038
+ "--find-renames",
14039
+ "--unified=0",
14040
+ "--no-color",
14041
+ "--diff-filter=ACDMRTUXB"
14042
+ ];
14043
+ if (opts.base) {
14044
+ const head = opts.head ?? "HEAD";
14045
+ args = gitDiffArgs(opts.base, head, ["--unified=0", "--no-color", "--diff-filter=ACDMRTUXB"]);
14046
+ } else if (opts.changedSince) {
14047
+ args.push("--end-of-options", assertSafeRevision(opts.changedSince, "changedSince"));
14048
+ } else {
14049
+ return "";
14050
+ }
14051
+ args.push("--");
13785
14052
  try {
13786
- const realRoot = await fsp4.realpath(root);
13787
- const allPatterns = PROJECT_FILE_DEFINITIONS.flatMap((def) => def.patterns.map(toProjectGlob));
13788
- const matches = await (0, import_fast_glob2.default)(allPatterns, {
13789
- cwd: root,
13790
- absolute: true,
13791
- dot: true,
13792
- followSymbolicLinks: false,
13793
- ignore: DEFAULT_PROJECT_FILE_IGNORES,
13794
- markDirectories: true,
13795
- onlyFiles: false
13796
- });
13797
- const linkedMatches = await listEntriesFromSafeSymlinkDirectories(root, realRoot, allPatterns, DEFAULT_PROJECT_FILE_IGNORES, {
13798
- markDirectories: true,
13799
- onlyFiles: false,
13800
- ...options?.knownSymlinkDirectories !== void 0 ? { knownSymlinkDirectories: options.knownSymlinkDirectories } : {},
13801
- ...options?.onSymlinkDirectoriesDiscovered ? { onSymlinkDirectoriesDiscovered: options.onSymlinkDirectoriesDiscovered } : {}
13802
- });
13803
- const rootSafeMatches = await filterRealPathsWithinRoot([...matches, ...linkedMatches].map((match) => match.endsWith("/") ? match.slice(0, -1) : match), realRoot);
13804
- const projectFileDefinitionMatchers = PROJECT_FILE_DEFINITIONS.map((definition) => definition.patterns.map((pattern) => pattern.includes("*") || pattern.includes("?") ? new RegExp("^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$") : void 0));
13805
- const entries = [];
13806
- const matchTasks = rootSafeMatches.map(async (cleanMatch) => {
13807
- const stats = await fsp4.stat(cleanMatch);
13808
- const isDir = stats.isDirectory();
13809
- const fileName = path8.basename(cleanMatch);
13810
- for (let definitionIndex = 0; definitionIndex < PROJECT_FILE_DEFINITIONS.length; definitionIndex++) {
13811
- const def = PROJECT_FILE_DEFINITIONS[definitionIndex];
13812
- if (isDir && def.kind !== "dir")
13813
- continue;
13814
- if (!isDir && def.kind !== "file")
13815
- continue;
13816
- const matchesPattern = def.patterns.some((pattern, patternIndex) => {
13817
- const matcher = projectFileDefinitionMatchers[definitionIndex][patternIndex];
13818
- return matcher ? matcher.test(fileName) : pattern === fileName;
13819
- });
13820
- if (matchesPattern) {
13821
- entries.push(await buildProjectFileInfo(def, cleanMatch));
13822
- }
13823
- }
13824
- });
13825
- await Promise.all(matchTasks);
13826
- const byKey2 = /* @__PURE__ */ new Map();
13827
- for (const entry of entries) {
13828
- const key = `${entry.path}::${entry.type}::${entry.role}`;
13829
- const existing = byKey2.get(key);
13830
- if (!existing || !existing.name && entry.name) {
13831
- byKey2.set(key, entry);
13832
- }
13833
- }
13834
- return Array.from(byKey2.values()).sort((a, b) => {
13835
- if (a.path === b.path)
13836
- return a.type.localeCompare(b.type);
13837
- return a.path.localeCompare(b.path);
13838
- });
14053
+ return await runGitCollectStdout(projectRoot, args);
13839
14054
  } catch (error51) {
13840
- logWithLevel(options?.logLevel, "debug", `discoverProjectFiles failed for ${root}: ${stringifyUnknown(error51)}`);
13841
- throw new Error(`Failed to discover project files in ${root}: ${stringifyUnknown(error51)}`);
14055
+ throw createGitError(projectRoot, args, error51);
13842
14056
  }
13843
14057
  }
13844
- var import_fast_glob2, import_picomatch, DEFAULT_PROJECT_FILE_IGNORES, DEFAULT_PROJECT_MANIFESTS, DEFAULT_PROJECT_PATTERNS, REALPATH_FILTER_CONCURRENCY;
13845
- var init_projectFiles = __esm({
13846
- "dist/util/projectFiles.js"() {
14058
+ function createGitError(projectRoot, args, error51) {
14059
+ let detail = stringifyUnknown(error51);
14060
+ if (typeof error51 === "object" && error51 !== null && "stderr" in error51 && typeof error51.stderr === "string" && error51.stderr.trim()) {
14061
+ detail = error51.stderr.trim();
14062
+ } else if (error51 instanceof Error && error51.message) {
14063
+ detail = error51.message;
14064
+ }
14065
+ return new Error(`git ${args.join(" ")} failed in ${projectRoot}: ${detail}`);
14066
+ }
14067
+ var DEFAULT_GIT_TIMEOUT_MS, gitRepositoryChecks, gitDiscoveryRootIgnoredChecks, gitExcludeFileChecks, MAX_GIT_HASH_OBJECT_ARGUMENT_BYTES, gitExecutableForTests, GIT_QUOTED_PATH_SINGLE_BYTE_ESCAPES, UNSAFE_REVISION_CHARACTERS;
14068
+ var init_git = __esm({
14069
+ "dist/util/git.js"() {
13847
14070
  "use strict";
13848
- import_fast_glob2 = __toESM(require_out4(), 1);
13849
- import_picomatch = __toESM(require_picomatch4(), 1);
13850
- init_logging();
13851
14071
  init_ast();
13852
14072
  init_paths();
13853
- init_discoveryPath();
13854
- init_discoveryPath();
13855
- init_definitions();
13856
- init_parsers();
13857
- init_concurrency();
13858
- DEFAULT_PROJECT_FILE_IGNORES = [
13859
- "**/node_modules/**",
13860
- "**/.git/**",
13861
- // Never treat Codegraph's own on-disk state as project input. `.codegraph/` is the
13862
- // lifecycle manifest root; `.codegraph-cache/` is the indexer disk cache. Either can
13863
- // contain JSON/markdown/source-looking files that would otherwise match discovery
13864
- // patterns and create perpetual "new untracked file" / reindex churn.
13865
- "**/.codegraph/**",
13866
- "**/.codegraph-cache/**",
13867
- "**/dist/**",
13868
- "**/build/**",
13869
- "**/target/**",
13870
- "**/.venv/**",
13871
- "**/venv/**",
13872
- "**/site-packages/**",
13873
- "**/__pycache__/**",
13874
- "**/vendor/bundle/**",
13875
- "**/.build/**",
13876
- "**/Pods/**"
13877
- ];
13878
- DEFAULT_PROJECT_MANIFESTS = [
13879
- "package.json",
13880
- "package-lock.json",
13881
- "pnpm-lock.yaml",
13882
- "yarn.lock",
13883
- "bun.lockb",
13884
- "pnpm-workspace.yaml",
13885
- "lerna.json",
13886
- "nx.json",
13887
- "turbo.json",
13888
- "tsconfig.json",
13889
- "jsconfig.json",
13890
- "requirements.txt",
13891
- "requirements.in",
13892
- "pyproject.toml",
13893
- "Pipfile",
13894
- "Pipfile.lock",
13895
- "poetry.lock",
13896
- "setup.py",
13897
- "setup.cfg",
13898
- "Cargo.toml",
13899
- "Cargo.lock",
13900
- "rust-toolchain",
13901
- "rust-toolchain.toml",
13902
- "go.mod",
13903
- "go.sum",
13904
- "go.work",
13905
- "Gemfile",
13906
- "Gemfile.lock",
13907
- "*.gemspec",
13908
- "pom.xml",
13909
- "mvnw",
13910
- "build.gradle",
13911
- "build.gradle.kts",
13912
- "settings.gradle",
13913
- "settings.gradle.kts",
13914
- "gradle.properties",
13915
- "gradlew",
13916
- "*.csproj",
13917
- "*.fsproj",
13918
- "*.vbproj",
13919
- "*.sln",
13920
- "Directory.Build.props",
13921
- "Directory.Build.targets",
13922
- "global.json",
13923
- "composer.json",
13924
- "composer.lock",
13925
- "Package.swift",
13926
- "Package.resolved",
13927
- "CMakeLists.txt",
13928
- "CMakePresets.json",
13929
- "CMakeUserPresets.json",
13930
- "Makefile",
13931
- "makefile",
13932
- "GNUmakefile",
13933
- "configure.ac",
13934
- "configure.in",
13935
- "meson.build",
13936
- "meson_options.txt",
13937
- "conanfile.txt",
13938
- "conanfile.py",
13939
- "vcpkg.json"
13940
- ];
13941
- DEFAULT_PROJECT_PATTERNS = [
13942
- "**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs,py,pyi,php,vue,svelte,astro,hbs,handlebars,md,mdx,rst,adoc,asciidoc,go,java,cs,rb,rs,html,htm,css,scss,less,kt,kts,swift,zig,c,h,cc,cpp,cxx,c++,hpp,hh,hxx,ipp,tpp,inl,sql}",
13943
- ...DEFAULT_PROJECT_MANIFESTS.map((name) => `**/${name}`)
13944
- ];
13945
- REALPATH_FILTER_CONCURRENCY = 64;
14073
+ init_logging();
14074
+ DEFAULT_GIT_TIMEOUT_MS = 3e4;
14075
+ gitRepositoryChecks = /* @__PURE__ */ new Map();
14076
+ gitDiscoveryRootIgnoredChecks = /* @__PURE__ */ new Map();
14077
+ gitExcludeFileChecks = /* @__PURE__ */ new Map();
14078
+ MAX_GIT_HASH_OBJECT_ARGUMENT_BYTES = 24 * 1024;
14079
+ gitExecutableForTests = null;
14080
+ GIT_QUOTED_PATH_SINGLE_BYTE_ESCAPES = {
14081
+ a: 7,
14082
+ b: 8,
14083
+ f: 12,
14084
+ n: 10,
14085
+ r: 13,
14086
+ t: 9,
14087
+ v: 11
14088
+ };
14089
+ UNSAFE_REVISION_CHARACTERS = /[\0\r\n]/;
13946
14090
  }
13947
14091
  });
13948
14092
 
13949
- // dist/util/git.js
13950
- var git_exports = {};
13951
- __export(git_exports, {
13952
- DEFAULT_GIT_TIMEOUT_MS: () => DEFAULT_GIT_TIMEOUT_MS,
13953
- assertSafeRevision: () => assertSafeRevision,
13954
- clearGitRepositoryCheckCacheForTests: () => clearGitRepositoryCheckCacheForTests,
13955
- decodeGitPath: () => decodeGitPath,
13956
- getGitBlobHashes: () => getGitBlobHashes,
13957
- getGitHead: () => getGitHead,
13958
- getUnifiedDiff: () => getUnifiedDiff,
13959
- gitDiffArgs: () => gitDiffArgs,
13960
- isGitIndexSentinel: () => isGitIndexSentinel,
13961
- isGitPathIgnored: () => isGitPathIgnored,
13962
- isGitPathTracked: () => isGitPathTracked,
13963
- isGitRepo: () => isGitRepo,
13964
- isGitWorktreeSentinel: () => isGitWorktreeSentinel,
13965
- listChangedFiles: () => listChangedFiles,
13966
- listUntrackedFiles: () => listUntrackedFiles,
13967
- runGit: () => runGit,
13968
- setGitExecutableForTests: () => setGitExecutableForTests
14093
+ // dist/util/projectFiles.js
14094
+ var projectFiles_exports = {};
14095
+ __export(projectFiles_exports, {
14096
+ DEFAULT_PROJECT_FILE_IGNORES: () => DEFAULT_PROJECT_FILE_IGNORES,
14097
+ DEFAULT_PROJECT_MANIFESTS: () => DEFAULT_PROJECT_MANIFESTS,
14098
+ DEFAULT_PROJECT_PATTERNS: () => DEFAULT_PROJECT_PATTERNS,
14099
+ createDiscoveredFileMatcher: () => createDiscoveredFileMatcher,
14100
+ discoverProjectFiles: () => discoverProjectFiles,
14101
+ filterRealPathsWithinRoot: () => filterRealPathsWithinRoot,
14102
+ isRelativePathInside: () => isRelativePathInside,
14103
+ listProjectFiles: () => listProjectFiles,
14104
+ matchesDiscoveryGlob: () => matchesDiscoveryGlob,
14105
+ translateGlobRootIgnoreGlobsForScanRoot: () => translateGlobRootIgnoreGlobsForScanRoot
13969
14106
  });
13970
- import { spawn } from "node:child_process";
13971
- import { StringDecoder } from "node:string_decoder";
14107
+ import fs6 from "node:fs";
14108
+ import fsp5 from "node:fs/promises";
13972
14109
  import path9 from "node:path";
13973
- function decodeGitPath(rawPath) {
13974
- if (!rawPath.startsWith('"') || !rawPath.endsWith('"')) {
13975
- return rawPath;
14110
+ function isPresent(value) {
14111
+ return value !== void 0;
14112
+ }
14113
+ function normalizeGlobPattern(globPattern) {
14114
+ return globPattern.trim().replace(/\\/g, "/");
14115
+ }
14116
+ function isIgnoreGlobReopenedByIncludes(ignoreGlob, includeGlobs) {
14117
+ const literalSegments = ignoreGlob.split("/").filter((segment) => segment && !segment.includes("*"));
14118
+ if (!literalSegments.length)
14119
+ return false;
14120
+ return includeGlobs.some((includeGlob) => {
14121
+ const includeSegments = includeGlob.split("/");
14122
+ return literalSegments.every((segment) => includeSegments.includes(segment));
14123
+ });
14124
+ }
14125
+ function isLocationIndependentGlob(globPattern) {
14126
+ return globPattern.startsWith("**/");
14127
+ }
14128
+ function translateGlobRootIgnoreGlobsForScanRoot(scanRoot, globRoot, ignoreGlobs) {
14129
+ const relativeScanRoot = normalizePath(path9.relative(globRoot, scanRoot));
14130
+ if (!relativeScanRoot || !isRelativePathInside(relativeScanRoot)) {
14131
+ return ignoreGlobs.map(normalizeGlobPattern).filter(Boolean);
13976
14132
  }
13977
- const inner = rawPath.slice(1, -1);
13978
- const bytes = [];
13979
- for (let index = 0; index < inner.length; ) {
13980
- const char = inner[index];
13981
- if (char !== "\\") {
13982
- const codePoint = inner.codePointAt(index);
13983
- bytes.push(...Buffer.from(String.fromCodePoint(codePoint), "utf8"));
13984
- index += codePoint > 65535 ? 2 : 1;
13985
- continue;
14133
+ const rootPrefix = `${relativeScanRoot}/`;
14134
+ return ignoreGlobs.map(normalizeGlobPattern).filter(Boolean).map((globPattern) => {
14135
+ const rootRelativePattern = globPattern.startsWith("/") ? globPattern.slice(1) : globPattern;
14136
+ if (isLocationIndependentGlob(rootRelativePattern))
14137
+ return rootRelativePattern;
14138
+ if (rootRelativePattern === relativeScanRoot || rootRelativePattern === `${relativeScanRoot}/**`)
14139
+ return "**";
14140
+ if (rootRelativePattern.startsWith(rootPrefix))
14141
+ return rootRelativePattern.slice(rootPrefix.length) || "**";
14142
+ return void 0;
14143
+ }).filter(isPresent);
14144
+ }
14145
+ function stripGitignoreTrailingSpaces(line) {
14146
+ let end = line.length;
14147
+ while (end > 0 && line[end - 1] === " ") {
14148
+ let slashCount = 0;
14149
+ for (let i = end - 2; i >= 0 && line[i] === "\\"; i -= 1) {
14150
+ slashCount += 1;
13986
14151
  }
13987
- const octal = inner.slice(index + 1, index + 4).match(/^[0-7]{1,3}/);
13988
- if (octal) {
13989
- bytes.push(parseInt(octal[0], 8) & 255);
13990
- index += 1 + octal[0].length;
13991
- continue;
14152
+ if (slashCount % 2 === 1) {
14153
+ break;
13992
14154
  }
13993
- const next = inner[index + 1];
13994
- if (next === "\\" || next === '"') {
13995
- bytes.push(next.charCodeAt(0));
13996
- index += 2;
14155
+ end -= 1;
14156
+ }
14157
+ return line.slice(0, end);
14158
+ }
14159
+ function parseGitignoreRule(baseDir, rawLine) {
14160
+ const trimmedLine = stripGitignoreTrailingSpaces(rawLine);
14161
+ if (!trimmedLine)
14162
+ return null;
14163
+ if (trimmedLine.startsWith("#"))
14164
+ return null;
14165
+ const negated = trimmedLine.startsWith("!");
14166
+ let pattern = negated ? trimmedLine.slice(1) : trimmedLine;
14167
+ if (!pattern || pattern === "/")
14168
+ return null;
14169
+ const dirOnly = pattern.endsWith("/");
14170
+ if (dirOnly) {
14171
+ pattern = pattern.slice(0, -1);
14172
+ }
14173
+ if (pattern.startsWith("/")) {
14174
+ pattern = pattern.slice(1);
14175
+ }
14176
+ pattern = pattern.replace(/^\\([#!])/, "$1").replace(/\\/g, "/");
14177
+ if (!pattern)
14178
+ return null;
14179
+ const anchored = trimmedLine.startsWith("/") || negated && trimmedLine.startsWith("!/");
14180
+ const baseMatcherPattern = anchored || pattern.includes("/") ? pattern : `**/${pattern}`;
14181
+ const matcherPattern = dirOnly ? `${baseMatcherPattern}/**` : [baseMatcherPattern, `${baseMatcherPattern}/**`];
14182
+ const matches = (0, import_picomatch.default)(matcherPattern, { dot: true });
14183
+ return {
14184
+ baseDir: normalizePath(baseDir),
14185
+ negated,
14186
+ dirOnly,
14187
+ matches
14188
+ };
14189
+ }
14190
+ async function buildGitignoreIndex(sources) {
14191
+ const sorted = [...sources].map(({ file: file2, baseDir }) => ({ file: normalizePath(file2), baseDir: normalizePath(baseDir) })).sort((left, right) => {
14192
+ const leftFile = left.file;
14193
+ const rightFile = right.file;
14194
+ const depthDelta = leftFile.split("/").length - rightFile.split("/").length;
14195
+ if (depthDelta !== 0)
14196
+ return depthDelta;
14197
+ if (leftFile < rightFile)
14198
+ return -1;
14199
+ return leftFile > rightFile ? 1 : 0;
14200
+ });
14201
+ const gitignoreIndex = { hasRules: false, byBaseDir: /* @__PURE__ */ new Map() };
14202
+ for (const { file: file2, baseDir } of sorted) {
14203
+ if (isIgnoredByGitignore(file2, gitignoreIndex)) {
13997
14204
  continue;
13998
14205
  }
13999
- const singleByteEscape = GIT_QUOTED_PATH_SINGLE_BYTE_ESCAPES[next ?? ""];
14000
- if (singleByteEscape !== void 0) {
14001
- bytes.push(singleByteEscape);
14002
- index += 2;
14206
+ let raw;
14207
+ try {
14208
+ raw = await fsp5.readFile(file2, "utf8");
14209
+ } catch {
14003
14210
  continue;
14004
14211
  }
14005
- bytes.push(92);
14006
- index += 1;
14212
+ const baseKey = fileIdentityKey(baseDir);
14213
+ for (const line of raw.split(/\r?\n/)) {
14214
+ const rule = parseGitignoreRule(baseDir, line);
14215
+ if (!rule)
14216
+ continue;
14217
+ const existing = gitignoreIndex.byBaseDir.get(baseKey);
14218
+ if (existing)
14219
+ existing.rules.push(rule);
14220
+ else
14221
+ gitignoreIndex.byBaseDir.set(baseKey, { baseDir, rules: [rule] });
14222
+ gitignoreIndex.hasRules = true;
14223
+ }
14007
14224
  }
14008
- return Buffer.from(bytes).toString("utf8");
14225
+ return gitignoreIndex;
14009
14226
  }
14010
- function setGitExecutableForTests(executable) {
14011
- gitExecutableForTests = executable;
14012
- }
14013
- function clearGitRepositoryCheckCacheForTests() {
14014
- gitRepositoryChecks.clear();
14227
+ async function findGitignoreFiles(projectRoot) {
14228
+ return await (0, import_fast_glob2.default)(["**/.gitignore"], {
14229
+ cwd: projectRoot,
14230
+ absolute: true,
14231
+ dot: true,
14232
+ followSymbolicLinks: false,
14233
+ ignore: DEFAULT_PROJECT_FILE_IGNORES
14234
+ });
14015
14235
  }
14016
- function resolveGitTimeoutMs(timeoutMs) {
14017
- if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0) {
14018
- return Math.floor(timeoutMs);
14236
+ async function loadGitignoreIndexForRootAliases(projectRoot) {
14237
+ const roots = [projectRoot];
14238
+ const realRoot = await fsp5.realpath(projectRoot);
14239
+ if (normalizePath(realRoot) !== normalizePath(projectRoot)) {
14240
+ roots.push(realRoot);
14019
14241
  }
14020
- return DEFAULT_GIT_TIMEOUT_MS;
14021
- }
14022
- function appendBoundedTail(current, chunk, maxBytes) {
14023
- function utf8Tail(value, byteLimit) {
14024
- let start = value.length;
14025
- let bytes = 0;
14026
- while (start) {
14027
- const last = value.charCodeAt(start - 1);
14028
- const preceding = start > 1 ? value.charCodeAt(start - 2) : void 0;
14029
- const isSurrogatePair = last >= 56320 && last <= 57343 && preceding !== void 0 && preceding >= 55296 && preceding <= 56319;
14030
- const codeUnits = isSurrogatePair ? 2 : 1;
14031
- let codePointBytes = 3;
14032
- if (isSurrogatePair) {
14033
- codePointBytes = 4;
14034
- } else if (last <= 127) {
14035
- codePointBytes = 1;
14036
- } else if (last <= 2047) {
14037
- codePointBytes = 2;
14038
- }
14039
- if (bytes + codePointBytes > byteLimit)
14040
- break;
14041
- bytes += codePointBytes;
14042
- start -= codeUnits;
14242
+ const sources = [];
14243
+ for (const root of roots) {
14244
+ for (const file2 of await findGitignoreFiles(root)) {
14245
+ sources.push({ file: file2, baseDir: path9.dirname(file2) });
14043
14246
  }
14044
- return value.slice(start);
14045
14247
  }
14046
- if (maxBytes <= 0)
14047
- return "";
14048
- const retainedChunk = utf8Tail(chunk, maxBytes);
14049
- const retainedChunkBytes = Buffer.byteLength(retainedChunk, "utf8");
14050
- if (retainedChunkBytes >= maxBytes)
14051
- return retainedChunk;
14052
- return utf8Tail(current, maxBytes - retainedChunkBytes) + retainedChunk;
14053
- }
14054
- function killGitChild(child) {
14055
- if (child.killed || child.exitCode !== null)
14056
- return;
14057
- try {
14058
- child.kill("SIGTERM");
14059
- } catch {
14060
- }
14061
- setTimeout(() => {
14062
- if (child.killed || child.exitCode !== null)
14063
- return;
14064
- try {
14065
- child.kill("SIGKILL");
14066
- } catch {
14067
- }
14068
- }, 1e3).unref?.();
14248
+ return await buildGitignoreIndex(sources);
14069
14249
  }
14070
- async function runGit(projectRoot, args, options) {
14071
- const timeoutMs = resolveGitTimeoutMs(options?.timeoutMs);
14072
- const maxBuffer = options?.maxBuffer ?? 64 * 1024 * 1024;
14073
- const executable = gitExecutableForTests ?? "git";
14074
- const signal = options?.signal;
14075
- if (signal?.aborted) {
14076
- throw createGitError(projectRoot, args, new Error("aborted before start"));
14250
+ function isIgnoredByGitignore(absolutePath, gitignoreIndex) {
14251
+ if (!gitignoreIndex.hasRules)
14252
+ return false;
14253
+ const chain = [];
14254
+ let current = normalizePath(path9.dirname(absolutePath));
14255
+ for (; ; ) {
14256
+ const group = gitignoreIndex.byBaseDir.get(fileIdentityKey(current));
14257
+ if (group)
14258
+ chain.push(group);
14259
+ const parent = normalizePath(path9.dirname(current));
14260
+ if (parent === current)
14261
+ break;
14262
+ current = parent;
14077
14263
  }
14078
- return await new Promise((resolve, reject) => {
14079
- const child = spawn(executable, args, {
14080
- cwd: projectRoot,
14081
- env: process.env,
14082
- stdio: [options?.input !== void 0 ? "pipe" : "ignore", "pipe", "pipe"],
14083
- windowsHide: true
14084
- });
14085
- options?.onSpawn?.(child.pid === void 0 ? {} : { pid: child.pid });
14086
- let stdout = "";
14087
- let stderr = "";
14088
- let settled = false;
14089
- let timedOut = false;
14090
- let aborted2 = false;
14091
- let totalBytes = 0;
14092
- let stderrBytes = 0;
14093
- let stderrOverflowed = false;
14094
- const settle = (fn) => {
14095
- if (settled)
14096
- return;
14097
- settled = true;
14098
- clearTimeout(timer);
14099
- if (signal)
14100
- signal.removeEventListener("abort", onAbort);
14101
- fn();
14102
- };
14103
- const timer = setTimeout(() => {
14104
- timedOut = true;
14105
- killGitChild(child);
14106
- }, timeoutMs);
14107
- timer.unref?.();
14108
- const onAbort = () => {
14109
- aborted2 = true;
14110
- killGitChild(child);
14111
- };
14112
- if (signal)
14113
- signal.addEventListener("abort", onAbort, { once: true });
14114
- const stdoutStream = child.stdout;
14115
- const stderrStream = child.stderr;
14116
- if (!stdoutStream || !stderrStream) {
14117
- killGitChild(child);
14118
- settle(() => reject(createGitError(projectRoot, args, new Error("git child missing stdio pipes"))));
14119
- return;
14264
+ let ignored = false;
14265
+ for (let depth = chain.length - 1; depth >= 0; depth -= 1) {
14266
+ const group = chain[depth];
14267
+ const relativePath3 = path9.relative(group.baseDir, absolutePath);
14268
+ if (!isRelativePathInside(relativePath3)) {
14269
+ continue;
14120
14270
  }
14121
- const stdoutDecoder = new StringDecoder("utf8");
14122
- const stderrDecoder = new StringDecoder("utf8");
14123
- stdoutStream.on("data", (chunk) => {
14124
- const chunkBytes = typeof chunk === "string" ? Buffer.byteLength(chunk, "utf8") : chunk.length;
14125
- totalBytes += chunkBytes;
14126
- if (totalBytes > maxBuffer) {
14127
- killGitChild(child);
14128
- settle(() => reject(createGitError(projectRoot, args, new Error(`stdout exceeded maxBuffer (${maxBuffer} bytes)`))));
14129
- return;
14130
- }
14131
- stdout += typeof chunk === "string" ? chunk : stdoutDecoder.write(chunk);
14132
- });
14133
- stderrStream.on("data", (chunk) => {
14134
- if (stderrOverflowed)
14135
- return;
14136
- const chunkBytes = typeof chunk === "string" ? Buffer.byteLength(chunk, "utf8") : chunk.length;
14137
- stderrBytes += chunkBytes;
14138
- const decoded = typeof chunk === "string" ? chunk : stderrDecoder.write(chunk);
14139
- stderr = appendBoundedTail(stderr, decoded, maxBuffer);
14140
- if (stderrBytes > maxBuffer) {
14141
- stderrOverflowed = true;
14142
- killGitChild(child);
14143
- settle(() => reject(createGitError(projectRoot, args, new Error(`stderr exceeded maxBuffer (${maxBuffer} bytes): ${stderr.trim()}`))));
14271
+ const normalizedRelativePath2 = normalizePath(relativePath3);
14272
+ const hasSeparator = normalizedRelativePath2.includes("/");
14273
+ for (const rule of group.rules) {
14274
+ if (rule.dirOnly && !hasSeparator) {
14275
+ continue;
14144
14276
  }
14145
- });
14146
- child.on("error", (error51) => {
14147
- settle(() => reject(createGitError(projectRoot, args, error51)));
14148
- });
14149
- child.on("close", (code, signalName) => {
14150
- stdout += stdoutDecoder.end();
14151
- stderr = appendBoundedTail(stderr, stderrDecoder.end(), maxBuffer);
14152
- settle(() => {
14153
- if (timedOut) {
14154
- reject(createGitError(projectRoot, args, new Error(`timed out after ${timeoutMs}ms${stderr ? `: ${stderr.trim()}` : ""}`)));
14155
- return;
14156
- }
14157
- if (aborted2) {
14158
- reject(createGitError(projectRoot, args, new Error(`aborted${stderr ? `: ${stderr.trim()}` : ""}`)));
14159
- return;
14160
- }
14161
- if (code !== 0) {
14162
- reject(new Error(`git ${args.join(" ")} failed (${code ?? signalName ?? "unknown"}): ${stderr || stdout || "unknown error"}`));
14163
- return;
14164
- }
14165
- resolve({ stdout, stderr });
14166
- });
14167
- });
14168
- if (options?.input !== void 0) {
14169
- if (!child.stdin) {
14170
- killGitChild(child);
14171
- settle(() => reject(createGitError(projectRoot, args, new Error("git child missing stdin pipe"))));
14172
- return;
14277
+ if (rule.matches(normalizedRelativePath2)) {
14278
+ ignored = !rule.negated;
14173
14279
  }
14174
- child.stdin.write(options.input);
14175
- child.stdin.end();
14176
14280
  }
14177
- });
14178
- }
14179
- async function runGitCollectStdout(projectRoot, args) {
14180
- const { stdout } = await runGit(projectRoot, args);
14181
- return stdout;
14182
- }
14183
- function isGitWorktreeSentinel(value) {
14184
- return value.toUpperCase() === "WORKTREE";
14185
- }
14186
- function isGitIndexSentinel(value) {
14187
- const normalized = value.toUpperCase();
14188
- return normalized === "INDEX" || normalized === "STAGED";
14189
- }
14190
- function assertSafeRevision(value, label) {
14191
- if (UNSAFE_REVISION_CHARACTERS.test(value)) {
14192
- throw new Error(`Invalid ${label}: revisions must not contain NUL or newline characters.`);
14193
- }
14194
- const trimmed = value.trim();
14195
- if (!trimmed) {
14196
- throw new Error(`Invalid ${label}: revision must not be empty.`);
14197
- }
14198
- if (trimmed.startsWith("-")) {
14199
- throw new Error(`Invalid ${label} "${trimmed}": revisions must not start with "-".`);
14200
14281
  }
14201
- return trimmed;
14282
+ return ignored;
14202
14283
  }
14203
- function gitDiffArgs(base, head, extraArgs = []) {
14204
- const safeBase = assertSafeRevision(base, "base");
14205
- const diffSafetyArgs = ["--no-ext-diff", "--no-textconv"];
14206
- const renameArgs = ["--find-renames"];
14207
- if (isGitWorktreeSentinel(head)) {
14208
- return ["diff", ...diffSafetyArgs, ...renameArgs, ...extraArgs, "--end-of-options", safeBase];
14284
+ async function ensureDirectoryReadable(directoryPath, label) {
14285
+ const resolvedPath = path9.resolve(directoryPath);
14286
+ let stats;
14287
+ try {
14288
+ stats = await fsp5.stat(resolvedPath);
14289
+ } catch (error51) {
14290
+ throw new Error(`${label} does not exist or is not readable: ${resolvedPath} (${stringifyUnknown(error51)})`);
14209
14291
  }
14210
- if (isGitIndexSentinel(head)) {
14211
- return ["diff", "--cached", ...diffSafetyArgs, ...renameArgs, ...extraArgs, "--end-of-options", safeBase];
14292
+ if (!stats.isDirectory()) {
14293
+ throw new Error(`${label} is not a directory: ${resolvedPath}`);
14212
14294
  }
14213
- const safeHead = assertSafeRevision(head, "head");
14214
- return ["diff", ...diffSafetyArgs, ...renameArgs, ...extraArgs, "--end-of-options", `${safeBase}..${safeHead}`];
14295
+ return resolvedPath;
14215
14296
  }
14216
- async function getGitHead(projectRoot) {
14217
- try {
14218
- const { stdout } = await runGit(projectRoot, ["rev-parse", "HEAD"]);
14219
- const hash2 = stdout.trim();
14220
- return hash2 || null;
14221
- } catch {
14222
- return null;
14297
+ function collectCandidateAncestorDirectories(root, files) {
14298
+ const rootKey = fileIdentityKey(root);
14299
+ const seen = /* @__PURE__ */ new Set([rootKey]);
14300
+ const directories = [normalizePath(root)];
14301
+ for (const file2 of files) {
14302
+ let current = normalizePath(path9.dirname(file2));
14303
+ while (fileIdentityKey(current) !== rootKey) {
14304
+ if (seen.has(fileIdentityKey(current)))
14305
+ break;
14306
+ seen.add(fileIdentityKey(current));
14307
+ directories.push(current);
14308
+ const parent = normalizePath(path9.dirname(current));
14309
+ if (parent === current)
14310
+ break;
14311
+ current = parent;
14312
+ }
14223
14313
  }
14314
+ return directories;
14224
14315
  }
14225
- async function isGitRepo(projectRoot) {
14226
- const resolvedRoot = path9.resolve(projectRoot);
14227
- const cached2 = gitRepositoryChecks.get(resolvedRoot);
14228
- if (cached2)
14229
- return await cached2;
14230
- const check2 = (async () => {
14316
+ async function findGitIgnoreSources(root, files) {
14317
+ const directories = collectCandidateAncestorDirectories(root, files);
14318
+ const present = await mapLimitSemaphore(directories, REALPATH_FILTER_CONCURRENCY, async (directory) => {
14319
+ const candidate = normalizePath(path9.join(directory, ".gitignore"));
14231
14320
  try {
14232
- const { stdout } = await runGit(resolvedRoot, ["rev-parse", "--is-inside-work-tree"]);
14233
- return stdout.trim() === "true";
14321
+ return (await fsp5.stat(candidate)).isFile() ? { file: candidate, baseDir: directory } : null;
14234
14322
  } catch {
14235
- return false;
14323
+ return null;
14236
14324
  }
14237
- })();
14238
- gitRepositoryChecks.set(resolvedRoot, check2);
14239
- return await check2;
14240
- }
14241
- async function isGitPathTracked(projectRoot, file2) {
14242
- return await runGitPathPredicate(projectRoot, ["ls-files", "--error-unmatch", "--", normalizePath(file2)]);
14325
+ });
14326
+ const sources = present.filter((entry) => entry !== null);
14327
+ sources.push(...await listGitExcludeFiles(root));
14328
+ return sources;
14243
14329
  }
14244
- async function isGitPathIgnored(projectRoot, file2) {
14245
- return await runGitPathPredicate(projectRoot, ["check-ignore", "--quiet", "--no-index", "--", normalizePath(file2)]);
14330
+ async function listGitCandidateFiles(root, logLevel) {
14331
+ try {
14332
+ if (!await isGitRepo(root))
14333
+ return null;
14334
+ if (await isGitProjectRootIgnored(root))
14335
+ return null;
14336
+ const [tracked, untracked, submoduleDirectories] = await Promise.all([
14337
+ listTrackedFiles(root, { recurseSubmodules: true, ...logLevel === void 0 ? {} : { logLevel } }),
14338
+ listUntrackedFiles(root, { respectGitignore: true }),
14339
+ listGitSubmoduleDirectories(root, { recurse: true })
14340
+ ]);
14341
+ const submoduleUntracked = await Promise.all(submoduleDirectories.map(async (directory) => await listUntrackedFiles(directory, { respectGitignore: true })));
14342
+ const files = Array.from(/* @__PURE__ */ new Set([...tracked, ...untracked, ...submoduleUntracked.flat()])).sort();
14343
+ return { files, gitignoreFiles: await findGitIgnoreSources(root, files) };
14344
+ } catch (error51) {
14345
+ logWithLevel(logLevel, "debug", `Git discovery unavailable for ${root}: ${stringifyUnknown(error51)}`);
14346
+ return null;
14347
+ }
14246
14348
  }
14247
- async function runGitPathPredicate(projectRoot, args) {
14349
+ async function listProjectFiles(projectRoot, patterns = DEFAULT_PROJECT_PATTERNS, options) {
14350
+ const root = await ensureDirectoryReadable(projectRoot, "Project root");
14351
+ const globRoot = options?.globRoot ? await ensureDirectoryReadable(options.globRoot, "Glob root") : root;
14352
+ const includeGlobs = (options?.includeGlobs ?? []).map(normalizeGlobPattern).filter(Boolean);
14353
+ const includeMatchers = includeGlobs.map((globPattern) => (0, import_picomatch.default)(globPattern, { dot: true }));
14354
+ const userIgnoreGlobs = (options?.ignoreGlobs ?? []).map(normalizeGlobPattern).filter(Boolean);
14355
+ const userIgnoreMatchers = userIgnoreGlobs.map((globPattern) => (0, import_picomatch.default)(globPattern, { dot: true }));
14356
+ const defaultIgnoreMatchers = DEFAULT_PROJECT_FILE_IGNORES.map((globPattern) => (0, import_picomatch.default)(globPattern, { dot: true }));
14357
+ const patternMatchers = patterns.map((pattern) => (0, import_picomatch.default)(normalizeGlobPattern(pattern), { dot: true }));
14358
+ const translatedUserIgnoreGlobs = translateGlobRootIgnoreGlobsForScanRoot(root, globRoot, userIgnoreGlobs);
14359
+ const fastGlobIgnoreGlobs = [...DEFAULT_PROJECT_FILE_IGNORES, ...translatedUserIgnoreGlobs];
14360
+ const symlinkProbeIgnoreGlobs = includeGlobs.length ? [
14361
+ ...translatedUserIgnoreGlobs,
14362
+ ...DEFAULT_PROJECT_FILE_IGNORES.filter((ignoreGlob) => !isIgnoreGlobReopenedByIncludes(ignoreGlob, includeGlobs))
14363
+ ] : fastGlobIgnoreGlobs;
14248
14364
  try {
14249
- await runGit(projectRoot, args);
14250
- return true;
14365
+ const useGitignore = options?.useGitignore ?? true;
14366
+ const realRoot = await fsp5.realpath(root);
14367
+ const gitCandidates = useGitignore && options?.gitignoreRoot === void 0 && normalizePath(realRoot) === normalizePath(root) ? await listGitCandidateFiles(root, options?.logLevel) : null;
14368
+ let gitignoreIndex = EMPTY_GITIGNORE_INDEX;
14369
+ if (useGitignore && gitCandidates) {
14370
+ gitignoreIndex = await buildGitignoreIndex(gitCandidates.gitignoreFiles);
14371
+ } else if (useGitignore) {
14372
+ const gitignoreRoot = options?.gitignoreRoot ? await ensureDirectoryReadable(options.gitignoreRoot, "Gitignore root") : root;
14373
+ gitignoreIndex = await loadGitignoreIndexForRootAliases(gitignoreRoot);
14374
+ }
14375
+ const files = gitCandidates ? gitCandidates.files : await (0, import_fast_glob2.default)(patterns, {
14376
+ cwd: root,
14377
+ absolute: true,
14378
+ dot: true,
14379
+ followSymbolicLinks: false,
14380
+ ignore: fastGlobIgnoreGlobs
14381
+ });
14382
+ const includedOverrideFiles = includeGlobs.length && !gitCandidates ? await (0, import_fast_glob2.default)(translateGlobRootIgnoreGlobsForScanRoot(root, globRoot, includeGlobs), {
14383
+ cwd: root,
14384
+ absolute: true,
14385
+ dot: true,
14386
+ followSymbolicLinks: false,
14387
+ ignore: translatedUserIgnoreGlobs
14388
+ }) : [];
14389
+ const symlinkOptions = {
14390
+ globRoot,
14391
+ ...gitCandidates ? { candidatePaths: gitCandidates.files } : {},
14392
+ ...options?.knownSymlinkDirectories !== void 0 ? { knownSymlinkDirectories: options.knownSymlinkDirectories } : {},
14393
+ ...options?.onSymlinkDirectoriesDiscovered ? { onSymlinkDirectoriesDiscovered: options.onSymlinkDirectoriesDiscovered } : {}
14394
+ };
14395
+ const safeSymlinkDirectories = await resolveSafeSymlinkDirectories(root, realRoot, symlinkProbeIgnoreGlobs, symlinkOptions);
14396
+ const linkedFiles = await listEntriesFromSafeSymlinkDirectories(root, realRoot, patterns, fastGlobIgnoreGlobs, {
14397
+ ...symlinkOptions,
14398
+ filterIgnoreGlobs: [...DEFAULT_PROJECT_FILE_IGNORES, ...userIgnoreGlobs],
14399
+ resolvedSafeSymlinkDirectories: safeSymlinkDirectories
14400
+ });
14401
+ const linkedOverrideFiles = includeGlobs.length === 0 ? [] : await listEntriesFromSafeSymlinkDirectories(root, realRoot, patterns, translatedUserIgnoreGlobs, {
14402
+ globRoot,
14403
+ filterIgnoreGlobs: userIgnoreGlobs,
14404
+ resolvedSafeSymlinkDirectories: safeSymlinkDirectories
14405
+ });
14406
+ const rootSafeFiles = await filterRealPathsWithinRootEntries([...files, ...includedOverrideFiles, ...linkedFiles, ...linkedOverrideFiles], realRoot);
14407
+ const seen = /* @__PURE__ */ new Set();
14408
+ return rootSafeFiles.map(({ path: filePath, realPath }) => ({ filePath: normalizePath(filePath), realPath })).filter(({ filePath, realPath }) => {
14409
+ if (seen.has(filePath))
14410
+ return false;
14411
+ seen.add(filePath);
14412
+ const rootRelative = normalizePath(path9.relative(root, filePath));
14413
+ if (!isRelativePathInside(rootRelative))
14414
+ return false;
14415
+ if (!patternMatchers.some((matcher) => matcher(rootRelative)))
14416
+ return false;
14417
+ const matchesInclude = !includeMatchers.length || includeMatchers.some((matcher) => matchesDiscoveryGlob(filePath, globRoot, matcher));
14418
+ if (!matchesInclude)
14419
+ return false;
14420
+ if (userIgnoreMatchers.some((matcher) => matchesDiscoveryGlob(filePath, globRoot, matcher))) {
14421
+ return false;
14422
+ }
14423
+ const ignoredByDefault = defaultIgnoreMatchers.some((matcher) => matcher(rootRelative));
14424
+ if (ignoredByDefault && !(includeMatchers.length > 0 && matchesInclude)) {
14425
+ return false;
14426
+ }
14427
+ if (isIgnoredByGitignore(filePath, gitignoreIndex))
14428
+ return false;
14429
+ return normalizePath(realPath) === filePath || !isIgnoredByGitignore(realPath, gitignoreIndex);
14430
+ }).map(({ filePath }) => filePath);
14251
14431
  } catch (error51) {
14252
- const message = error51 instanceof Error ? error51.message : String(error51);
14253
- if (/\bfailed \(1\):/.test(message))
14432
+ logWithLevel(options?.logLevel, "debug", `listProjectFiles failed for ${root}: ${stringifyUnknown(error51)}`);
14433
+ throw new Error(`Failed to list files in ${root}: ${stringifyUnknown(error51)}`);
14434
+ }
14435
+ }
14436
+ function createDiscoveredFileMatcher(root, globRoot, patterns, options) {
14437
+ const patternMatchers = patterns.map((pattern) => (0, import_picomatch.default)(normalizeGlobPattern(pattern), { dot: true }));
14438
+ const defaultIgnoreMatchers = DEFAULT_PROJECT_FILE_IGNORES.map((pattern) => (0, import_picomatch.default)(normalizeGlobPattern(pattern), { dot: true }));
14439
+ const includeMatchers = (options?.includeGlobs ?? []).map(normalizeGlobPattern).filter(Boolean).map((pattern) => (0, import_picomatch.default)(pattern, { dot: true }));
14440
+ const userIgnoreMatchers = (options?.ignoreGlobs ?? []).map(normalizeGlobPattern).filter(Boolean).map((pattern) => (0, import_picomatch.default)(pattern, { dot: true }));
14441
+ return (absolutePath) => {
14442
+ const rootRelative = path9.relative(root, absolutePath);
14443
+ if (!isRelativePathInside(rootRelative))
14254
14444
  return false;
14255
- if (error51 instanceof Error && message.startsWith(`git ${args.join(" ")} failed in ${projectRoot}:`)) {
14256
- throw error51;
14445
+ const normalizedRootRelative = normalizePath(rootRelative);
14446
+ if (!patternMatchers.some((matcher) => matcher(normalizedRootRelative)))
14447
+ return false;
14448
+ const matchesInclude = !includeMatchers.length || includeMatchers.some((matcher) => matchesDiscoveryGlob(absolutePath, globRoot, matcher));
14449
+ if (!matchesInclude)
14450
+ return false;
14451
+ if (defaultIgnoreMatchers.some((matcher) => matcher(normalizedRootRelative))) {
14452
+ if (!(includeMatchers.length > 0 && matchesInclude))
14453
+ return false;
14257
14454
  }
14258
- throw createGitError(projectRoot, args, error51);
14259
- }
14455
+ return !userIgnoreMatchers.some((matcher) => matchesDiscoveryGlob(absolutePath, globRoot, matcher));
14456
+ };
14260
14457
  }
14261
- async function getGitBlobHashes(projectRoot, files, opts) {
14262
- const gitAvailable = opts?.gitAvailable ?? true;
14263
- if (!gitAvailable)
14264
- return /* @__PURE__ */ new Map();
14265
- const relFileSet = new Set(files.map((file2) => normalizePath(path9.relative(projectRoot, file2))).filter((rel) => rel && !rel.startsWith("..") && !path9.isAbsolute(rel) && rel !== "."));
14266
- if (!relFileSet.size)
14267
- return /* @__PURE__ */ new Map();
14458
+ async function isSafeSymlinkDirectory(root, linkPath, realRoot) {
14268
14459
  try {
14269
- const { stdout: trackedStdout } = await runGit(projectRoot, ["ls-files", "-z"], {
14270
- maxBuffer: 64 * 1024 * 1024
14460
+ if (!isRelativePathInside(path9.relative(root, linkPath)))
14461
+ return false;
14462
+ if (!(await fsp5.lstat(linkPath)).isSymbolicLink())
14463
+ return false;
14464
+ const [realPath, targetStats] = await Promise.all([fsp5.realpath(linkPath), fsp5.stat(linkPath)]);
14465
+ if (!targetStats.isDirectory())
14466
+ return false;
14467
+ if (!isFilePathWithinRoot(realRoot, realPath))
14468
+ return false;
14469
+ return normalizePath(realPath) !== normalizePath(realRoot);
14470
+ } catch {
14471
+ return false;
14472
+ }
14473
+ }
14474
+ async function resolveSafeSymlinkDirectories(root, realRoot, ignore, options) {
14475
+ if (options.knownSymlinkDirectories !== void 0) {
14476
+ const verified = await mapLimitSemaphore(Array.from(new Set(options.knownSymlinkDirectories)), REALPATH_FILTER_CONCURRENCY, async (linkPath) => await isSafeSymlinkDirectory(root, linkPath, realRoot) ? linkPath : null);
14477
+ const resolved = verified.filter((entry) => entry !== null);
14478
+ options.onSymlinkDirectoriesDiscovered?.(resolved, "known");
14479
+ return resolved;
14480
+ }
14481
+ if (options.candidatePaths !== void 0) {
14482
+ const ignoreMatchers = ignore.map(normalizeGlobPattern).filter(Boolean).map((pattern) => (0, import_picomatch.default)(pattern, { dot: true }));
14483
+ const candidatePaths = Array.from(new Set(options.candidatePaths)).filter((candidatePath) => {
14484
+ const relativePath3 = normalizePath(path9.relative(root, candidatePath));
14485
+ if (!isRelativePathInside(relativePath3))
14486
+ return false;
14487
+ return !ignoreMatchers.some((matcher) => matcher(relativePath3));
14271
14488
  });
14272
- const trackedRel = trackedStdout.split("\0").filter((rel) => rel && relFileSet.has(rel));
14273
- if (!trackedRel.length)
14274
- return /* @__PURE__ */ new Map();
14275
- const hashes = await hashGitPaths(projectRoot, trackedRel);
14276
- if (hashes.length !== trackedRel.length) {
14277
- logWithLevel(opts?.logLevel, "warn", `Warning: git hash-object returned ${hashes.length} hash(es) for ${trackedRel.length} requested file(s); discarding Git signatures for this build.`);
14278
- return /* @__PURE__ */ new Map();
14279
- }
14280
- const out = /* @__PURE__ */ new Map();
14281
- for (let i = 0; i < trackedRel.length; i += 1) {
14282
- const rel = trackedRel[i];
14283
- const hash2 = hashes[i];
14284
- if (!hash2)
14285
- continue;
14286
- const abs = normalizePath(path9.resolve(projectRoot, rel));
14287
- out.set(abs, hash2);
14489
+ const verified = await mapLimitSemaphore(candidatePaths, REALPATH_FILTER_CONCURRENCY, async (linkPath) => await isSafeSymlinkDirectory(root, linkPath, realRoot) ? linkPath : null);
14490
+ const discovered2 = verified.filter((entry) => entry !== null);
14491
+ options.onSymlinkDirectoriesDiscovered?.(discovered2, "git-candidates");
14492
+ return discovered2;
14493
+ }
14494
+ const entries = await (0, import_fast_glob2.default)(["**/*"], {
14495
+ cwd: root,
14496
+ absolute: true,
14497
+ dot: true,
14498
+ onlyFiles: false,
14499
+ followSymbolicLinks: false,
14500
+ objectMode: true,
14501
+ ignore
14502
+ });
14503
+ const candidates = await mapLimitSemaphore(entries.filter((entry) => entry.dirent.isSymbolicLink()).map((entry) => entry.path), REALPATH_FILTER_CONCURRENCY, async (linkPath) => await isSafeSymlinkDirectory(root, linkPath, realRoot) ? linkPath : null);
14504
+ const discovered = candidates.filter((entry) => entry !== null);
14505
+ options.onSymlinkDirectoriesDiscovered?.(discovered, "filesystem");
14506
+ return discovered;
14507
+ }
14508
+ async function listEntriesFromSafeSymlinkDirectories(root, realRoot, patterns, ignore, options = {}) {
14509
+ const globRoot = options.globRoot ?? root;
14510
+ const filterIgnoreGlobs = options.filterIgnoreGlobs ?? ignore;
14511
+ const rootRelativeIgnoreMatchers = filterIgnoreGlobs.map(normalizeGlobPattern).filter(Boolean).map((globPattern) => (0, import_picomatch.default)(globPattern, { dot: true }));
14512
+ const locationIndependentIgnores = ignore.map(normalizeGlobPattern).filter(isLocationIndependentGlob);
14513
+ const safeSymlinkDirectories = options.resolvedSafeSymlinkDirectories ?? await resolveSafeSymlinkDirectories(root, realRoot, ignore, options);
14514
+ if (!safeSymlinkDirectories.length)
14515
+ return [];
14516
+ const filesByPath = /* @__PURE__ */ new Map();
14517
+ const filesByDirectory = await mapLimitSemaphore(Array.from(safeSymlinkDirectories), REALPATH_FILTER_CONCURRENCY, async (directory) => (await (0, import_fast_glob2.default)(patterns, {
14518
+ cwd: directory,
14519
+ absolute: true,
14520
+ dot: true,
14521
+ followSymbolicLinks: false,
14522
+ ignore: locationIndependentIgnores,
14523
+ ...options.onlyFiles !== void 0 ? { onlyFiles: options.onlyFiles } : {},
14524
+ ...options.markDirectories !== void 0 ? { markDirectories: options.markDirectories } : {}
14525
+ })).filter((filePath) => {
14526
+ const cleanPath = filePath.endsWith("/") ? filePath.slice(0, -1) : filePath;
14527
+ return !rootRelativeIgnoreMatchers.some((matcher) => matchesDiscoveryGlob(cleanPath, globRoot, matcher));
14528
+ }));
14529
+ for (const files of filesByDirectory) {
14530
+ for (const file2 of files) {
14531
+ filesByPath.set(normalizePath(file2), file2);
14288
14532
  }
14289
- return out;
14290
- } catch (error51) {
14291
- logWithLevel(opts?.logLevel, "warn", "Warning: Failed to read Git blob hashes; falling back to content hashing.", error51);
14292
- return /* @__PURE__ */ new Map();
14293
14533
  }
14534
+ return [...filesByPath.values()];
14294
14535
  }
14295
- async function hashGitPaths(projectRoot, trackedRel) {
14296
- const batches = [];
14297
- let currentBatch = [];
14298
- let currentBatchBytes = 0;
14299
- for (const rel of trackedRel) {
14300
- const absolutePath = path9.resolve(projectRoot, rel);
14301
- const pathBytes = Buffer.byteLength(absolutePath, "utf8") + 1;
14302
- const wouldExceedBatchLimit = currentBatch.length && currentBatchBytes + pathBytes > MAX_GIT_HASH_OBJECT_ARGUMENT_BYTES;
14303
- if (wouldExceedBatchLimit) {
14304
- batches.push(currentBatch);
14305
- currentBatch = [];
14306
- currentBatchBytes = 0;
14536
+ async function filterRealPathsWithinRootEntries(paths, realRoot) {
14537
+ const filtered = await mapLimitSemaphore(paths, REALPATH_FILTER_CONCURRENCY, async (filePath) => {
14538
+ try {
14539
+ const realPath = await fsp5.realpath(filePath);
14540
+ return isFilePathWithinRoot(realRoot, realPath) ? { path: filePath, realPath } : null;
14541
+ } catch {
14542
+ return null;
14307
14543
  }
14308
- currentBatch.push(absolutePath);
14309
- currentBatchBytes += pathBytes;
14310
- }
14311
- if (currentBatch.length)
14312
- batches.push(currentBatch);
14313
- const hashes = [];
14314
- for (const batch of batches) {
14315
- const { stdout } = await runGit(projectRoot, ["hash-object", "--", ...batch]);
14316
- hashes.push(...stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
14317
- }
14318
- return hashes;
14544
+ });
14545
+ return filtered.filter((entry) => entry !== null);
14319
14546
  }
14320
- async function listChangedFiles(projectRoot, opts) {
14321
- let args = [
14322
- "diff",
14323
- "--no-ext-diff",
14324
- "--no-textconv",
14325
- "--find-renames",
14326
- "--name-only",
14327
- "-z",
14328
- "--diff-filter=ACDMRTUXB"
14329
- ];
14330
- if (opts.base) {
14331
- const head = opts.head ?? "HEAD";
14332
- args = gitDiffArgs(opts.base, head, ["--name-only", "-z", "--diff-filter=ACDMRTUXB"]);
14333
- } else if (opts.changedSince) {
14334
- args.push("--end-of-options", assertSafeRevision(opts.changedSince, "changedSince"));
14335
- } else {
14336
- return [];
14337
- }
14338
- args.push("--");
14339
- try {
14340
- const stdout = await runGitCollectStdout(projectRoot, args);
14341
- const relFiles = stdout.split("\0").filter(Boolean);
14342
- const out = [];
14343
- for (const rel of relFiles) {
14344
- const abs = normalizePath(path9.resolve(projectRoot, rel));
14345
- if (abs)
14346
- out.push(abs);
14547
+ async function filterRealPathsWithinRoot(paths, realRoot) {
14548
+ const entries = await filterRealPathsWithinRootEntries(paths, realRoot);
14549
+ return entries.map((entry) => entry.path);
14550
+ }
14551
+ function toProjectGlob(pattern) {
14552
+ return pattern.startsWith("**/") ? pattern : `**/${pattern}`;
14553
+ }
14554
+ async function buildProjectFileInfo(def, filePath) {
14555
+ const normalizedPath = normalizePath(filePath);
14556
+ const projectRoot = normalizePath(path9.dirname(filePath));
14557
+ let name = null;
14558
+ if (def.parseName && def.kind === "file") {
14559
+ try {
14560
+ const raw = await fsp5.readFile(filePath, "utf8");
14561
+ name = def.parseName(raw, filePath);
14562
+ } catch {
14563
+ name = null;
14347
14564
  }
14348
- return Array.from(new Set(out));
14349
- } catch (error51) {
14350
- throw createGitError(projectRoot, args, error51);
14351
14565
  }
14352
- }
14353
- async function listUntrackedFiles(projectRoot, opts) {
14354
- const gitAvailable = opts?.gitAvailable ?? true;
14355
- if (!gitAvailable)
14356
- return [];
14357
- const args = ["ls-files", "--others", "-z"];
14358
- if (opts?.respectGitignore ?? true)
14359
- args.push("--exclude-standard");
14360
- try {
14361
- const stdout = await runGitCollectStdout(projectRoot, args);
14362
- const relFiles = stdout.toString().split("\0").filter(Boolean);
14363
- const out = [];
14364
- for (const rel of relFiles) {
14365
- const abs = normalizePath(path9.resolve(projectRoot, rel));
14366
- if (abs)
14367
- out.push(abs);
14566
+ if (!name && def.nameFromPath) {
14567
+ if (def.nameFromPath === "file") {
14568
+ name = trimToNull(path9.basename(filePath, path9.extname(filePath)));
14569
+ } else {
14570
+ name = trimToNull(path9.basename(projectRoot));
14368
14571
  }
14369
- return Array.from(new Set(out));
14370
- } catch (error51) {
14371
- throw createGitError(projectRoot, args, error51);
14372
14572
  }
14573
+ return {
14574
+ path: normalizedPath,
14575
+ kind: def.kind,
14576
+ type: def.type,
14577
+ role: def.role,
14578
+ projectRoot,
14579
+ ...name ? { name } : {}
14580
+ };
14373
14581
  }
14374
- async function getUnifiedDiff(projectRoot, opts) {
14375
- let args = [
14376
- "diff",
14377
- "--no-ext-diff",
14378
- "--no-textconv",
14379
- "--find-renames",
14380
- "--unified=0",
14381
- "--no-color",
14382
- "--diff-filter=ACDMRTUXB"
14383
- ];
14384
- if (opts.base) {
14385
- const head = opts.head ?? "HEAD";
14386
- args = gitDiffArgs(opts.base, head, ["--unified=0", "--no-color", "--diff-filter=ACDMRTUXB"]);
14387
- } else if (opts.changedSince) {
14388
- args.push("--end-of-options", assertSafeRevision(opts.changedSince, "changedSince"));
14389
- } else {
14390
- return "";
14391
- }
14392
- args.push("--");
14582
+ async function discoverProjectFiles(projectRoot, options) {
14583
+ const root = await ensureDirectoryReadable(projectRoot, "Project root");
14393
14584
  try {
14394
- return await runGitCollectStdout(projectRoot, args);
14585
+ const realRoot = await fsp5.realpath(root);
14586
+ const allPatterns = PROJECT_FILE_DEFINITIONS.flatMap((def) => def.patterns.map(toProjectGlob));
14587
+ const matches = await (0, import_fast_glob2.default)(allPatterns, {
14588
+ cwd: root,
14589
+ absolute: true,
14590
+ dot: true,
14591
+ followSymbolicLinks: false,
14592
+ ignore: DEFAULT_PROJECT_FILE_IGNORES,
14593
+ markDirectories: true,
14594
+ onlyFiles: false
14595
+ });
14596
+ const linkedMatches = await listEntriesFromSafeSymlinkDirectories(root, realRoot, allPatterns, DEFAULT_PROJECT_FILE_IGNORES, {
14597
+ markDirectories: true,
14598
+ onlyFiles: false,
14599
+ ...options?.knownSymlinkDirectories !== void 0 ? { knownSymlinkDirectories: options.knownSymlinkDirectories } : {},
14600
+ ...options?.onSymlinkDirectoriesDiscovered ? { onSymlinkDirectoriesDiscovered: options.onSymlinkDirectoriesDiscovered } : {}
14601
+ });
14602
+ const rootSafeMatches = await filterRealPathsWithinRoot([...matches, ...linkedMatches].map((match) => match.endsWith("/") ? match.slice(0, -1) : match), realRoot);
14603
+ const projectFileDefinitionMatchers = PROJECT_FILE_DEFINITIONS.map((definition) => definition.patterns.map((pattern) => pattern.includes("*") || pattern.includes("?") ? new RegExp("^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$") : void 0));
14604
+ const entries = [];
14605
+ const matchTasks = rootSafeMatches.map(async (cleanMatch) => {
14606
+ const stats = await fsp5.stat(cleanMatch);
14607
+ const isDir = stats.isDirectory();
14608
+ const fileName = path9.basename(cleanMatch);
14609
+ for (let definitionIndex = 0; definitionIndex < PROJECT_FILE_DEFINITIONS.length; definitionIndex++) {
14610
+ const def = PROJECT_FILE_DEFINITIONS[definitionIndex];
14611
+ if (isDir && def.kind !== "dir")
14612
+ continue;
14613
+ if (!isDir && def.kind !== "file")
14614
+ continue;
14615
+ const matchesPattern = def.patterns.some((pattern, patternIndex) => {
14616
+ const matcher = projectFileDefinitionMatchers[definitionIndex][patternIndex];
14617
+ return matcher ? matcher.test(fileName) : pattern === fileName;
14618
+ });
14619
+ if (matchesPattern) {
14620
+ entries.push(await buildProjectFileInfo(def, cleanMatch));
14621
+ }
14622
+ }
14623
+ });
14624
+ await Promise.all(matchTasks);
14625
+ const byKey2 = /* @__PURE__ */ new Map();
14626
+ for (const entry of entries) {
14627
+ const key = `${entry.path}::${entry.type}::${entry.role}`;
14628
+ const existing = byKey2.get(key);
14629
+ if (!existing || !existing.name && entry.name) {
14630
+ byKey2.set(key, entry);
14631
+ }
14632
+ }
14633
+ return Array.from(byKey2.values()).sort((a, b) => {
14634
+ if (a.path === b.path)
14635
+ return a.type.localeCompare(b.type);
14636
+ return a.path.localeCompare(b.path);
14637
+ });
14395
14638
  } catch (error51) {
14396
- throw createGitError(projectRoot, args, error51);
14397
- }
14398
- }
14399
- function createGitError(projectRoot, args, error51) {
14400
- let detail = stringifyUnknown(error51);
14401
- if (typeof error51 === "object" && error51 !== null && "stderr" in error51 && typeof error51.stderr === "string" && error51.stderr.trim()) {
14402
- detail = error51.stderr.trim();
14403
- } else if (error51 instanceof Error && error51.message) {
14404
- detail = error51.message;
14639
+ logWithLevel(options?.logLevel, "debug", `discoverProjectFiles failed for ${root}: ${stringifyUnknown(error51)}`);
14640
+ throw new Error(`Failed to discover project files in ${root}: ${stringifyUnknown(error51)}`);
14405
14641
  }
14406
- return new Error(`git ${args.join(" ")} failed in ${projectRoot}: ${detail}`);
14407
14642
  }
14408
- var DEFAULT_GIT_TIMEOUT_MS, gitRepositoryChecks, MAX_GIT_HASH_OBJECT_ARGUMENT_BYTES, gitExecutableForTests, GIT_QUOTED_PATH_SINGLE_BYTE_ESCAPES, UNSAFE_REVISION_CHARACTERS;
14409
- var init_git = __esm({
14410
- "dist/util/git.js"() {
14643
+ var import_fast_glob2, import_picomatch, DEFAULT_PROJECT_FILE_IGNORES, DEFAULT_PROJECT_MANIFESTS, DEFAULT_PROJECT_PATTERNS, REALPATH_FILTER_CONCURRENCY, EMPTY_GITIGNORE_INDEX;
14644
+ var init_projectFiles = __esm({
14645
+ "dist/util/projectFiles.js"() {
14411
14646
  "use strict";
14647
+ import_fast_glob2 = __toESM(require_out4(), 1);
14648
+ import_picomatch = __toESM(require_picomatch4(), 1);
14649
+ init_logging();
14412
14650
  init_ast();
14413
14651
  init_paths();
14414
- init_logging();
14415
- DEFAULT_GIT_TIMEOUT_MS = 3e4;
14416
- gitRepositoryChecks = /* @__PURE__ */ new Map();
14417
- MAX_GIT_HASH_OBJECT_ARGUMENT_BYTES = 24 * 1024;
14418
- gitExecutableForTests = null;
14419
- GIT_QUOTED_PATH_SINGLE_BYTE_ESCAPES = {
14420
- a: 7,
14421
- b: 8,
14422
- f: 12,
14423
- n: 10,
14424
- r: 13,
14425
- t: 9,
14426
- v: 11
14427
- };
14428
- UNSAFE_REVISION_CHARACTERS = /[\0\r\n]/;
14652
+ init_discoveryPath();
14653
+ init_discoveryPath();
14654
+ init_definitions();
14655
+ init_parsers();
14656
+ init_concurrency();
14657
+ init_git();
14658
+ DEFAULT_PROJECT_FILE_IGNORES = [
14659
+ "**/node_modules/**",
14660
+ "**/.git/**",
14661
+ // Never treat Codegraph's own on-disk state as project input. `.codegraph/` contains
14662
+ // lifecycle metadata and the indexer disk cache. Keep the legacy cache ignore so
14663
+ // pre-migration checkouts stay excluded from discovery.
14664
+ "**/.codegraph/**",
14665
+ "**/.codegraph-cache/**",
14666
+ "**/dist/**",
14667
+ "**/build/**",
14668
+ "**/target/**",
14669
+ "**/.venv/**",
14670
+ "**/venv/**",
14671
+ "**/site-packages/**",
14672
+ "**/__pycache__/**",
14673
+ "**/vendor/bundle/**",
14674
+ "**/.build/**",
14675
+ "**/Pods/**"
14676
+ ];
14677
+ DEFAULT_PROJECT_MANIFESTS = [
14678
+ "package.json",
14679
+ "package-lock.json",
14680
+ "pnpm-lock.yaml",
14681
+ "yarn.lock",
14682
+ "bun.lockb",
14683
+ "pnpm-workspace.yaml",
14684
+ "lerna.json",
14685
+ "nx.json",
14686
+ "turbo.json",
14687
+ "tsconfig.json",
14688
+ "jsconfig.json",
14689
+ "requirements.txt",
14690
+ "requirements.in",
14691
+ "pyproject.toml",
14692
+ "Pipfile",
14693
+ "Pipfile.lock",
14694
+ "poetry.lock",
14695
+ "setup.py",
14696
+ "setup.cfg",
14697
+ "Cargo.toml",
14698
+ "Cargo.lock",
14699
+ "rust-toolchain",
14700
+ "rust-toolchain.toml",
14701
+ "go.mod",
14702
+ "go.sum",
14703
+ "go.work",
14704
+ "Gemfile",
14705
+ "Gemfile.lock",
14706
+ "*.gemspec",
14707
+ "pom.xml",
14708
+ "mvnw",
14709
+ "build.gradle",
14710
+ "build.gradle.kts",
14711
+ "settings.gradle",
14712
+ "settings.gradle.kts",
14713
+ "gradle.properties",
14714
+ "gradlew",
14715
+ "*.csproj",
14716
+ "*.fsproj",
14717
+ "*.vbproj",
14718
+ "*.sln",
14719
+ "Directory.Build.props",
14720
+ "Directory.Build.targets",
14721
+ "global.json",
14722
+ "composer.json",
14723
+ "composer.lock",
14724
+ "Package.swift",
14725
+ "Package.resolved",
14726
+ "CMakeLists.txt",
14727
+ "CMakePresets.json",
14728
+ "CMakeUserPresets.json",
14729
+ "Makefile",
14730
+ "makefile",
14731
+ "GNUmakefile",
14732
+ "configure.ac",
14733
+ "configure.in",
14734
+ "meson.build",
14735
+ "meson_options.txt",
14736
+ "conanfile.txt",
14737
+ "conanfile.py",
14738
+ "vcpkg.json"
14739
+ ];
14740
+ DEFAULT_PROJECT_PATTERNS = [
14741
+ "**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs,py,pyi,php,vue,svelte,astro,hbs,handlebars,md,mdx,rst,adoc,asciidoc,go,java,cs,rb,rs,html,htm,css,scss,less,kt,kts,swift,zig,c,h,cc,cpp,cxx,c++,hpp,hh,hxx,ipp,tpp,inl,sql}",
14742
+ ...DEFAULT_PROJECT_MANIFESTS.map((name) => `**/${name}`)
14743
+ ];
14744
+ REALPATH_FILTER_CONCURRENCY = 64;
14745
+ EMPTY_GITIGNORE_INDEX = { hasRules: false, byBaseDir: /* @__PURE__ */ new Map() };
14429
14746
  }
14430
14747
  });
14431
14748
 
@@ -14523,13 +14840,13 @@ var init_projectSymbols = __esm({
14523
14840
  });
14524
14841
 
14525
14842
  // dist/util/resolution/jvm.js
14526
- import fsp5 from "node:fs/promises";
14843
+ import fsp6 from "node:fs/promises";
14527
14844
  import path10 from "node:path";
14528
14845
  async function readJvmSymbolIndex(filePath, cache, options) {
14529
14846
  const cached2 = cache.get(filePath);
14530
14847
  if (cached2)
14531
14848
  return cached2;
14532
- const source = await fsp5.readFile(filePath, "utf8");
14849
+ const source = await fsp6.readFile(filePath, "utf8");
14533
14850
  const packageName = source.match(options.packagePattern)?.[1] ?? null;
14534
14851
  const symbols = /* @__PURE__ */ new Set();
14535
14852
  for (const match of source.matchAll(options.declarationPattern)) {
@@ -14684,7 +15001,7 @@ var init_findFirstExisting = __esm({
14684
15001
  });
14685
15002
 
14686
15003
  // dist/util/resolution/files.js
14687
- import fsp6 from "node:fs/promises";
15004
+ import fsp7 from "node:fs/promises";
14688
15005
  import path12 from "node:path";
14689
15006
  async function findNearestFile(startDir, stopDir, fileName) {
14690
15007
  let dir = path12.resolve(startDir);
@@ -14704,7 +15021,7 @@ async function findNearestFile(startDir, stopDir, fileName) {
14704
15021
  }
14705
15022
  async function isDirectory(p) {
14706
15023
  try {
14707
- const st = await fsp6.stat(p);
15024
+ const st = await fsp7.stat(p);
14708
15025
  return st.isDirectory();
14709
15026
  } catch {
14710
15027
  return false;
@@ -14719,7 +15036,7 @@ var init_files = __esm({
14719
15036
 
14720
15037
  // dist/util/resolution/go.js
14721
15038
  import fs7 from "node:fs";
14722
- import fsp7 from "node:fs/promises";
15039
+ import fsp8 from "node:fs/promises";
14723
15040
  import path13 from "node:path";
14724
15041
  function stripInlineComment(line) {
14725
15042
  const idx = line.indexOf("//");
@@ -14729,7 +15046,7 @@ async function parseGoMod(moduleRoot) {
14729
15046
  const modPath = path13.join(moduleRoot, "go.mod");
14730
15047
  if (!await fileExists(modPath))
14731
15048
  return null;
14732
- const raw = await fsp7.readFile(modPath, "utf8");
15049
+ const raw = await fsp8.readFile(modPath, "utf8");
14733
15050
  const lines = raw.split(/\r?\n/);
14734
15051
  let modulePath = null;
14735
15052
  const replacements = /* @__PURE__ */ new Map();
@@ -14765,7 +15082,7 @@ async function parseGoMod(moduleRoot) {
14765
15082
  };
14766
15083
  }
14767
15084
  async function parseGoWork(goWorkPath) {
14768
- const content = await fsp7.readFile(goWorkPath, "utf8");
15085
+ const content = await fsp8.readFile(goWorkPath, "utf8");
14769
15086
  const lines = content.split(/\r?\n/);
14770
15087
  const modules = [];
14771
15088
  let inUseBlock = false;
@@ -14794,7 +15111,7 @@ async function parseGoWork(goWorkPath) {
14794
15111
  }
14795
15112
  async function findGoPackageEntry(dirPath) {
14796
15113
  try {
14797
- const stat = await fsp7.stat(dirPath);
15114
+ const stat = await fsp8.stat(dirPath);
14798
15115
  if (!stat.isDirectory())
14799
15116
  return null;
14800
15117
  } catch {
@@ -14802,7 +15119,7 @@ async function findGoPackageEntry(dirPath) {
14802
15119
  }
14803
15120
  let entries = [];
14804
15121
  try {
14805
- entries = await fsp7.readdir(dirPath, { withFileTypes: true });
15122
+ entries = await fsp8.readdir(dirPath, { withFileTypes: true });
14806
15123
  } catch {
14807
15124
  return null;
14808
15125
  }
@@ -14954,7 +15271,7 @@ var init_node = __esm({
14954
15271
  });
14955
15272
 
14956
15273
  // dist/util/resolution/phpComposer.js
14957
- import fsp8 from "node:fs/promises";
15274
+ import fsp9 from "node:fs/promises";
14958
15275
  import path15 from "node:path";
14959
15276
  function readComposerNamespaceDirs(value, composerDir) {
14960
15277
  const result = /* @__PURE__ */ new Map();
@@ -15004,7 +15321,7 @@ async function loadPhpComposerConfig(composerPath) {
15004
15321
  return await cached2;
15005
15322
  const pending = (async () => {
15006
15323
  try {
15007
- const raw = await fsp8.readFile(composerPath, "utf8");
15324
+ const raw = await fsp9.readFile(composerPath, "utf8");
15008
15325
  const parsed = JSON.parse(raw);
15009
15326
  const composerDir = path15.dirname(composerPath);
15010
15327
  const autoload = parsed.autoload && typeof parsed.autoload === "object" ? parsed.autoload : {};
@@ -15141,7 +15458,7 @@ async function getPhpComposerAutoloadFiles(composerPath, composerConfig) {
15141
15458
  }
15142
15459
  for (const root of roots) {
15143
15460
  try {
15144
- const stat = await fsp8.stat(root.path);
15461
+ const stat = await fsp9.stat(root.path);
15145
15462
  if (stat.isDirectory()) {
15146
15463
  const files = await listProjectFiles(root.path, ["**/*.php"]);
15147
15464
  for (const filePath of files) {
@@ -15181,7 +15498,7 @@ async function phpFileMatchesNamespacePrefixes(filePath, namespacePrefixes) {
15181
15498
  }
15182
15499
  let source;
15183
15500
  try {
15184
- source = await fsp8.readFile(filePath, "utf8");
15501
+ source = await fsp9.readFile(filePath, "utf8");
15185
15502
  } catch {
15186
15503
  return false;
15187
15504
  }
@@ -15217,7 +15534,7 @@ var init_phpComposer = __esm({
15217
15534
  });
15218
15535
 
15219
15536
  // dist/util/resolution/php.js
15220
- import fsp9 from "node:fs/promises";
15537
+ import fsp10 from "node:fs/promises";
15221
15538
  import path16 from "node:path";
15222
15539
  async function resolvePhpPathLikeSpecifier(projectRoot, fromFile, spec) {
15223
15540
  let base = path16.resolve(path16.dirname(fromFile), spec);
@@ -15270,7 +15587,7 @@ async function readPhpSymbolIndex(filePath) {
15270
15587
  const cached2 = phpSymbolIndexCache.get(filePath);
15271
15588
  if (cached2)
15272
15589
  return cached2;
15273
- const source = await fsp9.readFile(filePath, "utf8");
15590
+ const source = await fsp10.readFile(filePath, "utf8");
15274
15591
  const packageEntries = extractPhpTopLevelPackageEntries(source);
15275
15592
  const primaryEntry = packageEntries[0] ?? {
15276
15593
  packageName: "",
@@ -15719,7 +16036,7 @@ var init_php2 = __esm({
15719
16036
 
15720
16037
  // dist/util/resolution/python.js
15721
16038
  import fs8 from "node:fs";
15722
- import fsp10 from "node:fs/promises";
16039
+ import fsp11 from "node:fs/promises";
15723
16040
  import path17 from "node:path";
15724
16041
  function pythonResolutionCacheKey(projectRoot, fromFile, moduleName, importDotCount) {
15725
16042
  const normalizedRoot = normalizePath(path17.resolve(projectRoot));
@@ -15731,7 +16048,7 @@ async function findPythonPackageAnchor(startDir) {
15731
16048
  let topWithInit = startDir;
15732
16049
  while (true) {
15733
16050
  try {
15734
- await fsp10.access(path17.join(dir, "__init__.py"), fs8.constants.R_OK);
16051
+ await fsp11.access(path17.join(dir, "__init__.py"), fs8.constants.R_OK);
15735
16052
  topWithInit = dir;
15736
16053
  } catch {
15737
16054
  }
@@ -15777,7 +16094,7 @@ async function resolvePythonModule(projectRoot, fromFile, moduleName, importDotC
15777
16094
  resolvePythonModuleCache.set(cacheKey3, res);
15778
16095
  return res;
15779
16096
  }
15780
- await fsp10.access(c, fs8.constants.R_OK);
16097
+ await fsp11.access(c, fs8.constants.R_OK);
15781
16098
  {
15782
16099
  const res = normalizePath(path17.resolve(c));
15783
16100
  resolvePythonModuleCache.set(cacheKey3, res);
@@ -15815,7 +16132,7 @@ async function resolvePythonModule(projectRoot, fromFile, moduleName, importDotC
15815
16132
  resolvePythonModuleCache.set(cacheKey3, res);
15816
16133
  return res;
15817
16134
  }
15818
- await fsp10.access(c, fs8.constants.R_OK);
16135
+ await fsp11.access(c, fs8.constants.R_OK);
15819
16136
  {
15820
16137
  const res = normalizePath(path17.resolve(c));
15821
16138
  resolvePythonModuleCache.set(cacheKey3, res);
@@ -17987,7 +18304,7 @@ var require_lib2 = __commonJS({
17987
18304
 
17988
18305
  // dist/util/resolution/tsconfig.js
17989
18306
  import fs9 from "node:fs";
17990
- import fsp11 from "node:fs/promises";
18307
+ import fsp12 from "node:fs/promises";
17991
18308
  import { createRequire } from "node:module";
17992
18309
  import path19 from "node:path";
17993
18310
  async function findNearestTsconfig(startFromFile, projectRoot) {
@@ -17997,7 +18314,7 @@ async function findNearestTsconfig(startFromFile, projectRoot) {
17997
18314
  while (isFilePathWithinRoot(resolvedProjectRoot, dir)) {
17998
18315
  const candidate = path19.join(dir, "tsconfig.json");
17999
18316
  try {
18000
- await fsp11.access(candidate, fs9.constants.R_OK);
18317
+ await fsp12.access(candidate, fs9.constants.R_OK);
18001
18318
  return candidate;
18002
18319
  } catch {
18003
18320
  }
@@ -18058,7 +18375,7 @@ async function loadTsconfigConfig(cfgPath, projectRoot, seen = /* @__PURE__ */ n
18058
18375
  return { baseUrl: path19.dirname(normalizedCfgPath).replace(/\\/g, "/"), paths: {} };
18059
18376
  }
18060
18377
  seen.add(cfgKey);
18061
- const raw = await fsp11.readFile(cfgPath, "utf8");
18378
+ const raw = await fsp12.readFile(cfgPath, "utf8");
18062
18379
  const json3 = parseJsonc(raw);
18063
18380
  const cfgDir = path19.dirname(cfgPath);
18064
18381
  const compilerOptions = json3.compilerOptions;
@@ -18181,7 +18498,7 @@ __export(resolution_exports, {
18181
18498
  resolveSpecifier: () => resolveSpecifier
18182
18499
  });
18183
18500
  import fs10 from "node:fs";
18184
- import fsp12 from "node:fs/promises";
18501
+ import fsp13 from "node:fs/promises";
18185
18502
  import path20 from "node:path";
18186
18503
  function getResolveSpecifierCacheEntry(key) {
18187
18504
  return lruMapGet(resolveSpecifierCache, key);
@@ -18237,7 +18554,7 @@ async function resolvePathLikeModule(projectRoot, spec, resolutionExtensions) {
18237
18554
  return path20.resolve(path20.join(p, "index" + e));
18238
18555
  }
18239
18556
  if (await fileExists(p)) {
18240
- const st = await fsp12.stat(p);
18557
+ const st = await fsp13.stat(p);
18241
18558
  if (!st.isDirectory())
18242
18559
  return path20.resolve(p);
18243
18560
  }
@@ -18584,7 +18901,7 @@ var init_confinedFile = __esm({
18584
18901
  });
18585
18902
 
18586
18903
  // dist/util/workerThreads.js
18587
- import os2 from "node:os";
18904
+ import os3 from "node:os";
18588
18905
  function resolveWorkerThreadCount(options) {
18589
18906
  const max = options?.max ?? 64;
18590
18907
  const requested = options?.requested;
@@ -18595,7 +18912,7 @@ function resolveWorkerThreadCount(options) {
18595
18912
  if (typeof defaultCount === "number" && Number.isFinite(defaultCount) && defaultCount > 0) {
18596
18913
  return Math.min(Math.max(Math.floor(defaultCount), 1), max);
18597
18914
  }
18598
- return Math.min(Math.max(os2.availableParallelism() - 1, 1), max);
18915
+ return Math.min(Math.max(os3.availableParallelism() - 1, 1), max);
18599
18916
  }
18600
18917
  var init_workerThreads = __esm({
18601
18918
  "dist/util/workerThreads.js"() {
@@ -19000,17 +19317,17 @@ var init_sfc = __esm({
19000
19317
  });
19001
19318
 
19002
19319
  // dist/languages/filePrep.js
19003
- import fsp13 from "node:fs/promises";
19320
+ import fsp14 from "node:fs/promises";
19004
19321
  async function prepareSourceInput(file2, opts) {
19005
19322
  const framework = detectSFCFramework(file2);
19006
19323
  if (framework) {
19007
- const rawSource2 = opts?.source ?? await fsp13.readFile(file2, "utf8");
19324
+ const rawSource2 = opts?.source ?? await fsp14.readFile(file2, "utf8");
19008
19325
  return prepareSFCSourceInput(rawSource2, framework);
19009
19326
  }
19010
19327
  const sup = supportForFile(file2, opts?.languageExtensions);
19011
19328
  if (!sup)
19012
19329
  throw new UnsupportedParserInputError(file2);
19013
- const rawSource = opts?.source ?? await fsp13.readFile(file2, "utf8");
19330
+ const rawSource = opts?.source ?? await fsp14.readFile(file2, "utf8");
19014
19331
  return {
19015
19332
  source: rawSource,
19016
19333
  sup
@@ -20962,7 +21279,7 @@ var init_angularjs = __esm({
20962
21279
  });
20963
21280
 
20964
21281
  // dist/graphs/angularjs.js
20965
- import fsp14 from "node:fs/promises";
21282
+ import fsp15 from "node:fs/promises";
20966
21283
  async function collectAngularJsFrameworkEdges(projectRoot, files, workspaceConfig, parsed) {
20967
21284
  const jsFiles = files.filter((file2) => file2.toLowerCase().endsWith(".js"));
20968
21285
  if (!jsFiles.length)
@@ -20975,7 +21292,7 @@ async function collectAngularJsFrameworkEdges(projectRoot, files, workspaceConfi
20975
21292
  continue;
20976
21293
  }
20977
21294
  try {
20978
- const source = await fsp14.readFile(file2, "utf8");
21295
+ const source = await fsp15.readFile(file2, "utf8");
20979
21296
  contexts.push({ file: file2, source });
20980
21297
  } catch {
20981
21298
  continue;
@@ -24725,7 +25042,7 @@ var init_extractFacts = __esm({
24725
25042
 
24726
25043
  // dist/sql/sourceGraph.js
24727
25044
  import crypto2 from "node:crypto";
24728
- import fsp15 from "node:fs/promises";
25045
+ import fsp16 from "node:fs/promises";
24729
25046
  function isSqlFile(filePath, languageExtensions) {
24730
25047
  return supportForFile(filePath, languageExtensions)?.id === "sql";
24731
25048
  }
@@ -24854,7 +25171,7 @@ function buildSqlModuleIndex(filePath, source) {
24854
25171
  };
24855
25172
  }
24856
25173
  async function readSqlFacts(filePath) {
24857
- return extractSqlFactsFromSource(filePath, await fsp15.readFile(filePath, "utf8"));
25174
+ return extractSqlFactsFromSource(filePath, await fsp16.readFile(filePath, "utf8"));
24858
25175
  }
24859
25176
  async function buildSqlFactCache(allFiles, languageExtensions) {
24860
25177
  const sqlFiles2 = Array.from(new Set(allFiles.map(normalizePath).filter((file2) => isSqlFile(file2, languageExtensions)))).sort((left, right) => left.localeCompare(right));
@@ -25290,7 +25607,7 @@ var init_adjacency = __esm({
25290
25607
  });
25291
25608
 
25292
25609
  // dist/worker/nativeExtractWorker.js
25293
- import fsp16 from "node:fs/promises";
25610
+ import fsp17 from "node:fs/promises";
25294
25611
  import { createRequire as createRequire3 } from "node:module";
25295
25612
  import path32 from "node:path";
25296
25613
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -25493,9 +25810,9 @@ var init_nativeExtractWorker = __esm({
25493
25810
  localNativePackageRoot2 = path32.resolve(path32.dirname(fileURLToPath3(import.meta.url)), "../../packages/codegraph-native");
25494
25811
  runExtraction = createNativeExtractor({
25495
25812
  loadBinding: loadProductionBinding,
25496
- readFile: fsp16.readFile,
25813
+ readFile: fsp17.readFile,
25497
25814
  statFile: async (filePath) => {
25498
- const stat = await fsp16.stat(filePath);
25815
+ const stat = await fsp17.stat(filePath);
25499
25816
  return { size: stat.size };
25500
25817
  }
25501
25818
  });
@@ -29805,7 +30122,7 @@ var init_reports = __esm({
29805
30122
  // dist/indexer/build-cache/location.js
29806
30123
  import crypto4 from "node:crypto";
29807
30124
  import fs16 from "node:fs";
29808
- import os3 from "node:os";
30125
+ import os4 from "node:os";
29809
30126
  import path35 from "node:path";
29810
30127
  function isWritableDirectory(candidate) {
29811
30128
  try {
@@ -29816,7 +30133,7 @@ function isWritableDirectory(candidate) {
29816
30133
  }
29817
30134
  function isForbiddenAnchor(candidate) {
29818
30135
  const resolved = path35.resolve(candidate);
29819
- const home = path35.resolve(os3.homedir());
30136
+ const home = path35.resolve(os4.homedir());
29820
30137
  const parsed = path35.parse(resolved);
29821
30138
  return fileIdentityKey(resolved) === fileIdentityKey(home) || fileIdentityKey(resolved) === fileIdentityKey(parsed.root);
29822
30139
  }
@@ -29840,7 +30157,7 @@ function findRepositoryAnchor(projectRoot) {
29840
30157
  return { anchor: start, layer: "project" };
29841
30158
  }
29842
30159
  function resolveCodegraphUserCacheRoot2() {
29843
- const base = process.platform === "win32" ? process.env.LOCALAPPDATA?.trim() || path35.join(os3.homedir(), "AppData", "Local") : process.env.XDG_CACHE_HOME?.trim() || path35.join(os3.homedir(), ".cache");
30160
+ const base = process.platform === "win32" ? process.env.LOCALAPPDATA?.trim() || path35.join(os4.homedir(), "AppData", "Local") : process.env.XDG_CACHE_HOME?.trim() || path35.join(os4.homedir(), ".cache");
29844
30161
  return path35.join(base, "codegraph");
29845
30162
  }
29846
30163
  function projectCacheNamespace(projectRoot, anchor) {
@@ -29874,7 +30191,33 @@ function assertAnchorNotForbidden(resolution) {
29874
30191
  return;
29875
30192
  if (!isForbiddenAnchor(resolution.anchor))
29876
30193
  return;
29877
- throw new Error(`Cache anchor "${resolution.anchor}" (from ${CONFIGURED_ANCHOR_LAYER_LABELS[resolution.layer]}) is the home directory or a filesystem root. codegraph refuses to use it as a cache root. Use a subdirectory instead, e.g. "${path35.join(resolution.anchor, ".codegraph-cache")}".`);
30194
+ throw new Error(`Cache anchor "${resolution.anchor}" (from ${CONFIGURED_ANCHOR_LAYER_LABELS[resolution.layer]}) is the home directory or a filesystem root. codegraph refuses to use it as a cache root. Use a subdirectory instead, e.g. "${path35.join(resolution.anchor, PROJECT_CACHE_DIRECTORY)}".`);
30195
+ }
30196
+ function migrateLegacyProjectCache(candidate, legacyCandidates, logLevel) {
30197
+ if (fs16.existsSync(candidate))
30198
+ return;
30199
+ const legacy = legacyCandidates.find(({ path: legacyPath }) => fs16.existsSync(legacyPath));
30200
+ if (!legacy)
30201
+ return;
30202
+ try {
30203
+ fs16.mkdirSync(path35.dirname(candidate), { recursive: true });
30204
+ fs16.renameSync(legacy.path, candidate);
30205
+ } catch (error51) {
30206
+ if (logLevel === "debug")
30207
+ console.debug(`Unable to migrate legacy cache from ${legacy.path}:`, error51);
30208
+ return;
30209
+ }
30210
+ let current = path35.dirname(legacy.path);
30211
+ for (; ; ) {
30212
+ try {
30213
+ fs16.rmdirSync(current);
30214
+ } catch {
30215
+ break;
30216
+ }
30217
+ if (fileIdentityKey(current) === fileIdentityKey(legacy.cleanupBoundary))
30218
+ break;
30219
+ current = path35.dirname(current);
30220
+ }
29878
30221
  }
29879
30222
  function resolveCacheLocation(projectRoot, opts) {
29880
30223
  const root = path35.resolve(projectRoot);
@@ -29885,34 +30228,57 @@ function resolveCacheLocation(projectRoot, opts) {
29885
30228
  const anchor = anchorWritable ? resolution.anchor : root;
29886
30229
  const effectiveLayer = anchorWritable ? resolution.layer : "project";
29887
30230
  const sameRoot2 = fileIdentityKey(anchor) === fileIdentityKey(root);
29888
- if (sameRoot2 && !opts?.cacheDir && !process.env.CODEGRAPH_CACHE_DIR?.trim() && (!opts?.cacheLocation || opts.cacheLocation === "project")) {
29889
- return { path: path35.join(root, ".codegraph-cache", "index-v1"), anchor, layer: effectiveLayer };
30231
+ const explicitBase = opts?.cacheDir?.trim() || process.env.CODEGRAPH_CACHE_DIR?.trim();
30232
+ if (sameRoot2 && !explicitBase && (!opts?.cacheLocation || opts.cacheLocation === "project")) {
30233
+ const candidate2 = path35.join(root, PROJECT_CACHE_RELATIVE_PATH);
30234
+ migrateLegacyProjectCache(candidate2, [
30235
+ {
30236
+ path: path35.join(root, LEGACY_PROJECT_CACHE_RELATIVE_PATH),
30237
+ cleanupBoundary: path35.join(root, ".codegraph-cache")
30238
+ }
30239
+ ], opts?.logLevel);
30240
+ return { path: candidate2, anchor, layer: effectiveLayer };
29890
30241
  }
29891
30242
  const namespace = projectCacheNamespace(root, effectiveLayer === "git" || effectiveLayer === "manifest" ? anchor : void 0);
29892
- const explicitBase = opts?.cacheDir?.trim() || process.env.CODEGRAPH_CACHE_DIR?.trim();
29893
30243
  if (explicitBase) {
29894
30244
  const configured = path35.resolve(explicitBase);
29895
30245
  const configuredPath = path35.basename(configured) === namespace ? configured : path35.join(configured, namespace);
29896
30246
  return { path: configuredPath, anchor, layer: effectiveLayer };
29897
30247
  }
29898
- const base = opts?.cacheLocation === "user" ? resolveCodegraphUserCacheRoot2() : path35.join(anchor, ".codegraph-cache");
29899
- const candidate = path35.join(path35.resolve(base), "index-v1", namespace);
29900
- if (!sameRoot2 && !opts?.cacheLocation) {
29901
- const legacy = path35.join(root, ".codegraph-cache", "index-v1");
29902
- if (!fs16.existsSync(candidate) && fs16.existsSync(legacy)) {
29903
- return { path: legacy, anchor: root, layer: "project" };
30248
+ if (opts?.cacheLocation === "user") {
30249
+ return {
30250
+ path: path35.join(resolveCodegraphUserCacheRoot2(), "index-v1", namespace),
30251
+ anchor,
30252
+ layer: effectiveLayer
30253
+ };
30254
+ }
30255
+ const candidate = path35.join(anchor, PROJECT_CACHE_RELATIVE_PATH, namespace);
30256
+ const legacyCandidates = [
30257
+ {
30258
+ path: path35.join(anchor, LEGACY_PROJECT_CACHE_RELATIVE_PATH, namespace),
30259
+ cleanupBoundary: path35.join(anchor, ".codegraph-cache")
29904
30260
  }
30261
+ ];
30262
+ if (!sameRoot2 && !opts?.cacheLocation) {
30263
+ legacyCandidates.push({
30264
+ path: path35.join(root, LEGACY_PROJECT_CACHE_RELATIVE_PATH),
30265
+ cleanupBoundary: path35.join(root, ".codegraph-cache")
30266
+ });
29905
30267
  }
30268
+ migrateLegacyProjectCache(candidate, legacyCandidates, opts?.logLevel);
29906
30269
  return { path: candidate, anchor, layer: effectiveLayer };
29907
30270
  }
29908
30271
  function cacheRoot(projectRoot, opts) {
29909
30272
  return resolveCacheLocation(projectRoot, opts).path;
29910
30273
  }
29911
- var CONFIGURED_ANCHOR_LAYER_LABELS;
30274
+ var PROJECT_CACHE_DIRECTORY, PROJECT_CACHE_RELATIVE_PATH, LEGACY_PROJECT_CACHE_RELATIVE_PATH, CONFIGURED_ANCHOR_LAYER_LABELS;
29912
30275
  var init_location = __esm({
29913
30276
  "dist/indexer/build-cache/location.js"() {
29914
30277
  "use strict";
29915
30278
  init_paths();
30279
+ PROJECT_CACHE_DIRECTORY = ".codegraph";
30280
+ PROJECT_CACHE_RELATIVE_PATH = path35.join(PROJECT_CACHE_DIRECTORY, "cache", "index-v1");
30281
+ LEGACY_PROJECT_CACHE_RELATIVE_PATH = path35.join(".codegraph-cache", "index-v1");
29916
30282
  CONFIGURED_ANCHOR_LAYER_LABELS = {
29917
30283
  explicit: "--cache-dir (or an absolute cache.location)",
29918
30284
  environment: "CODEGRAPH_CACHE_DIR",
@@ -30158,7 +30524,7 @@ var init_options2 = __esm({
30158
30524
  import { brotliCompressSync, brotliDecompressSync, constants as zlibConstants } from "node:zlib";
30159
30525
  import crypto6 from "node:crypto";
30160
30526
  import fs17 from "node:fs";
30161
- import fsp17 from "node:fs/promises";
30527
+ import fsp18 from "node:fs/promises";
30162
30528
  import path37 from "node:path";
30163
30529
  function memoryCacheKey(projectRoot, file2) {
30164
30530
  return `${normalizePath(projectRoot)}::${file2}`;
@@ -30310,14 +30676,14 @@ function pruneDiskModuleCache(projectRoot, liveFiles, opts) {
30310
30676
  }
30311
30677
  }
30312
30678
  async function fileContentHash(file2) {
30313
- const buffer = await fsp17.readFile(file2);
30679
+ const buffer = await fsp18.readFile(file2);
30314
30680
  const hash2 = crypto6.createHash("sha1");
30315
30681
  hash2.update(buffer);
30316
30682
  return hash2.digest("hex");
30317
30683
  }
30318
30684
  async function fileStatSignature(file2, strict, opts) {
30319
30685
  try {
30320
- const stat = await fsp17.stat(file2);
30686
+ const stat = await fsp18.stat(file2);
30321
30687
  const useStrict = strict ?? true;
30322
30688
  const shouldHash = useStrict || !!opts?.includeContentHash;
30323
30689
  const contentHash = shouldHash ? await fileContentHash(file2) : void 0;
@@ -30375,7 +30741,7 @@ async function cacheSignatureForFile(file2, sigInfo, opts) {
30375
30741
  }
30376
30742
  async function buildBloomFilterForFile(file2, opts) {
30377
30743
  try {
30378
- const source = await fsp17.readFile(file2, "utf8");
30744
+ const source = await fsp18.readFile(file2, "utf8");
30379
30745
  const support = supportForFile(file2, opts?.languageExtensions);
30380
30746
  if (!support)
30381
30747
  return null;
@@ -31085,7 +31451,7 @@ __export(units_exports, {
31085
31451
  shouldKeepUnit: () => shouldKeepUnit
31086
31452
  });
31087
31453
  import crypto7 from "node:crypto";
31088
- import fsp18 from "node:fs/promises";
31454
+ import fsp19 from "node:fs/promises";
31089
31455
  import path39 from "node:path";
31090
31456
  function lineSpan(unit) {
31091
31457
  return Math.max(1, unit.endLine - unit.startLine + 1);
@@ -31474,7 +31840,7 @@ async function buildDuplicateUnitsForFile(index, file2, projectRoot, minTokens,
31474
31840
  let source = index.parsed?.get(fileIdentityKey(file2))?.source;
31475
31841
  if (source === void 0) {
31476
31842
  try {
31477
- source = await fsp18.readFile(file2, "utf8");
31843
+ source = await fsp19.readFile(file2, "utf8");
31478
31844
  } catch {
31479
31845
  return [];
31480
31846
  }
@@ -48363,8 +48729,8 @@ __export(config_exports, {
48363
48729
  mergeDiscoveryOptions: () => mergeDiscoveryOptions,
48364
48730
  mergeGraphOptions: () => mergeGraphOptions
48365
48731
  });
48366
- import fsp19 from "node:fs/promises";
48367
- import os4 from "node:os";
48732
+ import fsp20 from "node:fs/promises";
48733
+ import os5 from "node:os";
48368
48734
  import path40 from "node:path";
48369
48735
  function uniq(values) {
48370
48736
  return Array.from(new Set(values.map((value) => value.trim().replace(/\\/g, "/")).filter(Boolean)));
@@ -48434,10 +48800,10 @@ function normalizeConfigLanguageExtensions(extensions) {
48434
48800
  return normalizeLanguageExtensions(extensions);
48435
48801
  }
48436
48802
  async function loadUserCacheLocation() {
48437
- const configRoot = process.platform === "win32" ? process.env.APPDATA?.trim() || path40.join(os4.homedir(), "AppData", "Roaming") : process.env.XDG_CONFIG_HOME?.trim() || path40.join(os4.homedir(), ".config");
48803
+ const configRoot = process.platform === "win32" ? process.env.APPDATA?.trim() || path40.join(os5.homedir(), "AppData", "Roaming") : process.env.XDG_CONFIG_HOME?.trim() || path40.join(os5.homedir(), ".config");
48438
48804
  const configPath = path40.join(configRoot, "codegraph", "config.json");
48439
48805
  try {
48440
- const parsedJson = JSON.parse(await fsp19.readFile(configPath, "utf8"));
48806
+ const parsedJson = JSON.parse(await fsp20.readFile(configPath, "utf8"));
48441
48807
  const parsed = codegraphConfigSchema.safeParse(parsedJson);
48442
48808
  if (!parsed.success)
48443
48809
  throw new Error(external_exports.prettifyError(parsed.error));
@@ -48453,7 +48819,7 @@ async function loadCodegraphConfig(projectRoot) {
48453
48819
  const configPath = path40.join(projectRoot, CODEGRAPH_CONFIG_FILE);
48454
48820
  let raw;
48455
48821
  try {
48456
- raw = await fsp19.readFile(configPath, "utf8");
48822
+ raw = await fsp20.readFile(configPath, "utf8");
48457
48823
  } catch (error51) {
48458
48824
  if (error51 instanceof Error && "code" in error51 && error51.code === "ENOENT") {
48459
48825
  return userCacheLocation ? { cache: { location: userCacheLocation } } : {};
@@ -48519,7 +48885,7 @@ var init_config = __esm({
48519
48885
  // dist/indexer/build-cache/manifest.js
48520
48886
  import crypto8 from "node:crypto";
48521
48887
  import fs19 from "node:fs";
48522
- import fsp20, {} from "node:fs/promises";
48888
+ import fsp21, {} from "node:fs/promises";
48523
48889
  import path41 from "node:path";
48524
48890
  async function collectWorkspaceManifestDependencyEdges(projectRoot, manifestFiles, discovery, logLevel) {
48525
48891
  const manifestPaths = manifestFiles ?? await listProjectFiles(projectRoot, ["**/package.json"], {
@@ -48532,7 +48898,7 @@ async function collectWorkspaceManifestDependencyEdges(projectRoot, manifestFile
48532
48898
  const parsedByPath = /* @__PURE__ */ new Map();
48533
48899
  for (const manifestPath of manifestPaths) {
48534
48900
  try {
48535
- const raw = await fsp20.readFile(manifestPath, "utf8");
48901
+ const raw = await fsp21.readFile(manifestPath, "utf8");
48536
48902
  const parsed = JSON.parse(raw);
48537
48903
  parsedByPath.set(manifestPath, parsed);
48538
48904
  if (typeof parsed.name === "string" && parsed.name.trim()) {
@@ -48605,7 +48971,7 @@ function normalizeIndexedFileInputs(projectRoot, files, label, realProjectRoot)
48605
48971
  return [...filesByIdentity.values()];
48606
48972
  }
48607
48973
  async function normalizeIndexedFileInputsWithinRoot(projectRoot, files, label) {
48608
- const realRoot = await fsp20.realpath(projectRoot);
48974
+ const realRoot = await fsp21.realpath(projectRoot);
48609
48975
  const normalized = normalizeIndexedFileInputs(projectRoot, files, label, realRoot);
48610
48976
  await Promise.all(normalized.map(async (file2) => {
48611
48977
  try {
@@ -48673,7 +49039,7 @@ async function computeConfigHash(projectRoot, logLevel) {
48673
49039
  let firstError;
48674
49040
  for (const file2 of configFiles) {
48675
49041
  try {
48676
- const content = await fsp20.readFile(file2, "utf8");
49042
+ const content = await fsp21.readFile(file2, "utf8");
48677
49043
  const relative = path41.relative(projectRoot, file2).replace(/\\/g, "/");
48678
49044
  hash2.update(relative);
48679
49045
  hash2.update(content);
@@ -48719,7 +49085,7 @@ function isManifestTempName(name, manifestName) {
48719
49085
  async function cleanupStaleManifestTemps(manifestPath) {
48720
49086
  let entries;
48721
49087
  try {
48722
- entries = await fsp20.readdir(path41.dirname(manifestPath), { withFileTypes: true });
49088
+ entries = await fsp21.readdir(path41.dirname(manifestPath), { withFileTypes: true });
48723
49089
  } catch {
48724
49090
  return;
48725
49091
  }
@@ -48730,15 +49096,15 @@ async function cleanupStaleManifestTemps(manifestPath) {
48730
49096
  continue;
48731
49097
  const candidate = path41.join(path41.dirname(manifestPath), entry.name);
48732
49098
  try {
48733
- const stat = await fsp20.lstat(candidate);
49099
+ const stat = await fsp21.lstat(candidate);
48734
49100
  if (stat.mtimeMs <= cutoff)
48735
- await fsp20.rm(candidate, { force: true });
49101
+ await fsp21.rm(candidate, { force: true });
48736
49102
  } catch {
48737
49103
  }
48738
49104
  }
48739
49105
  }
48740
49106
  async function fsyncManifestFile(filePath) {
48741
- const handle = await fsp20.open(filePath, "r+");
49107
+ const handle = await fsp21.open(filePath, "r+");
48742
49108
  try {
48743
49109
  await handle.sync();
48744
49110
  } finally {
@@ -48748,7 +49114,7 @@ async function fsyncManifestFile(filePath) {
48748
49114
  async function syncManifestDirectory(directoryPath) {
48749
49115
  let directory;
48750
49116
  try {
48751
- directory = await fsp20.open(directoryPath, "r");
49117
+ directory = await fsp21.open(directoryPath, "r");
48752
49118
  await directory.sync();
48753
49119
  } catch {
48754
49120
  } finally {
@@ -48771,14 +49137,14 @@ async function writeManifestAtomically(manifestPath, payload) {
48771
49137
  for (let attempt = 0; attempt <= retryDelays.length; attempt += 1) {
48772
49138
  const tempPath = manifestTempFilePath(manifestPath);
48773
49139
  try {
48774
- await fsp20.writeFile(tempPath, payload, "utf8");
49140
+ await fsp21.writeFile(tempPath, payload, "utf8");
48775
49141
  await fsyncManifestFile(tempPath);
48776
- await fsp20.rename(tempPath, manifestPath);
49142
+ await fsp21.rename(tempPath, manifestPath);
48777
49143
  await syncManifestDirectory(path41.dirname(manifestPath));
48778
49144
  return;
48779
49145
  } catch (error51) {
48780
49146
  try {
48781
- await fsp20.rm(tempPath, { force: true });
49147
+ await fsp21.rm(tempPath, { force: true });
48782
49148
  } catch {
48783
49149
  }
48784
49150
  const canRetry = attempt < retryDelays.length && isTransientFileContentionError(error51);
@@ -48801,7 +49167,7 @@ function recordManifestCorruption(report, artifact, reason, logLevel) {
48801
49167
  async function loadManifest(projectRoot, opts, report) {
48802
49168
  const manifestPath = manifestFilePath(projectRoot, opts);
48803
49169
  try {
48804
- const raw = await fsp20.readFile(manifestPath, "utf8");
49170
+ const raw = await fsp21.readFile(manifestPath, "utf8");
48805
49171
  const parsed = JSON.parse(raw);
48806
49172
  let invalidReason;
48807
49173
  if (parsed.version !== MANIFEST_VERSION && parsed.version !== 4 && parsed.version !== 3) {
@@ -48837,7 +49203,7 @@ async function loadManifest(projectRoot, opts, report) {
48837
49203
  async function writeManifest2(projectRoot, opts, manifest) {
48838
49204
  try {
48839
49205
  const manifestPath = manifestFilePath(projectRoot, opts);
48840
- await fsp20.mkdir(path41.dirname(manifestPath), { recursive: true });
49206
+ await fsp21.mkdir(path41.dirname(manifestPath), { recursive: true });
48841
49207
  await writeManifestAtomically(manifestPath, JSON.stringify(manifest, null, 2));
48842
49208
  return true;
48843
49209
  } catch (error51) {
@@ -49612,7 +49978,7 @@ var init_symbol_graph = __esm({
49612
49978
  });
49613
49979
 
49614
49980
  // dist/indexer/build-cache/project-snapshot.js
49615
- import fsp21 from "node:fs/promises";
49981
+ import fsp22 from "node:fs/promises";
49616
49982
  import { createHash as createHash3, randomUUID } from "node:crypto";
49617
49983
  import { brotliCompressSync as brotliCompressSync3, brotliDecompressSync as brotliDecompressSync3, constants as zlibConstants3 } from "node:zlib";
49618
49984
  import path43 from "node:path";
@@ -49763,7 +50129,7 @@ function sameSnapshotFileIdentity(left, right) {
49763
50129
  return left.size === right.size && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs;
49764
50130
  }
49765
50131
  async function snapshotFileIdentity(snapshotPath) {
49766
- const stat = await fsp21.stat(snapshotPath);
50132
+ const stat = await fsp22.stat(snapshotPath);
49767
50133
  return {
49768
50134
  size: stat.size,
49769
50135
  mtimeMs: stat.mtimeMs,
@@ -49771,7 +50137,7 @@ async function snapshotFileIdentity(snapshotPath) {
49771
50137
  };
49772
50138
  }
49773
50139
  async function fsyncSnapshotFile(snapshotPath) {
49774
- const handle = await fsp21.open(snapshotPath, "r+");
50140
+ const handle = await fsp22.open(snapshotPath, "r+");
49775
50141
  try {
49776
50142
  await handle.sync();
49777
50143
  } finally {
@@ -49788,7 +50154,7 @@ function isSnapshotTempName(name, snapshotName) {
49788
50154
  async function cleanupStaleSnapshotTemps(snapshotPath) {
49789
50155
  let entries;
49790
50156
  try {
49791
- entries = await fsp21.readdir(path43.dirname(snapshotPath), { withFileTypes: true });
50157
+ entries = await fsp22.readdir(path43.dirname(snapshotPath), { withFileTypes: true });
49792
50158
  } catch {
49793
50159
  return;
49794
50160
  }
@@ -49799,26 +50165,26 @@ async function cleanupStaleSnapshotTemps(snapshotPath) {
49799
50165
  continue;
49800
50166
  const candidate = path43.join(path43.dirname(snapshotPath), entry.name);
49801
50167
  try {
49802
- const stat = await fsp21.lstat(candidate);
50168
+ const stat = await fsp22.lstat(candidate);
49803
50169
  if (stat.mtimeMs > cutoff)
49804
50170
  continue;
49805
- await fsp21.rm(candidate, { force: true });
50171
+ await fsp22.rm(candidate, { force: true });
49806
50172
  } catch {
49807
50173
  }
49808
50174
  }
49809
50175
  }
49810
50176
  async function writeSnapshotAtomically(snapshotPath, data) {
49811
- await fsp21.mkdir(path43.dirname(snapshotPath), { recursive: true });
50177
+ await fsp22.mkdir(path43.dirname(snapshotPath), { recursive: true });
49812
50178
  await cleanupStaleSnapshotTemps(snapshotPath);
49813
50179
  let tempPath = path43.join(path43.dirname(snapshotPath), `.${path43.basename(snapshotPath)}.${process.pid}.${randomUUID()}${SNAPSHOT_TEMP_SUFFIX}`);
49814
50180
  try {
49815
- await fsp21.writeFile(tempPath, data, { flag: "wx" });
50181
+ await fsp22.writeFile(tempPath, data, { flag: "wx" });
49816
50182
  await fsyncSnapshotFile(tempPath);
49817
- await fsp21.rename(tempPath, snapshotPath);
50183
+ await fsp22.rename(tempPath, snapshotPath);
49818
50184
  tempPath = void 0;
49819
50185
  } finally {
49820
50186
  if (tempPath) {
49821
- await fsp21.rm(tempPath, { force: true }).catch(() => void 0);
50187
+ await fsp22.rm(tempPath, { force: true }).catch(() => void 0);
49822
50188
  }
49823
50189
  }
49824
50190
  }
@@ -49873,7 +50239,7 @@ async function readParsedSnapshot(snapshotPath) {
49873
50239
  setBoundedSnapshotCache(parsedSnapshotCache, snapshotPath, cached2);
49874
50240
  return { identity: cached2.identity, payload: cached2.payload };
49875
50241
  }
49876
- const compressed = Buffer.from(await fsp21.readFile(snapshotPath));
50242
+ const compressed = Buffer.from(await fsp22.readFile(snapshotPath));
49877
50243
  const payload = decodeSnapshotPayload(compressed);
49878
50244
  const after = await snapshotFileIdentity(snapshotPath);
49879
50245
  const entry = { identity: before, compressed, payload };
@@ -50393,11 +50759,11 @@ function sameSymbolEdgeMultiset(expected, actual) {
50393
50759
  return false;
50394
50760
  const counts = /* @__PURE__ */ new Map();
50395
50761
  for (const edge of expected) {
50396
- const key = serializedSymbolEdge(edge);
50762
+ const key = symbolEdgeKey(edge);
50397
50763
  counts.set(key, (counts.get(key) ?? 0) + 1);
50398
50764
  }
50399
50765
  for (const edge of actual) {
50400
- const key = serializedSymbolEdge(edge);
50766
+ const key = symbolEdgeKey(edge);
50401
50767
  const remaining = counts.get(key);
50402
50768
  if (remaining === void 0)
50403
50769
  return false;
@@ -50408,27 +50774,30 @@ function sameSymbolEdgeMultiset(expected, actual) {
50408
50774
  }
50409
50775
  return !counts.size;
50410
50776
  }
50411
- function serializedSymbolEdge(edge) {
50412
- return JSON.stringify({
50413
- from: edge.from,
50414
- to: edge.to,
50415
- label: edge.label,
50416
- site: edge.site ? {
50417
- file: edge.site.file,
50418
- range: {
50419
- start: {
50420
- line: edge.site.range.start.line,
50421
- column: edge.site.range.start.column,
50422
- ...edge.site.range.start.index !== void 0 ? { index: edge.site.range.start.index } : {}
50423
- },
50424
- end: {
50425
- line: edge.site.range.end.line,
50426
- column: edge.site.range.end.column,
50427
- ...edge.site.range.end.index !== void 0 ? { index: edge.site.range.end.index } : {}
50428
- }
50429
- }
50430
- } : void 0
50431
- });
50777
+ function symbolEdgeKey(edge) {
50778
+ const from = lengthPrefixedSymbolEdgeField(edge.from);
50779
+ const to = lengthPrefixedSymbolEdgeField(edge.to);
50780
+ const label = edge.label === void 0 ? "u" : `s${lengthPrefixedSymbolEdgeField(edge.label)}`;
50781
+ if (!edge.site)
50782
+ return `f${from}t${to}l${label}s`;
50783
+ const { file: file2, range } = edge.site;
50784
+ const startIndex = range.start.index === void 0 ? "u" : `n${range.start.index}`;
50785
+ const endIndex = range.end.index === void 0 ? "u" : `n${range.end.index}`;
50786
+ return [
50787
+ `f${from}`,
50788
+ `t${to}`,
50789
+ `l${label}`,
50790
+ `s${lengthPrefixedSymbolEdgeField(file2)}`,
50791
+ `sl${range.start.line}`,
50792
+ `sc${range.start.column}`,
50793
+ `si${startIndex}`,
50794
+ `el${range.end.line}`,
50795
+ `ec${range.end.column}`,
50796
+ `ei${endIndex}`
50797
+ ].join("");
50798
+ }
50799
+ function lengthPrefixedSymbolEdgeField(value) {
50800
+ return `${value.length}:${value}`;
50432
50801
  }
50433
50802
  function normalizedSnapshotNativeMode(nativeMode) {
50434
50803
  if (nativeMode === void 0 || nativeMode === "auto")
@@ -50735,21 +51104,21 @@ var init_build_cache = __esm({
50735
51104
 
50736
51105
  // dist/indexer/build-cache/resolver-environment.js
50737
51106
  import crypto9 from "node:crypto";
50738
- import fsp22 from "node:fs/promises";
51107
+ import fsp23 from "node:fs/promises";
50739
51108
  import path44 from "node:path";
50740
51109
  function normalizedRelativePath(projectRoot, target) {
50741
51110
  return path44.relative(projectRoot, target).replace(/\\/g, "/");
50742
51111
  }
50743
51112
  async function statInput(projectRoot, target) {
50744
51113
  try {
50745
- const stat = await fsp22.stat(target);
51114
+ const stat = await fsp23.stat(target);
50746
51115
  if (!stat.isFile())
50747
51116
  return null;
50748
51117
  const sampleBytes = Math.min(stat.size, RESOLUTION_INPUT_SAMPLE_BYTES);
50749
51118
  const hash2 = crypto9.createHash("sha256");
50750
51119
  if (sampleBytes) {
50751
51120
  const first = Buffer.allocUnsafe(sampleBytes);
50752
- const handle = await fsp22.open(target, "r");
51121
+ const handle = await fsp23.open(target, "r");
50753
51122
  try {
50754
51123
  const firstRead = await handle.read(first, 0, sampleBytes, 0);
50755
51124
  hash2.update(first.subarray(0, firstRead.bytesRead));
@@ -50797,7 +51166,7 @@ async function activeNodeModulesRoots(projectRoot, files) {
50797
51166
  return null;
50798
51167
  const roots = await Promise.all(candidates.map(async (candidate) => {
50799
51168
  try {
50800
- return (await fsp22.stat(candidate)).isDirectory() ? candidate : null;
51169
+ return (await fsp23.stat(candidate)).isDirectory() ? candidate : null;
50801
51170
  } catch {
50802
51171
  return null;
50803
51172
  }
@@ -50807,7 +51176,7 @@ async function activeNodeModulesRoots(projectRoot, files) {
50807
51176
  async function installedPackageManifests(projectRoot, root) {
50808
51177
  let entries;
50809
51178
  try {
50810
- entries = await fsp22.readdir(root, { withFileTypes: true });
51179
+ entries = await fsp23.readdir(root, { withFileTypes: true });
50811
51180
  } catch {
50812
51181
  return [];
50813
51182
  }
@@ -50818,7 +51187,7 @@ async function installedPackageManifests(projectRoot, root) {
50818
51187
  const entryPath = path44.join(root, entry.name);
50819
51188
  if (entry.name.startsWith("@")) {
50820
51189
  try {
50821
- const scopedEntries = await fsp22.readdir(entryPath, { withFileTypes: true });
51190
+ const scopedEntries = await fsp23.readdir(entryPath, { withFileTypes: true });
50822
51191
  for (const scopedEntry of scopedEntries) {
50823
51192
  if (scopedEntry.isDirectory())
50824
51193
  packageDirectories.push(path44.join(entryPath, scopedEntry.name));
@@ -50969,7 +51338,7 @@ var init_finalize = __esm({
50969
51338
  });
50970
51339
 
50971
51340
  // dist/indexer/build-manifest.js
50972
- import fsp23 from "node:fs/promises";
51341
+ import fsp24 from "node:fs/promises";
50973
51342
  import path45 from "node:path";
50974
51343
  import { performance as performance3 } from "node:perf_hooks";
50975
51344
  function toManifestFileEntry(entry) {
@@ -50989,7 +51358,7 @@ async function writeIndexManifestSnapshot(args) {
50989
51358
  const writeManifestStart = performance3.now();
50990
51359
  const lastCommit = await getGitHead(args.projectRoot);
50991
51360
  const configHashResult = await computeConfigHash(args.projectRoot, args.opts?.logLevel);
50992
- const projectRootMtimeMs = args.symlinkDirectories !== void 0 ? (await fsp23.stat(args.projectRoot)).mtimeMs : void 0;
51361
+ const projectRootMtimeMs = args.symlinkDirectories !== void 0 ? (await fsp24.stat(args.projectRoot)).mtimeMs : void 0;
50993
51362
  const configHash = recordConfigHashResult(args.manifestReport, configHashResult, args.opts?.logLevel);
50994
51363
  const manifestData = {
50995
51364
  version: MANIFEST_VERSION,
@@ -52617,10 +52986,10 @@ var init_build_workers = __esm({
52617
52986
 
52618
52987
  // dist/indexer/incremental-plan.js
52619
52988
  import fs22 from "node:fs";
52620
- import fsp24 from "node:fs/promises";
52989
+ import fsp25 from "node:fs/promises";
52621
52990
  async function pathExists2(filePath) {
52622
52991
  try {
52623
- await fsp24.access(filePath);
52992
+ await fsp25.access(filePath);
52624
52993
  return true;
52625
52994
  } catch {
52626
52995
  return false;
@@ -52797,7 +53166,7 @@ __export(build_index_exports, {
52797
53166
  });
52798
53167
  import crypto10 from "node:crypto";
52799
53168
  import fs23 from "node:fs";
52800
- import fsp25 from "node:fs/promises";
53169
+ import fsp26 from "node:fs/promises";
52801
53170
  import path47 from "node:path";
52802
53171
  import { performance as performance5 } from "node:perf_hooks";
52803
53172
  function initParserBackendDegradationReport(report) {
@@ -53151,7 +53520,7 @@ async function buildIndexFromFileListShared(projectRoot, rawFiles, opts, helperO
53151
53520
  const shouldWriteManifest = manifestMode === "read-write";
53152
53521
  const projectFiles = helperOpts?.projectFiles;
53153
53522
  initManifestReport(report, useManifest, false);
53154
- const confinedRoot = helperOpts?.confineReads ? await fsp25.realpath(projectRoot) : void 0;
53523
+ const confinedRoot = helperOpts?.confineReads ? await fsp26.realpath(projectRoot) : void 0;
53155
53524
  const [trustedSources, normalizedFiles] = [
53156
53525
  confinedRoot ? /* @__PURE__ */ new Map() : void 0,
53157
53526
  Array.from(new Set(normalizeIndexedFileInputs(projectRoot, rawFiles ?? [], "Index file")))
@@ -53529,7 +53898,7 @@ async function buildProjectIndexWithManifestOptions(projectRoot, opts, helperOpt
53529
53898
  const useDiskCache = (opts?.cache ?? "off") === "disk";
53530
53899
  const wantsMaxSymlinkCorrectness = !!opts?.cacheStrict || !!opts?.cacheVerify;
53531
53900
  const symlinkHintManifest = helperOpts?.ignoreExistingManifest || wantsMaxSymlinkCorrectness || !useDiskCache ? null : await loadManifest(projectRoot, opts);
53532
- const rootMtime = symlinkHintManifest ? (await fsp25.stat(projectRoot)).mtimeMs : void 0;
53901
+ const rootMtime = symlinkHintManifest ? (await fsp26.stat(projectRoot)).mtimeMs : void 0;
53533
53902
  const symlinkHintIsFresh = symlinkHintManifest?.symlinkDirectoriesRootMtimeMs !== void 0 && rootMtime === symlinkHintManifest.symlinkDirectoriesRootMtimeMs;
53534
53903
  const knownSymlinkDirectories = symlinkHintIsFresh ? symlinkHintManifest?.symlinkDirectories : void 0;
53535
53904
  let discoveredSymlinkDirectories = knownSymlinkDirectories;
@@ -53886,7 +54255,7 @@ async function buildProjectIndexIncremental(projectRoot, opts) {
53886
54255
  if (!entry?.sig)
53887
54256
  return true;
53888
54257
  try {
53889
- const stat = await fsp25.stat(file2);
54258
+ const stat = await fsp26.stat(file2);
53890
54259
  const prefix = `${stat.mtimeMs}:${stat.size}`;
53891
54260
  return !(entry.sig === prefix || entry.sig.startsWith(`${prefix}:`));
53892
54261
  } catch {
@@ -55467,6 +55836,17 @@ function toolFollowUp(tool, arguments_ = {}, label) {
55467
55836
  ...label !== void 0 ? { label } : {}
55468
55837
  };
55469
55838
  }
55839
+ function dedupeAgentFollowUps(followUps) {
55840
+ const seen = /* @__PURE__ */ new Set();
55841
+ return followUps.filter((followUp) => {
55842
+ const serializedArguments = JSON.stringify(followUp.arguments);
55843
+ const key = `${followUp.tool.length}|${followUp.tool}${serializedArguments}`;
55844
+ if (seen.has(key))
55845
+ return false;
55846
+ seen.add(key);
55847
+ return true;
55848
+ });
55849
+ }
55470
55850
  function formatAgentFollowUpAsCli(followUp) {
55471
55851
  const args = followUp.arguments;
55472
55852
  switch (followUp.tool) {
@@ -55693,7 +56073,7 @@ var init_sessionLifecycle = __esm({
55693
56073
  });
55694
56074
 
55695
56075
  // dist/agent/session.js
55696
- import fsp26 from "node:fs/promises";
56076
+ import fsp27 from "node:fs/promises";
55697
56077
  import path49 from "node:path";
55698
56078
  function assertNoPrebuiltSessionWithBuildOptions(options, consumer) {
55699
56079
  if (options.session && options.buildOptions) {
@@ -55772,7 +56152,7 @@ async function collectAgentFileSignatures(files) {
55772
56152
  await mapLimit([...files], FILE_SIGNATURE_STAT_CONCURRENCY, async (file2) => {
55773
56153
  const resolvedFile = normalizePath(path49.resolve(file2));
55774
56154
  try {
55775
- const stat = await fsp26.stat(resolvedFile);
56155
+ const stat = await fsp27.stat(resolvedFile);
55776
56156
  if (!stat.isFile())
55777
56157
  return;
55778
56158
  signatures.set(resolvedFile, {
@@ -56133,13 +56513,13 @@ var init_errors4 = __esm({
56133
56513
  });
56134
56514
 
56135
56515
  // dist/lifecycle/gitignore.js
56136
- import fsp27 from "node:fs/promises";
56516
+ import fsp28 from "node:fs/promises";
56137
56517
  import path50 from "node:path";
56138
56518
  async function readGitignoreFile(gitignorePath) {
56139
56519
  let existing = "";
56140
56520
  let stats;
56141
56521
  try {
56142
- stats = await fsp27.lstat(gitignorePath);
56522
+ stats = await fsp28.lstat(gitignorePath);
56143
56523
  } catch (error51) {
56144
56524
  if (!(error51 instanceof Error && "code" in error51 && error51.code === "ENOENT")) {
56145
56525
  const detail = errorMessage(error51);
@@ -56156,7 +56536,7 @@ async function readGitignoreFile(gitignorePath) {
56156
56536
  throw new CodegraphLifecycleUserError(`Cannot update ${gitignorePath}: expected a regular file, but found a ${kind}. Replace it with a regular file or rerun with --no-update-gitignore.`);
56157
56537
  }
56158
56538
  try {
56159
- existing = await fsp27.readFile(gitignorePath, "utf8");
56539
+ existing = await fsp28.readFile(gitignorePath, "utf8");
56160
56540
  } catch (error51) {
56161
56541
  const detail = errorMessage(error51);
56162
56542
  throw new CodegraphLifecycleUserError(`Unable to read ${gitignorePath}: ${detail}. Check file permissions or rerun with --no-update-gitignore.`);
@@ -56178,43 +56558,38 @@ async function prepareCodegraphLifecycleGitignore(root, options = {}) {
56178
56558
  const manifestTracked = await isGitPathTracked(resolvedRoot, LIFECYCLE_MANIFEST_PATH);
56179
56559
  if (manifestTracked)
56180
56560
  return { status: "tracked", path: GITIGNORE_PATH };
56181
- const [lifecycleIgnored, diskCacheIgnored] = await Promise.all([
56182
- isGitPathIgnored(resolvedRoot, LIFECYCLE_MANIFEST_PATH),
56183
- isGitPathIgnored(resolvedRoot, DISK_CACHE_PROBE_PATH)
56184
- ]);
56185
- const missingRules = [
56186
- ...!lifecycleIgnored ? [LIFECYCLE_GITIGNORE_RULE] : [],
56187
- ...!diskCacheIgnored ? [DISK_CACHE_GITIGNORE_RULE] : []
56188
- ];
56189
- if (!missingRules.length) {
56561
+ const manifestIgnored = await isGitPathIgnored(resolvedRoot, LIFECYCLE_MANIFEST_PATH);
56562
+ const cacheIgnored = await isGitPathIgnored(resolvedRoot, LIFECYCLE_CACHE_PATH);
56563
+ if (manifestIgnored && cacheIgnored) {
56190
56564
  return { status: "already-ignored", path: GITIGNORE_PATH, rules: [...CODEGRAPH_GITIGNORE_RULES] };
56191
56565
  }
56566
+ const missingRules = [LIFECYCLE_GITIGNORE_RULE];
56192
56567
  const gitignorePath = path50.join(resolvedRoot, GITIGNORE_PATH);
56193
56568
  const { existing, newline } = await readGitignoreFile(gitignorePath);
56194
56569
  let suffix = missingRules.map((rule) => `${rule}${newline}`).join("");
56195
56570
  if (existing && !existing.endsWith("\n"))
56196
56571
  suffix = `${newline}${suffix}`;
56197
56572
  try {
56198
- await fsp27.appendFile(gitignorePath, suffix, "utf8");
56573
+ await fsp28.appendFile(gitignorePath, suffix, "utf8");
56199
56574
  } catch (error51) {
56200
56575
  const detail = errorMessage(error51);
56201
56576
  throw new CodegraphLifecycleUserError(`Unable to update ${gitignorePath}: ${detail}. Check file permissions or rerun with --no-update-gitignore.`);
56202
56577
  }
56203
56578
  return { status: "added", path: GITIGNORE_PATH, rules: missingRules };
56204
56579
  }
56205
- var LIFECYCLE_MANIFEST_PATH, DISK_CACHE_PROBE_PATH, GITIGNORE_PATH, LIFECYCLE_GITIGNORE_RULE, DISK_CACHE_GITIGNORE_RULE, CODEGRAPH_GITIGNORE_RULES;
56580
+ var LIFECYCLE_MANIFEST_PATH, LIFECYCLE_CACHE_PATH, GITIGNORE_PATH, LIFECYCLE_GITIGNORE_RULE, CODEGRAPH_GITIGNORE_RULES;
56206
56581
  var init_gitignore = __esm({
56207
56582
  "dist/lifecycle/gitignore.js"() {
56208
56583
  "use strict";
56584
+ init_location();
56209
56585
  init_git();
56210
56586
  init_errors();
56211
56587
  init_errors4();
56212
56588
  LIFECYCLE_MANIFEST_PATH = ".codegraph/manifest.json";
56213
- DISK_CACHE_PROBE_PATH = ".codegraph-cache/index-v1/manifest.json";
56589
+ LIFECYCLE_CACHE_PATH = PROJECT_CACHE_RELATIVE_PATH.replaceAll(path50.sep, "/");
56214
56590
  GITIGNORE_PATH = ".gitignore";
56215
56591
  LIFECYCLE_GITIGNORE_RULE = ".codegraph/";
56216
- DISK_CACHE_GITIGNORE_RULE = ".codegraph-cache/";
56217
- CODEGRAPH_GITIGNORE_RULES = [LIFECYCLE_GITIGNORE_RULE, DISK_CACHE_GITIGNORE_RULE];
56592
+ CODEGRAPH_GITIGNORE_RULES = [LIFECYCLE_GITIGNORE_RULE];
56218
56593
  }
56219
56594
  });
56220
56595
 
@@ -56230,7 +56605,7 @@ __export(manifest_exports, {
56230
56605
  uninitCodegraphLifecycle: () => uninitCodegraphLifecycle
56231
56606
  });
56232
56607
  import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypto";
56233
- import fsp28 from "node:fs/promises";
56608
+ import fsp29 from "node:fs/promises";
56234
56609
  import path51 from "node:path";
56235
56610
  function codegraphLifecycleManifestPath(root) {
56236
56611
  return path51.join(root, CODEGRAPH_DIR, MANIFEST_FILE);
@@ -56331,11 +56706,11 @@ async function uninitCodegraphLifecycle(root, options = {}) {
56331
56706
  if (!entries.length) {
56332
56707
  return { schemaVersion: MANIFEST_SCHEMA_VERSION, root, removed: false, manifestPath };
56333
56708
  }
56334
- const unknownEntries = entries.filter((entry) => !KNOWN_CODEGRAPH_FILES.has(entry));
56709
+ const unknownEntries = entries.filter((entry) => !Object.hasOwn(KNOWN_CODEGRAPH_FILES, entry));
56335
56710
  if (unknownEntries.length && !options.force) {
56336
56711
  throw new CodegraphLifecycleUserError(`Refusing to remove .codegraph with unknown entries: ${unknownEntries.join(", ")}. Use --force to remove them.`);
56337
56712
  }
56338
- const removableEntries = entries.filter((entry) => entry !== SERVER_REGISTRY_FILE && entry !== SERVER_LOG_FILE);
56713
+ const removableEntries = entries.filter((entry) => entry !== SERVER_REGISTRY_FILE && entry !== SERVER_LOG_FILE && entry !== CACHE_DIRECTORY);
56339
56714
  if (options.force) {
56340
56715
  for (const entry of removableEntries) {
56341
56716
  await removeCodegraphPath(path51.join(dir, entry), { recursive: true });
@@ -56375,7 +56750,7 @@ async function readLifecycleManifest(root, options = {}) {
56375
56750
  const manifestPath = codegraphLifecycleManifestPath(root);
56376
56751
  let raw;
56377
56752
  try {
56378
- raw = await fsp28.readFile(manifestPath, "utf8");
56753
+ raw = await fsp29.readFile(manifestPath, "utf8");
56379
56754
  } catch (error51) {
56380
56755
  if (error51 instanceof Error && "code" in error51 && error51.code === "ENOENT")
56381
56756
  return null;
@@ -56399,14 +56774,14 @@ function lifecycleManifestTempFilePath(manifestPath) {
56399
56774
  }
56400
56775
  async function writeLifecycleManifest(root, manifest) {
56401
56776
  const manifestPath = codegraphLifecycleManifestPath(root);
56402
- await fsp28.mkdir(path51.dirname(manifestPath), { recursive: true });
56777
+ await fsp29.mkdir(path51.dirname(manifestPath), { recursive: true });
56403
56778
  const tempPath = lifecycleManifestTempFilePath(manifestPath);
56404
56779
  try {
56405
- await fsp28.writeFile(tempPath, `${JSON.stringify(manifest, null, 2)}
56780
+ await fsp29.writeFile(tempPath, `${JSON.stringify(manifest, null, 2)}
56406
56781
  `, "utf8");
56407
- await fsp28.rename(tempPath, manifestPath);
56782
+ await fsp29.rename(tempPath, manifestPath);
56408
56783
  } catch (error51) {
56409
- await fsp28.rm(tempPath, { force: true }).catch(() => {
56784
+ await fsp29.rm(tempPath, { force: true }).catch(() => {
56410
56785
  });
56411
56786
  throw new CodegraphLifecycleUserError(`Unable to write Codegraph lifecycle manifest at ${manifestPath}: ${stringifyError(error51)}`);
56412
56787
  }
@@ -56447,7 +56822,7 @@ async function hashDiscoveredFiles(files, root) {
56447
56822
  const signatures = /* @__PURE__ */ new Map();
56448
56823
  await mapLimit(sorted, FILE_SIGNATURE_STAT_CONCURRENCY2, async (file2) => {
56449
56824
  try {
56450
- const stat = await fsp28.stat(file2);
56825
+ const stat = await fsp29.stat(file2);
56451
56826
  signatures.set(file2, { size: stat.size, mtimeMs: stat.mtimeMs });
56452
56827
  } catch (error51) {
56453
56828
  if (isMissingStatRace2(error51))
@@ -56506,18 +56881,18 @@ function sha256(value) {
56506
56881
  }
56507
56882
  async function removeCodegraphPath(target, options) {
56508
56883
  try {
56509
- await fsp28.rm(target, { ...options.recursive ? { recursive: true } : {}, force: true });
56884
+ await fsp29.rm(target, { ...options.recursive ? { recursive: true } : {}, force: true });
56510
56885
  } catch (error51) {
56511
56886
  throw new CodegraphLifecycleUserError(`Unable to remove ${target}: ${stringifyError(error51)}`);
56512
56887
  }
56513
56888
  }
56514
56889
  async function readCodegraphDirEntries(dir) {
56515
56890
  try {
56516
- const stats = await fsp28.lstat(dir);
56891
+ const stats = await fsp29.lstat(dir);
56517
56892
  if (stats.isSymbolicLink()) {
56518
56893
  throw new CodegraphLifecycleUserError(`Refusing to traverse symbolic link as .codegraph lifecycle directory: ${dir}`);
56519
56894
  }
56520
- return await fsp28.readdir(dir);
56895
+ return await fsp29.readdir(dir);
56521
56896
  } catch (error51) {
56522
56897
  if (error51 instanceof CodegraphLifecycleUserError)
56523
56898
  throw error51;
@@ -56528,7 +56903,7 @@ async function readCodegraphDirEntries(dir) {
56528
56903
  }
56529
56904
  async function removeDirIfEmpty(dir) {
56530
56905
  try {
56531
- await fsp28.rmdir(dir);
56906
+ await fsp29.rmdir(dir);
56532
56907
  } catch (error51) {
56533
56908
  if (error51 instanceof Error && "code" in error51 && error51.code === "ENOTEMPTY")
56534
56909
  return;
@@ -56550,7 +56925,7 @@ function isOptionalStringArray(value) {
56550
56925
  return true;
56551
56926
  return Array.isArray(value) && value.every((entry) => typeof entry === "string");
56552
56927
  }
56553
- var MANIFEST_SCHEMA_VERSION, CODEGRAPH_DIR, MANIFEST_FILE, SERVER_REGISTRY_FILE, SERVER_LOG_FILE, KNOWN_CODEGRAPH_FILES, FILE_SIGNATURE_STAT_CONCURRENCY2;
56928
+ var MANIFEST_SCHEMA_VERSION, CODEGRAPH_DIR, MANIFEST_FILE, SERVER_REGISTRY_FILE, SERVER_LOG_FILE, CACHE_DIRECTORY, KNOWN_CODEGRAPH_FILES, FILE_SIGNATURE_STAT_CONCURRENCY2;
56554
56929
  var init_manifest2 = __esm({
56555
56930
  "dist/lifecycle/manifest.js"() {
56556
56931
  "use strict";
@@ -56567,7 +56942,13 @@ var init_manifest2 = __esm({
56567
56942
  MANIFEST_FILE = "manifest.json";
56568
56943
  SERVER_REGISTRY_FILE = "server.json";
56569
56944
  SERVER_LOG_FILE = "server.log";
56570
- KNOWN_CODEGRAPH_FILES = /* @__PURE__ */ new Set([MANIFEST_FILE, SERVER_REGISTRY_FILE, SERVER_LOG_FILE]);
56945
+ CACHE_DIRECTORY = "cache";
56946
+ KNOWN_CODEGRAPH_FILES = {
56947
+ [MANIFEST_FILE]: true,
56948
+ [SERVER_REGISTRY_FILE]: true,
56949
+ [SERVER_LOG_FILE]: true,
56950
+ [CACHE_DIRECTORY]: true
56951
+ };
56571
56952
  FILE_SIGNATURE_STAT_CONCURRENCY2 = 64;
56572
56953
  }
56573
56954
  });
@@ -56620,7 +57001,7 @@ function formatSyncResult(label, result) {
56620
57001
  const summary = `${label} Codegraph at ${result.root}: ${result.manifest.fileCount} files${changeLabel}. Manifest: ${result.manifestPath}`;
56621
57002
  if (result.gitignore?.status === "added") {
56622
57003
  const gitignorePath = path52.join(result.root, result.gitignore.path);
56623
- const rules = result.gitignore.rules?.length ? result.gitignore.rules.join(", ") : ".codegraph/, .codegraph-cache/";
57004
+ const rules = result.gitignore.rules?.length ? result.gitignore.rules.join(", ") : ".codegraph/";
56624
57005
  return `${summary}
56625
57006
  Updated Git ignore policy at ${gitignorePath}: added ${rules}.`;
56626
57007
  }
@@ -57413,14 +57794,14 @@ __export(skill_exports, {
57413
57794
  getSkillTargetDirForAgent: () => getSkillTargetDirForAgent,
57414
57795
  handleSkillCommand: () => handleSkillCommand
57415
57796
  });
57416
- import fsp29 from "node:fs/promises";
57417
- import os5 from "node:os";
57797
+ import fsp30 from "node:fs/promises";
57798
+ import os6 from "node:os";
57418
57799
  import path53 from "node:path";
57419
57800
  function getBundledSkillDir(packageRoot) {
57420
57801
  const candidate = path53.join(packageRoot, "codegraph-skill", "codegraph");
57421
57802
  return pathExists(path53.join(candidate, "SKILL.md")) ? candidate : null;
57422
57803
  }
57423
- function getSkillTargetDirForAgent(agent, homeDir = os5.homedir(), env = process.env) {
57804
+ function getSkillTargetDirForAgent(agent, homeDir = os6.homedir(), env = process.env) {
57424
57805
  if (agent === "agents") {
57425
57806
  return path53.join(homeDir, ".agents", "skills", "codegraph");
57426
57807
  }
@@ -57481,10 +57862,10 @@ function isCommandAvailableOnPath(command) {
57481
57862
  }
57482
57863
  async function copyDirectoryRecursive(sourceDir, targetDir, overwrite) {
57483
57864
  if (overwrite && pathExists(targetDir)) {
57484
- await fsp29.rm(targetDir, { recursive: true, force: true });
57865
+ await fsp30.rm(targetDir, { recursive: true, force: true });
57485
57866
  }
57486
- await fsp29.mkdir(targetDir, { recursive: true });
57487
- const entries = await fsp29.readdir(sourceDir, { withFileTypes: true });
57867
+ await fsp30.mkdir(targetDir, { recursive: true });
57868
+ const entries = await fsp30.readdir(sourceDir, { withFileTypes: true });
57488
57869
  for (const entry of entries) {
57489
57870
  const sourcePath = path53.join(sourceDir, entry.name);
57490
57871
  const targetPath = path53.join(targetDir, entry.name);
@@ -57495,7 +57876,7 @@ async function copyDirectoryRecursive(sourceDir, targetDir, overwrite) {
57495
57876
  if (!overwrite && pathExists(targetPath)) {
57496
57877
  throw new Error(`Target file already exists: ${normalizePathForDisplay(targetPath)}. Re-run with --force to overwrite.`);
57497
57878
  }
57498
- await fsp29.copyFile(sourcePath, targetPath);
57879
+ await fsp30.copyFile(sourcePath, targetPath);
57499
57880
  }
57500
57881
  }
57501
57882
  function normalizePathSegmentForComparison(segment) {
@@ -57638,8 +58019,8 @@ var init_skill = __esm({
57638
58019
 
57639
58020
  // dist/installer/locks.js
57640
58021
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
57641
- import fsp30, {} from "node:fs/promises";
57642
- import os6 from "node:os";
58022
+ import fsp31, {} from "node:fs/promises";
58023
+ import os7 from "node:os";
57643
58024
  import path54 from "node:path";
57644
58025
  function opencodeConfigHome(settings) {
57645
58026
  const configHome = settings.env.XDG_CONFIG_HOME?.trim();
@@ -57675,7 +58056,7 @@ function installerTransactionLockScopes(settings) {
57675
58056
  }
57676
58057
  function installerTransactionLockPath(scope) {
57677
58058
  const digest = createHash5("sha256").update(scope).digest("hex");
57678
- return path54.join(os6.tmpdir(), `codegraph-installer-${digest}.lock`);
58059
+ return path54.join(os7.tmpdir(), `codegraph-installer-${digest}.lock`);
57679
58060
  }
57680
58061
  async function withInstallerLeaseLock(lockPath, resourceName, operation) {
57681
58062
  const lock = await acquireInstallerLeaseLock(lockPath, resourceName);
@@ -57693,14 +58074,14 @@ async function acquireInstallerLeaseLock(lockPath, resourceName) {
57693
58074
  let file2;
57694
58075
  try {
57695
58076
  const owner = randomUUID3();
57696
- file2 = await fsp30.open(acquisitionPath, "wx", 384);
58077
+ file2 = await fsp31.open(acquisitionPath, "wx", 384);
57697
58078
  await writeInstallerLeaseMetadata(file2, owner);
57698
58079
  await file2.close();
57699
58080
  file2 = void 0;
57700
- await fsp30.link(acquisitionPath, lockPath);
58081
+ await fsp31.link(acquisitionPath, lockPath);
57701
58082
  published = true;
57702
- await fsp30.rm(acquisitionPath, { force: true });
57703
- file2 = await fsp30.open(lockPath, "r+");
58083
+ await fsp31.rm(acquisitionPath, { force: true });
58084
+ file2 = await fsp31.open(lockPath, "r+");
57704
58085
  const lockFile = file2;
57705
58086
  const renewalTimer = setInterval(() => {
57706
58087
  void writeInstallerLeaseMetadata(lockFile, owner).catch(() => void 0);
@@ -57709,9 +58090,9 @@ async function acquireInstallerLeaseLock(lockPath, resourceName) {
57709
58090
  return { file: lockFile, lockPath, metadataPath: lockPath, owner, renewalTimer };
57710
58091
  } catch (error51) {
57711
58092
  await file2?.close().catch(() => void 0);
57712
- await fsp30.rm(acquisitionPath, { force: true }).catch(() => void 0);
58093
+ await fsp31.rm(acquisitionPath, { force: true }).catch(() => void 0);
57713
58094
  if (published)
57714
- await fsp30.rm(lockPath, { force: true }).catch(() => void 0);
58095
+ await fsp31.rm(lockPath, { force: true }).catch(() => void 0);
57715
58096
  if (!isInstallerLockPublishConflict(error51, lockPath))
57716
58097
  throw error51;
57717
58098
  const conflict = await resolveInstallerLeaseConflict(lockPath);
@@ -57729,10 +58110,10 @@ async function acquireInstallerLeaseLock(lockPath, resourceName) {
57729
58110
  async function resolveInstallerLeaseConflict(lockPath) {
57730
58111
  let observed;
57731
58112
  try {
57732
- const stats = await fsp30.lstat(lockPath);
58113
+ const stats = await fsp31.lstat(lockPath);
57733
58114
  if (!stats.isFile())
57734
58115
  return "blocked";
57735
- observed = { content: await fsp30.readFile(lockPath, "utf8"), mtimeMs: stats.mtimeMs };
58116
+ observed = { content: await fsp31.readFile(lockPath, "utf8"), mtimeMs: stats.mtimeMs };
57736
58117
  } catch (error51) {
57737
58118
  if (isFileSystemErrorCode(error51, "ENOENT"))
57738
58119
  return "absent";
@@ -57754,7 +58135,7 @@ async function resolveInstallerLeaseConflict(lockPath) {
57754
58135
  async function reclaimAbandonedInstallerLease(lockPath, observed) {
57755
58136
  const reclaimPath = `${lockPath}.reclaim-${process.pid}-${randomUUID3()}`;
57756
58137
  try {
57757
- await fsp30.rename(lockPath, reclaimPath);
58138
+ await fsp31.rename(lockPath, reclaimPath);
57758
58139
  } catch (error51) {
57759
58140
  if (isFileSystemErrorCode(error51, "ENOENT"))
57760
58141
  return "absent";
@@ -57762,20 +58143,20 @@ async function reclaimAbandonedInstallerLease(lockPath, observed) {
57762
58143
  }
57763
58144
  let verified = false;
57764
58145
  try {
57765
- const stats = await fsp30.lstat(reclaimPath);
57766
- verified = stats.isFile() && stats.mtimeMs === observed.mtimeMs && await fsp30.readFile(reclaimPath, "utf8") === observed.content;
58146
+ const stats = await fsp31.lstat(reclaimPath);
58147
+ verified = stats.isFile() && stats.mtimeMs === observed.mtimeMs && await fsp31.readFile(reclaimPath, "utf8") === observed.content;
57767
58148
  } catch {
57768
58149
  verified = false;
57769
58150
  }
57770
58151
  if (verified) {
57771
- await fsp30.rm(reclaimPath, { force: true });
58152
+ await fsp31.rm(reclaimPath, { force: true });
57772
58153
  return "reclaimed";
57773
58154
  }
57774
58155
  try {
57775
- await fsp30.link(reclaimPath, lockPath);
58156
+ await fsp31.link(reclaimPath, lockPath);
57776
58157
  } catch {
57777
58158
  }
57778
- await fsp30.rm(reclaimPath, { force: true });
58159
+ await fsp31.rm(reclaimPath, { force: true });
57779
58160
  return "blocked";
57780
58161
  }
57781
58162
  function isInstallerLockPublishConflict(error51, lockPath) {
@@ -57812,9 +58193,9 @@ async function releaseInstallerLeaseLock(lock) {
57812
58193
  clearInterval(lock.renewalTimer);
57813
58194
  await lock.file.close().catch(() => void 0);
57814
58195
  try {
57815
- const metadata = parseInstallerLeaseMetadata(await fsp30.readFile(lock.metadataPath, "utf8"));
58196
+ const metadata = parseInstallerLeaseMetadata(await fsp31.readFile(lock.metadataPath, "utf8"));
57816
58197
  if (metadata?.owner === lock.owner)
57817
- await fsp30.rm(lock.lockPath, { force: true });
58198
+ await fsp31.rm(lock.lockPath, { force: true });
57818
58199
  } catch (error51) {
57819
58200
  if (!isFileSystemErrorCode(error51, "ENOENT"))
57820
58201
  throw error51;
@@ -57840,8 +58221,8 @@ var init_locks = __esm({
57840
58221
 
57841
58222
  // dist/installer/registry.js
57842
58223
  import { createHash as createHash6, randomUUID as randomUUID4 } from "node:crypto";
57843
- import fsp31 from "node:fs/promises";
57844
- import os7 from "node:os";
58224
+ import fsp32 from "node:fs/promises";
58225
+ import os8 from "node:os";
57845
58226
  import path55 from "node:path";
57846
58227
  import { applyEdits, modify, parse as parseJsonc2 } from "jsonc-parser";
57847
58228
  function listInstallTargets() {
@@ -57968,7 +58349,7 @@ async function installDefinitions(definitions, options) {
57968
58349
  return await withInstallerTransactionLocks(settings, install);
57969
58350
  }
57970
58351
  async function prepareInstallPlan(definitions, settings, dryRun, force) {
57971
- const bundledSkill = await fsp31.readFile(bundledSkillFilePath());
58352
+ const bundledSkill = await fsp32.readFile(bundledSkillFilePath());
57972
58353
  const files = [];
57973
58354
  const conflicts = [];
57974
58355
  for (const definition of definitions) {
@@ -58096,7 +58477,7 @@ async function rollbackInstallPlan(plan) {
58096
58477
  }
58097
58478
  async function removePlannedInstallFile(filePath, root) {
58098
58479
  await assertNoSymbolicLinksWithinRoot(filePath, root);
58099
- await fsp31.rm(filePath, { force: true });
58480
+ await fsp32.rm(filePath, { force: true });
58100
58481
  }
58101
58482
  async function verifyInstallPlan(plan, verifyInstalledConfig) {
58102
58483
  for (const file2 of plan.files) {
@@ -58590,7 +58971,7 @@ function installerSettings(options) {
58590
58971
  if (env === void 0)
58591
58972
  env = options.homeDir === void 0 ? process.env : {};
58592
58973
  return {
58593
- homeDir: options.homeDir ?? os7.homedir(),
58974
+ homeDir: options.homeDir ?? os8.homedir(),
58594
58975
  env
58595
58976
  };
58596
58977
  }
@@ -58607,14 +58988,14 @@ function bundledSkillFilePath() {
58607
58988
  async function snapshotInstallFile(filePath, root) {
58608
58989
  await assertNoSymbolicLinksWithinRoot(filePath, root);
58609
58990
  try {
58610
- const stats = await fsp31.lstat(filePath);
58991
+ const stats = await fsp32.lstat(filePath);
58611
58992
  if (stats.isSymbolicLink())
58612
58993
  throw unsafeSymbolicLinkError(filePath);
58613
58994
  if (!stats.isFile()) {
58614
58995
  throw new Error(`Expected a regular file at ${normalizePathForDisplay(filePath)} before running the installer.`);
58615
58996
  }
58616
58997
  return {
58617
- bytes: await fsp31.readFile(filePath),
58998
+ bytes: await fsp32.readFile(filePath),
58618
58999
  mode: stats.mode & 4095
58619
59000
  };
58620
59001
  } catch (error51) {
@@ -58634,7 +59015,7 @@ async function assertNoSymbolicLinksWithinRoot(filePath, root) {
58634
59015
  }
58635
59016
  for (let current = resolvedFilePath; current !== resolvedRoot; current = path55.dirname(current)) {
58636
59017
  try {
58637
- const stats = await fsp31.lstat(current);
59018
+ const stats = await fsp32.lstat(current);
58638
59019
  if (stats.isSymbolicLink())
58639
59020
  throw unsafeSymbolicLinkError(current);
58640
59021
  } catch (error51) {
@@ -58649,7 +59030,7 @@ async function confirmMissingPathHasDirectoryAncestors(filePath) {
58649
59030
  let current = path55.dirname(filePath);
58650
59031
  while (true) {
58651
59032
  try {
58652
- const stats = await fsp31.lstat(current);
59033
+ const stats = await fsp32.lstat(current);
58653
59034
  if (stats.isSymbolicLink())
58654
59035
  throw unsafeSymbolicLinkError(current);
58655
59036
  if (!stats.isDirectory()) {
@@ -58688,7 +59069,7 @@ async function writeTextFileAtomic(filePath, content, options = {}) {
58688
59069
  await assertNoSymbolicLinkAtPath(filePath);
58689
59070
  else
58690
59071
  await assertNoSymbolicLinksWithinRoot(filePath, options.root);
58691
- await fsp31.mkdir(path55.dirname(filePath), { recursive: true });
59072
+ await fsp32.mkdir(path55.dirname(filePath), { recursive: true });
58692
59073
  await withInstallerLeaseLock(`${filePath}.codegraph-lock`, normalizePathForDisplay(filePath), async () => {
58693
59074
  if (options.root === void 0)
58694
59075
  await assertNoSymbolicLinkAtPath(filePath);
@@ -58700,7 +59081,7 @@ async function writeTextFileAtomic(filePath, content, options = {}) {
58700
59081
  let mode = options.mode ?? 384;
58701
59082
  if (options.mode === void 0) {
58702
59083
  try {
58703
- const existing = await fsp31.lstat(filePath);
59084
+ const existing = await fsp32.lstat(filePath);
58704
59085
  if (existing.isSymbolicLink())
58705
59086
  throw unsafeSymbolicLinkError(filePath);
58706
59087
  mode = existing.mode & 4095;
@@ -58709,8 +59090,8 @@ async function writeTextFileAtomic(filePath, content, options = {}) {
58709
59090
  throw error51;
58710
59091
  }
58711
59092
  }
58712
- await fsp31.writeFile(temporaryPath, content, { encoding: "utf8", flag: "wx", mode });
58713
- await fsp31.chmod(temporaryPath, mode);
59093
+ await fsp32.writeFile(temporaryPath, content, { encoding: "utf8", flag: "wx", mode });
59094
+ await fsp32.chmod(temporaryPath, mode);
58714
59095
  await renameTemporaryFile(temporaryPath, filePath);
58715
59096
  } catch (error51) {
58716
59097
  if (isFileSystemErrorCode(error51, "EACCES") || isFileSystemErrorCode(error51, "EPERM")) {
@@ -58718,13 +59099,13 @@ async function writeTextFileAtomic(filePath, content, options = {}) {
58718
59099
  }
58719
59100
  throw error51;
58720
59101
  } finally {
58721
- await fsp31.rm(temporaryPath, { force: true }).catch(() => void 0);
59102
+ await fsp32.rm(temporaryPath, { force: true }).catch(() => void 0);
58722
59103
  }
58723
59104
  });
58724
59105
  }
58725
59106
  async function assertNoSymbolicLinkAtPath(filePath) {
58726
59107
  try {
58727
- const stats = await fsp31.lstat(filePath);
59108
+ const stats = await fsp32.lstat(filePath);
58728
59109
  if (stats.isSymbolicLink())
58729
59110
  throw unsafeSymbolicLinkError(filePath);
58730
59111
  } catch (error51) {
@@ -58739,7 +59120,7 @@ function unsafeSymbolicLinkError(filePath) {
58739
59120
  async function renameTemporaryFile(temporaryPath, filePath) {
58740
59121
  for (let attempt = 0; attempt < 5; attempt += 1) {
58741
59122
  try {
58742
- await fsp31.rename(temporaryPath, filePath);
59123
+ await fsp32.rename(temporaryPath, filePath);
58743
59124
  return;
58744
59125
  } catch (error51) {
58745
59126
  if (!isFileSystemErrorCode(error51, "EPERM") || attempt === 4)
@@ -58927,7 +59308,7 @@ __export(doctor_exports, {
58927
59308
  formatDoctorSummary: () => formatDoctorSummary
58928
59309
  });
58929
59310
  import fs25 from "node:fs";
58930
- import os8 from "node:os";
59311
+ import os9 from "node:os";
58931
59312
  import path57 from "node:path";
58932
59313
  function statIfExists(filePath) {
58933
59314
  try {
@@ -58960,15 +59341,15 @@ function detectIndexedArtifactType(filePath, stats) {
58960
59341
  return "artifactBundle";
58961
59342
  }
58962
59343
  const normalized = normalizePathForDisplay(filePath).toLowerCase();
59344
+ if (normalized.endsWith("/.codegraph/cache") || normalized.includes("/.codegraph/cache/") || normalized.endsWith("/.codegraph-cache") || normalized.includes("/.codegraph-cache/")) {
59345
+ return "diskCache";
59346
+ }
58963
59347
  if (normalized.endsWith("/codegraph.json") || normalized.endsWith(".json")) {
58964
59348
  return "jsonGraph";
58965
59349
  }
58966
59350
  if (normalized.endsWith("/graph.sqlite") || normalized.endsWith(".sqlite")) {
58967
59351
  return "sqliteGraph";
58968
59352
  }
58969
- if (normalized.endsWith("/.codegraph-cache") || normalized.includes("/.codegraph-cache/")) {
58970
- return "diskCache";
58971
- }
58972
59353
  return "unknown";
58973
59354
  }
58974
59355
  function buildIndexedArtifactReport(indexPath) {
@@ -59155,7 +59536,7 @@ function readCacheLocationField(configPath) {
59155
59536
  }
59156
59537
  }
59157
59538
  function readUserCacheLocation() {
59158
- const configRoot = process.platform === "win32" ? process.env.APPDATA?.trim() || path57.join(os8.homedir(), "AppData", "Roaming") : process.env.XDG_CONFIG_HOME?.trim() || path57.join(os8.homedir(), ".config");
59539
+ const configRoot = process.platform === "win32" ? process.env.APPDATA?.trim() || path57.join(os9.homedir(), "AppData", "Roaming") : process.env.XDG_CONFIG_HOME?.trim() || path57.join(os9.homedir(), ".config");
59159
59540
  return readCacheLocationField(path57.join(configRoot, "codegraph", "config.json"));
59160
59541
  }
59161
59542
  function readEffectiveCacheLocation(root) {
@@ -60485,7 +60866,7 @@ async function handleSqlCommand(context) {
60485
60866
  const dbOpt = context.getOpt("--db") ?? context.getOpt("--sqlite") ?? context.positionals[0];
60486
60867
  const queryText = context.getOpt("--query") ?? context.positionals[1];
60487
60868
  if (!dbOpt || !queryText) {
60488
- context.writeStderrLine('Usage: sql <sqlite-path> "SELECT ..." OR sql --db <sqlite-path> --query "SELECT ..."');
60869
+ context.writeStderrLine(getCliCommandUsage("sql"));
60489
60870
  context.exit(2);
60490
60871
  }
60491
60872
  const dbPath = path60.isAbsolute(dbOpt) ? normalizePath(dbOpt) : normalizePath(resolveFilePathFromRoot(context.cwd(), dbOpt));
@@ -60498,6 +60879,7 @@ var init_sql2 = __esm({
60498
60879
  init_sqlite();
60499
60880
  init_paths();
60500
60881
  init_pretty();
60882
+ init_options();
60501
60883
  }
60502
60884
  });
60503
60885
 
@@ -60668,7 +61050,7 @@ var chunk_exports = {};
60668
61050
  __export(chunk_exports, {
60669
61051
  handleChunkCommand: () => handleChunkCommand
60670
61052
  });
60671
- import fsp32 from "node:fs/promises";
61053
+ import fsp33 from "node:fs/promises";
60672
61054
  import path61 from "node:path";
60673
61055
  function normalizeChunkLanguageId(languageId) {
60674
61056
  return chunkLanguageAliases2[languageId] ?? languageId;
@@ -60691,7 +61073,7 @@ function formatChunkSummary(chunks) {
60691
61073
  async function handleChunkCommand(context) {
60692
61074
  const inputFilePath = context.positionals[0];
60693
61075
  if (!inputFilePath) {
60694
- context.writeStderrLine("Usage: chunk <file-path> [options]");
61076
+ context.writeStderrLine(getCliCommandUsage("chunk"));
60695
61077
  context.writeStderrLine("Options:");
60696
61078
  context.writeStderrLine(" --min-tokens N Minimum tokens per chunk (default: 150)");
60697
61079
  context.writeStderrLine(" --max-tokens N Maximum tokens per chunk (default: 400)");
@@ -60699,16 +61081,25 @@ async function handleChunkCommand(context) {
60699
61081
  context.writeStderrLine(" --text Force text chunking mode");
60700
61082
  context.exit(2);
60701
61083
  }
61084
+ const languageOverride = context.getOpt("--language");
61085
+ if (languageOverride) {
61086
+ const languageId = normalizeChunkLanguageId(languageOverride);
61087
+ const isSupported = supportedChunkLanguageIds.has(languageId);
61088
+ if (!isSupported) {
61089
+ context.writeStderrLine(`Unsupported --language value "${languageId}". Supported languages: ${chunkLanguageHelp}.`);
61090
+ context.exit(2);
61091
+ }
61092
+ }
60702
61093
  try {
60703
61094
  const filePath = path61.resolve(context.cwd(), parseSourceLocationInput(inputFilePath).file);
60704
- const source = await fsp32.readFile(filePath, "utf8");
61095
+ const source = await fsp33.readFile(filePath, "utf8");
60705
61096
  const ext = path61.extname(filePath).toLowerCase();
60706
- let languageId = context.getOpt("--language");
60707
- if (!languageId) {
61097
+ let languageId;
61098
+ if (!languageOverride) {
60708
61099
  const support = supportForFile(filePath);
60709
61100
  languageId = support ? normalizeChunkLanguageId(support.id) : chunkTextLanguageByExtension[ext] || "text";
60710
61101
  } else {
60711
- languageId = normalizeChunkLanguageId(languageId);
61102
+ languageId = normalizeChunkLanguageId(languageOverride);
60712
61103
  }
60713
61104
  const forceText = context.hasFlag("--text");
60714
61105
  const minTokensRaw = context.getOpt("--min-tokens");
@@ -60755,7 +61146,7 @@ async function handleChunkCommand(context) {
60755
61146
  exitWithError(context, error51, 1, "Chunking failed");
60756
61147
  }
60757
61148
  }
60758
- var chunkLanguageAliases2, chunkTextLanguageByExtension, chunkLanguageHelp;
61149
+ var chunkLanguageAliases2, chunkTextLanguageByExtension, supportedChunkLanguageIds, chunkLanguageHelp;
60759
61150
  var init_chunk = __esm({
60760
61151
  "dist/cli/chunk.js"() {
60761
61152
  "use strict";
@@ -60777,7 +61168,8 @@ var init_chunk = __esm({
60777
61168
  ".yaml": "yaml",
60778
61169
  ".yml": "yaml"
60779
61170
  };
60780
- chunkLanguageHelp = Array.from(/* @__PURE__ */ new Set([...Object.keys(LANG_CONFIGS).sort(), "vue", "svelte", "json", "yaml", "text"])).join(", ");
61171
+ supportedChunkLanguageIds = /* @__PURE__ */ new Set([...Object.keys(LANG_CONFIGS), "vue", "svelte", "json", "yaml", "text"]);
61172
+ chunkLanguageHelp = [...supportedChunkLanguageIds].sort().join(", ");
60781
61173
  }
60782
61174
  });
60783
61175
 
@@ -61791,7 +62183,7 @@ var init_fileView = __esm({
61791
62183
  });
61792
62184
 
61793
62185
  // dist/documentLinks/check.js
61794
- import fsp33 from "node:fs/promises";
62186
+ import fsp34 from "node:fs/promises";
61795
62187
  import path64 from "node:path";
61796
62188
  async function checkMarkdownLinks(projectRoot, discovery) {
61797
62189
  const root = path64.resolve(projectRoot);
@@ -61799,14 +62191,14 @@ async function checkMarkdownLinks(projectRoot, discovery) {
61799
62191
  }
61800
62192
  async function checkMarkdownLinksInFiles(projectRoot, files) {
61801
62193
  const root = path64.resolve(projectRoot);
61802
- const realRoot = await fsp33.realpath(root);
62194
+ const realRoot = await fsp34.realpath(root);
61803
62195
  const markdownFileCandidates = await Promise.all(Array.from(files, async (file2) => {
61804
62196
  const absoluteFile = path64.resolve(root, file2);
61805
62197
  const relativeFile = toProjectRelativePath(root, absoluteFile);
61806
62198
  if (relativeFile !== null)
61807
62199
  return path64.resolve(realRoot, relativeFile);
61808
62200
  try {
61809
- return await fsp33.realpath(absoluteFile);
62201
+ return await fsp34.realpath(absoluteFile);
61810
62202
  } catch {
61811
62203
  return absoluteFile;
61812
62204
  }
@@ -61875,7 +62267,7 @@ async function recoverDisplayFileCasing(file2) {
61875
62267
  const dir = path64.dirname(file2);
61876
62268
  const base = path64.basename(file2);
61877
62269
  try {
61878
- const entries = await fsp33.readdir(dir);
62270
+ const entries = await fsp34.readdir(dir);
61879
62271
  const match = entries.find((entry) => entry.toLowerCase() === base.toLowerCase());
61880
62272
  return match ? path64.join(dir, match) : file2;
61881
62273
  } catch {
@@ -61949,8 +62341,8 @@ async function cachedTargetStatus(target, targetStatusByPath) {
61949
62341
  }
61950
62342
  async function readTargetStatus(target) {
61951
62343
  try {
61952
- const realPath = await fsp33.realpath(target);
61953
- const stats = await fsp33.stat(realPath);
62344
+ const realPath = await fsp34.realpath(target);
62345
+ const stats = await fsp34.stat(realPath);
61954
62346
  return { status: "found", isDirectory: stats.isDirectory(), realPath };
61955
62347
  } catch (error51) {
61956
62348
  if (isMissingPathError3(error51))
@@ -61971,7 +62363,7 @@ async function cachedMarkdownAnchors(target, anchorsByPath) {
61971
62363
  }
61972
62364
  async function readMarkdownFile(file2) {
61973
62365
  try {
61974
- return await fsp33.readFile(file2, "utf8");
62366
+ return await fsp34.readFile(file2, "utf8");
61975
62367
  } catch (error51) {
61976
62368
  if (error51 instanceof Error) {
61977
62369
  throw new Error(`Failed to read Markdown file ${normalizePath(file2)}: ${error51.message}`);
@@ -63352,7 +63744,7 @@ var init_navigation_references = __esm({
63352
63744
  });
63353
63745
 
63354
63746
  // dist/sql/navigation.js
63355
- import fsp34 from "node:fs/promises";
63747
+ import fsp35 from "node:fs/promises";
63356
63748
  import path65 from "node:path";
63357
63749
  function isSqlFile2(filePath) {
63358
63750
  return path65.extname(filePath).toLowerCase() === ".sql";
@@ -63638,7 +64030,7 @@ async function sourceForFile(filePath, index) {
63638
64030
  if (cached2 !== void 0)
63639
64031
  return cached2;
63640
64032
  const parsed = index.parsed?.get(key);
63641
- const source = parsed ? parsed.source : await fsp34.readFile(filePath, "utf8");
64033
+ const source = parsed ? parsed.source : await fsp35.readFile(filePath, "utf8");
63642
64034
  cache.sourceByFile.set(key, source);
63643
64035
  return source;
63644
64036
  }
@@ -65342,7 +65734,7 @@ var init_changes = __esm({
65342
65734
  });
65343
65735
 
65344
65736
  // dist/sql/review.js
65345
- import fsp35 from "node:fs/promises";
65737
+ import fsp36 from "node:fs/promises";
65346
65738
  import path67 from "node:path";
65347
65739
  function isSqlFile3(filePath) {
65348
65740
  return path67.extname(filePath).toLowerCase() === ".sql";
@@ -65357,7 +65749,7 @@ function sortEntries(left, right) {
65357
65749
  }
65358
65750
  async function readExistingFile(filePath) {
65359
65751
  try {
65360
- return await fsp35.readFile(filePath, "utf8");
65752
+ return await fsp36.readFile(filePath, "utf8");
65361
65753
  } catch {
65362
65754
  return null;
65363
65755
  }
@@ -68720,7 +69112,7 @@ var init_referenceCache = __esm({
68720
69112
  });
68721
69113
 
68722
69114
  // dist/review/summaries.js
68723
- import fsp36 from "node:fs/promises";
69115
+ import fsp37 from "node:fs/promises";
68724
69116
  import path70 from "node:path";
68725
69117
  import { performance as performance9 } from "node:perf_hooks";
68726
69118
  function relativePath2(root, file2) {
@@ -68982,7 +69374,7 @@ function sameRange(left, right) {
68982
69374
  }
68983
69375
  async function summarizeChangedFiles2(input) {
68984
69376
  const { projectRoot, index, changedFileList, diffHunksByFile, diffKindsByFile, diffChangesByFile, explicitFiles, existenceByFile, deletedSnapshots, loadSource: suppliedLoadSource, includeSymbolDetails, includeDiffContext, diffContextLines, maxCallsites, referenceConcurrency, diagnostics, reviewTimings } = input;
68985
- const readSource = suppliedLoadSource ?? (async (file2) => await fsp36.readFile(file2, "utf8"));
69377
+ const readSource = suppliedLoadSource ?? (async (file2) => await fsp37.readFile(file2, "utf8"));
68986
69378
  const sourceCache = /* @__PURE__ */ new Map();
68987
69379
  const loadSource = (file2) => {
68988
69380
  const key = fileIdentityKey(file2);
@@ -74587,11 +74979,12 @@ function suggestCliCommands(input, limit = 3) {
74587
74979
  function routeForCliIntent(input) {
74588
74980
  return INTENT_ROUTES.find((route) => route.pattern.test(input))?.command;
74589
74981
  }
74590
- function renderCliCommandList() {
74591
- const width = Math.max(...CLI_COMMAND_CATALOG.map((command) => command.name.length));
74592
- return CLI_COMMAND_CATALOG.map((command) => ` ${command.name.padEnd(width)} ${command.summary}`).join("\n");
74982
+ function renderCliCommandList(advanced = false) {
74983
+ const commands = advanced ? CLI_COMMAND_CATALOG : CLI_COMMAND_CATALOG.filter((command) => CORE_COMMAND_NAMES[command.name]);
74984
+ const width = Math.max(...commands.map((command) => command.name.length));
74985
+ return commands.map((command) => ` ${command.name.padEnd(width)} ${command.summary}`).join("\n");
74593
74986
  }
74594
- var CLI_COMMAND_CATALOG, commandByName, aliasToCommand, INTENT_ROUTES;
74987
+ var CLI_COMMAND_CATALOG, commandByName, aliasToCommand, INTENT_ROUTES, CORE_COMMAND_NAMES;
74595
74988
  var init_commandCatalog = __esm({
74596
74989
  "dist/cli/commandCatalog.js"() {
74597
74990
  "use strict";
@@ -74663,6 +75056,15 @@ var init_commandCatalog = __esm({
74663
75056
  { pattern: /^(configure|setup|agent)$/i, command: "codegraph install" },
74664
75057
  { pattern: /^(health|diagnose|check)$/i, command: "codegraph doctor" }
74665
75058
  ];
75059
+ CORE_COMMAND_NAMES = {
75060
+ doctor: true,
75061
+ orient: true,
75062
+ explore: true,
75063
+ search: true,
75064
+ file: true,
75065
+ deps: true,
75066
+ review: true
75067
+ };
74666
75068
  }
74667
75069
  });
74668
75070
 
@@ -74765,7 +75167,7 @@ function helpTextForCommand(command, positionals) {
74765
75167
  }
74766
75168
  return void 0;
74767
75169
  }
74768
- var CLI_TASK_HELP_TEXT, CLI_HELP_TEXT, LIFECYCLE_HELP_TEXT, AFFECTED_HELP_TEXT, INSTALL_HELP_TEXT, UNINSTALL_HELP_TEXT, SHARED_INDEX_OPTIONS_HELP, EXPLORE_HELP_TEXT, FILE_HELP_TEXT, SEARCH_HELP_TEXT, GOTO_HELP_TEXT, REFS_HELP_TEXT, GREP_HELP_TEXT, SQL_HELP_TEXT, SYMBOLS_HELP_TEXT, CALLERS_HELP_TEXT, CALLEES_HELP_TEXT, SUPERTYPES_HELP_TEXT, SUBTYPES_HELP_TEXT, IMPLEMENTATIONS_HELP_TEXT, RENAME_PREVIEW_HELP_TEXT, REFACTOR_PLAN_HELP_TEXT, ORIENT_HELP_TEXT, PACKET_HELP_TEXT, EXPLAIN_HELP_TEXT, ARTIFACT_HELP_TEXT, SERVER_HELP_TEXT, MCP_HELP_TEXT, MCP_SERVE_HELP_TEXT, DUPLICATES_HELP_TEXT, DRIFT_HELP_TEXT, VIEWER_HELP_TEXT, INDEX_HELP_TEXT, GRAPH_HELP_TEXT, REVIEW_HELP_TEXT, IMPACT_HELP_TEXT, INSPECT_HELP_TEXT, DEPS_HELP_TEXT, RDEPS_HELP_TEXT, PATH_HELP_TEXT, CYCLES_HELP_TEXT, HOTSPOTS_HELP_TEXT, UNRESOLVED_HELP_TEXT, LINKS_HELP_TEXT, APISURFACE_HELP_TEXT, GRAPH_DELTA_HELP_TEXT, DOCTOR_HELP_TEXT, CHUNK_HELP_TEXT, SKILL_HELP_TEXT, DUMPMOD_HELP_TEXT, VERSION_HELP_TEXT;
75170
+ var CLI_TASK_HELP_TEXT, CLI_HELP_TEXT, ADVANCED_HELP_TEXT, LIFECYCLE_HELP_TEXT, AFFECTED_HELP_TEXT, INSTALL_HELP_TEXT, UNINSTALL_HELP_TEXT, SHARED_INDEX_OPTIONS_HELP, EXPLORE_HELP_TEXT, FILE_HELP_TEXT, SEARCH_HELP_TEXT, GOTO_HELP_TEXT, REFS_HELP_TEXT, GREP_HELP_TEXT, SQL_HELP_TEXT, SYMBOLS_HELP_TEXT, CALLERS_HELP_TEXT, CALLEES_HELP_TEXT, SUPERTYPES_HELP_TEXT, SUBTYPES_HELP_TEXT, IMPLEMENTATIONS_HELP_TEXT, RENAME_PREVIEW_HELP_TEXT, REFACTOR_PLAN_HELP_TEXT, ORIENT_HELP_TEXT, PACKET_HELP_TEXT, EXPLAIN_HELP_TEXT, ARTIFACT_HELP_TEXT, SERVER_HELP_TEXT, MCP_HELP_TEXT, MCP_SERVE_HELP_TEXT, DUPLICATES_HELP_TEXT, DRIFT_HELP_TEXT, VIEWER_HELP_TEXT, INDEX_HELP_TEXT, GRAPH_HELP_TEXT, REVIEW_HELP_TEXT, IMPACT_HELP_TEXT, INSPECT_HELP_TEXT, DEPS_HELP_TEXT, RDEPS_HELP_TEXT, PATH_HELP_TEXT, CYCLES_HELP_TEXT, HOTSPOTS_HELP_TEXT, UNRESOLVED_HELP_TEXT, LINKS_HELP_TEXT, APISURFACE_HELP_TEXT, GRAPH_DELTA_HELP_TEXT, DOCTOR_HELP_TEXT, CHUNK_HELP_TEXT, SKILL_HELP_TEXT, DUMPMOD_HELP_TEXT, VERSION_HELP_TEXT;
74769
75171
  var init_help = __esm({
74770
75172
  "dist/cli/help.js"() {
74771
75173
  "use strict";
@@ -74792,9 +75194,11 @@ Start here:
74792
75194
 
74793
75195
  Usage: codegraph <command> [options] [path]
74794
75196
 
74795
- Commands:
75197
+ Core commands:
74796
75198
  ${renderCliCommandList()}
74797
75199
 
75200
+ For the complete catalog, run: codegraph help advanced
75201
+
74798
75202
  Graph Options:
74799
75203
  --fast-graph Use text import extraction for plain .js and .ts
74800
75204
  files. May miss multiline or complex patterns;
@@ -74893,6 +75297,11 @@ Examples:
74893
75297
  codegraph refactor-plan Service --rename RenamedService
74894
75298
  codegraph version
74895
75299
  codegraph -v
75300
+ `;
75301
+ ADVANCED_HELP_TEXT = `codegraph - Complete command catalog
75302
+
75303
+ Commands:
75304
+ ${renderCliCommandList(true)}
74896
75305
  `;
74897
75306
  LIFECYCLE_HELP_TEXT = `codegraph init/status/sync/uninit - Initialize, inspect, refresh, or remove project-local Codegraph state
74898
75307
 
@@ -74907,9 +75316,9 @@ Usage:
74907
75316
  codegraph uninit --root <path> [--force] [--json | --pretty]
74908
75317
 
74909
75318
  State:
74910
- Lifecycle commands own only .codegraph/manifest.json metadata; a shared MCP server separately owns .codegraph/server.json and .codegraph/server.log. In a Git worktree, init and sync --init ensure the manifest is effectively ignored, appending .codegraph/ and .codegraph-cache/ to the resolved root's .gitignore only when needed; opt out with --no-update-gitignore.
74911
- A tracked manifest is left tracked with a warning. Uninit preserves server.json and server.log even with --force, so stop a shared server explicitly; it leaves the root .gitignore rule, and ordinary sync never changes ignore policy.
74912
- Init and sync may warm or update the disk cache under .codegraph-cache/index-v1/. Other commands do not depend on the manifest.
75319
+ Lifecycle commands own .codegraph/manifest.json metadata; a shared MCP server owns .codegraph/server.json and .codegraph/server.log. In a Git worktree, init and sync --init ensure the .codegraph/ directory is effectively ignored, appending .codegraph/ to the resolved root's .gitignore only when needed; opt out with --no-update-gitignore.
75320
+ A tracked manifest is left tracked with a warning. Uninit preserves server.json, server.log, and cache even with --force, so stop a shared server explicitly; it leaves the root .gitignore rule, and ordinary sync never changes ignore policy.
75321
+ Init and sync may warm or update the disk cache under .codegraph/cache/index-v1/. Existing .codegraph-cache/ directories migrate automatically on the next run. Other commands do not depend on the manifest.
74913
75322
  Positional paths and --root are alternatives for lifecycle commands; do not combine them.
74914
75323
  `;
74915
75324
  AFFECTED_HELP_TEXT = `codegraph affected - List tests likely affected by changed files
@@ -74989,20 +75398,20 @@ ${SHARED_INDEX_OPTIONS_HELP}
74989
75398
  `;
74990
75399
  GOTO_HELP_TEXT = `codegraph goto - Go to a definition
74991
75400
 
74992
- Usage: codegraph goto <file>::<symbol> [--root <path>] [--json | --pretty]
75401
+ Usage: codegraph goto <file|file::symbol|symbol:...> [--root <path>] [--json | --pretty]
74993
75402
  codegraph goto <file>[:line[:column]] [line] [column] [--root <path>] [--json | --pretty]
74994
75403
 
74995
- A qualified symbol path resolves one definition without a location. A file-only target succeeds when the file defines one symbol; otherwise JSON returns bounded candidates. Search-result locations and portable symbol handles can be pasted directly.
75404
+ A qualified symbol path or portable symbol handle resolves one definition without a location. A file-only target succeeds when the file defines one symbol; otherwise JSON returns bounded candidates. Search-result locations and portable symbol handles can be pasted directly.
74996
75405
 
74997
75406
  ${SHARED_INDEX_OPTIONS_HELP}
74998
75407
  `;
74999
75408
  REFS_HELP_TEXT = `codegraph refs - Find semantic references
75000
75409
 
75001
- Usage: codegraph refs <file>::<symbol> [--root <path>] [--json | --pretty]
75410
+ Usage: codegraph refs <file|file::symbol|symbol:...> [--root <path>] [--json | --pretty]
75002
75411
  codegraph refs <file>[:line[:column]] [line] [column] [--root <path>] [--json | --pretty]
75003
75412
  codegraph refs --file <file> [--line <line> --col <column>] [--root <path>] [--json | --pretty]
75004
75413
 
75005
- A qualified symbol path finds references for one declaration without a location. A file-only target finds references for every symbol defined in that file. Search-result locations and portable symbol handles can be pasted directly.
75414
+ A qualified symbol path or portable symbol handle resolves one definition and reports references to it without a location. A file-only target finds references for every definition in the file.
75006
75415
 
75007
75416
  ${SHARED_INDEX_OPTIONS_HELP}
75008
75417
  `;
@@ -75358,7 +75767,9 @@ ${SHARED_INDEX_OPTIONS_HELP}
75358
75767
  `;
75359
75768
  DEPS_HELP_TEXT = `codegraph deps - List file dependencies
75360
75769
 
75361
- Usage: codegraph deps <file|file::symbol|symbol:...> [--root <path>] [--depth <n>] [--json | --pretty]
75770
+ Usage: codegraph deps <file|file::symbol|symbol:...> [--root <path>] [--depth <n> | --all] [--json | --pretty]
75771
+
75772
+ By default, outputs dependencies through depth 1. Use --depth for another finite output depth, or --all to include every depth.
75362
75773
 
75363
75774
  A qualified symbol path or portable symbol handle uses its declaring file for file-graph traversal. Use callees for symbol-level call relationships.
75364
75775
 
@@ -75366,7 +75777,9 @@ ${SHARED_INDEX_OPTIONS_HELP}
75366
75777
  `;
75367
75778
  RDEPS_HELP_TEXT = `codegraph rdeps - List reverse file dependencies
75368
75779
 
75369
- Usage: codegraph rdeps <file|file::symbol|symbol:...> [--root <path>] [--depth <n>] [--json | --pretty]
75780
+ Usage: codegraph rdeps <file|file::symbol|symbol:...> [--root <path>] [--depth <n> | --all] [--json | --pretty]
75781
+
75782
+ By default, outputs reverse dependencies through depth 1. Use --depth for another finite output depth, or --all to include every depth.
75370
75783
 
75371
75784
  A qualified symbol path or portable symbol handle uses its declaring file for file-graph traversal. Use callers for symbol-level call relationships.
75372
75785
 
@@ -81458,8 +81871,17 @@ async function handleTypeHierarchyCommand(command, context) {
81458
81871
  context.writeStderrLine(helpText2(command).trimEnd());
81459
81872
  context.exit(2);
81460
81873
  }
81874
+ let limit;
81875
+ let depth;
81876
+ try {
81877
+ limit = parseBoundedIntegerOption(context.getOpt("--limit"), "--limit", DEFAULT_HIERARCHY_LIMIT2, 0, MAX_HIERARCHY_LIMIT2);
81878
+ if (command !== "implementations") {
81879
+ depth = parseBoundedIntegerOption(context.getOpt("--depth"), "--depth", DEFAULT_HIERARCHY_DEPTH, 1, MAX_HIERARCHY_DEPTH2);
81880
+ }
81881
+ } catch (error51) {
81882
+ exitWithError(context, error51, 2);
81883
+ }
81461
81884
  try {
81462
- const limit = parseBoundedIntegerOption(context.getOpt("--limit"), "--limit", DEFAULT_HIERARCHY_LIMIT2, 0, MAX_HIERARCHY_LIMIT2);
81463
81885
  if (command === "implementations") {
81464
81886
  const response2 = await findImplementations2({
81465
81887
  root: context.root,
@@ -81470,7 +81892,6 @@ async function handleTypeHierarchyCommand(command, context) {
81470
81892
  writeResponse(context, response2, formatImplementationsResponse);
81471
81893
  return;
81472
81894
  }
81473
- const depth = parseBoundedIntegerOption(context.getOpt("--depth"), "--depth", DEFAULT_HIERARCHY_DEPTH, 1, MAX_HIERARCHY_DEPTH2);
81474
81895
  const request = {
81475
81896
  root: context.root,
81476
81897
  handle,
@@ -81576,6 +81997,9 @@ async function handleExplainCommand(context) {
81576
81997
  } else {
81577
81998
  context.writeStdoutLine(formatAgentExplanation(response));
81578
81999
  }
82000
+ if (response.target.kind === "not_found") {
82001
+ context.exit(1);
82002
+ }
81579
82003
  }
81580
82004
  var init_explain2 = __esm({
81581
82005
  "dist/cli/explain.js"() {
@@ -81863,10 +82287,10 @@ function buildFocusTargets(modules, packets, review) {
81863
82287
  kind: "hotspot",
81864
82288
  file: module2.file,
81865
82289
  why: `graph-central module: fan-in ${module2.fanIn}, fan-out ${module2.fanOut}, score ${module2.score}`,
81866
- followUps: [
82290
+ followUps: dedupeAgentFollowUps([
81867
82291
  toolFollowUp("packet_get", { target: packet.file }),
81868
82292
  toolFollowUp("packet_get", { target: module2.file })
81869
- ]
82293
+ ])
81870
82294
  });
81871
82295
  continue;
81872
82296
  }
@@ -98215,7 +98639,7 @@ var init_tools = __esm({
98215
98639
  limit: { type: "integer", minimum: 0, maximum: 50, default: 5 },
98216
98640
  maxPackets: { type: "integer", minimum: 0, maximum: 10, default: 3 },
98217
98641
  maxPaths: { type: "integer", minimum: 0, maximum: 10, default: 3 },
98218
- includeSource: { type: "boolean", default: true }
98642
+ includeSource: { type: "boolean", default: false }
98219
98643
  }, ["query"]),
98220
98644
  dispatch: { handler: "explore" }
98221
98645
  },
@@ -98830,7 +99254,7 @@ function createCodegraphMcpHandlersForSession(options, session) {
98830
99254
  ...request.limit !== void 0 ? { limit: request.limit } : {},
98831
99255
  ...request.maxPackets !== void 0 ? { maxPackets: request.maxPackets } : {},
98832
99256
  ...request.maxPaths !== void 0 ? { maxPaths: request.maxPaths } : {},
98833
- ...request.includeSource !== void 0 ? { includeSource: request.includeSource } : {}
99257
+ includeSource: request.includeSource ?? false
98834
99258
  })),
98835
99259
  orient: async (request) => await withFreshness(async () => await orientCodegraphWithSession(session, {
98836
99260
  root,
@@ -101603,7 +102027,7 @@ var init_dist4 = __esm({
101603
102027
  });
101604
102028
 
101605
102029
  // dist/mcp/http.js
101606
- import os9 from "node:os";
102030
+ import os10 from "node:os";
101607
102031
  function getRequestPath(request) {
101608
102032
  return new URL(request.url ?? "/", "http://127.0.0.1").pathname;
101609
102033
  }
@@ -101806,11 +102230,11 @@ function isWildcardBindHost(host) {
101806
102230
  }
101807
102231
  function localInterfaceHostnames() {
101808
102232
  const hosts = /* @__PURE__ */ new Set();
101809
- const hostname3 = os9.hostname().trim().toLowerCase();
102233
+ const hostname3 = os10.hostname().trim().toLowerCase();
101810
102234
  if (hostname3) {
101811
102235
  hosts.add(hostname3);
101812
102236
  }
101813
- const interfaces = os9.networkInterfaces();
102237
+ const interfaces = os10.networkInterfaces();
101814
102238
  for (const entries of Object.values(interfaces)) {
101815
102239
  for (const entry of entries ?? []) {
101816
102240
  if (entry.internal)
@@ -102399,11 +102823,82 @@ function toToolResult(value) {
102399
102823
  content: [
102400
102824
  {
102401
102825
  type: "text",
102402
- text: JSON.stringify(value, null, 2)
102826
+ text: JSON.stringify(translateMcpFollowUps(value))
102403
102827
  }
102404
102828
  ]
102405
102829
  };
102406
102830
  }
102831
+ function translateMcpFollowUps(value) {
102832
+ if (Array.isArray(value))
102833
+ return translateFollowUpCollection(value);
102834
+ if (typeof value !== "object" || value === null)
102835
+ return value;
102836
+ const source = value;
102837
+ let translated;
102838
+ for (const [key, nestedValue] of Object.entries(source)) {
102839
+ const nestedTranslation = translateMcpFollowUps(nestedValue);
102840
+ if (nestedTranslation === nestedValue)
102841
+ continue;
102842
+ translated ??= { ...source };
102843
+ translated[key] = nestedTranslation;
102844
+ }
102845
+ return translateFollowUps(translated ?? source);
102846
+ }
102847
+ function translateFollowUpCollection(value) {
102848
+ let translated;
102849
+ for (const [index, entry] of value.entries()) {
102850
+ const entryTranslation = translateMcpFollowUps(entry);
102851
+ if (entryTranslation === entry) {
102852
+ if (translated !== void 0)
102853
+ translated.push(entry);
102854
+ continue;
102855
+ }
102856
+ translated ??= [...value.slice(0, index)];
102857
+ translated.push(entryTranslation);
102858
+ }
102859
+ return translated ?? value;
102860
+ }
102861
+ function translateFollowUps(value) {
102862
+ const followUps = value.followUps;
102863
+ if (!Array.isArray(followUps) || !followUps.every(isAgentFollowUp))
102864
+ return value;
102865
+ const translatedFollowUps = followUps.map(translateMcpFollowUp);
102866
+ if (translatedFollowUps.every((followUp, index) => followUp === followUps[index]))
102867
+ return value;
102868
+ return { ...value, followUps: translatedFollowUps };
102869
+ }
102870
+ function isAgentFollowUp(value) {
102871
+ return typeof value === "object" && value !== null && "tool" in value && typeof value.tool === "string" && "arguments" in value && typeof value.arguments === "object" && value.arguments !== null && !Array.isArray(value.arguments);
102872
+ }
102873
+ function translateMcpFollowUp(followUp) {
102874
+ if (followUp.tool === "chunk")
102875
+ return { ...followUp, tool: "get_file" };
102876
+ if (followUp.tool === "duplicates") {
102877
+ return {
102878
+ ...followUp,
102879
+ tool: "packet_get",
102880
+ arguments: { target: firstDuplicateFile(followUp.arguments) }
102881
+ };
102882
+ }
102883
+ if (followUp.tool === "explore" && followUp.arguments.includeSource === void 0) {
102884
+ return {
102885
+ ...followUp,
102886
+ arguments: { ...followUp.arguments, includeSource: true }
102887
+ };
102888
+ }
102889
+ if (followUp.tool === "impact" && followUp.arguments.provider === "git") {
102890
+ const arguments_ = { ...followUp.arguments };
102891
+ delete arguments_.provider;
102892
+ return { ...followUp, arguments: arguments_ };
102893
+ }
102894
+ return followUp;
102895
+ }
102896
+ function firstDuplicateFile(arguments_) {
102897
+ const files = arguments_.files;
102898
+ if (!Array.isArray(files))
102899
+ return ".";
102900
+ return files.find((file2) => typeof file2 === "string") ?? ".";
102901
+ }
102407
102902
  function toToolErrorResult(error51) {
102408
102903
  const message = errorMessage(error51).replace(/(?:[A-Za-z]:)?[/\\][^'"\n]*?(?=(?:[/\\][^'"\n]*)?['"]|$)/g, "<path>");
102409
102904
  return { isError: true, content: [{ type: "text", text: message }] };
@@ -103067,7 +103562,7 @@ import path86 from "node:path";
103067
103562
  async function handleServerCommand(context) {
103068
103563
  const command = context.positionals[0];
103069
103564
  if (!isServerCommand(command)) {
103070
- context.writeStderrLine("Usage: codegraph server <start|status|stop> [--root <path>]");
103565
+ context.writeStderrLine(getCliCommandUsage("server"));
103071
103566
  context.exit(2);
103072
103567
  }
103073
103568
  try {
@@ -103897,7 +104392,8 @@ var init_server2 = __esm({
103897
104392
  "--warmup",
103898
104393
  "--warmup-symbols",
103899
104394
  "--workers",
103900
- "--json"
104395
+ "--json",
104396
+ "--pretty"
103901
104397
  ],
103902
104398
  status: ["--json", "--pretty"],
103903
104399
  stop: []
@@ -103929,7 +104425,7 @@ var graphDelta_exports = {};
103929
104425
  __export(graphDelta_exports, {
103930
104426
  handleGraphDeltaCommand: () => handleGraphDeltaCommand
103931
104427
  });
103932
- import fsp37 from "node:fs/promises";
104428
+ import fsp38 from "node:fs/promises";
103933
104429
  function formatGraphDeltaReport(delta) {
103934
104430
  const lines = [];
103935
104431
  lines.push("Graph delta");
@@ -103996,7 +104492,7 @@ async function handleGraphDeltaCommand(context) {
103996
104492
  const outputFile = outputArg ? normalizePath(resolveFilePathFromRoot(context.cwd(), outputArg)) : void 0;
103997
104493
  if (outputFile) {
103998
104494
  const output = context.hasFlag("--json") ? JSON.stringify(delta, null, 2) : formatGraphDeltaReport(delta);
103999
- await fsp37.writeFile(outputFile, `${output}
104495
+ await fsp38.writeFile(outputFile, `${output}
104000
104496
  `, "utf8");
104001
104497
  } else {
104002
104498
  writeCliOutput(context, delta, formatGraphDeltaReport);
@@ -104296,7 +104792,7 @@ var init_symbol_render = __esm({
104296
104792
  });
104297
104793
 
104298
104794
  // dist/sql/graph.js
104299
- import fsp38 from "node:fs/promises";
104795
+ import fsp39 from "node:fs/promises";
104300
104796
  function statementNodeId(fact) {
104301
104797
  return `sql:statement:${fact.filePath}:${fact.startLine}-${fact.endLine}`;
104302
104798
  }
@@ -104451,7 +104947,7 @@ function projectSqlFactsToGraph(facts) {
104451
104947
  };
104452
104948
  }
104453
104949
  async function buildSqlArtifactGraphFromFiles(files) {
104454
- const factGroups = await Promise.all(files.map(async (filePath) => extractSqlFactsFromSource(filePath, await fsp38.readFile(filePath, "utf8"))));
104950
+ const factGroups = await Promise.all(files.map(async (filePath) => extractSqlFactsFromSource(filePath, await fsp39.readFile(filePath, "utf8"))));
104455
104951
  return projectSqlFactsToGraph(factGroups.flat());
104456
104952
  }
104457
104953
  var init_graph = __esm({
@@ -104482,7 +104978,7 @@ __export(graph_exports, {
104482
104978
  handleGraphCommand: () => handleGraphCommand
104483
104979
  });
104484
104980
  import fs42 from "node:fs";
104485
- import fsp39 from "node:fs/promises";
104981
+ import fsp40 from "node:fs/promises";
104486
104982
  import path88 from "node:path";
104487
104983
  import { performance as performance12 } from "node:perf_hooks";
104488
104984
  function resolveGraphCacheMode(cache) {
@@ -104645,7 +105141,7 @@ async function handleGraphCommand(context) {
104645
105141
  ...cacheDir ? { cacheDir } : {},
104646
105142
  ...context.cacheLocation ? { cacheLocation: context.cacheLocation } : {}
104647
105143
  };
104648
- const stable = context.hasFlag("--stable");
105144
+ const stable = true;
104649
105145
  let format = "mermaid";
104650
105146
  if (context.hasFlag("--json")) {
104651
105147
  format = "json";
@@ -104676,7 +105172,7 @@ async function handleGraphCommand(context) {
104676
105172
  };
104677
105173
  const writeOut = async (text) => {
104678
105174
  if (outputFile) {
104679
- await fsp39.writeFile(outputFile, `${text}
105175
+ await fsp40.writeFile(outputFile, `${text}
104680
105176
  `, "utf8");
104681
105177
  } else {
104682
105178
  context.writeStdoutLine(text);
@@ -105619,7 +106115,7 @@ var init_report3 = __esm({
105619
106115
  });
105620
106116
 
105621
106117
  // dist/drift/artifact.js
105622
- import fsp40 from "node:fs/promises";
106118
+ import fsp41 from "node:fs/promises";
105623
106119
  import path90 from "node:path";
105624
106120
  function readStringRecord2(value) {
105625
106121
  if (!isPlainRecord(value))
@@ -105643,7 +106139,7 @@ function parseManifest(value) {
105643
106139
  }
105644
106140
  async function readJson(filePath, label) {
105645
106141
  try {
105646
- return JSON.parse(await fsp40.readFile(filePath, "utf8"));
106142
+ return JSON.parse(await fsp41.readFile(filePath, "utf8"));
105647
106143
  } catch (error51) {
105648
106144
  if (error51 instanceof Error && "code" in error51 && error51.code === "ENOENT") {
105649
106145
  throw new Error(`Codegraph artifact ${label} is missing.`);
@@ -105777,8 +106273,8 @@ var init_artifact3 = __esm({
105777
106273
 
105778
106274
  // dist/drift/git.js
105779
106275
  import { execFile } from "node:child_process";
105780
- import fsp41 from "node:fs/promises";
105781
- import os10 from "node:os";
106276
+ import fsp42 from "node:fs/promises";
106277
+ import os11 from "node:os";
105782
106278
  import path91 from "node:path";
105783
106279
  import { promisify } from "node:util";
105784
106280
  function snapshotOptions(options) {
@@ -105798,7 +106294,7 @@ async function cleanupTempDir(dir) {
105798
106294
  if (!dir)
105799
106295
  return;
105800
106296
  try {
105801
- await fsp41.rm(dir, { recursive: true, force: true });
106297
+ await fsp42.rm(dir, { recursive: true, force: true });
105802
106298
  } catch {
105803
106299
  }
105804
106300
  }
@@ -105817,7 +106313,7 @@ async function materializeGitRef(root, ref, prefix) {
105817
106313
  }
105818
106314
  if (checkoutRef === void 0 || checkoutRef === "." || isGitWorktreeSentinel(checkoutRef))
105819
106315
  return { root };
105820
- const tempRoot = await fsp41.mkdtemp(path91.join(os10.tmpdir(), prefix));
106316
+ const tempRoot = await fsp42.mkdtemp(path91.join(os11.tmpdir(), prefix));
105821
106317
  try {
105822
106318
  await execFileAsync("git", ["clone", "--quiet", "--no-checkout", root, tempRoot], { env: process.env });
105823
106319
  const checkoutCommit = await resolveGitCommit(root, checkoutRef);
@@ -106567,7 +107063,7 @@ function formatGotoOutput(projectRootFs, output) {
106567
107063
  async function handleDumpmodCommand(context) {
106568
107064
  const [fileArg] = context.positionals;
106569
107065
  if (!fileArg) {
106570
- context.writeStderrLine("Usage: dumpmod <file>");
107066
+ context.writeStderrLine(getCliCommandUsage("dumpmod"));
106571
107067
  context.exit(2);
106572
107068
  }
106573
107069
  const resolvedFile = resolveCliProjectFile(context.projectRootFs, fileArg, "File");
@@ -106689,9 +107185,14 @@ function writePrettyReferences(context, result) {
106689
107185
  }
106690
107186
  }
106691
107187
  async function handleGotoCommand(context) {
106692
- const input = parseNavigationInput(context, false);
107188
+ let input;
107189
+ try {
107190
+ input = parseNavigationInput(context, false);
107191
+ } catch (error51) {
107192
+ exitWithError(context, error51, 2);
107193
+ }
106693
107194
  if (!input) {
106694
- context.writeStderrLine("Usage: goto <file>[:line[:column]] [line] [column]");
107195
+ context.writeStderrLine(getCliCommandUsage("goto"));
106695
107196
  context.exit(2);
106696
107197
  }
106697
107198
  const resolvedFile = resolveCliProjectFile(context.projectRootFs, input.file, "File");
@@ -106744,9 +107245,14 @@ async function handleGotoCommand(context) {
106744
107245
  context.exit(1);
106745
107246
  }
106746
107247
  async function handleRefsCommand(context) {
106747
- const input = parseNavigationInput(context, true);
107248
+ let input;
107249
+ try {
107250
+ input = parseNavigationInput(context, true);
107251
+ } catch (error51) {
107252
+ exitWithError(context, error51, 2);
107253
+ }
106748
107254
  if (!input) {
106749
- context.writeStderrLine("Usage: refs <file>[:line[:column]] [line] [column] OR refs --file <file> [--line <line> --col <column>]");
107255
+ context.writeStderrLine(getCliCommandUsage("refs"));
106750
107256
  context.exit(2);
106751
107257
  }
106752
107258
  const pretty = !context.hasFlag("--json");
@@ -106832,11 +107338,12 @@ var init_navigation3 = __esm({
106832
107338
  init_location2();
106833
107339
  init_projectFile();
106834
107340
  init_pretty();
107341
+ init_context();
106835
107342
  }
106836
107343
  });
106837
107344
 
106838
107345
  // dist/graphs/grep.js
106839
- import fsp42 from "node:fs/promises";
107346
+ import fsp43 from "node:fs/promises";
106840
107347
  async function* streamAstGrep(projectRoot, querySource, patterns = [
106841
107348
  "**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs,py,vue,svelte,go,java,cs,rb,rs,html,htm,css,scss,less,kt,kts,swift,zig,c,h,cc,cpp,cxx,c++,hpp,hh,hxx,ipp,tpp,inl}"
106842
107349
  ], opts) {
@@ -106894,7 +107401,7 @@ async function* streamTextGrep(projectRoot, patternSource, patterns = [
106894
107401
  break;
106895
107402
  let source;
106896
107403
  try {
106897
- source = await fsp42.readFile(file2, "utf8");
107404
+ source = await fsp43.readFile(file2, "utf8");
106898
107405
  } catch {
106899
107406
  continue;
106900
107407
  }
@@ -106983,7 +107490,7 @@ async function handleGrepCommand(context) {
106983
107490
  exitWithError(context, error51, 2);
106984
107491
  }
106985
107492
  if ((querySource ? 1 : 0) + (patternSource ? 1 : 0) !== 1) {
106986
- context.writeStderrLine("Usage: grep <regex> [--root <dir>] OR grep --query '<tree-sitter query>' [--root <dir>]");
107493
+ context.writeStderrLine(getCliCommandUsage("grep"));
106987
107494
  context.exit(2);
106988
107495
  }
106989
107496
  if (querySource && maxHits !== void 0) {
@@ -107895,7 +108402,7 @@ function deletedPathsForChange2(change2) {
107895
108402
  async function buildAffectedReportFromContext(context) {
107896
108403
  const inputs = await collectChangedFileInputs(context);
107897
108404
  if (!inputs.files.length) {
107898
- throw new Error("Usage: codegraph affected <file...> [--stdin] [--base <ref> --head <ref>] [--root <path>]");
108405
+ throw new Error(getCliCommandUsage("affected"));
107899
108406
  }
107900
108407
  const normalizedChangedFiles = Array.from(new Set(inputs.files.map((input) => normalizeChangedFileInput(context.projectRootFs, input)))).sort();
107901
108408
  const normalizedDeletedFiles = Array.from(new Set(inputs.deletedFiles.map((input) => normalizeChangedFileInput(context.projectRootFs, input)))).sort();
@@ -108291,10 +108798,15 @@ async function loadGraph(context) {
108291
108798
  async function handleDepsCommand(context) {
108292
108799
  const [fileArg] = context.positionals;
108293
108800
  if (!fileArg) {
108294
- context.writeStderrLine(`Usage: ${context.command} <file|file::symbol|symbol:...> [--depth N] [--json]`);
108801
+ context.writeStderrLine(getCliCommandUsage(context.command));
108295
108802
  context.exit(2);
108296
108803
  }
108297
108804
  const depthRaw = context.getOpt("--depth");
108805
+ const all = context.hasFlag("--all");
108806
+ if (all && depthRaw !== void 0) {
108807
+ context.writeStderrLine("--all cannot be combined with --depth.");
108808
+ context.exit(2);
108809
+ }
108298
108810
  let depth;
108299
108811
  try {
108300
108812
  depth = parseOptionalNonNegativeIntegerOption(depthRaw, "--depth");
@@ -108371,18 +108883,21 @@ async function handleDepsCommand(context) {
108371
108883
  context.exit(1);
108372
108884
  }
108373
108885
  targetFile = matchedGraphNode;
108374
- const results = context.command === "deps" ? getDependencies(loaded.graph, targetFile, {
108375
- ...depth !== void 0 ? { depth } : {},
108376
- ...loaded.adjacency ? { adjacency: loaded.adjacency } : {}
108377
- }) : getReverseDependencies(loaded.graph, targetFile, {
108378
- ...depth !== void 0 ? { depth } : {},
108379
- ...loaded.adjacency ? { adjacency: loaded.adjacency } : {}
108380
- });
108886
+ const effectiveDepth = depth ?? 1;
108887
+ const traversalOptions = {
108888
+ ...loaded.adjacency ? { adjacency: loaded.adjacency } : {},
108889
+ ...json3 && !all ? { depth: effectiveDepth + 1 } : {}
108890
+ };
108891
+ const allResults = context.command === "deps" ? getDependencies(loaded.graph, targetFile, traversalOptions) : getReverseDependencies(loaded.graph, targetFile, traversalOptions);
108892
+ const results = all ? allResults : allResults.filter((result) => result.depth <= effectiveDepth);
108893
+ const truncated = results.length !== allResults.length;
108894
+ const omittedCount = allResults.length - results.length;
108381
108895
  if (json3) {
108382
- context.writeJSONLine(results);
108896
+ context.writeJSONLine({ items: results, truncated });
108383
108897
  return;
108384
108898
  }
108385
- context.writeStdoutLine(`${context.command === "deps" ? "Dependencies" : "Reverse dependencies"} for ${fileArg}:`);
108899
+ const depthDescription = all ? "all depths" : `depth ${effectiveDepth}`;
108900
+ context.writeStdoutLine(`${context.command === "deps" ? "Dependencies" : "Reverse dependencies"} for ${fileArg} (${depthDescription}; ${omittedCount} omitted):`);
108386
108901
  for (const result of results) {
108387
108902
  const rel = toProjectDisplayPath(context.projectRootFs, result.file);
108388
108903
  context.writeStdoutLine(`${" ".repeat(result.depth)} ${rel} (depth ${result.depth})`);
@@ -108391,7 +108906,7 @@ async function handleDepsCommand(context) {
108391
108906
  async function handlePathCommand(context) {
108392
108907
  const [fromArg, toArg] = context.positionals;
108393
108908
  if (!fromArg || !toArg) {
108394
- context.writeStderrLine("Usage: path <from-file> <to-file> [--json]");
108909
+ context.writeStderrLine(getCliCommandUsage("path"));
108395
108910
  context.exit(2);
108396
108911
  }
108397
108912
  const json3 = context.hasFlag("--json");
@@ -109655,10 +110170,7 @@ function resolveCliRootPolicy(input) {
109655
110170
  if (!isLegacyImpactRoot) {
109656
110171
  return {
109657
110172
  status: "error",
109658
- messages: [
109659
- `Unexpected positional argument for impact: ${impactRootArg}`,
109660
- "Usage: codegraph impact [project-root] [--provider git|github|raw] [options]"
109661
- ]
110173
+ messages: [`Unexpected positional argument for impact: ${impactRootArg}`, getCliCommandUsage("impact")]
109662
110174
  };
109663
110175
  }
109664
110176
  }
@@ -109751,6 +110263,7 @@ var init_rootPolicy = __esm({
109751
110263
  "dist/cli/rootPolicy.js"() {
109752
110264
  "use strict";
109753
110265
  init_paths();
110266
+ init_options();
109754
110267
  WINDOWS_DRIVE_QUALIFIED_PATTERN = /^[A-Za-z]:[\\/]/;
109755
110268
  WINDOWS_DRIVE_RELATIVE_ROOT_PATTERN = /^[\\/](?![\\/])/;
109756
110269
  }
@@ -110527,6 +111040,10 @@ async function runCliWithActiveRuntime(rawArgs) {
110527
111040
  writeStdoutLine(CLI_HELP_TEXT.trimEnd());
110528
111041
  return;
110529
111042
  }
111043
+ if (command === "advanced") {
111044
+ writeStdoutLine(ADVANCED_HELP_TEXT.trimEnd());
111045
+ return;
111046
+ }
110530
111047
  if (!isKnownCliCommand(command)) {
110531
111048
  writeStderrLine(`Unknown command "${command}".`);
110532
111049
  const suggestions = suggestCliCommands(command);