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.
- package/README.md +36 -40
- package/SECURITY.md +28 -10
- package/THIRD_PARTY_NOTICES.md +40 -3
- package/dist/native/linux-arm64/tg-hook +0 -0
- package/dist/native/linux-x64/tg-hook +0 -0
- package/dist/ort-wasm-simd-threaded.mjs +59 -0
- package/dist/token-goat-chunk-3CHUNJ2O.mjs +53 -0
- package/dist/token-goat-chunk-3Z5S4KGM.mjs +918 -0
- package/dist/token-goat-chunk-4FVIAHSI.mjs +211 -0
- package/dist/{token-goat-chunk-66LODAUT.mjs → token-goat-chunk-4JJS54TR.mjs} +4 -3
- package/dist/token-goat-chunk-4LCCNRTG.mjs +313 -0
- package/dist/token-goat-chunk-54K3EUV3.mjs +35 -0
- package/dist/token-goat-chunk-56EIMS3E.mjs +159 -0
- package/dist/token-goat-chunk-5BCIJXMO.mjs +609 -0
- package/dist/token-goat-chunk-5KDMCMF2.mjs +362 -0
- package/dist/token-goat-chunk-5RG5SBL6.mjs +221 -0
- package/dist/{token-goat-chunk-RRNZMM3A.mjs → token-goat-chunk-5Y6ZYL6U.mjs} +21 -2
- package/dist/{token-goat-chunk-NILVK7XM.mjs → token-goat-chunk-64FBPDTK.mjs} +12 -6
- package/dist/{token-goat-chunk-LEG3JNJ6.mjs → token-goat-chunk-664RTTUV.mjs} +31 -15
- package/dist/token-goat-chunk-67TQJ76B.mjs +57 -0
- package/dist/token-goat-chunk-6QMAS7AB.mjs +42 -0
- package/dist/token-goat-chunk-7BXSNBY5.mjs +253 -0
- package/dist/{token-goat-chunk-F7QZRIWM.mjs → token-goat-chunk-7F2UBPD3.mjs} +42 -35
- package/dist/{token-goat-chunk-CC2PYJM7.mjs → token-goat-chunk-7IN2MKDS.mjs} +718 -595
- package/dist/token-goat-chunk-7NADEDKJ.mjs +124 -0
- package/dist/{token-goat-chunk-6SGLIPQB.mjs → token-goat-chunk-A3CK6N5Z.mjs} +621 -72
- package/dist/{token-goat-chunk-HXNAC4ZF.mjs → token-goat-chunk-AFLYLHDH.mjs} +48 -33
- package/dist/token-goat-chunk-AM5V6GSX.mjs +367 -0
- package/dist/{token-goat-chunk-I7CB3X4K.mjs → token-goat-chunk-APGZXSPS.mjs} +44 -148
- package/dist/{token-goat-chunk-BJBRGPBX.mjs → token-goat-chunk-ARDNEC2U.mjs} +11 -7
- package/dist/token-goat-chunk-ATCPY7EQ.mjs +134 -0
- package/dist/token-goat-chunk-B2RZZCSS.mjs +354 -0
- package/dist/{token-goat-chunk-QM76S6YS.mjs → token-goat-chunk-B45VMIIH.mjs} +6 -5
- package/dist/token-goat-chunk-BPGCUMR7.mjs +30 -0
- package/dist/{token-goat-chunk-GGLEE35R.mjs → token-goat-chunk-C63JQV5K.mjs} +49 -10
- package/dist/{token-goat-chunk-RJH64KQH.mjs → token-goat-chunk-COBQGL5S.mjs} +2661 -2440
- package/dist/token-goat-chunk-COTXZHTS.mjs +39 -0
- package/dist/token-goat-chunk-CYCEYAEO.mjs +265 -0
- package/dist/token-goat-chunk-CZ3LUXYZ.mjs +4115 -0
- package/dist/token-goat-chunk-DZX2YDPT.mjs +15 -0
- package/dist/token-goat-chunk-E3OQX2XT.mjs +824 -0
- package/dist/token-goat-chunk-EFHMMIGP.mjs +68 -0
- package/dist/token-goat-chunk-ETBHVDB6.mjs +328 -0
- package/dist/token-goat-chunk-EVMZJ27Z.mjs +147 -0
- package/dist/token-goat-chunk-EXKGNHEG.mjs +2096 -0
- package/dist/{token-goat-chunk-6Z5ULNE5.mjs → token-goat-chunk-FBNQNSJK.mjs} +2490 -4957
- package/dist/{token-goat-chunk-NMTKNYGF.mjs → token-goat-chunk-FNFBKLRD.mjs} +74 -9
- package/dist/{token-goat-chunk-XI2VFAV6.mjs → token-goat-chunk-FZ3NEW5O.mjs} +33 -19
- package/dist/token-goat-chunk-G5PJ7AGM.mjs +36 -0
- package/dist/{token-goat-chunk-VE27HPIF.mjs → token-goat-chunk-GFBSN7BD.mjs} +30 -398
- package/dist/{token-goat-chunk-L45ZQRT7.mjs → token-goat-chunk-GOLEOXZP.mjs} +780 -786
- package/dist/token-goat-chunk-H2K3QP6H.mjs +24 -0
- package/dist/token-goat-chunk-H2PHHGM5.mjs +188 -0
- package/dist/token-goat-chunk-HAVAOB3S.mjs +87 -0
- package/dist/token-goat-chunk-HG45AF2B.mjs +2228 -0
- package/dist/{token-goat-chunk-CBMY2FGI.mjs → token-goat-chunk-HOJLCGDP.mjs} +17113 -15554
- package/dist/{token-goat-chunk-HUDC5GS5.mjs → token-goat-chunk-HYBFA5X3.mjs} +120 -19
- package/dist/token-goat-chunk-IVALLT3S.mjs +48 -0
- package/dist/token-goat-chunk-IVE6U7KV.mjs +53 -0
- package/dist/{token-goat-chunk-UBUKHVYW.mjs → token-goat-chunk-IVKIZWAB.mjs} +3 -3
- package/dist/{token-goat-chunk-4JMWPWCS.mjs → token-goat-chunk-JB6QN6VF.mjs} +6 -4
- package/dist/{token-goat-chunk-Q4S3UQGO.mjs → token-goat-chunk-JDHE26RP.mjs} +10 -3
- package/dist/{token-goat-chunk-7R2XYKC5.mjs → token-goat-chunk-JDU3EXSU.mjs} +14 -10
- package/dist/token-goat-chunk-JDWCRD6M.mjs +253 -0
- package/dist/{token-goat-chunk-XBESNWEX.mjs → token-goat-chunk-JN4XMLSD.mjs} +7 -4
- package/dist/token-goat-chunk-JTQQGXZH.mjs +733 -0
- package/dist/token-goat-chunk-KKUFXAPD.mjs +25 -0
- package/dist/token-goat-chunk-KQNZDCBD.mjs +47 -0
- package/dist/{token-goat-chunk-4RN64ZND.mjs → token-goat-chunk-KWPNJBDH.mjs} +28 -3
- package/dist/token-goat-chunk-LOBGHXPB.mjs +242 -0
- package/dist/token-goat-chunk-MKPF4CL5.mjs +37 -0
- package/dist/token-goat-chunk-MNWKPRAL.mjs +13 -0
- package/dist/{token-goat-chunk-YWSP6IU7.mjs → token-goat-chunk-N2NMER56.mjs} +25 -15
- package/dist/token-goat-chunk-NYKZLXMK.mjs +218 -0
- package/dist/token-goat-chunk-OJBNYTJP.mjs +3087 -0
- package/dist/token-goat-chunk-P4QMM4WK.mjs +295 -0
- package/dist/token-goat-chunk-PD5VKH46.mjs +184 -0
- package/dist/token-goat-chunk-PEPTHRFF.mjs +129 -0
- package/dist/{token-goat-chunk-VWZKZPSR.mjs → token-goat-chunk-PU7V3FD6.mjs} +8752 -11098
- package/dist/token-goat-chunk-PY533T7F.mjs +46 -0
- package/dist/token-goat-chunk-Q5VTGRH7.mjs +761 -0
- package/dist/{token-goat-chunk-IHKZ3QNA.mjs → token-goat-chunk-QCLDAQTS.mjs} +33 -19
- package/dist/{token-goat-chunk-LKSXAMJB.mjs → token-goat-chunk-QSBRTGM2.mjs} +9 -2
- package/dist/{token-goat-chunk-4STDIGYC.mjs → token-goat-chunk-RC35RBUL.mjs} +7 -5
- package/dist/{token-goat-chunk-D5ICEPSB.mjs → token-goat-chunk-RHXKCZX3.mjs} +887 -507
- package/dist/{token-goat-chunk-RSS3TLXU.mjs → token-goat-chunk-RZRTDST7.mjs} +17 -7
- package/dist/{token-goat-chunk-MAMQPC43.mjs → token-goat-chunk-SJHWVNDQ.mjs} +15 -21
- package/dist/token-goat-chunk-SMVRN2HU.mjs +188 -0
- package/dist/{token-goat-chunk-EL7M26WP.mjs → token-goat-chunk-SQSSNQPE.mjs} +2100 -1442
- package/dist/{token-goat-chunk-OZRSREKR.mjs → token-goat-chunk-T4FK7VOV.mjs} +3 -3
- package/dist/token-goat-chunk-U4N3BN2W.mjs +92 -0
- package/dist/token-goat-chunk-UA5V6P3P.mjs +122 -0
- package/dist/{token-goat-chunk-XKY4VKJ4.mjs → token-goat-chunk-UQ5UK2T7.mjs} +15 -4
- package/dist/token-goat-chunk-USILZ3T3.mjs +53 -0
- package/dist/token-goat-chunk-VES7EL7O.mjs +205 -0
- package/dist/{token-goat-chunk-M56BWPBN.mjs → token-goat-chunk-VL3YIFHZ.mjs} +550 -49
- package/dist/{token-goat-chunk-IZXKVT4A.mjs → token-goat-chunk-VPBBTSJR.mjs} +362 -128
- package/dist/{token-goat-chunk-F2ARFHLJ.mjs → token-goat-chunk-W3QCJS6G.mjs} +10 -2
- package/dist/{token-goat-chunk-FSTWAOW6.mjs → token-goat-chunk-WY3L4DVT.mjs} +11 -5
- package/dist/{token-goat-chunk-OSUFN2FV.mjs → token-goat-chunk-XB5KKOCG.mjs} +5 -25
- package/dist/token-goat-chunk-XKI4SEMX.mjs +66 -0
- package/dist/token-goat-chunk-XU4P6Z24.mjs +133 -0
- package/dist/{token-goat-chunk-6CCQDAYY.mjs → token-goat-chunk-Y776AENC.mjs} +138 -33
- package/dist/token-goat-chunk-YARZFCZS.mjs +359 -0
- package/dist/{token-goat-chunk-EEIDFMEM.mjs → token-goat-chunk-YZAAZU2S.mjs} +12 -3
- package/dist/{token-goat-chunk-E3K3BTEQ.mjs → token-goat-chunk-Z44YO3QZ.mjs} +12 -6
- package/dist/token-goat-chunk-Z57YODA2.mjs +35 -0
- package/dist/{token-goat-chunk-DA43OE7L.mjs → token-goat-chunk-ZCDTCVIS.mjs} +49 -30
- package/dist/token-goat-chunk-ZEJVMUQH.mjs +48 -0
- package/dist/{token-goat-chunk-BGHSSJUA.mjs → token-goat-chunk-ZFOBKUWH.mjs} +1 -1
- package/dist/token-goat-chunk-ZMBL6UVV.mjs +110 -0
- package/dist/{token-goat-chunk-SJEKA7Y2.mjs → token-goat-chunk-ZQSJMO2R.mjs} +381 -201
- package/dist/token-goat-hook-client.cjs +539 -0
- package/dist/token-goat-hook-client.mjs +26 -0
- package/dist/token-goat-hook.mjs +48 -28
- package/dist/token-goat.core.mjs +10 -45
- package/docs/C4_RUNTIME_ARCHITECTURE.md +3 -3
- package/docs/cli.md +82 -47
- package/docs/install.md +58 -23
- package/docs/security.md +7 -7
- package/package.json +17 -7
- package/scripts/verify-native-dist.mjs +386 -0
- package/dist/token-goat-chunk-4JJBFT7V.mjs +0 -20
- package/dist/token-goat-chunk-5NNYIUGV.mjs +0 -143
- package/dist/token-goat-chunk-6DQNNFJ6.mjs +0 -294
- package/dist/token-goat-chunk-7MAFDRVR.mjs +0 -47
- package/dist/token-goat-chunk-BCN2YQC6.mjs +0 -32
- package/dist/token-goat-chunk-D7GHQKQD.mjs +0 -30
- package/dist/token-goat-chunk-GMOUBOX4.mjs +0 -386
- package/dist/token-goat-chunk-IFORDBGC.mjs +0 -192
- package/dist/token-goat-chunk-JXK2HQXS.mjs +0 -318
- package/dist/token-goat-chunk-L7SDVGBT.mjs +0 -430
- package/dist/token-goat-chunk-MMLMALWK.mjs +0 -1381
- 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
|
-
|
|
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-
|
|
30
|
-
import "./token-goat-chunk-
|
|
31
|
-
import "./token-goat-chunk-
|
|
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
|
-
|
|
49
|
+
isIndexEmptyForProject,
|
|
39
50
|
mapLookupBytesSaved
|
|
40
|
-
} from "./token-goat-chunk-
|
|
41
|
-
import "./token-goat-chunk-
|
|
42
|
-
import "./token-goat-chunk-
|
|
43
|
-
import
|
|
44
|
-
|
|
45
|
-
|
|
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-
|
|
49
|
-
import "./token-goat-chunk-
|
|
50
|
-
import "./token-goat-chunk-
|
|
51
|
-
import
|
|
52
|
-
|
|
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-
|
|
57
|
-
import
|
|
58
|
-
|
|
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-
|
|
103
|
+
import "./token-goat-chunk-Y4AFKTHK.mjs";
|
|
63
104
|
import {
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
70
|
-
} from "./token-goat-chunk-
|
|
108
|
+
foldCaseForContainment
|
|
109
|
+
} from "./token-goat-chunk-AM5V6GSX.mjs";
|
|
71
110
|
import {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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-
|
|
86
|
-
import
|
|
87
|
-
|
|
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
|
|
177
|
-
const
|
|
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
|
|
240
|
-
const
|
|
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
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
|
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
|
|
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
|
|
413
|
-
const
|
|
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
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
|
443
|
-
const
|
|
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
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
|
474
|
-
const
|
|
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
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
|
508
|
-
const
|
|
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
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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
|
|
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("
|
|
533
|
-
excludeTests: z.boolean().optional().describe("hide hits
|
|
534
|
-
preflight: z.boolean().optional().describe("
|
|
535
|
-
warm: z.boolean().optional().describe("
|
|
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:
|
|
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: '
|
|
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:
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
"
|
|
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
|
|
639
|
-
const
|
|
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: "
|
|
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
|
|
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("
|
|
665
|
-
excludeTests: z.boolean().optional().describe("hide callers
|
|
666
|
-
grep: z.string().optional().describe("only
|
|
667
|
-
projectRoot:
|
|
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
|
|
674
|
-
const
|
|
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
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
() =>
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
754
|
-
const
|
|
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
|
|
787
|
-
const
|
|
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
|
|
811
|
-
const
|
|
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:
|
|
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:
|
|
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
|
};
|