token-goat 2.8.6 → 2.9.1

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.
@@ -29,6 +29,7 @@ import {
29
29
  formatCsvTable,
30
30
  getEmbeddingCoverage,
31
31
  getFileEntry,
32
+ getTrackedFiles,
32
33
  indexFileSync,
33
34
  isAvailable,
34
35
  isBlobStale,
@@ -66,7 +67,7 @@ import {
66
67
  walkProject,
67
68
  yamlLineClosesQuote,
68
69
  yamlOpenQuoteAfter
69
- } from "./token-goat-chunk-DK4VLLYB.mjs";
70
+ } from "./token-goat-chunk-FDXINYK4.mjs";
70
71
  import {
71
72
  Database,
72
73
  PER_FILE_COUNTERFACTUAL_CEILING,
@@ -120,36 +121,11 @@ import {
120
121
  unsupportedLanguageName,
121
122
  windowsCmdQuoteArg,
122
123
  withExtension
123
- } from "./token-goat-chunk-2JZ66BBE.mjs";
124
+ } from "./token-goat-chunk-4ZDRTNGV.mjs";
124
125
  import {
125
126
  registerReset
126
127
  } from "./token-goat-chunk-AO2QD2AG.mjs";
127
128
 
128
- // src/repomap.ts
129
- import * as fs from "fs";
130
- import * as path from "path";
131
- function getTrackedFiles(cwd = process.cwd()) {
132
- try {
133
- let dir = cwd;
134
- let onlyFile = "";
135
- try {
136
- if (fs.statSync(cwd).isFile()) {
137
- dir = path.dirname(cwd);
138
- onlyFile = path.basename(cwd);
139
- }
140
- } catch {
141
- }
142
- const args = onlyFile === "" ? ["ls-files"] : ["ls-files", "--error-unmatch", "--", onlyFile];
143
- const result = runGit(args, { cwd: dir });
144
- if (result.exitCode !== 0 || !result.stdout) {
145
- return [];
146
- }
147
- return result.stdout.split("\n").filter((line) => line.trim().length > 0).map((rel) => path.join(dir, rel));
148
- } catch {
149
- return [];
150
- }
151
- }
152
-
153
129
  // src/section_reader.ts
154
130
  import { readFileSync } from "node:fs";
155
131
  function parseHeadingSpec(spec, headers) {
@@ -477,8 +453,8 @@ import { execFileSync, spawnSync as spawnSync2 } from "node:child_process";
477
453
  import { randomUUID } from "node:crypto";
478
454
 
479
455
  // src/read_commands.ts
480
- import * as fs5 from "node:fs";
481
- import * as path5 from "node:path";
456
+ import * as fs4 from "node:fs";
457
+ import * as path4 from "node:path";
482
458
 
483
459
  // src/json_query.ts
484
460
  function jsonType(value) {
@@ -3444,11 +3420,11 @@ var CHOMPING_STRIP = CHOMPING_MODE.STRIP;
3444
3420
  var CHOMPING_KEEP = CHOMPING_MODE.KEEP;
3445
3421
 
3446
3422
  // src/openapi_query.ts
3447
- import * as path2 from "node:path";
3423
+ import * as path from "node:path";
3448
3424
  var HTTP_METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
3449
3425
  function parseOpenApiSpec(text, filePath) {
3450
3426
  text = stripBom(text);
3451
- const ext = path2.extname(filePath).toLowerCase();
3427
+ const ext = path.extname(filePath).toLowerCase();
3452
3428
  if (ext === ".yaml" || ext === ".yml") return load(text);
3453
3429
  if (ext === ".json") return JSON.parse(text);
3454
3430
  try {
@@ -3802,24 +3778,24 @@ ${c.body}`).join("\n\n");
3802
3778
  }
3803
3779
 
3804
3780
  // src/sqlite_query.ts
3805
- import * as fs2 from "node:fs";
3781
+ import * as fs from "node:fs";
3806
3782
  var SQLITE_QUERY_ROW_CAP = 5e3;
3807
3783
  var SQLITE_MAGIC = Buffer.from([83, 81, 76, 105, 116, 101, 32, 102, 111, 114, 109, 97, 116, 32, 51, 0]);
3808
3784
  function readMagicBytes(filePath) {
3809
3785
  let fd;
3810
3786
  try {
3811
- fd = fs2.openSync(filePath, "r");
3787
+ fd = fs.openSync(filePath, "r");
3812
3788
  } catch {
3813
3789
  return null;
3814
3790
  }
3815
3791
  try {
3816
3792
  const buf = Buffer.alloc(SQLITE_MAGIC.length);
3817
- const bytesRead = fs2.readSync(fd, buf, 0, buf.length, 0);
3793
+ const bytesRead = fs.readSync(fd, buf, 0, buf.length, 0);
3818
3794
  return bytesRead === buf.length ? buf : null;
3819
3795
  } catch {
3820
3796
  return null;
3821
3797
  } finally {
3822
- fs2.closeSync(fd);
3798
+ fs.closeSync(fd);
3823
3799
  }
3824
3800
  }
3825
3801
  function isSqliteFile(filePath) {
@@ -3827,7 +3803,7 @@ function isSqliteFile(filePath) {
3827
3803
  return magic !== null && magic.equals(SQLITE_MAGIC);
3828
3804
  }
3829
3805
  function openReadonlySqlite(filePath) {
3830
- if (!fs2.existsSync(filePath)) {
3806
+ if (!fs.existsSync(filePath)) {
3831
3807
  throw new Error(`file not found: ${filePath}`);
3832
3808
  }
3833
3809
  if (!isSqliteFile(filePath)) {
@@ -4530,8 +4506,8 @@ function formatConflictSummaries(summaries) {
4530
4506
 
4531
4507
  // src/screenshot.ts
4532
4508
  import dns from "node:dns/promises";
4533
- import fs3 from "node:fs";
4534
- import path3 from "node:path";
4509
+ import fs2 from "node:fs";
4510
+ import path2 from "node:path";
4535
4511
  var loadPuppeteer = createLazyModuleLoader(
4536
4512
  async () => await import("puppeteer-core"),
4537
4513
  "screenshot disabled (puppeteer-core unavailable)"
@@ -4540,7 +4516,7 @@ var PLAYWRIGHT_CHROME_SUBDIRS = ["chrome-win64", "chrome-win"];
4540
4516
  function findPlaywrightChromium(msPlaywrightDir) {
4541
4517
  let entries;
4542
4518
  try {
4543
- entries = fs3.readdirSync(msPlaywrightDir);
4519
+ entries = fs2.readdirSync(msPlaywrightDir);
4544
4520
  } catch {
4545
4521
  return [];
4546
4522
  }
@@ -4548,7 +4524,7 @@ function findPlaywrightChromium(msPlaywrightDir) {
4548
4524
  const candidates = [];
4549
4525
  for (const m of versioned) {
4550
4526
  for (const sub of PLAYWRIGHT_CHROME_SUBDIRS) {
4551
- candidates.push(path3.join(msPlaywrightDir, m[0], sub, "chrome.exe"));
4527
+ candidates.push(path2.join(msPlaywrightDir, m[0], sub, "chrome.exe"));
4552
4528
  }
4553
4529
  }
4554
4530
  return candidates;
@@ -4559,33 +4535,33 @@ function platformCandidatePaths() {
4559
4535
  const programFiles = process.env["PROGRAMFILES"];
4560
4536
  const programFilesX86 = process.env["PROGRAMFILES(X86)"];
4561
4537
  const localAppData = process.env["LOCALAPPDATA"];
4562
- if (programFiles) candidates2.push(path3.join(programFiles, "Google", "Chrome", "Application", "chrome.exe"));
4563
- if (programFilesX86) candidates2.push(path3.join(programFilesX86, "Google", "Chrome", "Application", "chrome.exe"));
4538
+ if (programFiles) candidates2.push(path2.join(programFiles, "Google", "Chrome", "Application", "chrome.exe"));
4539
+ if (programFilesX86) candidates2.push(path2.join(programFilesX86, "Google", "Chrome", "Application", "chrome.exe"));
4564
4540
  if (localAppData) {
4565
- candidates2.push(path3.join(localAppData, "Google", "Chrome", "Application", "chrome.exe"));
4566
- candidates2.push(...findPlaywrightChromium(path3.join(localAppData, "ms-playwright")));
4541
+ candidates2.push(path2.join(localAppData, "Google", "Chrome", "Application", "chrome.exe"));
4542
+ candidates2.push(...findPlaywrightChromium(path2.join(localAppData, "ms-playwright")));
4567
4543
  }
4568
4544
  return candidates2;
4569
4545
  }
4570
4546
  if (process.platform === "darwin") {
4571
4547
  const home2 = process.env["HOME"];
4572
4548
  const candidates2 = ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"];
4573
- if (home2) candidates2.push(...findPlaywrightChromium(path3.join(home2, "Library", "Caches", "ms-playwright")));
4549
+ if (home2) candidates2.push(...findPlaywrightChromium(path2.join(home2, "Library", "Caches", "ms-playwright")));
4574
4550
  return candidates2;
4575
4551
  }
4576
4552
  const home = process.env["HOME"];
4577
4553
  const candidates = ["/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/usr/bin/chromium-browser", "/usr/bin/chromium"];
4578
- if (home) candidates.push(...findPlaywrightChromium(path3.join(home, ".cache", "ms-playwright")));
4554
+ if (home) candidates.push(...findPlaywrightChromium(path2.join(home, ".cache", "ms-playwright")));
4579
4555
  return candidates;
4580
4556
  }
4581
4557
  function resolveBrowserExecutablePath(explicit) {
4582
- if (explicit && fs3.existsSync(explicit)) return explicit;
4558
+ if (explicit && fs2.existsSync(explicit)) return explicit;
4583
4559
  const cfgPath = loadConfig().screenshot.chrome_path;
4584
- if (cfgPath && fs3.existsSync(cfgPath)) return cfgPath;
4560
+ if (cfgPath && fs2.existsSync(cfgPath)) return cfgPath;
4585
4561
  const envPath = process.env["TOKEN_GOAT_CHROME_PATH"];
4586
- if (envPath && fs3.existsSync(envPath)) return envPath;
4562
+ if (envPath && fs2.existsSync(envPath)) return envPath;
4587
4563
  for (const candidate of platformCandidatePaths()) {
4588
- if (fs3.existsSync(candidate)) return candidate;
4564
+ if (fs2.existsSync(candidate)) return candidate;
4589
4565
  }
4590
4566
  return null;
4591
4567
  }
@@ -4892,7 +4868,7 @@ async function takeScreenshot(url, destPath, opts) {
4892
4868
  }
4893
4869
 
4894
4870
  // src/notes.ts
4895
- import * as fs4 from "node:fs";
4871
+ import * as fs3 from "node:fs";
4896
4872
  var WHOLE_FILE_NOTE_SYMBOL = "";
4897
4873
  function toNoteRow(row) {
4898
4874
  return {
@@ -4917,7 +4893,7 @@ function resolveSymbolMatch(filePath, symbolName, dbPath = globalDbPath()) {
4917
4893
  }
4918
4894
  function bodyFromSource(entry) {
4919
4895
  try {
4920
- return fs4.readFileSync(entry.filePath, "utf8").split(/\r?\n/).slice(Math.max(0, entry.lineStart - 1), entry.lineEnd).join("\n");
4896
+ return fs3.readFileSync(entry.filePath, "utf8").split(/\r?\n/).slice(Math.max(0, entry.lineStart - 1), entry.lineEnd).join("\n");
4921
4897
  } catch {
4922
4898
  return "";
4923
4899
  }
@@ -4968,7 +4944,7 @@ function isNoteStale(note, dbPath = globalDbPath()) {
4968
4944
 
4969
4945
  // src/ts_refs.ts
4970
4946
  import { createRequire } from "node:module";
4971
- import * as path4 from "node:path";
4947
+ import * as path3 from "node:path";
4972
4948
  var _require = createRequire(import.meta.url);
4973
4949
  var MAX_CANDIDATE_FILES = 50;
4974
4950
  var _ts = null;
@@ -4999,7 +4975,7 @@ registerReset(() => {
4999
4975
  var TS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts"]);
5000
4976
  var TS_JS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]);
5001
4977
  function isTsPath(filePath) {
5002
- return TS_EXTENSIONS.has(path4.extname(filePath).toLowerCase());
4978
+ return TS_EXTENSIONS.has(path3.extname(filePath).toLowerCase());
5003
4979
  }
5004
4980
  function resolveTypedRefs(input) {
5005
4981
  const ts = loadTs();
@@ -5029,7 +5005,7 @@ function resolveTypedRefs(input) {
5029
5005
  if (defSymbol === null) return null;
5030
5006
  const out = [];
5031
5007
  for (const ref of input.candidates) {
5032
- if (!TS_JS_EXTENSIONS.has(path4.extname(ref.filePath).toLowerCase())) {
5008
+ if (!TS_JS_EXTENSIONS.has(path3.extname(ref.filePath).toLowerCase())) {
5033
5009
  out.push(ref);
5034
5010
  continue;
5035
5011
  }
@@ -5055,12 +5031,12 @@ function buildScopedProgram(ts, rootNames, searchFrom) {
5055
5031
  resolveJsonModule: true,
5056
5032
  esModuleInterop: true
5057
5033
  };
5058
- const configPath = ts.findConfigFile(path4.dirname(searchFrom), ts.sys.fileExists, "tsconfig.json");
5034
+ const configPath = ts.findConfigFile(path3.dirname(searchFrom), ts.sys.fileExists, "tsconfig.json");
5059
5035
  if (configPath !== void 0) {
5060
5036
  try {
5061
5037
  const raw = ts.readConfigFile(configPath, ts.sys.readFile);
5062
5038
  if (raw.error === void 0) {
5063
- const parsed = ts.parseJsonConfigFileContent(raw.config, ts.sys, path4.dirname(configPath));
5039
+ const parsed = ts.parseJsonConfigFileContent(raw.config, ts.sys, path3.dirname(configPath));
5064
5040
  Object.assign(options, parsed.options);
5065
5041
  }
5066
5042
  } catch {
@@ -5164,7 +5140,7 @@ var STRUCTURED_MISS_MAX_NODES = 2e4;
5164
5140
  var REFS_TOP_SCAN_LIMIT = 2e4;
5165
5141
  function fileExists(p) {
5166
5142
  try {
5167
- fs5.statSync(p);
5143
+ fs4.statSync(p);
5168
5144
  return true;
5169
5145
  } catch {
5170
5146
  return false;
@@ -5195,30 +5171,30 @@ function withPinnedReads(pins, fn) {
5195
5171
  }
5196
5172
  }
5197
5173
  function readPinnedBytes(p, pinned) {
5198
- const fd = fs5.openSync(p, fs5.constants.O_RDONLY);
5174
+ const fd = fs4.openSync(p, fs4.constants.O_RDONLY);
5199
5175
  try {
5200
- const actual = fileIdentity(fs5.fstatSync(fd, { bigint: true }));
5176
+ const actual = fileIdentity(fs4.fstatSync(fd, { bigint: true }));
5201
5177
  if (actual !== pinned) {
5202
5178
  throw new ConfinementIdentityError(
5203
5179
  `refused: "${p}" changed identity between validation and read (validated ${pinned}, opened ${actual}). The file was replaced or redirected after the confinement check, so the read was not performed.`
5204
5180
  );
5205
5181
  }
5206
- return fs5.readFileSync(fd);
5182
+ return fs4.readFileSync(fd);
5207
5183
  } finally {
5208
- fs5.closeSync(fd);
5184
+ fs4.closeSync(fd);
5209
5185
  }
5210
5186
  }
5211
5187
  function verifyPinnedIdentity(p, pinned) {
5212
- const fd = fs5.openSync(p, fs5.constants.O_RDONLY);
5188
+ const fd = fs4.openSync(p, fs4.constants.O_RDONLY);
5213
5189
  try {
5214
- const actual = fileIdentity(fs5.fstatSync(fd, { bigint: true }));
5190
+ const actual = fileIdentity(fs4.fstatSync(fd, { bigint: true }));
5215
5191
  if (actual !== pinned) {
5216
5192
  throw new ConfinementIdentityError(
5217
5193
  `refused: "${p}" changed identity between validation and read (validated ${pinned}, opened ${actual}). The file was replaced or redirected after the confinement check, so the read was not performed.`
5218
5194
  );
5219
5195
  }
5220
5196
  } finally {
5221
- fs5.closeSync(fd);
5197
+ fs4.closeSync(fd);
5222
5198
  }
5223
5199
  }
5224
5200
  function verifyStillAbsent(p) {
@@ -5236,7 +5212,7 @@ function verifyPin(p, pinned) {
5236
5212
  verifyPinnedIdentity(p, pinned);
5237
5213
  }
5238
5214
  function indexFileSyncPinned(resolvedPath, dbPath) {
5239
- const pinned = activePins?.get(pinKey(path5.resolve(resolvedPath)));
5215
+ const pinned = activePins?.get(pinKey(path4.resolve(resolvedPath)));
5240
5216
  if (pinned === void 0) {
5241
5217
  indexFileSync(resolvedPath, dbPath);
5242
5218
  return;
@@ -5258,21 +5234,21 @@ function indexFileSyncPinned(resolvedPath, dbPath) {
5258
5234
  indexFileSync(resolvedPath, dbPath, bytes);
5259
5235
  }
5260
5236
  function readFileText(p) {
5261
- const pinned = activePins?.get(pinKey(path5.resolve(p)));
5237
+ const pinned = activePins?.get(pinKey(path4.resolve(p)));
5262
5238
  try {
5263
5239
  if (pinned === ABSENT_PIN) {
5264
5240
  verifyStillAbsent(p);
5265
5241
  return null;
5266
5242
  }
5267
5243
  if (pinned !== void 0) return redactIfDotenv(p, decodeSource(readPinnedBytes(p, pinned)));
5268
- return redactIfDotenv(p, decodeSource(fs5.readFileSync(p)));
5244
+ return redactIfDotenv(p, decodeSource(fs4.readFileSync(p)));
5269
5245
  } catch (err) {
5270
5246
  if (err instanceof ConfinementIdentityError) throw err;
5271
5247
  return null;
5272
5248
  }
5273
5249
  }
5274
5250
  function readFileBytes(p) {
5275
- const pinned = activePins?.get(pinKey(path5.resolve(p)));
5251
+ const pinned = activePins?.get(pinKey(path4.resolve(p)));
5276
5252
  try {
5277
5253
  if (pinned === ABSENT_PIN) {
5278
5254
  verifyStillAbsent(p);
@@ -5283,9 +5259,9 @@ function readFileBytes(p) {
5283
5259
  if (bytes.length > MAX_ZIP_INPUT_BYTES) throw new ZipInputTooLargeError(p, bytes.length, MAX_ZIP_INPUT_BYTES);
5284
5260
  return bytes;
5285
5261
  }
5286
- const stat = fs5.statSync(p);
5262
+ const stat = fs4.statSync(p);
5287
5263
  if (stat.size > MAX_ZIP_INPUT_BYTES) throw new ZipInputTooLargeError(p, stat.size, MAX_ZIP_INPUT_BYTES);
5288
- return fs5.readFileSync(p);
5264
+ return fs4.readFileSync(p);
5289
5265
  } catch (err) {
5290
5266
  if (err instanceof ConfinementIdentityError || err instanceof ZipInputTooLargeError) throw err;
5291
5267
  return null;
@@ -5304,7 +5280,7 @@ var STALE_WARNING = "\u26A0 STALE: index is older than the file on disk (worker
5304
5280
  var DELETED_WARNING = "\u26A0 DELETED: this file is no longer on disk \u2014 what follows is what the index last saw of it";
5305
5281
  var DELETED_TAG = "\u26A0 DELETED: file no longer on disk";
5306
5282
  function fileIsGone(absPath) {
5307
- if (!path5.isAbsolute(absPath)) return false;
5283
+ if (!path4.isAbsolute(absPath)) return false;
5308
5284
  return fileIsAbsent(absPath);
5309
5285
  }
5310
5286
  function staleWarning(resolvedPath) {
@@ -5372,12 +5348,18 @@ function sumFileSizes(filePaths) {
5372
5348
  let total = 0;
5373
5349
  for (const fp of new Set(filePaths)) {
5374
5350
  try {
5375
- total += Math.min(fs5.statSync(fp).size, PER_FILE_COUNTERFACTUAL_CEILING);
5351
+ total += Math.min(fs4.statSync(fp).size, PER_FILE_COUNTERFACTUAL_CEILING);
5376
5352
  } catch {
5377
5353
  }
5378
5354
  }
5379
5355
  return total;
5380
5356
  }
5357
+ function refsSearchBaselineBytes(rows) {
5358
+ let total = 0;
5359
+ for (const ref of rows) total += Buffer.byteLength(`${refsDisplayPath(ref.filePath)}:${ref.line}: ${ref.context}
5360
+ `, "utf8");
5361
+ return total;
5362
+ }
5381
5363
  function recordReadStat(kind, fullSourceBytes, emittedText, detail) {
5382
5364
  const emittedBytes = Buffer.byteLength(emittedText, "utf8");
5383
5365
  const bytesSaved = Math.max(1, fullSourceBytes - emittedBytes);
@@ -5550,7 +5532,7 @@ function findStructuredKeyPath(name, filePaths) {
5550
5532
  if (!isYaml && !lower.endsWith(".json")) continue;
5551
5533
  let size;
5552
5534
  try {
5553
- size = fs5.statSync(filePath).size;
5535
+ size = fs4.statSync(filePath).size;
5554
5536
  } catch {
5555
5537
  continue;
5556
5538
  }
@@ -6088,7 +6070,7 @@ ${sub.text}`);
6088
6070
  return { text, code: 1 };
6089
6071
  }
6090
6072
  function resolveAgainstProjectRoot(file, projectRoot) {
6091
- return projectRoot !== void 0 && !path5.isAbsolute(file) ? path5.resolve(projectRoot, file) : file;
6073
+ return projectRoot !== void 0 && !path4.isAbsolute(file) ? path4.resolve(projectRoot, file) : file;
6092
6074
  }
6093
6075
  function literalHeadingExists(filePath, heading) {
6094
6076
  const ordinalMatch = /^([^#\r\n]+)#(\d+)$/.exec(heading);
@@ -6112,7 +6094,7 @@ function runSection(opts) {
6112
6094
  }
6113
6095
  const result = readSection(filePath, heading, readFileText);
6114
6096
  if (result === null) {
6115
- if (!fs5.existsSync(filePath)) {
6097
+ if (!fs4.existsSync(filePath)) {
6116
6098
  return { text: `File not found: '${filePath}'`, code: 1 };
6117
6099
  }
6118
6100
  const ordSpec = /^(.*?)#(\d+)$/.exec(heading);
@@ -6199,7 +6181,7 @@ function runSectionCrossFile(pairs, opts) {
6199
6181
  textBlocks.push(`${key}:
6200
6182
  ${sub.text}`);
6201
6183
  }
6202
- const resolvePath = (f) => opts.projectRoot !== void 0 && !path5.isAbsolute(f) ? path5.resolve(opts.projectRoot, f) : f;
6184
+ const resolvePath = (f) => opts.projectRoot !== void 0 && !path4.isAbsolute(f) ? path4.resolve(opts.projectRoot, f) : f;
6203
6185
  const text = opts.json === true ? JSON.stringify(jsonOut, null, 2) : textBlocks.join("\n\n");
6204
6186
  if (anyFound) {
6205
6187
  const fullSourceBytes = sumFileSizes(Array.from(distinctFiles, resolvePath));
@@ -6272,7 +6254,7 @@ function renderRefsTargets(targets, opts, {
6272
6254
  const jsonOut = {};
6273
6255
  let anyFound = false;
6274
6256
  const lines = [];
6275
- const refFilePaths = [];
6257
+ const refRows = [];
6276
6258
  for (const { file, symbol, key } of targets) {
6277
6259
  const queryOpts = { name: symbol };
6278
6260
  if (opts.excludeTests === true || opts.grep !== void 0) queryOpts.limit = REFS_TOP_SCAN_LIMIT;
@@ -6298,7 +6280,7 @@ function renderRefsTargets(targets, opts, {
6298
6280
  results = results.slice(0, opts.limit ?? 100);
6299
6281
  }
6300
6282
  if (results.length > 0) anyFound = true;
6301
- refFilePaths.push(...results.map((r) => r.filePath));
6283
+ refRows.push(...results);
6302
6284
  if (opts.json === true) {
6303
6285
  const hiddenByGrep = matchesGrep !== void 0 ? preGrepCount - (filteredTotal ?? results.length) : 0;
6304
6286
  const withHidden = (payload) => ({ ...payload, ...annotateHiddenByGrep && hiddenByGrep > 0 ? { hiddenByGrep } : {} });
@@ -6340,7 +6322,7 @@ function renderRefsTargets(targets, opts, {
6340
6322
  if (notice !== null) lines.push(` token-goat: ${notice}`);
6341
6323
  }
6342
6324
  }
6343
- const fullSourceBytes = sumFileSizes(refFilePaths);
6325
+ const fullSourceBytes = refsSearchBaselineBytes(refRows);
6344
6326
  if (opts.json === true) {
6345
6327
  const text2 = JSON.stringify(jsonOut, null, 2);
6346
6328
  emit(text2);
@@ -6449,7 +6431,7 @@ function runRefsSingle(opts) {
6449
6431
  }
6450
6432
  return 1;
6451
6433
  }
6452
- const fullSourceBytes = sumFileSizes(results.map((r) => r.filePath));
6434
+ const fullSourceBytes = refsSearchBaselineBytes(results);
6453
6435
  if (opts.json === true) {
6454
6436
  let payload;
6455
6437
  if (opts.top !== void 0) {
@@ -6522,7 +6504,7 @@ function renderCallerGroups(refs, contextLines = 0) {
6522
6504
  return lines;
6523
6505
  }
6524
6506
  function noSymbolsMessage(displayPath, resolvedPath) {
6525
- if (!fs5.existsSync(resolvedPath)) {
6507
+ if (!fs4.existsSync(resolvedPath)) {
6526
6508
  return `Could not read: ${displayPath}`;
6527
6509
  }
6528
6510
  const lang = unsupportedLanguageName(resolvedPath);
@@ -7361,10 +7343,10 @@ function runConflicts(opts) {
7361
7343
  if (opts.path === void 0) {
7362
7344
  files = walkProject(process.cwd()).files;
7363
7345
  } else {
7364
- const abs = path5.resolve(opts.path);
7346
+ const abs = path4.resolve(opts.path);
7365
7347
  let stat;
7366
7348
  try {
7367
- stat = fs5.statSync(abs);
7349
+ stat = fs4.statSync(abs);
7368
7350
  } catch {
7369
7351
  emitErr(`Could not read: ${opts.path}`);
7370
7352
  return 1;
@@ -7399,7 +7381,7 @@ async function runPdfExtractText(file, pagesSpec, layout = false) {
7399
7381
  if (!fileExists(file)) {
7400
7382
  throw new Error(`Could not read: ${file}`);
7401
7383
  }
7402
- const data = fs5.readFileSync(file);
7384
+ const data = fs4.readFileSync(file);
7403
7385
  const result = await extractPdfText(new Uint8Array(data), pagesSpec, layout);
7404
7386
  return result.text;
7405
7387
  }
@@ -7407,21 +7389,21 @@ async function runPdfOutline(file) {
7407
7389
  if (!fileExists(file)) {
7408
7390
  throw new Error(`Could not read: ${file}`);
7409
7391
  }
7410
- const data = fs5.readFileSync(file);
7392
+ const data = fs4.readFileSync(file);
7411
7393
  return extractPdfOutline(new Uint8Array(data));
7412
7394
  }
7413
7395
  async function runPdfMeta(file) {
7414
7396
  if (!fileExists(file)) {
7415
7397
  throw new Error(`Could not read: ${file}`);
7416
7398
  }
7417
- const data = fs5.readFileSync(file);
7399
+ const data = fs4.readFileSync(file);
7418
7400
  return extractPdfMeta(new Uint8Array(data));
7419
7401
  }
7420
7402
  async function runPdfLocate(file, pattern, opts) {
7421
7403
  if (!fileExists(file)) {
7422
7404
  throw new Error(`Could not read: ${file}`);
7423
7405
  }
7424
- const data = fs5.readFileSync(file);
7406
+ const data = fs4.readFileSync(file);
7425
7407
  return locatePdfPages(new Uint8Array(data), pattern, opts);
7426
7408
  }
7427
7409
  async function runImageMeta(file) {
@@ -7431,7 +7413,7 @@ async function runImageMeta(file) {
7431
7413
  if (!isImagePath(file)) {
7432
7414
  throw new Error(`Not an image file: ${file}`);
7433
7415
  }
7434
- const data = fs5.readFileSync(file);
7416
+ const data = fs4.readFileSync(file);
7435
7417
  const bytes = data.length;
7436
7418
  let probe;
7437
7419
  try {
@@ -7463,7 +7445,7 @@ async function runImageText(file) {
7463
7445
  if (!isImagePath(file)) {
7464
7446
  throw new Error(`Not an image file: ${file}`);
7465
7447
  }
7466
- const data = fs5.readFileSync(file);
7448
+ const data = fs4.readFileSync(file);
7467
7449
  const ocr = await ocrImage(data);
7468
7450
  if (ocr === null) {
7469
7451
  if (ocrIntegrityFailed()) {
@@ -8167,7 +8149,7 @@ function runGrep(opts) {
8167
8149
  if (activePins !== null) {
8168
8150
  let realDir;
8169
8151
  try {
8170
- realDir = foldRealpath(fs5.realpathSync(dir));
8152
+ realDir = foldRealpath(fs4.realpathSync(dir));
8171
8153
  } catch {
8172
8154
  return;
8173
8155
  }
@@ -8176,12 +8158,12 @@ function runGrep(opts) {
8176
8158
  visitedRealDirs.add(realDir);
8177
8159
  }
8178
8160
  try {
8179
- for (const entry of fs5.readdirSync(dir)) {
8161
+ for (const entry of fs4.readdirSync(dir)) {
8180
8162
  if (entry.startsWith(".")) continue;
8181
- const full = path5.join(dir, entry);
8163
+ const full = path4.join(dir, entry);
8182
8164
  let lst;
8183
8165
  try {
8184
- lst = fs5.lstatSync(full);
8166
+ lst = fs4.lstatSync(full);
8185
8167
  } catch {
8186
8168
  continue;
8187
8169
  }
@@ -8189,7 +8171,7 @@ function runGrep(opts) {
8189
8171
  if (lst.isSymbolicLink()) {
8190
8172
  let real;
8191
8173
  try {
8192
- real = fs5.realpathSync(full);
8174
+ real = fs4.realpathSync(full);
8193
8175
  } catch {
8194
8176
  continue;
8195
8177
  }
@@ -8198,7 +8180,7 @@ function runGrep(opts) {
8198
8180
  }
8199
8181
  let stat;
8200
8182
  try {
8201
- stat = fs5.statSync(target);
8183
+ stat = fs4.statSync(target);
8202
8184
  } catch {
8203
8185
  continue;
8204
8186
  }
@@ -8218,16 +8200,16 @@ function runGrep(opts) {
8218
8200
  emitErr(`Path not found: ${searchPath}`);
8219
8201
  return 1;
8220
8202
  }
8221
- const stat = fs5.statSync(searchPath);
8203
+ const stat = fs4.statSync(searchPath);
8222
8204
  if (stat.isDirectory()) {
8223
- const pinned = activePins?.get(pinKey(path5.resolve(searchPath)));
8205
+ const pinned = activePins?.get(pinKey(path4.resolve(searchPath)));
8224
8206
  if (pinned !== void 0) verifyPin(searchPath, pinned);
8225
8207
  let boundaryReal;
8226
8208
  try {
8227
- boundaryReal = fs5.realpathSync(searchPath);
8209
+ boundaryReal = fs4.realpathSync(searchPath);
8228
8210
  } catch (err) {
8229
8211
  if (pinned === void 0) {
8230
- boundaryReal = path5.resolve(searchPath);
8212
+ boundaryReal = path4.resolve(searchPath);
8231
8213
  } else {
8232
8214
  throw new ConfinementIdentityError(
8233
8215
  `refused: "${searchPath}" could not be resolved after validation (${String(err)}). The path may have been replaced or redirected after the confinement check, so the search was not performed.`
@@ -8358,7 +8340,7 @@ function runConfigGet(opts) {
8358
8340
  emit(value);
8359
8341
  return 0;
8360
8342
  }
8361
- const ext = path5.extname(opts.file).toLowerCase();
8343
+ const ext = path4.extname(opts.file).toLowerCase();
8362
8344
  if (ext === ".json") {
8363
8345
  try {
8364
8346
  let obj = JSON.parse(text);
@@ -8481,7 +8463,7 @@ function runExports(opts) {
8481
8463
  names.push(s.name);
8482
8464
  }
8483
8465
  }
8484
- const ext = path5.extname(opts.file).toLowerCase();
8466
+ const ext = path4.extname(opts.file).toLowerCase();
8485
8467
  const text = readFileText(diskPath);
8486
8468
  if (text === null && symbols.length === 0) {
8487
8469
  emitErr(`Could not read: ${opts.file}`);
@@ -8818,9 +8800,9 @@ function extractImports(text, ext) {
8818
8800
  return found;
8819
8801
  }
8820
8802
  function importsExtensionFor(filePath) {
8821
- const base = path5.basename(filePath).toLowerCase();
8803
+ const base = path4.basename(filePath).toLowerCase();
8822
8804
  if (base === "makefile" || base === "gnumakefile" || base === "bsdmakefile") return ".mk";
8823
- return path5.extname(filePath);
8805
+ return path4.extname(filePath);
8824
8806
  }
8825
8807
  function runImports(opts) {
8826
8808
  const multiFiles = parseMultiFileSpec(opts.file);
@@ -8912,7 +8894,7 @@ async function runSemantic(query, opts) {
8912
8894
  }
8913
8895
  const n = opts.limit !== void 0 && Number.isFinite(opts.limit) ? opts.limit : 20;
8914
8896
  if (opts.projectRoot !== void 0) {
8915
- if (!path5.isAbsolute(opts.projectRoot) || !fs5.existsSync(opts.projectRoot) || !fs5.statSync(opts.projectRoot).isDirectory()) {
8897
+ if (!path4.isAbsolute(opts.projectRoot) || !fs4.existsSync(opts.projectRoot) || !fs4.statSync(opts.projectRoot).isDirectory()) {
8916
8898
  const message = `token-goat: projectRoot must be an absolute, existing directory, got '${opts.projectRoot}'`;
8917
8899
  if (opts.json === true) {
8918
8900
  return { text: JSON.stringify({ error: message }, null, 2), code: 1 };
@@ -9150,6 +9132,318 @@ function runNoteList(opts = {}) {
9150
9132
  return { text: lines.join("\n"), code: 0 };
9151
9133
  }
9152
9134
 
9135
+ // src/import_graph.ts
9136
+ import * as fs5 from "node:fs";
9137
+ import * as path5 from "node:path";
9138
+ var DIRECT_PROBE_EXTENSIONS = ["", ".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs", ".cjs", ".cts", ".py"];
9139
+ var INDEX_PROBE_EXTENSIONS = [".ts", ".js", ".tsx", ".jsx", ".mts", ".cts"];
9140
+ function buildImportGraph(cwd) {
9141
+ const files = getTrackedFiles(cwd);
9142
+ const filesByFoldedPath = /* @__PURE__ */ new Map();
9143
+ for (const f of files) filesByFoldedPath.set(foldPath(f), f);
9144
+ const resolve5 = (fromFile, spec) => {
9145
+ if (!spec.startsWith(".")) return null;
9146
+ const dir = path5.dirname(fromFile);
9147
+ const strippedSpec = spec.replace(/\.(m?js|cjs)$/, "");
9148
+ const base = path5.resolve(dir, strippedSpec);
9149
+ for (const ext of DIRECT_PROBE_EXTENSIONS) {
9150
+ const match = filesByFoldedPath.get(foldPath(base + ext));
9151
+ if (match !== void 0) return match;
9152
+ }
9153
+ const idx = path5.join(base, "index");
9154
+ for (const ext of INDEX_PROBE_EXTENSIONS) {
9155
+ const match = filesByFoldedPath.get(foldPath(idx + ext));
9156
+ if (match !== void 0) return match;
9157
+ }
9158
+ return null;
9159
+ };
9160
+ const graph = /* @__PURE__ */ new Map();
9161
+ const importedBy = /* @__PURE__ */ new Map();
9162
+ for (const file of files) {
9163
+ let text;
9164
+ try {
9165
+ text = fs5.readFileSync(file, "utf8");
9166
+ } catch {
9167
+ continue;
9168
+ }
9169
+ const internal = [];
9170
+ for (const spec of extractImports(text, importsExtensionFor(file))) {
9171
+ const resolved = resolve5(file, spec);
9172
+ if (resolved === null) continue;
9173
+ internal.push(resolved);
9174
+ let importers = importedBy.get(resolved);
9175
+ if (importers === void 0) {
9176
+ importers = /* @__PURE__ */ new Set();
9177
+ importedBy.set(resolved, importers);
9178
+ }
9179
+ importers.add(file);
9180
+ }
9181
+ graph.set(file, internal);
9182
+ }
9183
+ return { files, graph, importedBy, resolve: resolve5 };
9184
+ }
9185
+
9186
+ // src/modules.ts
9187
+ var WEAK_MODULARITY = 0.3;
9188
+ var MAX_PASSES = 32;
9189
+ var GAIN_EPSILON = 1e-12;
9190
+ function emptyGraph(n) {
9191
+ return { n, adj: Array.from({ length: n }, () => /* @__PURE__ */ new Map()), degree: new Array(n).fill(0), m: 0 };
9192
+ }
9193
+ function addEdge(g, a, b, w) {
9194
+ if (a === b) {
9195
+ g.adj[a].set(a, (g.adj[a].get(a) ?? 0) + w);
9196
+ g.degree[a] += 2 * w;
9197
+ } else {
9198
+ g.adj[a].set(b, (g.adj[a].get(b) ?? 0) + w);
9199
+ g.adj[b].set(a, (g.adj[b].get(a) ?? 0) + w);
9200
+ g.degree[a] += w;
9201
+ g.degree[b] += w;
9202
+ }
9203
+ g.m += w;
9204
+ }
9205
+ function localMoving(g) {
9206
+ const community = Array.from({ length: g.n }, (_, i) => i);
9207
+ const tot = g.degree.slice();
9208
+ const twoM = 2 * g.m;
9209
+ let movedEver = false;
9210
+ if (twoM === 0) return { community, moved: false };
9211
+ for (let pass = 0; pass < MAX_PASSES; pass++) {
9212
+ let improved = false;
9213
+ for (let i = 0; i < g.n; i++) {
9214
+ const ki = g.degree[i];
9215
+ const ci = community[i];
9216
+ const links = /* @__PURE__ */ new Map();
9217
+ for (const [j, w] of g.adj[i]) {
9218
+ if (j === i) continue;
9219
+ const cj = community[j];
9220
+ links.set(cj, (links.get(cj) ?? 0) + w);
9221
+ }
9222
+ tot[ci] = tot[ci] - ki;
9223
+ let bestC = ci;
9224
+ let bestGain = (links.get(ci) ?? 0) - tot[ci] * ki / twoM;
9225
+ for (const c of [...links.keys()].sort((a, b) => a - b)) {
9226
+ if (c === ci) continue;
9227
+ const gain = links.get(c) - tot[c] * ki / twoM;
9228
+ if (gain > bestGain + GAIN_EPSILON) {
9229
+ bestGain = gain;
9230
+ bestC = c;
9231
+ }
9232
+ }
9233
+ tot[bestC] = tot[bestC] + ki;
9234
+ community[i] = bestC;
9235
+ if (bestC !== ci) {
9236
+ improved = true;
9237
+ movedEver = true;
9238
+ }
9239
+ }
9240
+ if (!improved) break;
9241
+ }
9242
+ return { community, moved: movedEver };
9243
+ }
9244
+ function densify(community) {
9245
+ const firstMember = /* @__PURE__ */ new Map();
9246
+ for (let i = 0; i < community.length; i++) {
9247
+ const c = community[i];
9248
+ if (!firstMember.has(c)) firstMember.set(c, i);
9249
+ }
9250
+ const ordered = [...firstMember.entries()].sort((a, b) => a[1] - b[1]).map(([c]) => c);
9251
+ const remap = /* @__PURE__ */ new Map();
9252
+ ordered.forEach((c, idx) => remap.set(c, idx));
9253
+ return { dense: community.map((c) => remap.get(c)), count: ordered.length };
9254
+ }
9255
+ function modularityOf(g, partition) {
9256
+ const twoM = 2 * g.m;
9257
+ if (twoM === 0) return null;
9258
+ const internal = /* @__PURE__ */ new Map();
9259
+ const tot = /* @__PURE__ */ new Map();
9260
+ for (let i = 0; i < g.n; i++) {
9261
+ const ci = partition[i];
9262
+ tot.set(ci, (tot.get(ci) ?? 0) + g.degree[i]);
9263
+ for (const [j, w] of g.adj[i]) {
9264
+ if (j < i) continue;
9265
+ if (partition[j] === ci) internal.set(ci, (internal.get(ci) ?? 0) + w);
9266
+ }
9267
+ }
9268
+ let q = 0;
9269
+ for (const [c, t] of tot) {
9270
+ q += 2 * (internal.get(c) ?? 0) / twoM - (t / twoM) ** 2;
9271
+ }
9272
+ return q;
9273
+ }
9274
+ function louvain(g) {
9275
+ let current = g;
9276
+ let mapping = Array.from({ length: g.n }, (_, i) => i);
9277
+ for (let level = 0; level < MAX_PASSES; level++) {
9278
+ const { community, moved } = localMoving(current);
9279
+ const { dense, count } = densify(community);
9280
+ mapping = mapping.map((c) => dense[c]);
9281
+ if (!moved || count === current.n) break;
9282
+ const next = emptyGraph(count);
9283
+ for (let i = 0; i < current.n; i++) {
9284
+ for (const [j, w] of current.adj[i]) {
9285
+ if (j < i) continue;
9286
+ addEdge(next, dense[i], dense[j], w);
9287
+ }
9288
+ }
9289
+ current = next;
9290
+ }
9291
+ return mapping;
9292
+ }
9293
+ function commonDirLabel(files) {
9294
+ const dirs = new Set(files.map(dirOf));
9295
+ if (dirs.size === 1) {
9296
+ const only = [...dirs][0];
9297
+ return only === "." ? "." : `${only}/`;
9298
+ }
9299
+ const segmentLists = files.map((f) => {
9300
+ const parts = f.split("/");
9301
+ parts.pop();
9302
+ return parts;
9303
+ });
9304
+ const first = segmentLists[0];
9305
+ if (first === void 0) return null;
9306
+ const prefix = [];
9307
+ for (let i = 0; i < first.length; i++) {
9308
+ const seg = first[i];
9309
+ if (!segmentLists.every((s) => s[i] === seg)) break;
9310
+ prefix.push(seg);
9311
+ }
9312
+ return prefix.length === 0 ? null : `${prefix.join("/")}/`;
9313
+ }
9314
+ function dirOf(file) {
9315
+ const idx = file.lastIndexOf("/");
9316
+ return idx === -1 ? "." : file.slice(0, idx);
9317
+ }
9318
+ function detectModules(imports, cwd, top) {
9319
+ const { files, graph } = imports;
9320
+ const root = getDisplayRoot(cwd);
9321
+ const degreeOf = /* @__PURE__ */ new Map();
9322
+ const bump = (f) => {
9323
+ degreeOf.set(f, (degreeOf.get(f) ?? 0) + 1);
9324
+ };
9325
+ for (const [from, targets] of graph) {
9326
+ for (const to of targets) {
9327
+ if (from === to) continue;
9328
+ bump(from);
9329
+ bump(to);
9330
+ }
9331
+ }
9332
+ const connected = files.filter((f) => (degreeOf.get(f) ?? 0) > 0);
9333
+ const isolatedCount = files.length - connected.length;
9334
+ if (connected.length === 0) {
9335
+ return { modules: [], modulesTotal: 0, modularity: null, isolatedCount, crossImports: [], crossImportsTotal: 0, noEdges: true };
9336
+ }
9337
+ const ordered = [...connected].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
9338
+ const idOf = /* @__PURE__ */ new Map();
9339
+ ordered.forEach((f, i) => idOf.set(f, i));
9340
+ const g = emptyGraph(ordered.length);
9341
+ const pairWeight = /* @__PURE__ */ new Map();
9342
+ for (const [from, targets] of graph) {
9343
+ const a = idOf.get(from);
9344
+ if (a === void 0) continue;
9345
+ for (const to of targets) {
9346
+ const b = idOf.get(to);
9347
+ if (b === void 0 || a === b) continue;
9348
+ const key = a < b ? `${a}:${b}` : `${b}:${a}`;
9349
+ pairWeight.set(key, (pairWeight.get(key) ?? 0) + 1);
9350
+ }
9351
+ }
9352
+ for (const key of [...pairWeight.keys()].sort()) {
9353
+ const [a, b] = key.split(":").map(Number);
9354
+ addEdge(g, a, b, pairWeight.get(key));
9355
+ }
9356
+ const partition = louvain(g);
9357
+ const modularity = modularityOf(g, partition);
9358
+ const membersOf = /* @__PURE__ */ new Map();
9359
+ for (let i = 0; i < ordered.length; i++) {
9360
+ const c = partition[i];
9361
+ const list = membersOf.get(c);
9362
+ if (list === void 0) membersOf.set(c, [ordered[i]]);
9363
+ else list.push(ordered[i]);
9364
+ }
9365
+ const inModuleDegree = /* @__PURE__ */ new Map();
9366
+ for (const [from, targets] of graph) {
9367
+ const a = idOf.get(from);
9368
+ if (a === void 0) continue;
9369
+ for (const to of targets) {
9370
+ const b = idOf.get(to);
9371
+ if (b === void 0 || a === b || partition[a] !== partition[b]) continue;
9372
+ inModuleDegree.set(from, (inModuleDegree.get(from) ?? 0) + 1);
9373
+ inModuleDegree.set(to, (inModuleDegree.get(to) ?? 0) + 1);
9374
+ }
9375
+ }
9376
+ const built = [...membersOf.entries()].map(([c, absFiles]) => {
9377
+ const display = absFiles.map((f) => toDisplayPath(root, f)).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
9378
+ const coreAbs = [...absFiles].sort((a, b) => (inModuleDegree.get(b) ?? 0) - (inModuleDegree.get(a) ?? 0) || (a < b ? -1 : a > b ? 1 : 0))[0];
9379
+ return { community: c, core: toDisplayPath(root, coreAbs), commonDir: commonDirLabel(display), files: display, directories: new Set(display.map(dirOf)).size };
9380
+ }).sort((a, b) => b.files.length - a.files.length || (a.core < b.core ? -1 : a.core > b.core ? 1 : 0) || (a.files[0] < b.files[0] ? -1 : 1));
9381
+ const indexOfCommunity = /* @__PURE__ */ new Map();
9382
+ built.forEach((m, i) => indexOfCommunity.set(m.community, i + 1));
9383
+ const coreOfIndex = /* @__PURE__ */ new Map();
9384
+ built.forEach((m, i) => coreOfIndex.set(i + 1, m.core));
9385
+ const crossCount = /* @__PURE__ */ new Map();
9386
+ for (const [from, targets] of graph) {
9387
+ const a = idOf.get(from);
9388
+ if (a === void 0) continue;
9389
+ const ca = indexOfCommunity.get(partition[a]);
9390
+ if (ca === void 0) continue;
9391
+ for (const to of targets) {
9392
+ const b = idOf.get(to);
9393
+ if (b === void 0) continue;
9394
+ const cb = indexOfCommunity.get(partition[b]);
9395
+ if (cb === void 0 || ca === cb) continue;
9396
+ const key = `${ca}:${cb}`;
9397
+ crossCount.set(key, (crossCount.get(key) ?? 0) + 1);
9398
+ }
9399
+ }
9400
+ const allCross = [...crossCount.entries()].map(([key, imports2]) => {
9401
+ const [fromIndex, toIndex] = key.split(":").map(Number);
9402
+ return { fromIndex, toIndex, fromCore: coreOfIndex.get(fromIndex), toCore: coreOfIndex.get(toIndex), imports: imports2 };
9403
+ }).sort((a, b) => b.imports - a.imports || a.fromIndex - b.fromIndex || a.toIndex - b.toIndex);
9404
+ const modules = built.slice(0, top).map((m, i) => ({ index: i + 1, core: m.core, commonDir: m.commonDir, files: m.files, size: m.files.length, directories: m.directories }));
9405
+ return {
9406
+ modules,
9407
+ modulesTotal: built.length,
9408
+ modularity,
9409
+ isolatedCount,
9410
+ crossImports: allCross.slice(0, top),
9411
+ crossImportsTotal: allCross.length,
9412
+ noEdges: false
9413
+ };
9414
+ }
9415
+ function renderModules(result, top) {
9416
+ const lines = [];
9417
+ if (result.noEdges) {
9418
+ lines.push(`modules: none. No file in this project imports another by a relative path, so there is nothing to group (${countNoun(result.isolatedCount, "file")} checked).`);
9419
+ return lines;
9420
+ }
9421
+ const q = result.modularity;
9422
+ const strength = q === null ? "" : q < WEAK_MODULARITY ? `, modularity ${q.toFixed(2)} -- weak, so treat this grouping as a hint rather than a finding` : `, modularity ${q.toFixed(2)}`;
9423
+ lines.push(result.modules.length < result.modulesTotal ? `modules (top ${result.modules.length} of ${result.modulesTotal}${strength}):` : `modules (${result.modulesTotal} found${strength}):`);
9424
+ for (const m of result.modules) {
9425
+ const where = m.commonDir === null ? `across ${m.directories} directories` : m.commonDir === "." ? "all in the repository root" : `all in ${m.commonDir}`;
9426
+ lines.push(` #${m.index} ${m.size} files ${m.core} (${where})`);
9427
+ }
9428
+ if (result.isolatedCount > 0) {
9429
+ lines.push(` ${result.isolatedCount} files are in no module (they neither import nor are imported within the project).`);
9430
+ }
9431
+ if (result.crossImportsTotal === 0) {
9432
+ lines.push("cross-module imports: none. Every import stays inside its own module.");
9433
+ } else {
9434
+ lines.push(
9435
+ result.crossImports.length < result.crossImportsTotal ? `cross-module imports (top ${result.crossImports.length} of ${result.crossImportsTotal} pairs):` : `cross-module imports (${result.crossImportsTotal} pairs):`
9436
+ );
9437
+ for (const c of result.crossImports) {
9438
+ lines.push(` ${c.imports} imports #${c.fromIndex} ${c.fromCore} -> #${c.toIndex} ${c.toCore}`);
9439
+ }
9440
+ }
9441
+ if (result.modules.length < result.modulesTotal && result.crossImports.some((c) => c.fromIndex > top || c.toIndex > top)) {
9442
+ lines.push(` (a #N above ${top} refers to a module the --top cap left out; raise --top to see it.)`);
9443
+ }
9444
+ return lines;
9445
+ }
9446
+
9153
9447
  // src/graph_commands.ts
9154
9448
  var DEFAULT_REF_QUERY_LIMIT = 500;
9155
9449
  var ALL_SYMBOLS_IN_FILE_LIMIT = 1e4;
@@ -10237,53 +10531,11 @@ function runArch(opts) {
10237
10531
  }
10238
10532
  const cwd = opts.cwd ?? process.cwd();
10239
10533
  const top = opts.top ?? 10;
10240
- const files = getTrackedFiles(cwd);
10534
+ const { files, graph, importedBy, resolve: resolve5 } = buildImportGraph(cwd);
10241
10535
  if (files.length === 0 && opts.json !== true) {
10242
10536
  emit2(`no tracked files found under '${toDisplayPath(getDisplayRoot(cwd), cwd)}' (is it a git repo?). Nothing to analyse.`);
10243
10537
  return 0;
10244
10538
  }
10245
- const filesByFoldedPath = /* @__PURE__ */ new Map();
10246
- for (const f of files) filesByFoldedPath.set(foldPath(f), f);
10247
- const graph = /* @__PURE__ */ new Map();
10248
- const importedBy = /* @__PURE__ */ new Map();
10249
- const resolveRelImport = (fromFile, spec) => {
10250
- if (!spec.startsWith(".")) return null;
10251
- const dir = path6.dirname(fromFile);
10252
- const strippedSpec = spec.replace(/\.(m?js|cjs)$/, "");
10253
- const base = path6.resolve(dir, strippedSpec);
10254
- for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs", ".cjs", ".cts", ".py"]) {
10255
- const candidate = base + ext;
10256
- const match = filesByFoldedPath.get(foldPath(candidate));
10257
- if (match !== void 0) return match;
10258
- }
10259
- const idx = path6.join(base, "index");
10260
- for (const ext of [".ts", ".js", ".tsx", ".jsx", ".mts", ".cts"]) {
10261
- const candidate = idx + ext;
10262
- const match = filesByFoldedPath.get(foldPath(candidate));
10263
- if (match !== void 0) return match;
10264
- }
10265
- return null;
10266
- };
10267
- for (const file of files) {
10268
- let text;
10269
- try {
10270
- text = fs6.readFileSync(file, "utf8");
10271
- } catch {
10272
- continue;
10273
- }
10274
- const ext = importsExtensionFor(file);
10275
- const rawImports = extractImports(text, ext);
10276
- const internal = [];
10277
- for (const spec of rawImports) {
10278
- const resolved = resolveRelImport(file, spec);
10279
- if (resolved !== null) {
10280
- internal.push(resolved);
10281
- if (!importedBy.has(resolved)) importedBy.set(resolved, /* @__PURE__ */ new Set());
10282
- importedBy.get(resolved).add(file);
10283
- }
10284
- }
10285
- graph.set(file, internal);
10286
- }
10287
10539
  const allHubs = [...importedBy.entries()].sort((a, b) => b[1].size - a[1].size).map(([f, importers]) => ({ file: f, importedBy: importers.size }));
10288
10540
  const hubsTotal = allHubs.length;
10289
10541
  const hubs = allHubs.slice(0, top);
@@ -10291,8 +10543,10 @@ function runArch(opts) {
10291
10543
  const entryPointsTotal = allEntryPoints.length;
10292
10544
  const entryPoints = allEntryPoints.slice(0, top);
10293
10545
  const { cycles, truncated: cyclesTruncated } = findCyclesCapped(graph);
10546
+ const moduleResult = opts.modules === true ? detectModules({ files, graph, importedBy, resolve: resolve5 }, cwd, top) : null;
10294
10547
  if (opts.json === true) {
10295
- emit2(JSON.stringify({ hubs, hubsTotal, hubsTruncated: hubs.length < hubsTotal, entryPoints, entryPointsTotal, entryPointsTruncated: entryPoints.length < entryPointsTotal, cycles, ...cyclesTruncated ? { cyclesTruncated: true } : {} }, null, 2));
10548
+ const modulePayload = moduleResult === null ? {} : { modules: moduleResult.modules, modulesTotal: moduleResult.modulesTotal, modulesTruncated: moduleResult.modules.length < moduleResult.modulesTotal, modularity: moduleResult.modularity, isolatedCount: moduleResult.isolatedCount, crossImports: moduleResult.crossImports, crossImportsTotal: moduleResult.crossImportsTotal, crossImportsTruncated: moduleResult.crossImports.length < moduleResult.crossImportsTotal, noImportEdges: moduleResult.noEdges };
10549
+ emit2(JSON.stringify({ hubs, hubsTotal, hubsTruncated: hubs.length < hubsTotal, entryPoints, entryPointsTotal, entryPointsTruncated: entryPoints.length < entryPointsTotal, cycles, ...cyclesTruncated ? { cyclesTruncated: true } : {}, ...modulePayload }, null, 2));
10296
10550
  return 0;
10297
10551
  }
10298
10552
  emit2(hubs.length < hubsTotal ? `hubs (top ${hubs.length} of ${hubsTotal} most-imported):` : `hubs (${hubsTotal} most-imported):`);
@@ -10301,6 +10555,7 @@ function runArch(opts) {
10301
10555
  for (const e of entryPoints) emit2(` ${toDisplayPath(getDisplayRoot(opts.cwd), e.file)}`);
10302
10556
  emit2(cyclesTruncated ? `cycles (first ${cycles.length}, truncated at the ${MAX_CYCLES}-cycle enumeration limit: more cycles exist):` : `cycles (${cycles.length} found):`);
10303
10557
  for (const c of cycles) emit2(` ${c.map((f) => toDisplayPath(getDisplayRoot(opts.cwd), f)).join(" -> ")}`);
10558
+ if (moduleResult !== null) for (const line of renderModules(moduleResult, top)) emit2(line);
10304
10559
  return 0;
10305
10560
  }
10306
10561
  function runBlame(opts) {
@@ -10556,10 +10811,10 @@ function resolveHandoff(name, opts = {}) {
10556
10811
  }
10557
10812
 
10558
10813
  export {
10559
- getTrackedFiles,
10560
10814
  extractSection,
10561
10815
  readSection,
10562
10816
  listSections,
10817
+ buildImportGraph,
10563
10818
  ALL_SYMBOLS_IN_FILE_LIMIT,
10564
10819
  enclosingSymbol,
10565
10820
  runCallers,