token-goat 2.9.15 → 2.9.17

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 (39) hide show
  1. package/README.md +2 -5
  2. package/dist/{token-goat-chunk-IVUQLQWN.mjs → token-goat-chunk-26YUO7MP.mjs} +5 -3
  3. package/dist/{token-goat-chunk-SZWYESBS.mjs → token-goat-chunk-2M33Q3O4.mjs} +172 -67
  4. package/dist/{token-goat-chunk-S4XRY446.mjs → token-goat-chunk-2VASS5VR.mjs} +4 -3
  5. package/dist/{token-goat-chunk-7ZYK25AO.mjs → token-goat-chunk-2XA5ZW4T.mjs} +4 -4
  6. package/dist/{token-goat-chunk-C5JIO6HK.mjs → token-goat-chunk-5WB7GDH4.mjs} +5 -5
  7. package/dist/{token-goat-chunk-MZDIJJ3R.mjs → token-goat-chunk-66224KCZ.mjs} +40 -3
  8. package/dist/{token-goat-chunk-NDRP4KJQ.mjs → token-goat-chunk-73GXYYZR.mjs} +248 -50
  9. package/dist/{token-goat-chunk-ATIFTMRC.mjs → token-goat-chunk-7EGKXBWT.mjs} +7 -7
  10. package/dist/{token-goat-chunk-GMQQA7E4.mjs → token-goat-chunk-7VQMOBPQ.mjs} +2453 -722
  11. package/dist/token-goat-chunk-BGHSSJUA.mjs +102 -0
  12. package/dist/{token-goat-chunk-2X2EBBC6.mjs → token-goat-chunk-BK3RPPTK.mjs} +2 -2
  13. package/dist/{token-goat-chunk-YQ7WI2CO.mjs → token-goat-chunk-C26ZBGBC.mjs} +393 -3957
  14. package/dist/{token-goat-chunk-SFAS46RE.mjs → token-goat-chunk-CICJFVIH.mjs} +2 -2
  15. package/dist/{token-goat-chunk-6DLVZDB6.mjs → token-goat-chunk-CKIJWTRQ.mjs} +13 -12
  16. package/dist/{token-goat-chunk-5V7DAC7V.mjs → token-goat-chunk-CPHVXKNU.mjs} +10 -3
  17. package/dist/{token-goat-chunk-LT7JRU6K.mjs → token-goat-chunk-EKGVCAES.mjs} +4 -4
  18. package/dist/{token-goat-chunk-Z6UXPYJA.mjs → token-goat-chunk-F2ARFHLJ.mjs} +1 -1
  19. package/dist/{token-goat-chunk-NDPO7GAH.mjs → token-goat-chunk-G75WIVFM.mjs} +21 -20
  20. package/dist/{token-goat-chunk-YZX7EFG4.mjs → token-goat-chunk-GLOM5W5C.mjs} +1 -1
  21. package/dist/{token-goat-chunk-GIIHUSZX.mjs → token-goat-chunk-GPCCYQSS.mjs} +41 -11
  22. package/dist/{token-goat-chunk-OUGNPMDA.mjs → token-goat-chunk-HRUGS6CO.mjs} +30 -14
  23. package/dist/{token-goat-chunk-LCZBPOIN.mjs → token-goat-chunk-IDHWKNKZ.mjs} +143 -51
  24. package/dist/{token-goat-chunk-YKG35VHC.mjs → token-goat-chunk-KZPO4DWC.mjs} +1 -1
  25. package/dist/{token-goat-chunk-NEI4NC54.mjs → token-goat-chunk-LKSXAMJB.mjs} +7 -2
  26. package/dist/{token-goat-chunk-K7F2BFIK.mjs → token-goat-chunk-MWSVUKVV.mjs} +1 -1
  27. package/dist/{token-goat-chunk-BL5LNGBG.mjs → token-goat-chunk-PKNOHVWP.mjs} +2966 -3869
  28. package/dist/{token-goat-chunk-A4JYKD5H.mjs → token-goat-chunk-RB24DBBS.mjs} +2 -2
  29. package/dist/{token-goat-chunk-3NSDDTGL.mjs → token-goat-chunk-RFFUCQK7.mjs} +5 -5
  30. package/dist/token-goat-chunk-RRNZMM3A.mjs +3364 -0
  31. package/dist/{token-goat-chunk-XEPXYDPI.mjs → token-goat-chunk-SWRKVA3J.mjs} +11 -2
  32. package/dist/{token-goat-chunk-NU7TLMQK.mjs → token-goat-chunk-THBLW56G.mjs} +10 -7
  33. package/dist/{token-goat-chunk-PM76YS22.mjs → token-goat-chunk-UBZCON6N.mjs} +1 -1
  34. package/dist/{token-goat-chunk-ASYEPR3S.mjs → token-goat-chunk-W2GUUQAQ.mjs} +7 -3
  35. package/dist/{token-goat-chunk-DQ4J5AFF.mjs → token-goat-chunk-YFGEUIEO.mjs} +69 -18
  36. package/dist/token-goat-hook.mjs +13 -12
  37. package/dist/token-goat.core.mjs +21 -20
  38. package/docs/cli.md +1 -0
  39. package/package.json +1 -1
@@ -1,5 +1,11 @@
1
1
  import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
+ import {
4
+ isOcrEngineAvailable,
5
+ isTextHeavy,
6
+ ocrImage,
7
+ ocrIntegrityFailed
8
+ } from "./token-goat-chunk-CICJFVIH.mjs";
3
9
  import {
4
10
  formatSqliteQueryTable,
5
11
  formatSqliteSchema,
@@ -13,8 +19,10 @@ import {
13
19
  NOTEBOOK_CELL_LINES_SUFFIX,
14
20
  SKIP_DIRS,
15
21
  canShrinkFormat,
22
+ capJsonRows,
16
23
  emptyIndexMessage,
17
24
  enqueueDirtyPathSafe,
25
+ estimateTokens,
18
26
  fenceUntrusted,
19
27
  formatSymbolLocation,
20
28
  getEmbeddingCoverage,
@@ -30,29 +38,35 @@ import {
30
38
  scanAndRecord,
31
39
  searchEvidenceSemantically,
32
40
  shrinkImage,
41
+ trimToBudget,
33
42
  urlPolicyDenialReason,
34
43
  virtualIndexedScopeNote,
35
44
  walkProject
36
- } from "./token-goat-chunk-BL5LNGBG.mjs";
45
+ } from "./token-goat-chunk-PKNOHVWP.mjs";
37
46
  import {
38
- capJsonRows,
39
- estimateTokens,
40
- trimToBudget
41
- } from "./token-goat-chunk-YQ7WI2CO.mjs";
47
+ colorStdout,
48
+ isBlobStale,
49
+ loadBlob,
50
+ recordStat,
51
+ redactSecrets,
52
+ savedTokensFromBytes,
53
+ storeBlob,
54
+ stripAnsi
55
+ } from "./token-goat-chunk-26YUO7MP.mjs";
42
56
  import {
43
57
  extractPdfMeta,
44
58
  extractPdfOutline,
45
59
  extractPdfText,
46
60
  locatePdfPages,
47
61
  readPdfFileWithinBounds
48
- } from "./token-goat-chunk-NEI4NC54.mjs";
62
+ } from "./token-goat-chunk-LKSXAMJB.mjs";
49
63
  import {
50
64
  formatCsvProfile,
51
65
  formatCsvTable,
52
66
  parseWhereSpecs,
53
67
  profileCsv,
54
68
  queryCsv
55
- } from "./token-goat-chunk-YKG35VHC.mjs";
69
+ } from "./token-goat-chunk-KZPO4DWC.mjs";
56
70
  import {
57
71
  MAX_ZIP_INPUT_BYTES,
58
72
  MAX_ZIP_OUTPUT_BYTES,
@@ -87,7 +101,7 @@ import {
87
101
  extractSolidity,
88
102
  extractThrift,
89
103
  stripLeadingAttributes
90
- } from "./token-goat-chunk-XEPXYDPI.mjs";
104
+ } from "./token-goat-chunk-SWRKVA3J.mjs";
91
105
  import {
92
106
  MAX_OVER_FETCH,
93
107
  OVER_FETCH_FACTOR,
@@ -100,7 +114,7 @@ import {
100
114
  mergeNearbyHits,
101
115
  readSection,
102
116
  searchSemantic
103
- } from "./token-goat-chunk-NDRP4KJQ.mjs";
117
+ } from "./token-goat-chunk-73GXYYZR.mjs";
104
118
  import {
105
119
  DEFAULT_QUERY_LIMIT,
106
120
  countRefs,
@@ -113,23 +127,7 @@ import {
113
127
  queryRefsByContext,
114
128
  querySymbols,
115
129
  searchSymbolsFts
116
- } from "./token-goat-chunk-2X2EBBC6.mjs";
117
- import {
118
- isOcrEngineAvailable,
119
- isTextHeavy,
120
- ocrImage,
121
- ocrIntegrityFailed
122
- } from "./token-goat-chunk-SFAS46RE.mjs";
123
- import {
124
- colorStdout,
125
- isBlobStale,
126
- loadBlob,
127
- recordStat,
128
- redactSecrets,
129
- savedTokensFromBytes,
130
- storeBlob,
131
- stripAnsi
132
- } from "./token-goat-chunk-IVUQLQWN.mjs";
130
+ } from "./token-goat-chunk-BK3RPPTK.mjs";
133
131
  import {
134
132
  FILENAME_LANGUAGE,
135
133
  TREE_SITTER_LANGUAGES,
@@ -148,7 +146,7 @@ import {
148
146
  partialRefsReason,
149
147
  redactIfDotenv,
150
148
  unsupportedLanguageName
151
- } from "./token-goat-chunk-K7F2BFIK.mjs";
149
+ } from "./token-goat-chunk-MWSVUKVV.mjs";
152
150
  import {
153
151
  fileIsAbsent,
154
152
  findProject,
@@ -158,7 +156,7 @@ import {
158
156
  loadConfig,
159
157
  resolveProjectRoot,
160
158
  shortFingerprint
161
- } from "./token-goat-chunk-S4XRY446.mjs";
159
+ } from "./token-goat-chunk-2VASS5VR.mjs";
162
160
  import {
163
161
  registerReset
164
162
  } from "./token-goat-chunk-EEIDFMEM.mjs";
@@ -190,7 +188,7 @@ import {
190
188
  supportRequestLine,
191
189
  windowsCmdQuoteArg,
192
190
  withExtension
193
- } from "./token-goat-chunk-PM76YS22.mjs";
191
+ } from "./token-goat-chunk-UBZCON6N.mjs";
194
192
  import {
195
193
  UNTRUSTED_FILE_TAG,
196
194
  UNTRUSTED_GITHUB_TAG,
@@ -467,6 +465,7 @@ function queryJson(data, spec) {
467
465
  // src/read_spec.ts
468
466
  init_define_import_meta_env();
469
467
  import * as fs13 from "node:fs";
468
+ import * as path13 from "node:path";
470
469
 
471
470
  // src/read_commands.ts
472
471
  init_define_import_meta_env();
@@ -1593,12 +1592,12 @@ function runScope(opts) {
1593
1592
  emitErr(`Invalid line number: ${lineStr}`);
1594
1593
  return 1;
1595
1594
  }
1596
- const confined = fileConfinementRefusal("This file", file, void 0);
1595
+ const confined = fileConfinementRefusal("This file", file, opts.projectRoot);
1597
1596
  if (confined !== null) {
1598
1597
  emitErr(confined);
1599
1598
  return 1;
1600
1599
  }
1601
- const filePath = resolveIndexPath(file);
1600
+ const filePath = resolveIndexPath(file, opts.projectRoot ?? process.cwd());
1602
1601
  const enclosing = querySymbols({ filePath, enclosingLine: line, limit: ALL_SYMBOLS_IN_FILE_LIMIT }).sort((a, b) => b.lineStart - a.lineStart || a.lineEnd - b.lineEnd || a.name.localeCompare(b.name, "en"));
1603
1602
  if (enclosing.length === 0) {
1604
1603
  if (querySymbols({ filePath, limit: 1 }).length === 0) {
@@ -1643,7 +1642,7 @@ function buildImportGraph(cwd) {
1643
1642
  const files = getTrackedFiles(cwd);
1644
1643
  const filesByFoldedPath = /* @__PURE__ */ new Map();
1645
1644
  for (const f of files) filesByFoldedPath.set(foldPath(f), f);
1646
- const resolve7 = (fromFile, spec) => {
1645
+ const resolve8 = (fromFile, spec) => {
1647
1646
  if (!spec.startsWith(".")) return null;
1648
1647
  const dir = path3.dirname(fromFile);
1649
1648
  const strippedSpec = spec.replace(/\.(m?js|cjs)$/, "");
@@ -1670,7 +1669,7 @@ function buildImportGraph(cwd) {
1670
1669
  }
1671
1670
  const internal = [];
1672
1671
  for (const spec of extractImports(text, importsExtensionFor(file))) {
1673
- const resolved = resolve7(file, spec);
1672
+ const resolved = resolve8(file, spec);
1674
1673
  if (resolved === null) continue;
1675
1674
  internal.push(resolved);
1676
1675
  let importers = importedBy.get(resolved);
@@ -1682,7 +1681,7 @@ function buildImportGraph(cwd) {
1682
1681
  }
1683
1682
  graph.set(file, internal);
1684
1683
  }
1685
- return { files, graph, importedBy, resolve: resolve7 };
1684
+ return { files, graph, importedBy, resolve: resolve8 };
1686
1685
  }
1687
1686
 
1688
1687
  // src/modules.ts
@@ -2106,7 +2105,7 @@ function runArch(opts) {
2106
2105
  }
2107
2106
  const cwd = opts.cwd ?? process.cwd();
2108
2107
  const top = opts.top ?? 10;
2109
- const { files, graph, importedBy, resolve: resolve7 } = buildImportGraph(cwd);
2108
+ const { files, graph, importedBy, resolve: resolve8 } = buildImportGraph(cwd);
2110
2109
  if (files.length === 0 && opts.json !== true) {
2111
2110
  emit2(`no tracked files found under '${toDisplayPath(getDisplayRoot(cwd), cwd)}' (is it a git repo?). Nothing to analyse.`);
2112
2111
  return 0;
@@ -2118,7 +2117,7 @@ function runArch(opts) {
2118
2117
  const entryPointsTotal = allEntryPoints.length;
2119
2118
  const entryPoints = allEntryPoints.slice(0, top);
2120
2119
  const { cycles, truncated: cyclesTruncated } = findCyclesCapped(graph);
2121
- const moduleResult = opts.modules === true ? detectModules({ files, graph, importedBy, resolve: resolve7 }, cwd, top) : null;
2120
+ const moduleResult = opts.modules === true ? detectModules({ files, graph, importedBy, resolve: resolve8 }, cwd, top) : null;
2122
2121
  if (opts.json === true) {
2123
2122
  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 };
2124
2123
  emit2(displaySafeJson({ hubs, hubsTotal, hubsTruncated: hubs.length < hubsTotal, entryPoints, entryPointsTotal, entryPointsTruncated: entryPoints.length < entryPointsTotal, cycles, ...cyclesTruncated ? { cyclesTruncated: true } : {}, ...modulePayload }));
@@ -2978,7 +2977,7 @@ async function captureOnce(url, ctx) {
2978
2977
  })();
2979
2978
  });
2980
2979
  try {
2981
- await page.goto(url, { waitUntil: "networkidle2", timeout: 3e4 });
2980
+ await page.goto(url, { waitUntil: "networkidle2", timeout: 6e4 });
2982
2981
  } catch (err) {
2983
2982
  if (hop.url !== null) return { redirectTo: hop.url };
2984
2983
  if (navRefusal.reason !== null) throw new Error(navRefusal.reason, { cause: err });
@@ -8263,7 +8262,7 @@ function filteredToEmptyNotice(preFilterCount, minLines, grep) {
8263
8262
  }
8264
8263
  function prepareSymbolListing(file, opts) {
8265
8264
  const resolved = resolveIndexPath(file, opts.projectRoot ?? process.cwd());
8266
- const confined = confinementRefusal("This file", resolved, confinedProjectRoot());
8265
+ const confined = confinementRefusal("This file", resolved, confinedProjectRoot(opts.projectRoot));
8267
8266
  if (confined !== null) return { kind: "confined", text: confined };
8268
8267
  if (opts.forceRefresh === true) {
8269
8268
  indexFileSyncPinned(resolved, globalDbPath());
@@ -8481,6 +8480,8 @@ ${emptyIndexMessage(rootDir2)}`, code: 1 };
8481
8480
  truncated,
8482
8481
  ...hiddenByExcludeTests > 0 ? { hiddenByExcludeTests } : {},
8483
8482
  ...hiddenByGrep > 0 ? { hiddenByGrep } : {},
8483
+ // Mirrors runRead's json branch: emitting the row verbatim would hand a JSON consumer a deleted file's stale body with no signal it is no longer live.
8484
+ ...fileIsGone(match.filePath) ? { deleted: true } : {},
8484
8485
  section
8485
8486
  };
8486
8487
  const jsonText = displaySafeJson(result);
@@ -8496,6 +8497,7 @@ ${emptyIndexMessage(rootDir2)}`, code: 1 };
8496
8497
  body,
8497
8498
  ""
8498
8499
  ];
8500
+ const warning = staleWarning(match.filePath);
8499
8501
  const hiddenNote = excludeTests && hiddenByExcludeTests > 0 ? ` (${excludeTestsHiddenNote(hiddenByExcludeTests)})` : "";
8500
8502
  if (callers.length === 0 && matchesGrep !== void 0 && preGrepCount > 0) {
8501
8503
  lines.push(`Callers (0): ${grepFilteredToEmptyNotice(preGrepCount, opts.grep ?? "", "caller", "callers").trim()}`);
@@ -8515,7 +8517,7 @@ ${emptyIndexMessage(rootDir2)}`, code: 1 };
8515
8517
  lines.push("");
8516
8518
  lines.push(`Section: ${section.heading} (lines ${section.lineStart}-${section.lineEnd})`);
8517
8519
  }
8518
- const text = guardText(trimBlankLines(lines).join("\n"), "symbol");
8520
+ const text = guardText(warning + trimBlankLines(lines).join("\n"), "symbol");
8519
8521
  if (opts.suppressStat !== true) recordReadStat("brief_view", fullSourceBytes, text, opts.spec);
8520
8522
  return { text, code: 0 };
8521
8523
  }
@@ -10196,6 +10198,78 @@ function runFind(opts) {
10196
10198
  }
10197
10199
  return 0;
10198
10200
  }
10201
+ function runLocate(opts) {
10202
+ if (opts.limit !== void 0 && opts.limit <= 0) {
10203
+ emitErr4(`--limit must be a positive number, got: ${opts.limit}`);
10204
+ return 1;
10205
+ }
10206
+ const rootDir = opts.projectRoot ?? resolveProjectRoot({ project: process.cwd() });
10207
+ let targetSpec = opts.spec;
10208
+ let targetFile = opts.file;
10209
+ if (targetSpec.includes("::")) {
10210
+ const colonIdx = targetSpec.indexOf("::");
10211
+ targetFile = targetSpec.slice(0, colonIdx);
10212
+ targetSpec = targetSpec.slice(colonIdx + 2);
10213
+ }
10214
+ const queryOpts = {
10215
+ limit: FIND_SCAN_LIMIT2
10216
+ };
10217
+ if (targetFile !== void 0) {
10218
+ queryOpts.filePath = resolveIndexPath(targetFile, rootDir);
10219
+ healStaleIndex(queryOpts.filePath);
10220
+ } else {
10221
+ queryOpts.rootDir = rootDir;
10222
+ }
10223
+ let rawSymbols = querySymbols(queryOpts);
10224
+ if (targetFile === void 0) {
10225
+ const heal = healStaleResultFiles(rawSymbols.map((s) => s.filePath));
10226
+ if (heal.healed) rawSymbols = querySymbols(queryOpts);
10227
+ }
10228
+ const specLower = targetSpec.toLowerCase();
10229
+ const exactMatches = rawSymbols.filter((s) => s.name.toLowerCase() === specLower);
10230
+ const partialMatches = rawSymbols.filter(
10231
+ (s) => s.name.toLowerCase() !== specLower && s.name.toLowerCase().includes(specLower)
10232
+ );
10233
+ const combined = [...exactMatches, ...partialMatches];
10234
+ let fuzzyNames = [];
10235
+ if (combined.length === 0) {
10236
+ fuzzyNames = rankSimilarNames(rawSymbols.map((s) => s.name), targetSpec);
10237
+ }
10238
+ const matched = fuzzyNames.length > 0 ? fuzzyNames.flatMap((n) => rawSymbols.filter((s) => s.name === n)) : combined;
10239
+ const limit = opts.limit ?? 25;
10240
+ const shown = matched.slice(0, limit);
10241
+ if (shown.length === 0) {
10242
+ emitErr4(`No landmark or symbol located for '${targetSpec}'`);
10243
+ return 1;
10244
+ }
10245
+ const hits = shown.map((s) => ({
10246
+ filePath: toDisplayPath(rootDir, s.filePath),
10247
+ name: s.name,
10248
+ kind: s.kind,
10249
+ lineStart: s.lineStart,
10250
+ lineEnd: s.lineEnd,
10251
+ span: `${s.lineStart}-${s.lineEnd}`
10252
+ }));
10253
+ if (opts.json === true) {
10254
+ emit4(displaySafeJson({
10255
+ items: hits,
10256
+ totalCount: matched.length,
10257
+ truncated: matched.length > limit,
10258
+ ...fuzzyNames.length > 0 ? { fuzzy: true, matchedNames: fuzzyNames } : {}
10259
+ }));
10260
+ return 0;
10261
+ }
10262
+ if (fuzzyNames.length > 0) {
10263
+ emitErr4(`No exact landmark for '${targetSpec}'; nearest matches: ${fuzzyNames.join(", ")}`);
10264
+ }
10265
+ for (const hit of hits) {
10266
+ emit4(`${displaySafeText(hit.filePath)}:${hit.span} [${displaySafeText(hit.kind)}] ${displaySafeText(hit.name)}`);
10267
+ }
10268
+ if (matched.length > limit) {
10269
+ emitErr4(`Showing ${limit} of ${matched.length} locations; rerun with --limit ${matched.length} to see them all`);
10270
+ }
10271
+ return 0;
10272
+ }
10199
10273
  function runListSections(opts) {
10200
10274
  const sections = listSections(opts.file);
10201
10275
  if (sections.length === 0) {
@@ -10723,10 +10797,11 @@ function runSymbol(opts) {
10723
10797
  }
10724
10798
  const matchesGrep = opts.grep !== void 0 ? compileGrepMatcher(opts.grep) : void 0;
10725
10799
  const excludeTests = opts.excludeTests === true;
10726
- const confinedRoot = confinedProjectRoot();
10727
- if (confinedRoot !== null) {
10800
+ const baseConfined = confinedProjectRoot();
10801
+ const confinedRoot = opts.projectRoot !== void 0 && baseConfined !== null && isProjectRootAllowed(opts.projectRoot, baseConfined) ? confinedProjectRoot(opts.projectRoot) ?? baseConfined : baseConfined;
10802
+ if (baseConfined !== null) {
10728
10803
  const requested = opts.projectRoot;
10729
- const projectDenial = requested === void 0 ? null : confinementRefusal("--project", requested, confinedRoot);
10804
+ const projectDenial = requested === void 0 || confinedRoot !== baseConfined ? null : confinementRefusal("--project", requested, baseConfined);
10730
10805
  if (projectDenial !== null) return { text: projectDenial, code: 1 };
10731
10806
  if (opts.file !== void 0) {
10732
10807
  const fileDenial = confinementRefusal("--file", resolveIndexPath(opts.file, requested ?? process.cwd()), confinedRoot);
@@ -11179,9 +11254,11 @@ function runRefs(opts) {
11179
11254
  emitErr4(`--top must be a positive number, got: ${opts.top}`);
11180
11255
  return 1;
11181
11256
  }
11182
- const confinedRoot = confinedProjectRoot();
11183
- if (confinedRoot !== null) {
11184
- const projectDenial = opts.projectRoot === void 0 ? null : confinementRefusal("--project", opts.projectRoot, confinedRoot);
11257
+ const baseConfined = confinedProjectRoot();
11258
+ const confinedRoot = opts.projectRoot !== void 0 && baseConfined !== null && isProjectRootAllowed(opts.projectRoot, baseConfined) ? confinedProjectRoot(opts.projectRoot) ?? baseConfined : baseConfined;
11259
+ if (baseConfined !== null) {
11260
+ const requested = opts.projectRoot;
11261
+ const projectDenial = requested === void 0 || confinedRoot !== baseConfined ? null : confinementRefusal("--project", requested, baseConfined);
11185
11262
  if (projectDenial !== null) {
11186
11263
  emitErr4(projectDenial);
11187
11264
  return 1;
@@ -11724,6 +11801,10 @@ function runGrep(opts) {
11724
11801
  }
11725
11802
  if (hits.length > maxLines) {
11726
11803
  emitErr4(`... (${hits.length - maxLines} more lines omitted)`);
11804
+ emitErr4(`Tip: To avoid broad grepping and reduce token expenditure, locate specific symbols with:`);
11805
+ emitErr4(` token-goat symbol <name>`);
11806
+ emitErr4(` token-goat locate <name>`);
11807
+ emitErr4(` token-goat outline <file> / token-goat skeleton <file>`);
11727
11808
  }
11728
11809
  return 0;
11729
11810
  }
@@ -11835,6 +11916,7 @@ async function runSemantic(query, opts) {
11835
11916
  mergedHits.forEach((h, denseRank) => {
11836
11917
  const enclosing = resolveEnclosingSymbol(h.filePath, h.startLine);
11837
11918
  const key = enclosing !== null ? `${h.filePath}::${enclosing.name}@${enclosing.lineStart}` : `${h.filePath}::L${h.startLine}`;
11919
+ if (fused.has(key)) return;
11838
11920
  fused.set(key, {
11839
11921
  filePath: h.filePath,
11840
11922
  startLine: h.startLine,
@@ -12164,15 +12246,24 @@ function formatAmbiguity(symbol, file, candidates, explicitRoot, commandName = "
12164
12246
  }
12165
12247
  return lines.join("\n");
12166
12248
  }
12167
- function confinedProjectRoot() {
12168
- return loadConfig().indexing.cross_project_symbols ? null : resolveProjectRoot();
12249
+ function confinedProjectRoot(explicitRoot) {
12250
+ if (loadConfig().indexing.cross_project_symbols) return null;
12251
+ return resolveProjectRoot(explicitRoot !== void 0 && explicitRoot.trim().length > 0 ? { project: explicitRoot } : {});
12252
+ }
12253
+ function isProjectRootAllowed(candidateRoot, baseConfinedRoot) {
12254
+ if (isInsideRoot(candidateRoot, baseConfinedRoot)) return true;
12255
+ const allowedRoots = loadConfig().mcp.allowed_roots;
12256
+ if (allowedRoots.length > 0 && allowedRoots.some((allowed) => isInsideRoot(candidateRoot, path13.resolve(allowed)))) {
12257
+ return true;
12258
+ }
12259
+ return false;
12169
12260
  }
12170
12261
  function confinementRefusal(label, resolved, root) {
12171
12262
  if (root === null || isInsideRoot(resolved, root)) return null;
12172
12263
  return `${label} is outside this project root, and indexing.cross_project_symbols = false confines symbol lookups to it: ${toDisplayPath(root, resolved)}`;
12173
12264
  }
12174
12265
  function fileConfinementRefusal(label, file, projectRoot) {
12175
- const root = confinedProjectRoot();
12266
+ const root = confinedProjectRoot(projectRoot);
12176
12267
  if (root === null) return null;
12177
12268
  return confinementRefusal(label, resolveIndexPath(file, projectRoot ?? process.cwd()), root);
12178
12269
  }
@@ -12183,7 +12274,7 @@ function resolveSymbolSpec(spec, forceRefresh, projectRoot) {
12183
12274
  const symbol = anchorMatch !== null ? anchorMatch[1] : rawSymbol;
12184
12275
  const lineAnchor = anchorMatch !== null ? parseInt(anchorMatch[2], 10) : void 0;
12185
12276
  const resolved = resolveIndexPath(file, projectRoot ?? process.cwd());
12186
- const confined = confinementRefusal("This file", resolved, confinedProjectRoot());
12277
+ const confined = confinementRefusal("This file", resolved, confinedProjectRoot(projectRoot));
12187
12278
  if (confined !== null) return { kind: "confined", message: confined };
12188
12279
  if (forceRefresh === true) {
12189
12280
  indexFileSyncPinned(resolved, globalDbPath());
@@ -12290,7 +12381,7 @@ function resolveSymbolSpecOrEmitError(commandName, spec, projectRoot) {
12290
12381
  // src/content_store.ts
12291
12382
  init_define_import_meta_env();
12292
12383
  import { deflateRawSync } from "node:zlib";
12293
- import * as path13 from "node:path";
12384
+ import * as path14 from "node:path";
12294
12385
  var CONTENT_MAX_INPUT_CHARS = 512 * 1024;
12295
12386
  var CONTENT_MAX_ITEMS = 256;
12296
12387
  var CONTENT_MAX_BYTES = 32 * 1024 * 1024;
@@ -12313,7 +12404,7 @@ function contentId(text) {
12313
12404
  return `tg_${shortFingerprint(text)}`;
12314
12405
  }
12315
12406
  function normalizeProjectRoot(projectRoot) {
12316
- const candidate = path13.resolve(projectRoot ?? process.cwd());
12407
+ const candidate = path14.resolve(projectRoot ?? process.cwd());
12317
12408
  return findProject(candidate)?.root ?? candidate;
12318
12409
  }
12319
12410
  function handoffId(name, projectRoot) {
@@ -12475,6 +12566,7 @@ export {
12475
12566
  runNoteGet,
12476
12567
  runNoteList,
12477
12568
  runFind,
12569
+ runLocate,
12478
12570
  runListSections,
12479
12571
  AMBIGUOUS_HEADING_LIMIT,
12480
12572
  runSection,
@@ -5,7 +5,7 @@ import {
5
5
  } from "./token-goat-chunk-3BTK54F3.mjs";
6
6
  import {
7
7
  filtersFilteredToEmptyNotice
8
- } from "./token-goat-chunk-PM76YS22.mjs";
8
+ } from "./token-goat-chunk-UBZCON6N.mjs";
9
9
  import {
10
10
  displaySafeText
11
11
  } from "./token-goat-chunk-ERTXEKB6.mjs";
@@ -280,7 +280,7 @@ async function extractPdfText(data, pagesSpec, layout = false) {
280
280
  for (let i = start; i <= end; i++) {
281
281
  const page = await getPageWithinDeadline(doc, i, deadline);
282
282
  const textItems = await readPageTextItems(page, MAX_PDF_TEXT_BYTES - spent, deadline);
283
- const pageText = layout ? reconstructLayout(textItems, deadline) : textItems.map((item) => item.str).join(" ");
283
+ const pageText = layout ? reconstructLayout(textItems, deadline) : joinTextItems(textItems, "\n");
284
284
  spent += pageText.length;
285
285
  pages.push(pageText.trim());
286
286
  }
@@ -306,7 +306,7 @@ async function locatePdfPages(data, pattern, opts) {
306
306
  for (; i <= end && matches.length < maxMatches; i++) {
307
307
  const page = await getPageWithinDeadline(doc, i, deadline);
308
308
  const textItems = await readPageTextItems(page, MAX_PDF_TEXT_BYTES, deadline);
309
- const pageText = textItems.map((item) => item.str).join(" ");
309
+ const pageText = joinTextItems(textItems, " ");
310
310
  const m = re.exec(pageText);
311
311
  if (m === null) continue;
312
312
  matches.push({ page: i, snippet: locateSnippet(pageText, m.index, m[0].length, context) });
@@ -315,6 +315,11 @@ async function locatePdfPages(data, pattern, opts) {
315
315
  return { matches, truncated };
316
316
  });
317
317
  }
318
+ function joinTextItems(items, eol) {
319
+ let out = "";
320
+ for (const item of items) out += item.str + (item.hasEOL ? eol : "");
321
+ return out;
322
+ }
318
323
  function locateSnippet(text, index, matchLen, context) {
319
324
  const kept = Math.min(matchLen, context);
320
325
  const pad = Math.max(0, context - kept);
@@ -15,7 +15,7 @@ import {
15
15
  foldCase,
16
16
  foldPath,
17
17
  sleepSync
18
- } from "./token-goat-chunk-PM76YS22.mjs";
18
+ } from "./token-goat-chunk-UBZCON6N.mjs";
19
19
  import {
20
20
  safeJoin
21
21
  } from "./token-goat-chunk-ERTXEKB6.mjs";