token-goat 2.9.28 → 2.9.30

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 (134) hide show
  1. package/README.md +36 -40
  2. package/SECURITY.md +28 -10
  3. package/THIRD_PARTY_NOTICES.md +40 -3
  4. package/dist/native/linux-arm64/tg-hook +0 -0
  5. package/dist/native/linux-x64/tg-hook +0 -0
  6. package/dist/ort-wasm-simd-threaded.mjs +59 -0
  7. package/dist/token-goat-chunk-3CHUNJ2O.mjs +53 -0
  8. package/dist/token-goat-chunk-3Z5S4KGM.mjs +918 -0
  9. package/dist/token-goat-chunk-4FVIAHSI.mjs +211 -0
  10. package/dist/{token-goat-chunk-66LODAUT.mjs → token-goat-chunk-4JJS54TR.mjs} +4 -3
  11. package/dist/token-goat-chunk-4LCCNRTG.mjs +313 -0
  12. package/dist/token-goat-chunk-54K3EUV3.mjs +35 -0
  13. package/dist/token-goat-chunk-56EIMS3E.mjs +159 -0
  14. package/dist/token-goat-chunk-5BCIJXMO.mjs +609 -0
  15. package/dist/token-goat-chunk-5KDMCMF2.mjs +362 -0
  16. package/dist/token-goat-chunk-5RG5SBL6.mjs +221 -0
  17. package/dist/{token-goat-chunk-RRNZMM3A.mjs → token-goat-chunk-5Y6ZYL6U.mjs} +21 -2
  18. package/dist/{token-goat-chunk-NILVK7XM.mjs → token-goat-chunk-64FBPDTK.mjs} +12 -6
  19. package/dist/{token-goat-chunk-LEG3JNJ6.mjs → token-goat-chunk-664RTTUV.mjs} +31 -15
  20. package/dist/token-goat-chunk-67TQJ76B.mjs +57 -0
  21. package/dist/token-goat-chunk-6QMAS7AB.mjs +42 -0
  22. package/dist/token-goat-chunk-7BXSNBY5.mjs +253 -0
  23. package/dist/{token-goat-chunk-F7QZRIWM.mjs → token-goat-chunk-7F2UBPD3.mjs} +42 -35
  24. package/dist/{token-goat-chunk-CC2PYJM7.mjs → token-goat-chunk-7IN2MKDS.mjs} +718 -595
  25. package/dist/token-goat-chunk-7NADEDKJ.mjs +124 -0
  26. package/dist/{token-goat-chunk-6SGLIPQB.mjs → token-goat-chunk-A3CK6N5Z.mjs} +621 -72
  27. package/dist/{token-goat-chunk-HXNAC4ZF.mjs → token-goat-chunk-AFLYLHDH.mjs} +48 -33
  28. package/dist/token-goat-chunk-AM5V6GSX.mjs +367 -0
  29. package/dist/{token-goat-chunk-I7CB3X4K.mjs → token-goat-chunk-APGZXSPS.mjs} +44 -148
  30. package/dist/{token-goat-chunk-BJBRGPBX.mjs → token-goat-chunk-ARDNEC2U.mjs} +11 -7
  31. package/dist/token-goat-chunk-ATCPY7EQ.mjs +134 -0
  32. package/dist/token-goat-chunk-B2RZZCSS.mjs +354 -0
  33. package/dist/{token-goat-chunk-QM76S6YS.mjs → token-goat-chunk-B45VMIIH.mjs} +6 -5
  34. package/dist/token-goat-chunk-BPGCUMR7.mjs +30 -0
  35. package/dist/{token-goat-chunk-GGLEE35R.mjs → token-goat-chunk-C63JQV5K.mjs} +49 -10
  36. package/dist/{token-goat-chunk-RJH64KQH.mjs → token-goat-chunk-COBQGL5S.mjs} +2661 -2440
  37. package/dist/token-goat-chunk-COTXZHTS.mjs +39 -0
  38. package/dist/token-goat-chunk-CYCEYAEO.mjs +265 -0
  39. package/dist/token-goat-chunk-CZ3LUXYZ.mjs +4115 -0
  40. package/dist/token-goat-chunk-DZX2YDPT.mjs +15 -0
  41. package/dist/token-goat-chunk-E3OQX2XT.mjs +824 -0
  42. package/dist/token-goat-chunk-EFHMMIGP.mjs +68 -0
  43. package/dist/token-goat-chunk-ETBHVDB6.mjs +328 -0
  44. package/dist/token-goat-chunk-EVMZJ27Z.mjs +147 -0
  45. package/dist/token-goat-chunk-EXKGNHEG.mjs +2096 -0
  46. package/dist/{token-goat-chunk-6Z5ULNE5.mjs → token-goat-chunk-FBNQNSJK.mjs} +2490 -4957
  47. package/dist/{token-goat-chunk-NMTKNYGF.mjs → token-goat-chunk-FNFBKLRD.mjs} +74 -9
  48. package/dist/{token-goat-chunk-XI2VFAV6.mjs → token-goat-chunk-FZ3NEW5O.mjs} +33 -19
  49. package/dist/token-goat-chunk-G5PJ7AGM.mjs +36 -0
  50. package/dist/{token-goat-chunk-VE27HPIF.mjs → token-goat-chunk-GFBSN7BD.mjs} +30 -398
  51. package/dist/{token-goat-chunk-L45ZQRT7.mjs → token-goat-chunk-GOLEOXZP.mjs} +780 -786
  52. package/dist/token-goat-chunk-H2K3QP6H.mjs +24 -0
  53. package/dist/token-goat-chunk-H2PHHGM5.mjs +188 -0
  54. package/dist/token-goat-chunk-HAVAOB3S.mjs +87 -0
  55. package/dist/token-goat-chunk-HG45AF2B.mjs +2228 -0
  56. package/dist/{token-goat-chunk-CBMY2FGI.mjs → token-goat-chunk-HOJLCGDP.mjs} +17113 -15554
  57. package/dist/{token-goat-chunk-HUDC5GS5.mjs → token-goat-chunk-HYBFA5X3.mjs} +120 -19
  58. package/dist/token-goat-chunk-IVALLT3S.mjs +48 -0
  59. package/dist/token-goat-chunk-IVE6U7KV.mjs +53 -0
  60. package/dist/{token-goat-chunk-UBUKHVYW.mjs → token-goat-chunk-IVKIZWAB.mjs} +3 -3
  61. package/dist/{token-goat-chunk-4JMWPWCS.mjs → token-goat-chunk-JB6QN6VF.mjs} +6 -4
  62. package/dist/{token-goat-chunk-Q4S3UQGO.mjs → token-goat-chunk-JDHE26RP.mjs} +10 -3
  63. package/dist/{token-goat-chunk-7R2XYKC5.mjs → token-goat-chunk-JDU3EXSU.mjs} +14 -10
  64. package/dist/token-goat-chunk-JDWCRD6M.mjs +253 -0
  65. package/dist/{token-goat-chunk-XBESNWEX.mjs → token-goat-chunk-JN4XMLSD.mjs} +7 -4
  66. package/dist/token-goat-chunk-JTQQGXZH.mjs +733 -0
  67. package/dist/token-goat-chunk-KKUFXAPD.mjs +25 -0
  68. package/dist/token-goat-chunk-KQNZDCBD.mjs +47 -0
  69. package/dist/{token-goat-chunk-4RN64ZND.mjs → token-goat-chunk-KWPNJBDH.mjs} +28 -3
  70. package/dist/token-goat-chunk-LOBGHXPB.mjs +242 -0
  71. package/dist/token-goat-chunk-MKPF4CL5.mjs +37 -0
  72. package/dist/token-goat-chunk-MNWKPRAL.mjs +13 -0
  73. package/dist/{token-goat-chunk-YWSP6IU7.mjs → token-goat-chunk-N2NMER56.mjs} +25 -15
  74. package/dist/token-goat-chunk-NYKZLXMK.mjs +218 -0
  75. package/dist/token-goat-chunk-OJBNYTJP.mjs +3087 -0
  76. package/dist/token-goat-chunk-P4QMM4WK.mjs +295 -0
  77. package/dist/token-goat-chunk-PD5VKH46.mjs +184 -0
  78. package/dist/token-goat-chunk-PEPTHRFF.mjs +129 -0
  79. package/dist/{token-goat-chunk-VWZKZPSR.mjs → token-goat-chunk-PU7V3FD6.mjs} +8752 -11098
  80. package/dist/token-goat-chunk-PY533T7F.mjs +46 -0
  81. package/dist/token-goat-chunk-Q5VTGRH7.mjs +761 -0
  82. package/dist/{token-goat-chunk-IHKZ3QNA.mjs → token-goat-chunk-QCLDAQTS.mjs} +33 -19
  83. package/dist/{token-goat-chunk-LKSXAMJB.mjs → token-goat-chunk-QSBRTGM2.mjs} +9 -2
  84. package/dist/{token-goat-chunk-4STDIGYC.mjs → token-goat-chunk-RC35RBUL.mjs} +7 -5
  85. package/dist/{token-goat-chunk-D5ICEPSB.mjs → token-goat-chunk-RHXKCZX3.mjs} +887 -507
  86. package/dist/{token-goat-chunk-RSS3TLXU.mjs → token-goat-chunk-RZRTDST7.mjs} +17 -7
  87. package/dist/{token-goat-chunk-MAMQPC43.mjs → token-goat-chunk-SJHWVNDQ.mjs} +15 -21
  88. package/dist/token-goat-chunk-SMVRN2HU.mjs +188 -0
  89. package/dist/{token-goat-chunk-EL7M26WP.mjs → token-goat-chunk-SQSSNQPE.mjs} +2100 -1442
  90. package/dist/{token-goat-chunk-OZRSREKR.mjs → token-goat-chunk-T4FK7VOV.mjs} +3 -3
  91. package/dist/token-goat-chunk-U4N3BN2W.mjs +92 -0
  92. package/dist/token-goat-chunk-UA5V6P3P.mjs +122 -0
  93. package/dist/{token-goat-chunk-XKY4VKJ4.mjs → token-goat-chunk-UQ5UK2T7.mjs} +15 -4
  94. package/dist/token-goat-chunk-USILZ3T3.mjs +53 -0
  95. package/dist/token-goat-chunk-VES7EL7O.mjs +205 -0
  96. package/dist/{token-goat-chunk-M56BWPBN.mjs → token-goat-chunk-VL3YIFHZ.mjs} +550 -49
  97. package/dist/{token-goat-chunk-IZXKVT4A.mjs → token-goat-chunk-VPBBTSJR.mjs} +362 -128
  98. package/dist/{token-goat-chunk-F2ARFHLJ.mjs → token-goat-chunk-W3QCJS6G.mjs} +10 -2
  99. package/dist/{token-goat-chunk-FSTWAOW6.mjs → token-goat-chunk-WY3L4DVT.mjs} +11 -5
  100. package/dist/{token-goat-chunk-OSUFN2FV.mjs → token-goat-chunk-XB5KKOCG.mjs} +5 -25
  101. package/dist/token-goat-chunk-XKI4SEMX.mjs +66 -0
  102. package/dist/token-goat-chunk-XU4P6Z24.mjs +133 -0
  103. package/dist/{token-goat-chunk-6CCQDAYY.mjs → token-goat-chunk-Y776AENC.mjs} +138 -33
  104. package/dist/token-goat-chunk-YARZFCZS.mjs +359 -0
  105. package/dist/{token-goat-chunk-EEIDFMEM.mjs → token-goat-chunk-YZAAZU2S.mjs} +12 -3
  106. package/dist/{token-goat-chunk-E3K3BTEQ.mjs → token-goat-chunk-Z44YO3QZ.mjs} +12 -6
  107. package/dist/token-goat-chunk-Z57YODA2.mjs +35 -0
  108. package/dist/{token-goat-chunk-DA43OE7L.mjs → token-goat-chunk-ZCDTCVIS.mjs} +49 -30
  109. package/dist/token-goat-chunk-ZEJVMUQH.mjs +48 -0
  110. package/dist/{token-goat-chunk-BGHSSJUA.mjs → token-goat-chunk-ZFOBKUWH.mjs} +1 -1
  111. package/dist/token-goat-chunk-ZMBL6UVV.mjs +110 -0
  112. package/dist/{token-goat-chunk-SJEKA7Y2.mjs → token-goat-chunk-ZQSJMO2R.mjs} +381 -201
  113. package/dist/token-goat-hook-client.cjs +539 -0
  114. package/dist/token-goat-hook-client.mjs +26 -0
  115. package/dist/token-goat-hook.mjs +48 -28
  116. package/dist/token-goat.core.mjs +10 -45
  117. package/docs/C4_RUNTIME_ARCHITECTURE.md +3 -3
  118. package/docs/cli.md +82 -47
  119. package/docs/install.md +58 -23
  120. package/docs/security.md +7 -7
  121. package/package.json +17 -7
  122. package/scripts/verify-native-dist.mjs +386 -0
  123. package/dist/token-goat-chunk-4JJBFT7V.mjs +0 -20
  124. package/dist/token-goat-chunk-5NNYIUGV.mjs +0 -143
  125. package/dist/token-goat-chunk-6DQNNFJ6.mjs +0 -294
  126. package/dist/token-goat-chunk-7MAFDRVR.mjs +0 -47
  127. package/dist/token-goat-chunk-BCN2YQC6.mjs +0 -32
  128. package/dist/token-goat-chunk-D7GHQKQD.mjs +0 -30
  129. package/dist/token-goat-chunk-GMOUBOX4.mjs +0 -386
  130. package/dist/token-goat-chunk-IFORDBGC.mjs +0 -192
  131. package/dist/token-goat-chunk-JXK2HQXS.mjs +0 -318
  132. package/dist/token-goat-chunk-L7SDVGBT.mjs +0 -430
  133. package/dist/token-goat-chunk-MMLMALWK.mjs +0 -1381
  134. package/dist/token-goat-chunk-MVSWBNMD.mjs +0 -1985
@@ -1,95 +1,124 @@
1
1
  import { createRequire as __cjsRequire } from 'node:module';
2
2
  const require = __cjsRequire(import.meta.url);
3
3
  import {
4
- ABSENT_PIN,
5
4
  CONTENT_MAX_INPUT_CHARS,
6
- ConfinementIdentityError,
5
+ checkSemanticReadiness,
7
6
  compressText,
8
7
  createHandoff,
9
- fileIdentity,
10
- findSpecSeparator,
11
- parseColonLineSpec,
12
- parseLineRange,
13
- pinKey,
14
8
  resolveHandoff,
15
9
  retrieveText,
16
10
  runBrief,
17
11
  runChanged,
18
12
  runExports,
19
- runGrep,
20
13
  runImports,
21
14
  runOutline,
22
- runRead,
23
15
  runRefs,
24
16
  runSection,
25
17
  runSemantic,
26
18
  runSkeleton,
27
19
  runSymbol,
20
+ statThroughHandle
21
+ } from "./token-goat-chunk-CZ3LUXYZ.mjs";
22
+ import "./token-goat-chunk-ZCDTCVIS.mjs";
23
+ import {
24
+ ABSENT_PIN,
25
+ ConfinementIdentityError,
26
+ expandSpecPath,
27
+ fileIdentity,
28
+ findSpecSeparator,
29
+ parseColonLineSpec,
30
+ parseLineRange,
31
+ pinKey,
32
+ resolveProjectConfinement,
33
+ runGrep,
34
+ runRead,
28
35
  withPinnedReads
29
- } from "./token-goat-chunk-VWZKZPSR.mjs";
30
- import "./token-goat-chunk-4STDIGYC.mjs";
31
- import "./token-goat-chunk-DA43OE7L.mjs";
36
+ } from "./token-goat-chunk-PU7V3FD6.mjs";
37
+ import "./token-goat-chunk-XKI4SEMX.mjs";
38
+ import "./token-goat-chunk-SMVRN2HU.mjs";
39
+ import "./token-goat-chunk-4JJS54TR.mjs";
40
+ import "./token-goat-chunk-RC35RBUL.mjs";
41
+ import "./token-goat-chunk-5RG5SBL6.mjs";
42
+ import "./token-goat-chunk-54K3EUV3.mjs";
43
+ import "./token-goat-chunk-YARZFCZS.mjs";
32
44
  import {
33
45
  buildProjectMap,
34
46
  formatProjectMap,
35
- getDirtyPathsFor,
36
47
  getEmbeddingCoverage,
37
48
  getProjectIndexCounts,
38
- isWorkerRunning,
49
+ isIndexEmptyForProject,
39
50
  mapLookupBytesSaved
40
- } from "./token-goat-chunk-6Z5ULNE5.mjs";
41
- import "./token-goat-chunk-IZXKVT4A.mjs";
42
- import "./token-goat-chunk-RJH64KQH.mjs";
43
- import "./token-goat-chunk-MMLMALWK.mjs";
44
- import "./token-goat-chunk-6CCQDAYY.mjs";
45
- import "./token-goat-chunk-GGLEE35R.mjs";
51
+ } from "./token-goat-chunk-FBNQNSJK.mjs";
52
+ import "./token-goat-chunk-MNWKPRAL.mjs";
53
+ import "./token-goat-chunk-67TQJ76B.mjs";
54
+ import {
55
+ getDirtyPathsFor
56
+ } from "./token-goat-chunk-H2PHHGM5.mjs";
57
+ import "./token-goat-chunk-P4QMM4WK.mjs";
58
+ import {
59
+ isWorkerRunning
60
+ } from "./token-goat-chunk-5KDMCMF2.mjs";
61
+ import "./token-goat-chunk-ATCPY7EQ.mjs";
62
+ import "./token-goat-chunk-VPBBTSJR.mjs";
63
+ import "./token-goat-chunk-Y776AENC.mjs";
64
+ import "./token-goat-chunk-SJHWVNDQ.mjs";
65
+ import "./token-goat-chunk-AFLYLHDH.mjs";
46
66
  import {
47
67
  claudeConfigDir
48
- } from "./token-goat-chunk-MAMQPC43.mjs";
49
- import "./token-goat-chunk-HXNAC4ZF.mjs";
50
- import "./token-goat-chunk-BJBRGPBX.mjs";
51
- import "./token-goat-chunk-4JMWPWCS.mjs";
52
- import "./token-goat-chunk-4RN64ZND.mjs";
68
+ } from "./token-goat-chunk-BPGCUMR7.mjs";
69
+ import "./token-goat-chunk-C63JQV5K.mjs";
70
+ import "./token-goat-chunk-JB6QN6VF.mjs";
71
+ import {
72
+ embeddingsDepsAvailable
73
+ } from "./token-goat-chunk-RHXKCZX3.mjs";
74
+ import "./token-goat-chunk-EVMZJ27Z.mjs";
75
+ import {
76
+ pathSuffixClause
77
+ } from "./token-goat-chunk-HYBFA5X3.mjs";
78
+ import "./token-goat-chunk-5Y6ZYL6U.mjs";
79
+ import "./token-goat-chunk-ARDNEC2U.mjs";
53
80
  import {
54
81
  recordStat,
55
82
  savedTokensFromBytes
56
- } from "./token-goat-chunk-MVSWBNMD.mjs";
57
- import "./token-goat-chunk-LKSXAMJB.mjs";
58
- import "./token-goat-chunk-YWSP6IU7.mjs";
83
+ } from "./token-goat-chunk-3Z5S4KGM.mjs";
84
+ import {
85
+ getDb
86
+ } from "./token-goat-chunk-VL3YIFHZ.mjs";
87
+ import "./token-goat-chunk-XB5KKOCG.mjs";
88
+ import "./token-goat-chunk-QSBRTGM2.mjs";
89
+ import "./token-goat-chunk-N2NMER56.mjs";
59
90
  import "./token-goat-chunk-3BTK54F3.mjs";
91
+ import "./token-goat-chunk-4FVIAHSI.mjs";
92
+ import {
93
+ loadConfig,
94
+ resolveProjectRoot
95
+ } from "./token-goat-chunk-7IN2MKDS.mjs";
96
+ import "./token-goat-chunk-U4N3BN2W.mjs";
97
+ import "./token-goat-chunk-YZAAZU2S.mjs";
98
+ import {
99
+ envStrList
100
+ } from "./token-goat-chunk-IVE6U7KV.mjs";
60
101
  import "./token-goat-chunk-XTQAOTSO.mjs";
61
102
  import "./token-goat-chunk-AH6QILZM.mjs";
62
- import "./token-goat-chunk-66LODAUT.mjs";
103
+ import "./token-goat-chunk-Y4AFKTHK.mjs";
63
104
  import {
64
- checkEmbeddingPreflight,
65
- embeddingsDepsAvailable
66
- } from "./token-goat-chunk-D5ICEPSB.mjs";
67
- import "./token-goat-chunk-HUDC5GS5.mjs";
105
+ extractErrorMessage
106
+ } from "./token-goat-chunk-GOLEOXZP.mjs";
68
107
  import {
69
- getDb
70
- } from "./token-goat-chunk-M56BWPBN.mjs";
108
+ foldCaseForContainment
109
+ } from "./token-goat-chunk-AM5V6GSX.mjs";
71
110
  import {
72
- envStrList,
73
- loadConfig,
74
- resolveProjectRoot
75
- } from "./token-goat-chunk-CC2PYJM7.mjs";
76
- import "./token-goat-chunk-EEIDFMEM.mjs";
77
- import "./token-goat-chunk-OSUFN2FV.mjs";
111
+ displaySafeJson,
112
+ normalizePath
113
+ } from "./token-goat-chunk-FNFBKLRD.mjs";
78
114
  import {
79
115
  ENV_KEYS,
80
116
  VERSION,
81
117
  dataDir,
82
- extractErrorMessage,
83
- foldCaseForContainment,
84
118
  globalDbPath
85
- } from "./token-goat-chunk-L45ZQRT7.mjs";
86
- import {
87
- displaySafeJson,
88
- normalizePath
89
- } from "./token-goat-chunk-NMTKNYGF.mjs";
90
- import "./token-goat-chunk-Y4AFKTHK.mjs";
91
- import "./token-goat-chunk-GMOUBOX4.mjs";
92
- import "./token-goat-chunk-RRNZMM3A.mjs";
119
+ } from "./token-goat-chunk-VES7EL7O.mjs";
120
+ import "./token-goat-chunk-KWPNJBDH.mjs";
121
+ import "./token-goat-chunk-HAVAOB3S.mjs";
93
122
  import {
94
123
  init_define_import_meta_env
95
124
  } from "./token-goat-chunk-A37V4PBF.mjs";
@@ -103,6 +132,7 @@ var TOKEN_GOAT_RETRY_RE = /token-goat (\w[\w-]*) "([^"]+)"/g;
103
132
  var MCP_MAX_LIMIT = 1e3;
104
133
  var MCP_MAX_CONTEXT_LINES = 50;
105
134
  var MCP_MAX_OUTPUT_LINES = 1e4;
135
+ var MCP_MAX_MIN_LINES = 1e5;
106
136
  var RETRY_PARAM_BY_COMMAND = {
107
137
  read: "spec",
108
138
  section: "spec",
@@ -173,8 +203,86 @@ function realPathForContainment(p) {
173
203
  function forCompare(p) {
174
204
  return process.platform === "win32" ? foldCaseForContainment(p) : p;
175
205
  }
176
- function resolveToolRoot(projectRoot) {
177
- const resolved = resolveProjectRoot(projectRoot !== void 0 ? { project: projectRoot } : {});
206
+ function inferProjectRootFromTarget(target) {
207
+ const file = specFilePart(target);
208
+ if (!file) return null;
209
+ if (path.isAbsolute(file)) {
210
+ if (fs.existsSync(file)) {
211
+ try {
212
+ return resolveProjectRoot({ project: path.dirname(file) });
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
217
+ return null;
218
+ }
219
+ try {
220
+ if (fs.existsSync(path.resolve(process.cwd(), file))) {
221
+ return null;
222
+ }
223
+ } catch {
224
+ }
225
+ const dbPath = globalDbPath();
226
+ if (fs.existsSync(dbPath)) {
227
+ try {
228
+ const db = getDb(dbPath);
229
+ const suffix = pathSuffixClause("path");
230
+ const rows = db.prepare(`SELECT path FROM files WHERE ${suffix.clause} ORDER BY length(path), path LIMIT 5`).all(...suffix.params(normalizePath(file)));
231
+ for (const row of rows) {
232
+ if (fs.existsSync(row.path)) {
233
+ return resolveProjectRoot({ project: path.dirname(row.path) });
234
+ }
235
+ }
236
+ } catch {
237
+ }
238
+ }
239
+ if (!file.includes("/") && !file.includes("\\") && !file.includes(".")) {
240
+ if (fs.existsSync(dbPath)) {
241
+ try {
242
+ const db = getDb(dbPath);
243
+ const rows = db.prepare(`SELECT file_path FROM symbols WHERE name = ? LIMIT 5`).all(file);
244
+ for (const row of rows) {
245
+ if (fs.existsSync(row.file_path)) {
246
+ return resolveProjectRoot({ project: path.dirname(row.file_path) });
247
+ }
248
+ }
249
+ } catch {
250
+ }
251
+ }
252
+ }
253
+ return null;
254
+ }
255
+ function rootHasIndexedFiles(root) {
256
+ const dbPath = globalDbPath();
257
+ if (!fs.existsSync(dbPath)) return false;
258
+ try {
259
+ const prefix = normalizePath(root).toLowerCase().replace(/\/+$/, "") + "/";
260
+ const row = getDb(dbPath).prepare(`SELECT 1 FROM files WHERE TG_LOWER(substr(path, 1, ?)) = ? LIMIT 1`).get(prefix.length, prefix);
261
+ return row !== void 0;
262
+ } catch {
263
+ return false;
264
+ }
265
+ }
266
+ function resolveToolRoot(projectRoot, candidateTargets) {
267
+ if (projectRoot !== void 0) {
268
+ const resolved2 = resolveProjectRoot({ project: projectRoot });
269
+ assertRootAllowed(resolved2);
270
+ return resolved2;
271
+ }
272
+ if (candidateTargets && candidateTargets.length > 0) {
273
+ for (const target of candidateTargets) {
274
+ if (!target) continue;
275
+ const inferred = inferProjectRootFromTarget(target);
276
+ if (inferred && rootHasIndexedFiles(inferred)) {
277
+ try {
278
+ assertRootAllowed(inferred);
279
+ return inferred;
280
+ } catch {
281
+ }
282
+ }
283
+ }
284
+ }
285
+ const resolved = resolveProjectRoot({});
178
286
  assertRootAllowed(resolved);
179
287
  return resolved;
180
288
  }
@@ -221,6 +329,12 @@ function assertRootAllowed(resolvedRoot) {
221
329
  }
222
330
  var NO_CHECK_PINS = [];
223
331
  function identityOf(p) {
332
+ try {
333
+ return fileIdentity(statThroughHandle(p));
334
+ } catch (err) {
335
+ const code = err.code;
336
+ if (code === "ENOENT" || code === "ENOTDIR") return null;
337
+ }
224
338
  try {
225
339
  return fileIdentity(fs.statSync(p, { bigint: true }));
226
340
  } catch {
@@ -236,8 +350,9 @@ function pinsFor(abs, real, identity) {
236
350
  }
237
351
  function checkWithinProjectRoot(target, resolvedRoot) {
238
352
  const rootReal = realPathForContainment(resolvedRoot);
239
- const abs = path.resolve(resolvedRoot, normalizePath(target));
240
- const absRaw = path.resolve(resolvedRoot, target);
353
+ const spelled = expandSpecPath(target);
354
+ const abs = path.resolve(resolvedRoot, normalizePath(spelled));
355
+ const absRaw = path.resolve(resolvedRoot, spelled);
241
356
  const identity = identityOf(absRaw);
242
357
  const realNative = realPathForContainment(abs);
243
358
  if (rootReal === null) return { inside: false, reason: "unresolvable-root", pins: NO_CHECK_PINS };
@@ -262,19 +377,26 @@ function specFilePart(spec) {
262
377
  const colonIdx = findSpecSeparator(spec);
263
378
  return colonIdx === -1 ? spec : spec.slice(0, colonIdx);
264
379
  }
380
+ function specFileCandidates(spec) {
381
+ const file = specFilePart(spec);
382
+ const out = [file];
383
+ for (let idx = file.indexOf("::"); idx !== -1; idx = file.indexOf("::", idx + 1)) if (idx > 0) out.push(file.slice(0, idx));
384
+ return out;
385
+ }
265
386
  var NO_PINS = /* @__PURE__ */ new Map();
266
- function refusalText(file, resolvedRoot, reason) {
387
+ function refusalText(file, resolvedRoot, reason, projectRootWasOmitted = false) {
267
388
  const escapeHatch = "Set mcp.confine_reads_to_project_root = false (or TOKEN_GOAT_MCP_CONFINE_READS=0) to allow cross-root reads.";
389
+ const omittedNote = projectRootWasOmitted ? ` Note: projectRoot was omitted and defaulted to server process cwd "${resolvedRoot}". Pass projectRoot: "<workspace-path>" if your target is in another directory.` : "";
268
390
  if (reason === "unresolvable-root") {
269
- return `refused: the project root "${resolvedRoot}" could not be resolved, so no path can be confirmed to sit inside it. This is a broken workspace root -- an unmounted share, a deleted directory, or a permission change on a parent -- not a request to read outside the project.`;
391
+ return `refused: the project root "${resolvedRoot}" could not be resolved, so no path can be confirmed to sit inside it. This is a broken workspace root -- an unmounted share, a deleted directory, or a permission change on a parent -- not a request to read outside the project.` + omittedNote;
270
392
  }
271
393
  if (reason === "unresolvable-target") {
272
- return `refused: "${file}" could not be resolved to a real location, so it cannot be confirmed to sit inside the project root. A symlink loop, a permission error on a parent directory, or a path past the operating system's length limit all produce this. The check fails closed rather than falling back to comparing the text of the path.`;
394
+ return `refused: "${file}" could not be resolved to a real location, so it cannot be confirmed to sit inside the project root. A symlink loop, a permission error on a parent directory, or a path past the operating system's length limit all produce this. The check fails closed rather than falling back to comparing the text of the path.` + omittedNote;
273
395
  }
274
396
  const rootScope = loadConfig().mcp.allowed_roots.length === 0 ? "Each call is confined to the projectRoot it names, and that root comes from the caller: set mcp.allowed_roots (or TOKEN_GOAT_MCP_ALLOWED_ROOTS) to pin which roots may be named at all." : "Each call is confined to the projectRoot it names, which must itself sit inside mcp.allowed_roots.";
275
- return `refused: "${file}" is outside the project root "${resolvedRoot}". ${rootScope} ${escapeHatch}`;
397
+ return `refused: "${file}" is outside the project root "${resolvedRoot}". ${rootScope} ${escapeHatch}${omittedNote}`;
276
398
  }
277
- function confineTargets(targets, resolvedRoot, splitCommas = true) {
399
+ function confineTargets(targets, resolvedRoot, splitCommas = true, projectRootWasOmitted = false) {
278
400
  if (!loadConfig(resolvedRoot).mcp.confine_reads_to_project_root) return { ok: true, targets, pins: NO_PINS };
279
401
  const allowedRoots = loadConfig().mcp.allowed_roots;
280
402
  const checked = [];
@@ -282,31 +404,32 @@ function confineTargets(targets, resolvedRoot, splitCommas = true) {
282
404
  for (const raw of targets) {
283
405
  const parts = splitCommas ? raw.split(",") : [raw];
284
406
  for (const part of parts) {
285
- const file = specFilePart(part);
286
- if (file === "") continue;
287
- let check = checkWithinProjectRoot(file, resolvedRoot);
288
- if (!check.inside) {
289
- for (const allowed of allowedRoots) {
290
- const altCheck = checkWithinProjectRoot(file, allowed);
291
- if (altCheck.inside) {
292
- check = altCheck;
293
- break;
407
+ for (const file of specFileCandidates(part)) {
408
+ if (file === "") continue;
409
+ let check = checkWithinProjectRoot(file, resolvedRoot);
410
+ if (!check.inside) {
411
+ for (const allowed of allowedRoots) {
412
+ const altCheck = checkWithinProjectRoot(file, allowed);
413
+ if (altCheck.inside) {
414
+ check = altCheck;
415
+ break;
416
+ }
294
417
  }
295
418
  }
296
- }
297
- if (!check.inside) {
298
- for (const aux of getStandardAuxiliaryRoots(file)) {
299
- const auxCheck = checkWithinProjectRoot(file, aux);
300
- if (auxCheck.inside) {
301
- check = auxCheck;
302
- break;
419
+ if (!check.inside) {
420
+ for (const aux of getStandardAuxiliaryRoots(file)) {
421
+ const auxCheck = checkWithinProjectRoot(file, aux);
422
+ if (auxCheck.inside) {
423
+ check = auxCheck;
424
+ break;
425
+ }
303
426
  }
304
427
  }
428
+ if (!check.inside) {
429
+ return { ok: false, refusal: toCallToolResult({ text: refusalText(file, resolvedRoot, check.reason, projectRootWasOmitted), code: 1 }) };
430
+ }
431
+ for (const [key, identity] of check.pins) pins.set(key, identity);
305
432
  }
306
- if (!check.inside) {
307
- return { ok: false, refusal: toCallToolResult({ text: refusalText(file, resolvedRoot, check.reason), code: 1 }) };
308
- }
309
- for (const [key, identity] of check.pins) pins.set(key, identity);
310
433
  }
311
434
  checked.push(splitCommas ? parts.join(",") : parts[0]);
312
435
  }
@@ -325,12 +448,28 @@ function withConfinedRead(pins, fn) {
325
448
  throw err;
326
449
  }
327
450
  }
451
+ function noteOmittedProjectRoot(result, root, projectRootWasOmitted) {
452
+ if (!projectRootWasOmitted) return result;
453
+ if (!isIndexEmptyForProject(globalDbPath(), root)) return result;
454
+ const note = `
455
+ (Note: projectRoot was omitted and defaulted to server cwd "${root}", which has no files indexed. Pass projectRoot: "<workspace-path>" to target your workspace.)`;
456
+ const contents = result.content;
457
+ if (!Array.isArray(contents)) return result;
458
+ for (const item of contents) {
459
+ if (item && typeof item === "object" && "text" in item && typeof item.text === "string") {
460
+ if (!item.text.includes("projectRoot was omitted")) {
461
+ item.text += note;
462
+ }
463
+ }
464
+ }
465
+ return result;
466
+ }
328
467
  function mcpToolAllowlist() {
329
468
  const names = envStrList(ENV_KEYS.MCP_TOOLS, [], ",");
330
469
  return names.length === 0 ? null : new Set(names);
331
470
  }
332
471
  async function createMcpServer() {
333
- const [{ McpServer }, { z }] = await Promise.all([import("./token-goat-chunk-XBESNWEX.mjs"), import("./token-goat-chunk-CHLHM42P.mjs")]);
472
+ const [{ McpServer }, { z }] = await Promise.all([import("./token-goat-chunk-JN4XMLSD.mjs"), import("./token-goat-chunk-CHLHM42P.mjs")]);
334
473
  const server = new McpServer({ name: "token-goat", version: VERSION });
335
474
  const allowlist = mcpToolAllowlist();
336
475
  if (allowlist !== null) {
@@ -349,10 +488,7 @@ async function createMcpServer() {
349
488
  }
350
489
  }, 0).unref();
351
490
  }
352
- const makeProjectRootField = (verb) => z.string().optional().describe(
353
- `absolute path to the workspace root to scope this ${verb} to; defaults to the MCP server process's cwd, which is not always the actual workspace root for MCP clients -- pass this explicitly when it might differ`
354
- );
355
- const projectRootField = makeProjectRootField("lookup");
491
+ const projectRootField = z.string().optional().describe("absolute workspace root; defaults to the server's cwd, which may not be the workspace, so pass it when unsure");
356
492
  server.registerTool(
357
493
  "symbol",
358
494
  {
@@ -369,36 +505,41 @@ async function createMcpServer() {
369
505
  },
370
506
  (args) => {
371
507
  const { name, limit, file, kind, json, projectRoot } = args;
372
- const root = resolveToolRoot(projectRoot);
508
+ const projectRootWasOmitted = projectRoot === void 0;
509
+ const root = resolveToolRoot(projectRoot, file ? [file] : name ? [name] : void 0);
373
510
  let confinedFile = file;
374
511
  let pins = NO_PINS;
375
512
  if (file !== void 0) {
376
- const gate = confineTargets([file], root);
513
+ const gate = confineTargets([file], root, true, projectRootWasOmitted);
377
514
  if (!gate.ok) return gate.refusal;
378
515
  confinedFile = gate.targets[0];
379
516
  pins = gate.pins;
380
517
  }
381
- return withConfinedRead(
382
- pins,
383
- () => toCallToolResult(
384
- runSymbol({
385
- name,
386
- limit: limit ?? 20,
387
- ...confinedFile !== void 0 ? { file: confinedFile } : {},
388
- ...kind !== void 0 ? { kind } : {},
389
- ...json === true ? { json: true } : {},
390
- projectRoot: root
391
- })
392
- )
518
+ return noteOmittedProjectRoot(
519
+ withConfinedRead(
520
+ pins,
521
+ () => toCallToolResult(
522
+ runSymbol({
523
+ name,
524
+ limit: limit ?? 20,
525
+ ...confinedFile !== void 0 ? { file: confinedFile } : {},
526
+ ...kind !== void 0 ? { kind } : {},
527
+ ...json === true ? { json: true } : {},
528
+ projectRoot: root
529
+ })
530
+ )
531
+ ),
532
+ root,
533
+ projectRootWasOmitted
393
534
  );
394
535
  }
395
536
  );
396
537
  server.registerTool(
397
538
  "read",
398
539
  {
399
- description: "Read one symbol's full body, given a spec of the form file::symbol, or a line range file@N-M / file@N, or a bare file path. Pass a comma-separated spec (file::a,b) to fetch several symbols' bodies from one file in a single call.",
540
+ description: "Read a symbol's full body (file::symbol), several from one file (file::a,b), a line range (file@N-M, file@N), or a file.",
400
541
  inputSchema: {
401
- spec: z.string().describe("file::symbol, file@N-M, file@N, a bare file path, or comma-separated file::a,b for a merged multi-symbol view"),
542
+ spec: z.string().describe("file::symbol, file::a,b, file@N-M, file@N, or a file path"),
402
543
  json: z.boolean().optional().describe("output as JSON"),
403
544
  forceRefresh: z.boolean().optional().describe("reparse file from disk before querying (ignore stale index)"),
404
545
  stats: z.boolean().optional().describe("add per-symbol reference count and doc-coverage flag"),
@@ -409,20 +550,25 @@ async function createMcpServer() {
409
550
  },
410
551
  (args) => {
411
552
  const { spec, json, forceRefresh, stats, projectRoot } = args;
412
- const root = resolveToolRoot(projectRoot);
413
- const gate = confineTargets([spec], root);
553
+ const projectRootWasOmitted = projectRoot === void 0;
554
+ const root = resolveToolRoot(projectRoot, [spec]);
555
+ const gate = confineTargets([spec], root, true, projectRootWasOmitted);
414
556
  if (!gate.ok) return gate.refusal;
415
- return withConfinedRead(
416
- gate.pins,
417
- () => toCallToolResult(
418
- runRead({
419
- spec: gate.targets[0],
420
- ...json === true ? { json: true } : {},
421
- ...forceRefresh === true ? { forceRefresh: true } : {},
422
- ...stats === true ? { stats: true } : {},
423
- projectRoot: root
424
- })
425
- )
557
+ return noteOmittedProjectRoot(
558
+ withConfinedRead(
559
+ gate.pins,
560
+ () => toCallToolResult(
561
+ runRead({
562
+ spec: gate.targets[0],
563
+ ...json === true ? { json: true } : {},
564
+ ...forceRefresh === true ? { forceRefresh: true } : {},
565
+ ...stats === true ? { stats: true } : {},
566
+ projectRoot: root
567
+ })
568
+ )
569
+ ),
570
+ root,
571
+ projectRootWasOmitted
426
572
  );
427
573
  }
428
574
  );
@@ -439,18 +585,23 @@ async function createMcpServer() {
439
585
  },
440
586
  (args) => {
441
587
  const { spec, json, projectRoot } = args;
442
- const root = resolveToolRoot(projectRoot);
443
- const gate = confineTargets([spec], root);
588
+ const projectRootWasOmitted = projectRoot === void 0;
589
+ const root = resolveToolRoot(projectRoot, [spec]);
590
+ const gate = confineTargets([spec], root, true, projectRootWasOmitted);
444
591
  if (!gate.ok) return gate.refusal;
445
- return withConfinedRead(
446
- gate.pins,
447
- () => toCallToolResult(
448
- runSection({
449
- spec: gate.targets[0],
450
- ...json === true ? { json: true } : {},
451
- projectRoot: root
452
- })
453
- )
592
+ return noteOmittedProjectRoot(
593
+ withConfinedRead(
594
+ gate.pins,
595
+ () => toCallToolResult(
596
+ runSection({
597
+ spec: gate.targets[0],
598
+ ...json === true ? { json: true } : {},
599
+ projectRoot: root
600
+ })
601
+ )
602
+ ),
603
+ root,
604
+ projectRootWasOmitted
454
605
  );
455
606
  }
456
607
  );
@@ -461,7 +612,7 @@ async function createMcpServer() {
461
612
  inputSchema: {
462
613
  file: z.string().describe("file path"),
463
614
  json: z.boolean().optional().describe("output as JSON"),
464
- minLines: z.number().int().optional().describe("only show symbols at least N lines long"),
615
+ minLines: z.number().int().nonnegative().max(MCP_MAX_MIN_LINES).optional().describe("only show symbols at least N lines long"),
465
616
  forceRefresh: z.boolean().optional().describe("reparse file from disk before querying (ignore stale index)"),
466
617
  stats: z.boolean().optional().describe("add per-symbol reference count and doc-coverage flag"),
467
618
  projectRoot: projectRootField
@@ -470,21 +621,26 @@ async function createMcpServer() {
470
621
  },
471
622
  (args) => {
472
623
  const { file, json, minLines, forceRefresh, stats, projectRoot } = args;
473
- const root = resolveToolRoot(projectRoot);
474
- const gate = confineTargets([file], root);
624
+ const projectRootWasOmitted = projectRoot === void 0;
625
+ const root = resolveToolRoot(projectRoot, [file]);
626
+ const gate = confineTargets([file], root, true, projectRootWasOmitted);
475
627
  if (!gate.ok) return gate.refusal;
476
- return withConfinedRead(
477
- gate.pins,
478
- () => toCallToolResult(
479
- runSkeleton({
480
- file: gate.targets[0],
481
- ...json === true ? { json: true } : {},
482
- ...minLines !== void 0 ? { minLines } : {},
483
- ...forceRefresh === true ? { forceRefresh: true } : {},
484
- ...stats === true ? { stats: true } : {},
485
- projectRoot: root
486
- })
487
- )
628
+ return noteOmittedProjectRoot(
629
+ withConfinedRead(
630
+ gate.pins,
631
+ () => toCallToolResult(
632
+ runSkeleton({
633
+ file: gate.targets[0],
634
+ ...json === true ? { json: true } : {},
635
+ ...minLines !== void 0 ? { minLines } : {},
636
+ ...forceRefresh === true ? { forceRefresh: true } : {},
637
+ ...stats === true ? { stats: true } : {},
638
+ projectRoot: root
639
+ })
640
+ )
641
+ ),
642
+ root,
643
+ projectRootWasOmitted
488
644
  );
489
645
  }
490
646
  );
@@ -495,7 +651,7 @@ async function createMcpServer() {
495
651
  inputSchema: {
496
652
  file: z.string().describe("file path"),
497
653
  json: z.boolean().optional().describe("output as JSON"),
498
- minLines: z.number().int().optional().describe("only show symbols at least N lines long"),
654
+ minLines: z.number().int().nonnegative().max(MCP_MAX_MIN_LINES).optional().describe("only show symbols at least N lines long"),
499
655
  forceRefresh: z.boolean().optional().describe("reparse file from disk before querying (ignore stale index)"),
500
656
  stats: z.boolean().optional().describe("add per-symbol reference count and doc-coverage flag"),
501
657
  projectRoot: projectRootField
@@ -504,37 +660,42 @@ async function createMcpServer() {
504
660
  },
505
661
  (args) => {
506
662
  const { file, json, minLines, forceRefresh, stats, projectRoot } = args;
507
- const root = resolveToolRoot(projectRoot);
508
- const gate = confineTargets([file], root);
663
+ const projectRootWasOmitted = projectRoot === void 0;
664
+ const root = resolveToolRoot(projectRoot, [file]);
665
+ const gate = confineTargets([file], root, true, projectRootWasOmitted);
509
666
  if (!gate.ok) return gate.refusal;
510
- return withConfinedRead(
511
- gate.pins,
512
- () => toCallToolResult(
513
- runOutline({
514
- file: gate.targets[0],
515
- ...json === true ? { json: true } : {},
516
- ...minLines !== void 0 ? { minLines } : {},
517
- ...forceRefresh === true ? { forceRefresh: true } : {},
518
- ...stats === true ? { stats: true } : {},
519
- projectRoot: root
520
- })
521
- )
667
+ return noteOmittedProjectRoot(
668
+ withConfinedRead(
669
+ gate.pins,
670
+ () => toCallToolResult(
671
+ runOutline({
672
+ file: gate.targets[0],
673
+ ...json === true ? { json: true } : {},
674
+ ...minLines !== void 0 ? { minLines } : {},
675
+ ...forceRefresh === true ? { forceRefresh: true } : {},
676
+ ...stats === true ? { stats: true } : {},
677
+ projectRoot: root
678
+ })
679
+ )
680
+ ),
681
+ root,
682
+ projectRootWasOmitted
522
683
  );
523
684
  }
524
685
  );
525
686
  server.registerTool(
526
687
  "semantic",
527
688
  {
528
- description: "Semantic search over the indexed project (falls back to full-text search when no embedding index is available). Scoped to projectRoot if given, else the MCP server process's own cwd -- which may not be the actual workspace root for a client that launched the server from elsewhere, so pass projectRoot explicitly when in doubt.",
689
+ description: "Semantic search over the indexed project; falls back to full-text search without an embedding index.",
529
690
  inputSchema: {
530
691
  query: z.string().describe("natural-language search query"),
531
692
  limit: z.number().int().positive().max(MCP_MAX_LIMIT).optional().describe("max results (default: 20)"),
532
- grep: z.string().optional().describe("filter to hits whose file path matches this regex (literal substring if it does not compile as regex); matched against the path as rendered, same convention as refs --grep"),
533
- excludeTests: z.boolean().optional().describe("hide hits whose file is a test file (opt-in; default output is unchanged)"),
534
- preflight: z.boolean().optional().describe("run semantic embedding preflight check and return status"),
535
- warm: z.boolean().optional().describe("warm up the embedding model session in memory before query execution"),
693
+ grep: z.string().optional().describe("only hits whose file path matches this regex (a literal substring if it is not valid regex)"),
694
+ excludeTests: z.boolean().optional().describe("hide hits in test files"),
695
+ preflight: z.boolean().optional().describe("report whether embeddings are ready, instead of searching"),
696
+ warm: z.boolean().optional().describe("load the embedding model before the query"),
536
697
  json: z.boolean().optional().describe("output as JSON"),
537
- projectRoot: makeProjectRootField("search")
698
+ projectRoot: projectRootField
538
699
  },
539
700
  // openWorldHint stays false although a first call on a machine with no cached model downloads one over the network: what this tool interacts with is the local index, and the download provisions the tool rather than being the tool reaching out. A client reading `true` here would take it as "this searches the internet", which is the wrong warning.
540
701
  annotations: { readOnlyHint: true, openWorldHint: false }
@@ -558,9 +719,9 @@ async function createMcpServer() {
558
719
  server.registerTool(
559
720
  "index_status",
560
721
  {
561
- description: 'Report whether the index for a project can be trusted right now: whether it has ever been indexed at all, current file/symbol counts, dirty-reindex-queue depth, whether the background worker is alive, and whether embeddings are available (semantic silently degrades to full-text search without them). Call this after an unexpectedly empty result from another token-goat tool (symbol/read/semantic/refs/brief/...) to tell apart "no match" from "the index is not ready yet" -- an MCP-only client has no hook layer to warn about this on its own, so an empty tool result and a stale/unindexed project look identical without this check.',
722
+ description: 'Whether the project index can be trusted now: indexed at all, file and symbol counts, reindex queue depth, worker alive, and embeddings available (without them semantic falls back to full-text search). Call it when another token-goat tool returns nothing, to tell "no match" from "index not ready".',
562
723
  inputSchema: {
563
- projectRoot: makeProjectRootField("check")
724
+ projectRoot: projectRootField
564
725
  },
565
726
  annotations: { readOnlyHint: true, openWorldHint: false }
566
727
  },
@@ -595,7 +756,7 @@ async function createMcpServer() {
595
756
  }
596
757
  }
597
758
  const embeddingsEnabled = loadConfig(rootDir).indexing?.embeddings_enabled ?? true;
598
- const preflight = await checkEmbeddingPreflight({
759
+ const preflight = await checkSemanticReadiness({
599
760
  projectRoot: rootDir,
600
761
  ...coverage !== void 0 ? { coverage } : {}
601
762
  });
@@ -620,13 +781,13 @@ async function createMcpServer() {
620
781
  server.registerTool(
621
782
  "refs",
622
783
  {
623
- description: "Find references to one or more symbols (spec: file::symbol, symbol, or comma-separated a,b,c / file::a,b for a merged multi-symbol view). For an unambiguous TypeScript symbol, automatically type-resolves candidates via the TypeScript compiler API to drop same-named-different-symbol false positives; falls back to name-based matching when that is not possible.",
784
+ description: "Find references to symbols. An unambiguous TypeScript symbol is type-resolved, dropping same-named symbols; anything else matches by name.",
624
785
  inputSchema: {
625
- spec: z.string().describe("file::symbol, symbol, or comma-separated a,b,c / file::a,b for a merged multi-symbol view"),
786
+ spec: z.string().describe("file::symbol, symbol, a,b,c, or file::a,b"),
626
787
  callers: z.boolean().optional().describe("group references by their enclosing caller symbol"),
627
788
  limit: z.number().int().positive().max(MCP_MAX_LIMIT).optional().describe("max results"),
628
789
  top: z.number().int().positive().max(MCP_MAX_LIMIT).optional().describe(
629
- "for a high-fanout symbol, group references by file (count only) and show only the top N files by reference count instead of a per-line dump"
790
+ "list only the top N files by reference count, instead of every reference"
630
791
  ),
631
792
  json: z.boolean().optional().describe("output as JSON"),
632
793
  projectRoot: projectRootField
@@ -635,9 +796,16 @@ async function createMcpServer() {
635
796
  },
636
797
  (args) => {
637
798
  const { spec, callers, limit, top, json, projectRoot } = args;
638
- const root = resolveToolRoot(projectRoot);
639
- const gate = confineTargets([spec], root);
799
+ const projectRootWasOmitted = projectRoot === void 0;
800
+ const root = resolveToolRoot(projectRoot, [spec]);
801
+ const gate = confineTargets([spec], root, true, projectRootWasOmitted);
640
802
  if (!gate.ok) return gate.refusal;
803
+ if (projectRootWasOmitted && isIndexEmptyForProject(globalDbPath(), root)) {
804
+ return toCallToolResult({
805
+ text: `Error: projectRoot was omitted and defaulted to server cwd "${root}", which has no files indexed. Pass projectRoot: "<workspace-path>" to target your workspace.`,
806
+ code: 1
807
+ });
808
+ }
641
809
  return withConfinedRead(
642
810
  gate.pins,
643
811
  () => toCallToolResultFromExitCode(
@@ -656,36 +824,41 @@ async function createMcpServer() {
656
824
  server.registerTool(
657
825
  "brief",
658
826
  {
659
- description: "One-shot symbol orientation: signature, location, token count, body, callers, and containing doc section, in a single call (spec: file::symbol; comma-separated file::a,b for a merged multi-symbol view; cross-file a.ts::x,b.ts::y is also supported -- unlike refs, a bare symbol name with no file is not accepted). Prefer this over separate read + refs calls when the goal is to understand a symbol, not just fetch its source: it folds the work of read (body) and refs --callers (call sites) into one result, at a fraction of the combined round-trip cost.",
827
+ description: "Understand a symbol in one call: signature, location, token count, body, callers and containing doc section. Prefer it to read plus refs. Spec file::symbol, file::a,b or a.ts::x,b.ts::y; a bare name without a file is not accepted.",
660
828
  inputSchema: {
661
- spec: z.string().describe("file::symbol; comma-separated file::a,b for a merged multi-symbol view; cross-file a.ts::x,b.ts::y is also supported"),
829
+ spec: z.string().describe("file::symbol, file::a,b, or a.ts::x,b.ts::y"),
662
830
  limit: z.number().int().positive().max(MCP_MAX_LIMIT).optional().describe("max callers to show (default: 20)"),
663
831
  json: z.boolean().optional().describe("output as JSON"),
664
- context: z.number().int().nonnegative().max(MCP_MAX_CONTEXT_LINES).optional().describe("lines of call-site source to show before and after each caller (default 0)"),
665
- excludeTests: z.boolean().optional().describe("hide callers whose call site lives in a test file (opt-in; default output is unchanged)"),
666
- grep: z.string().optional().describe("only show callers whose enclosing symbol name matches this regex (literal substring if it is not valid regex)"),
667
- projectRoot: makeProjectRootField("orient")
832
+ context: z.number().int().nonnegative().max(MCP_MAX_CONTEXT_LINES).optional().describe("call-site lines to show around each caller (default 0)"),
833
+ excludeTests: z.boolean().optional().describe("hide callers in test files"),
834
+ grep: z.string().optional().describe("only callers whose enclosing symbol matches this regex (a literal substring if it is not valid regex)"),
835
+ projectRoot: projectRootField
668
836
  },
669
837
  annotations: { readOnlyHint: true, openWorldHint: false }
670
838
  },
671
839
  (args) => {
672
840
  const { spec, limit, json, context, excludeTests, grep, projectRoot } = args;
673
- const root = resolveToolRoot(projectRoot);
674
- const gate = confineTargets([spec], root);
841
+ const projectRootWasOmitted = projectRoot === void 0;
842
+ const root = resolveToolRoot(projectRoot, [spec]);
843
+ const gate = confineTargets([spec], root, true, projectRootWasOmitted);
675
844
  if (!gate.ok) return gate.refusal;
676
- return withConfinedRead(
677
- gate.pins,
678
- () => toCallToolResultFromExitCode(
679
- () => runBrief({
680
- spec: gate.targets[0],
681
- ...json === true ? { json: true } : {},
682
- ...limit !== void 0 ? { limit } : {},
683
- ...context !== void 0 ? { context } : {},
684
- ...excludeTests === true ? { excludeTests: true } : {},
685
- ...grep !== void 0 ? { grep } : {},
686
- projectRoot: root
687
- })
688
- )
845
+ return noteOmittedProjectRoot(
846
+ withConfinedRead(
847
+ gate.pins,
848
+ () => toCallToolResultFromExitCode(
849
+ () => runBrief({
850
+ spec: gate.targets[0],
851
+ ...json === true ? { json: true } : {},
852
+ ...limit !== void 0 ? { limit } : {},
853
+ ...context !== void 0 ? { context } : {},
854
+ ...excludeTests === true ? { excludeTests: true } : {},
855
+ ...grep !== void 0 ? { grep } : {},
856
+ projectRoot: root
857
+ })
858
+ )
859
+ ),
860
+ root,
861
+ projectRootWasOmitted
689
862
  );
690
863
  }
691
864
  );
@@ -695,13 +868,16 @@ async function createMcpServer() {
695
868
  description: "Project overview: file count, languages, headline symbols, and recently modified files.",
696
869
  inputSchema: {
697
870
  compact: z.boolean().optional().describe("compact, low-token summary"),
698
- projectRoot: makeProjectRootField("overview")
871
+ projectRoot: projectRootField
699
872
  },
700
873
  annotations: { readOnlyHint: true, openWorldHint: false }
701
874
  },
702
875
  (args) => {
703
876
  const { compact, projectRoot } = args;
704
- const map = buildProjectMap(resolveToolRoot(projectRoot), { compact: compact === true });
877
+ const root = resolveToolRoot(projectRoot);
878
+ const projectDenial = resolveProjectConfinement(root).denial;
879
+ if (projectDenial !== null) return toCallToolResult({ text: projectDenial, code: 1 });
880
+ const map = buildProjectMap(root, { compact: compact === true });
705
881
  const text = formatProjectMap(map, map.compact);
706
882
  const bytesSaved = mapLookupBytesSaved(map, text);
707
883
  recordStat("map_lookup", bytesSaved, savedTokensFromBytes(bytesSaved));
@@ -744,14 +920,15 @@ async function createMcpServer() {
744
920
  recursive: z.boolean().optional().describe("descend into subdirectories (default: true)"),
745
921
  context: z.number().int().nonnegative().max(MCP_MAX_CONTEXT_LINES).optional().describe("lines of context to show before and after each match"),
746
922
  // runGrep takes no projectRoot of its own (its `path` array is its scope), so this field only names the root the confinement check is made against -- without it, a search rooted anywhere but the server process's cwd is refused.
747
- projectRoot: makeProjectRootField("search")
923
+ projectRoot: projectRootField
748
924
  },
749
925
  annotations: { readOnlyHint: true, openWorldHint: false }
750
926
  },
751
927
  (args) => {
752
928
  const { pattern, path: searchPath, maxLines, json, recursive, context, projectRoot } = args;
753
- const root = resolveToolRoot(projectRoot);
754
- const gate = confineTargets(searchPath === void 0 ? [root] : searchPath, root, false);
929
+ const projectRootWasOmitted = projectRoot === void 0;
930
+ const root = resolveToolRoot(projectRoot, searchPath);
931
+ const gate = confineTargets(searchPath === void 0 ? [root] : searchPath, root, false, projectRootWasOmitted);
755
932
  if (!gate.ok) return gate.refusal;
756
933
  const confinedPath = searchPath === void 0 ? void 0 : [...gate.targets];
757
934
  return withConfinedRead(
@@ -783,8 +960,9 @@ async function createMcpServer() {
783
960
  },
784
961
  (args) => {
785
962
  const { file, json, projectRoot } = args;
786
- const root = resolveToolRoot(projectRoot);
787
- const gate = confineTargets([file], root);
963
+ const projectRootWasOmitted = projectRoot === void 0;
964
+ const root = resolveToolRoot(projectRoot, [file]);
965
+ const gate = confineTargets([file], root, true, projectRootWasOmitted);
788
966
  if (!gate.ok) return gate.refusal;
789
967
  return withConfinedRead(
790
968
  gate.pins,
@@ -807,8 +985,9 @@ async function createMcpServer() {
807
985
  },
808
986
  (args) => {
809
987
  const { file, json, projectRoot } = args;
810
- const root = resolveToolRoot(projectRoot);
811
- const gate = confineTargets([file], root);
988
+ const projectRootWasOmitted = projectRoot === void 0;
989
+ const root = resolveToolRoot(projectRoot, [file]);
990
+ const gate = confineTargets([file], root, true, projectRootWasOmitted);
812
991
  if (!gate.ok) return gate.refusal;
813
992
  return withConfinedRead(
814
993
  gate.pins,
@@ -851,7 +1030,7 @@ async function createMcpServer() {
851
1030
  inputSchema: {
852
1031
  name: z.string().regex(/^[A-Za-z0-9._-]{1,128}$/).describe("handoff name"),
853
1032
  text: z.string().max(CONTENT_MAX_INPUT_CHARS).describe("handoff text"),
854
- projectRoot: makeProjectRootField("scope")
1033
+ projectRoot: projectRootField
855
1034
  },
856
1035
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
857
1036
  },
@@ -867,7 +1046,7 @@ async function createMcpServer() {
867
1046
  inputSchema: {
868
1047
  name: z.string().regex(/^[A-Za-z0-9._-]{1,128}$/).describe("handoff name"),
869
1048
  full: z.boolean().optional().describe("return full text instead of a compact payload"),
870
- projectRoot: makeProjectRootField("scope")
1049
+ projectRoot: projectRootField
871
1050
  },
872
1051
  // Reads like a read, and is not one: resolving compactly runs the handoff text back through compressText, which stores the compact payload so the recovery id it hands back can be redeemed. A measurement, not a reading of this code -- the guard hashes the store's bytes around every tool call, and this is the tool it caught.
873
1052
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
@@ -885,5 +1064,6 @@ async function createMcpServer() {
885
1064
  export {
886
1065
  checkWithinProjectRoot,
887
1066
  createMcpServer,
1067
+ inferProjectRootFromTarget,
888
1068
  mcpToolAllowlist
889
1069
  };