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
@@ -0,0 +1,4115 @@
1
+ import { createRequire as __cjsRequire } from 'node:module';
2
+ const require = __cjsRequire(import.meta.url);
3
+ import {
4
+ formatSqliteQueryTable,
5
+ formatSqliteSchema,
6
+ formatSqliteTables,
7
+ getSqliteSchema,
8
+ getSqliteTables,
9
+ runReadOnlySqliteQuery
10
+ } from "./token-goat-chunk-ZCDTCVIS.mjs";
11
+ import {
12
+ DELETED_TAG,
13
+ DIDYOUMEAN_LIMIT,
14
+ FIND_SCAN_LIMIT,
15
+ FirstRows,
16
+ REF_BLIND_DEF_PROBE_LIMIT,
17
+ UNBOUNDED_QUERY_LIMIT,
18
+ WHOLE_FILE_NOTE_SYMBOL,
19
+ buildContextWindow,
20
+ compareBinary,
21
+ confinedProjectRoot,
22
+ confinementRefusal,
23
+ didYouMean,
24
+ emit,
25
+ emitErr,
26
+ emitGuarded,
27
+ extractExportNames,
28
+ extractImports,
29
+ fileConfinementRefusal,
30
+ fileExists,
31
+ fileIsGone,
32
+ filterSimilarHeadings,
33
+ findSpecSeparator,
34
+ findStructuredKeyPath,
35
+ firstBodyLine,
36
+ forEachSymbol,
37
+ formatAmbiguity,
38
+ formatBareNameSpecError,
39
+ formatStatsSuffix,
40
+ getNote,
41
+ guardJsonRows,
42
+ guardText,
43
+ hasRealDocstring,
44
+ healStaleIndex,
45
+ healStaleResultFiles,
46
+ importsExtensionFor,
47
+ indexFileSyncPinned,
48
+ indexFreshness,
49
+ isNoteStale,
50
+ isRefIndexedFile,
51
+ isValidUtf8,
52
+ largestFileSize,
53
+ listNotes,
54
+ nearNamesSkippedNote,
55
+ nearSymbolNames,
56
+ noSymbolsMessage,
57
+ parseCrossFileMultiSpec,
58
+ parseMultiFileSpec,
59
+ parseReadSpec,
60
+ previewLines,
61
+ projectStructuredFiles,
62
+ rankSimilarNames,
63
+ readFileBytes,
64
+ readFileText,
65
+ recordReadStat,
66
+ recordStaleServed,
67
+ refBlindKindNotice,
68
+ refBlindKindPartialNote,
69
+ refBlindKindVerdict,
70
+ refBlindLanguageNotice,
71
+ renderContextWindow,
72
+ resolveAgainstProjectRoot,
73
+ resolveBody,
74
+ resolveCallers,
75
+ resolveProjectConfinement,
76
+ resolveQualifiedSpec,
77
+ resolveQualifiedSpecDef,
78
+ resolveSpecPath,
79
+ resolveSymbolSpec,
80
+ resolveSymbolSpecOrEmitError,
81
+ sinkGoneRows,
82
+ specScopeRoot,
83
+ staleWarning,
84
+ stripHtmlIdSpelling,
85
+ sumFileSizes,
86
+ symbolsById,
87
+ trimBlankLines,
88
+ truncationFooter,
89
+ truncationNotice,
90
+ typedRefsForDef,
91
+ unknownSymbolSuggestion,
92
+ warnIfFilesStale
93
+ } from "./token-goat-chunk-PU7V3FD6.mjs";
94
+ import {
95
+ parseJsonOrJsonc
96
+ } from "./token-goat-chunk-XKI4SEMX.mjs";
97
+ import {
98
+ MODEL_DOWNLOAD_HOST,
99
+ RUNTIME_DOWNLOAD_HOST,
100
+ activeDownloadCooldown,
101
+ isExplicitDownload,
102
+ lastDownloadFailure,
103
+ withExplicitDownload
104
+ } from "./token-goat-chunk-SMVRN2HU.mjs";
105
+ import {
106
+ commentSyntaxFor
107
+ } from "./token-goat-chunk-5RG5SBL6.mjs";
108
+ import {
109
+ deliveredOutputBytes
110
+ } from "./token-goat-chunk-54K3EUV3.mjs";
111
+ import {
112
+ NOTEBOOK_CELL_LINES_SUFFIX,
113
+ emptyIndexMessage,
114
+ formatSymbolLocation,
115
+ getEmbeddingCoverage,
116
+ isIgnoredIndexPath,
117
+ isIndexEmptyForProject,
118
+ isVirtualIndexedPath,
119
+ searchEvidenceSemantically,
120
+ virtualIndexedScopeNote,
121
+ walkProject
122
+ } from "./token-goat-chunk-FBNQNSJK.mjs";
123
+ import {
124
+ enqueueDirtyPathSafe
125
+ } from "./token-goat-chunk-H2PHHGM5.mjs";
126
+ import {
127
+ ensureWorkerAlive,
128
+ fetchBypassesProxy,
129
+ nodeFetchHonoursEnvProxy,
130
+ proxyConfigured
131
+ } from "./token-goat-chunk-5KDMCMF2.mjs";
132
+ import {
133
+ MAX_OVER_FETCH,
134
+ NATIVE_RUNTIME_INSTALL,
135
+ ORT_WEB_WASM,
136
+ OVER_FETCH_FACTOR,
137
+ RUNTIME_UNAVAILABLE_ADVICE,
138
+ activeRuntime,
139
+ checkEmbeddingPreflight,
140
+ eachUnfencedLine,
141
+ embeddingBackendLoadError,
142
+ findContainingSection,
143
+ isAvailable,
144
+ listSections,
145
+ modelDir,
146
+ modelFilesPresent,
147
+ readSection,
148
+ searchSemantic,
149
+ wasmBinaryPresent
150
+ } from "./token-goat-chunk-RHXKCZX3.mjs";
151
+ import {
152
+ ATX_CLASS_BREAK_RE,
153
+ matchAtxHeading
154
+ } from "./token-goat-chunk-EVMZJ27Z.mjs";
155
+ import {
156
+ DEFAULT_QUERY_LIMIT,
157
+ countRefs,
158
+ countSymbols,
159
+ queryRefCounts,
160
+ queryRefs,
161
+ querySymbols,
162
+ searchSymbolsFts
163
+ } from "./token-goat-chunk-HYBFA5X3.mjs";
164
+ import {
165
+ isBlobStale,
166
+ loadBlob,
167
+ storeBlob
168
+ } from "./token-goat-chunk-ARDNEC2U.mjs";
169
+ import {
170
+ BYTES_PER_TOKEN,
171
+ getGlobalDb,
172
+ recordStat,
173
+ savedTokensFromBytes,
174
+ withTelemetryWriteDb
175
+ } from "./token-goat-chunk-3Z5S4KGM.mjs";
176
+ import {
177
+ detectLanguage,
178
+ detectLanguageOfFile,
179
+ getDb
180
+ } from "./token-goat-chunk-VL3YIFHZ.mjs";
181
+ import {
182
+ redactSecrets
183
+ } from "./token-goat-chunk-4FVIAHSI.mjs";
184
+ import {
185
+ findProject,
186
+ getDisplayRoot,
187
+ getProjectConfigInfo,
188
+ loadConfig,
189
+ parse,
190
+ projectHash,
191
+ resolveConfigKeyLayer,
192
+ resolveProjectRoot,
193
+ shortFingerprint
194
+ } from "./token-goat-chunk-7IN2MKDS.mjs";
195
+ import {
196
+ MAX_ZIP_OUTPUT_BYTES,
197
+ ZipInputTooLargeError,
198
+ ZipOutputTooLargeError,
199
+ unzipBounded
200
+ } from "./token-goat-chunk-XTQAOTSO.mjs";
201
+ import {
202
+ createLazyModuleLoader
203
+ } from "./token-goat-chunk-AH6QILZM.mjs";
204
+ import {
205
+ compileGrepMatcher,
206
+ countNoun,
207
+ escapeRegExp,
208
+ excludeTestsHiddenNote,
209
+ extractErrorMessage,
210
+ filtersFilteredToEmptyNotice,
211
+ grepFilteredToEmptyNotice,
212
+ isTestFile,
213
+ requireNonNegativeStrictInt,
214
+ runGit,
215
+ stripBom
216
+ } from "./token-goat-chunk-GOLEOXZP.mjs";
217
+ import {
218
+ foldPath
219
+ } from "./token-goat-chunk-AM5V6GSX.mjs";
220
+ import {
221
+ displaySafeJson,
222
+ displaySafeText,
223
+ normalizePath,
224
+ resolveIndexPath,
225
+ toDisplayPath
226
+ } from "./token-goat-chunk-FNFBKLRD.mjs";
227
+ import {
228
+ SKELETON_SYMBOL_CAP,
229
+ globalDbPath
230
+ } from "./token-goat-chunk-VES7EL7O.mjs";
231
+ import {
232
+ init_define_import_meta_env
233
+ } from "./token-goat-chunk-A37V4PBF.mjs";
234
+
235
+ // src/embed_preflight.ts
236
+ init_define_import_meta_env();
237
+ var MODEL_MB = 35;
238
+ var WARM_COMMAND = "token-goat semantic --preflight --warm";
239
+ function pendingDownloadHosts() {
240
+ const hosts = [];
241
+ if (!modelFilesPresent()) hosts.push(MODEL_DOWNLOAD_HOST);
242
+ if (activeRuntime() === "onnxruntime-web" && !wasmBinaryPresent()) hosts.push(RUNTIME_DOWNLOAD_HOST);
243
+ return hosts;
244
+ }
245
+ function modelDownloadHeld(now = Date.now()) {
246
+ const hosts = pendingDownloadHosts();
247
+ if (hosts.length === 0) return false;
248
+ if (loadConfig().network?.offline === true) return true;
249
+ return hosts.some((host) => activeDownloadCooldown(host, now) !== null);
250
+ }
251
+ function foregroundDownloadDeferred(now = Date.now(), env = process.env, nodeVersion = process.versions.node) {
252
+ if (modelDownloadHeld(now)) return true;
253
+ return !isExplicitDownload() && fetchBypassesProxy(env, nodeVersion) && pendingDownloadHosts().length > 0;
254
+ }
255
+ function downloadAdvice(env = process.env, nodeVersion = process.versions.node) {
256
+ return `${proxyAdvice(env, nodeVersion)}, or copy the model files into ${modelDir()}. Then run \`${WARM_COMMAND}\` to download it now.`;
257
+ }
258
+ function runtimeDownloadAdvice(env = process.env, nodeVersion = process.versions.node) {
259
+ return `${proxyAdvice(env, nodeVersion)}, or ${NATIVE_RUNTIME_INSTALL}. Then run \`${WARM_COMMAND}\` to download it now.`;
260
+ }
261
+ function proxyAdvice(env, nodeVersion) {
262
+ const proxied = proxyConfigured(env);
263
+ const honoured = nodeFetchHonoursEnvProxy(nodeVersion);
264
+ if (proxied && !honoured) return `This machine sets a proxy, but Node ${nodeVersion} cannot send fetch through it: upgrade to Node 22.21+ or 24+`;
265
+ if (proxied && env["NODE_USE_ENV_PROXY"] === "0") return "NODE_USE_ENV_PROXY=0 sends token-goat's downloads around the proxy in HTTPS_PROXY. If this machine reaches the internet only through that proxy, remove it";
266
+ if (proxied) return `Check that the proxy in HTTPS_PROXY lets ${MODEL_DOWNLOAD_HOST} and ${RUNTIME_DOWNLOAD_HOST} through. If this machine reaches them without it, set NODE_USE_ENV_PROXY=0 and token-goat's downloads go around the proxy`;
267
+ if (!honoured) return "If this machine reaches the internet through a proxy, set HTTPS_PROXY to it and upgrade to Node 22.21+ or 24+, the first versions that can use it";
268
+ return "If this machine reaches the internet through a proxy, set HTTPS_PROXY to it";
269
+ }
270
+ function explainModelDownload(result, now = Date.now()) {
271
+ if (result.modelWarmed) return result.modelFilesPresent ? result : { ...result, modelFilesPresent: true };
272
+ if (result.status !== "ready" && result.status !== "no_embeddings" && result.status !== "load_error") return result;
273
+ const pending = pendingDownloadHosts();
274
+ if (pending.length === 0) return result;
275
+ const modelMissing = pending.includes(MODEL_DOWNLOAD_HOST);
276
+ const rebuild = (status, available, message, suggestion, error) => {
277
+ const { suggestion: _s, actionRequired: _a, error: _e, ...rest } = result;
278
+ return {
279
+ ...rest,
280
+ status,
281
+ available,
282
+ message,
283
+ summary: message,
284
+ modelFilesPresent: !modelMissing,
285
+ ...suggestion !== void 0 ? { suggestion, actionRequired: suggestion } : {},
286
+ ...error !== void 0 ? { error } : {}
287
+ };
288
+ };
289
+ let failure = null;
290
+ for (const host of pending) {
291
+ const record = lastDownloadFailure(host);
292
+ if (record !== null && (failure === null || record.at > failure.at)) failure = { host, at: record.at, message: record.message };
293
+ }
294
+ if (failure !== null) {
295
+ const cooldown = activeDownloadCooldown(failure.host, now);
296
+ const when = cooldown !== null ? `It is tried again automatically after ${new Date(cooldown.retryAt).toISOString()}.` : "It is tried again automatically the next time a file is embedded.";
297
+ const runtime = failure.host === RUNTIME_DOWNLOAD_HOST;
298
+ const what = runtime ? `The WebAssembly runtime the embedding model runs on (onnxruntime-web, from ${RUNTIME_DOWNLOAD_HOST}) is not downloaded yet` : "The embedding model is not downloaded yet";
299
+ const message = `${what}. The last try, at ${new Date(failure.at).toISOString()}, failed: ${failure.message}. ${when}`;
300
+ return rebuild(runtime ? "missing_runtime" : "missing_model_files", false, message, runtime ? runtimeDownloadAdvice() : downloadAdvice(), message);
301
+ }
302
+ if (result.status === "load_error" || !modelMissing) return result;
303
+ if (result.status === "no_embeddings") {
304
+ const message = `The embedding model (about ${MODEL_MB} MB, once) is not downloaded yet. It downloads by itself in the background, and this project's ${result.indexedFiles} indexed file(s) are embedded after it.`;
305
+ return rebuild("missing_model_files", false, message, `Nothing to do. To download it now instead, run \`${WARM_COMMAND}\`.`, void 0);
306
+ }
307
+ return rebuild("ready", true, `Semantic search is ready. The embedding model (about ${MODEL_MB} MB) is not downloaded yet; it is fetched on first use.`, void 0, void 0);
308
+ }
309
+ async function checkSemanticReadiness(options) {
310
+ return explainModelDownload(await checkEmbeddingPreflight(options));
311
+ }
312
+ function offlineEmbedNotice(fileCount, rootDir) {
313
+ const cfg = loadConfig(rootDir);
314
+ if (!cfg.network.offline) return null;
315
+ const state = resolveConfigKeyLayer("network.offline", cfg.network.offline, cfg, getProjectConfigInfo(rootDir));
316
+ const holder = state.layer === "env" || state.layer === "env-invalid" ? `${state.envVar} is set in your environment` : state.layer === "project" ? `${displaySafeText(state.path)} sets network.offline` : "network.offline is on in config.toml";
317
+ return `token-goat: index: ${fileCount} not embedded: the embedding model is not downloaded and ${holder}, so nothing will download it. symbol, read, outline and section work now; semantic falls back to keyword search. To embed them, unset it so the worker can download the model, or copy the pinned model files into ${modelDir()}; then run \`token-goat index\` again.`;
318
+ }
319
+
320
+ // src/read_symbol.ts
321
+ init_define_import_meta_env();
322
+ var SYMBOL_PREVIEW_LINES = 5;
323
+ var STALE_TAG = "\u26A0 STALE: file changed on disk and could not be reindexed";
324
+ var EMPTY_PATH_SET = /* @__PURE__ */ new Set();
325
+ var IMPORT_BIND_BODY_RE = /^\s*(?:const|let|var)\s*\{[^}]*\}\s*=\s*(?:await\s+)?(?:import|require)\s*\(/;
326
+ function stableSortImportBindsLast(rows) {
327
+ const isBind = (r) => typeof r.body === "string" && IMPORT_BIND_BODY_RE.test(r.body) ? 1 : 0;
328
+ return [...rows].sort((a, b) => isBind(a) - isBind(b));
329
+ }
330
+ function runSymbol(opts) {
331
+ if (opts.limit !== void 0 && opts.limit <= 0) {
332
+ return { text: `--limit must be a positive number, got: ${opts.limit}`, code: 1 };
333
+ }
334
+ if (opts.name !== void 0 && opts.grep !== void 0) {
335
+ return {
336
+ text: "symbol: --grep cannot be combined with a name; drop the name to search by pattern, or drop --grep to search by exact name",
337
+ code: 1
338
+ };
339
+ }
340
+ if (opts.name === void 0 && opts.grep === void 0) {
341
+ return { text: "symbol requires a name or --grep <pattern>", code: 1 };
342
+ }
343
+ const matchesGrep = opts.grep !== void 0 ? compileGrepMatcher(opts.grep) : void 0;
344
+ const excludeTests = opts.excludeTests === true;
345
+ const excludeVendored = opts.excludeVendored === true;
346
+ const { root: confinedRoot, denial: projectDenial } = resolveProjectConfinement(opts.projectRoot);
347
+ if (projectDenial !== null) return { text: projectDenial, code: 1 };
348
+ if (confinedRoot !== null && opts.file !== void 0) {
349
+ const fileDenial = confinementRefusal("--file", resolveSpecPath(opts.file, opts.projectRoot ?? process.cwd()), confinedRoot);
350
+ if (fileDenial !== null) return { text: fileDenial, code: 1 };
351
+ }
352
+ const queryOpts = {};
353
+ if (opts.file !== void 0) {
354
+ queryOpts.filePath = resolveSpecPath(opts.file, opts.projectRoot ?? process.cwd());
355
+ healStaleIndex(queryOpts.filePath);
356
+ }
357
+ if (opts.name !== void 0) queryOpts.name = queryOpts.filePath !== void 0 ? stripHtmlIdSpelling(opts.name, queryOpts.filePath) : opts.name;
358
+ if (opts.kind !== void 0) queryOpts.kind = opts.kind;
359
+ const anyClientFilter = matchesGrep !== void 0 || excludeTests || excludeVendored;
360
+ const effectiveLimit = opts.limit ?? DEFAULT_QUERY_LIMIT;
361
+ if (!anyClientFilter && opts.limit !== void 0) {
362
+ queryOpts.limit = opts.limit;
363
+ }
364
+ if (opts.file === void 0 && opts.projectRoot !== void 0) queryOpts.rootDir = opts.projectRoot;
365
+ if (opts.file === void 0 && queryOpts.rootDir === void 0 && confinedRoot !== null) queryOpts.rootDir = confinedRoot;
366
+ const preferRoot = opts.file === void 0 && queryOpts.rootDir === void 0 ? resolveProjectRoot({ project: process.cwd() }) : void 0;
367
+ const rowKept = (s) => (matchesGrep === void 0 || matchesGrep(s.name)) && !(excludeTests && isTestFile(s.filePath)) && !(excludeVendored && isIgnoredIndexPath(s.filePath));
368
+ const runSweep = () => {
369
+ const found = { kept: [], keptCount: 0, scanned: 0, hiddenByExcludeTests: 0, files: /* @__PURE__ */ new Set(), mineCount: 0, mineKept: 0 };
370
+ const admit = (s) => {
371
+ found.scanned++;
372
+ found.files.add(s.filePath);
373
+ const nameKept = matchesGrep === void 0 || matchesGrep(s.name);
374
+ if (rowKept(s)) {
375
+ found.keptCount++;
376
+ return true;
377
+ }
378
+ if (excludeTests && nameKept && isTestFile(s.filePath)) found.hiddenByExcludeTests++;
379
+ return false;
380
+ };
381
+ if (anyClientFilter) {
382
+ const mine = new FirstRows(effectiveLimit);
383
+ if (preferRoot !== void 0) {
384
+ forEachSymbol({ ...queryOpts, rootDir: preferRoot }, (s) => {
385
+ if (rowKept(s)) {
386
+ found.mineKept++;
387
+ mine.offer(s);
388
+ }
389
+ });
390
+ }
391
+ const mineRows = mine.rows();
392
+ const mineIds = new Set(mineRows.map((s) => s.id));
393
+ const first = new FirstRows(effectiveLimit + mineRows.length);
394
+ forEachSymbol(queryOpts, (s) => {
395
+ if (admit(s)) first.offer(s);
396
+ });
397
+ const rest = first.rows().filter((s) => !mineIds.has(s.id));
398
+ found.mineCount = mineRows.length;
399
+ found.kept = symbolsById([...mineRows, ...rest].slice(0, effectiveLimit).map((s) => s.id));
400
+ } else if (preferRoot !== void 0) {
401
+ const mineRows = querySymbols({ ...queryOpts, rootDir: preferRoot, limit: effectiveLimit });
402
+ const rowKey = (r) => [r.filePath, r.lineStart, r.name].join("|");
403
+ const mineKeys = new Set(mineRows.map(rowKey));
404
+ const all = querySymbols({ ...queryOpts, limit: effectiveLimit + mineRows.length });
405
+ for (const row of all) admit(row);
406
+ found.kept = [...mineRows, ...all.filter((r) => !mineKeys.has(rowKey(r)))].slice(0, effectiveLimit);
407
+ found.mineCount = mineRows.length;
408
+ } else {
409
+ for (const row of querySymbols(queryOpts)) if (admit(row) && found.kept.length < effectiveLimit) found.kept.push(row);
410
+ }
411
+ return found;
412
+ };
413
+ let sweep = runSweep();
414
+ let stillStale = EMPTY_PATH_SET;
415
+ if (opts.file === void 0) {
416
+ const heal = healStaleResultFiles([...sweep.files]);
417
+ stillStale = heal.stillStale;
418
+ if (heal.healed) sweep = runSweep();
419
+ }
420
+ const preFilterCount = sweep.scanned;
421
+ const unordered = sweep.kept.slice(0, effectiveLimit);
422
+ const orderGroup = (rows) => opts.name === void 0 ? rows : stableSortImportBindsLast(rows);
423
+ const ordered = [...orderGroup(unordered.slice(0, sweep.mineCount)), ...orderGroup(unordered.slice(sweep.mineCount))];
424
+ const results = sinkGoneRows(ordered, (s) => s.filePath);
425
+ if (opts.file === void 0) {
426
+ if (results.some((s) => stillStale.has(s.filePath))) recordStaleServed("symbol", "stale");
427
+ if (results.some((s) => fileIsGone(s.filePath))) recordStaleServed("symbol", "deleted");
428
+ }
429
+ const hiddenByExcludeTests = sweep.hiddenByExcludeTests;
430
+ if (excludeTests && sweep.keptCount === 0 && hiddenByExcludeTests > 0) {
431
+ const label = opts.name ?? opts.grep ?? "*";
432
+ const notice = `no non-test matches for '${label}' (${excludeTestsHiddenNote(hiddenByExcludeTests)})`;
433
+ if (opts.json === true) {
434
+ return { text: displaySafeJson({ items: [], truncated: false, totalCount: 0 }), code: 0 };
435
+ }
436
+ return { text: `token-goat: ${notice}`, code: 0 };
437
+ }
438
+ if (matchesGrep !== void 0 && sweep.keptCount === 0 && preFilterCount > 0) {
439
+ if (opts.json === true) {
440
+ const text2 = displaySafeJson({ items: [], truncated: false, totalCount: 0 });
441
+ return { text: text2, code: 0 };
442
+ }
443
+ return { text: grepFilteredToEmptyNotice(preFilterCount, opts.grep ?? "", "symbol", "symbols"), code: 0 };
444
+ }
445
+ if (results.length === 0) {
446
+ if (opts.json === true) {
447
+ return { text: displaySafeJson({ items: [], truncated: false, totalCount: 0 }), code: 0 };
448
+ }
449
+ let text2 = `No matches for '${opts.name ?? opts.grep ?? "*"}'`;
450
+ const emptyIndexRoot = opts.projectRoot ?? resolveProjectRoot({ project: process.cwd() });
451
+ const indexEmpty = isIndexEmptyForProject(globalDbPath(), emptyIndexRoot);
452
+ if (opts.name !== void 0) {
453
+ const rootDir = emptyIndexRoot;
454
+ const exactMatches = querySymbols({ name: opts.name, rootDir, limit: DIDYOUMEAN_LIMIT });
455
+ if (exactMatches.length > 0) {
456
+ const total = exactMatches.length < DIDYOUMEAN_LIMIT ? exactMatches.length : countSymbols({ name: opts.name, rootDir });
457
+ const where = exactMatches.map((s) => `${s.kind} at ${formatSymbolLocation(toDisplayPath(rootDir, s.filePath), s.lineStart)}`).join("; ");
458
+ const more = total > exactMatches.length ? ` (+${total - exactMatches.length} more)` : "";
459
+ const flags = [opts.kind !== void 0 ? "--kind" : null, opts.file !== void 0 ? "--file" : null].filter((f) => f !== null);
460
+ const widen = flags.length > 0 ? `drop ${flags.join("/")} to see it` : "widen the search scope to see it";
461
+ text2 += `
462
+ '${opts.name}' IS indexed (${where}${more}) -- ${widen}`;
463
+ } else {
464
+ const near = nearSymbolNames(opts.name, rootDir);
465
+ const semanticHint = indexEmpty ? "" : `
466
+ Try: token-goat semantic "${opts.name}"`;
467
+ if (near.skipped) text2 += `
468
+ ${nearNamesSkippedNote()}${semanticHint}`;
469
+ else text2 += near.candidates.length > 0 ? `
470
+ ${didYouMean(near.candidates)}` : semanticHint;
471
+ }
472
+ const structuredFiles = projectStructuredFiles(rootDir);
473
+ const hit = findStructuredKeyPath(opts.name, structuredFiles);
474
+ if (hit !== null) {
475
+ const display = toDisplayPath(rootDir, hit.filePath);
476
+ text2 += `
477
+ '${opts.name}' is a key in ${display} at ${hit.dotPath} -- JSON/YAML keys below the top level are not symbols; read it with: token-goat ${hit.command} ${display} '${hit.dotPath}'`;
478
+ }
479
+ }
480
+ if (indexEmpty) {
481
+ text2 += `
482
+ ${emptyIndexMessage(emptyIndexRoot)}`;
483
+ }
484
+ return { text: text2, code: 1 };
485
+ }
486
+ const fullSourceBytes = largestFileSize(results.map((s) => s.filePath));
487
+ const symbolDisplayRoot = getDisplayRoot(opts.projectRoot);
488
+ const refCounts = opts.stats === true ? queryRefCounts(
489
+ results.map((s) => s.name),
490
+ globalDbPath(),
491
+ resolveProjectRoot({ project: opts.projectRoot ?? process.cwd() })
492
+ ) : void 0;
493
+ if (opts.json === true) {
494
+ const capped = guardJsonRows(results);
495
+ let trueTotal;
496
+ let truncatedFlag;
497
+ if (anyClientFilter) {
498
+ trueTotal = sweep.keptCount;
499
+ truncatedFlag = capped.truncated || results.length < sweep.keptCount;
500
+ } else {
501
+ trueTotal = countSymbols(queryOpts);
502
+ truncatedFlag = capped.truncated || trueTotal > results.length;
503
+ }
504
+ const items = capped.items.map((s) => ({
505
+ ...s,
506
+ filePath: toDisplayPath(symbolDisplayRoot, s.filePath),
507
+ // Only present when true, so a result set of live files stays byte-identical to what this command has always emitted and only the genuinely-gone rows grow a field.
508
+ ...fileIsGone(s.filePath) ? { deleted: true } : {},
509
+ ...stillStale.has(s.filePath) ? { stale: true } : {},
510
+ ...refCounts !== void 0 ? { refCount: refCounts.get(s.name) ?? 0, hasDoc: hasRealDocstring(s.docstring) } : {}
511
+ }));
512
+ const payload = { items, truncated: truncatedFlag, totalCount: trueTotal };
513
+ if (opts.file !== void 0) recordStaleServed("symbol", indexFreshness(resolveSpecPath(opts.file, opts.projectRoot ?? process.cwd())));
514
+ const text2 = displaySafeJson(payload);
515
+ recordReadStat("symbol_lookup", fullSourceBytes, text2, opts.name ?? opts.file ?? opts.grep);
516
+ return { text: text2, code: 0 };
517
+ }
518
+ const blocks = results.map((sym) => {
519
+ const statsStr = formatStatsSuffix(refCounts, sym);
520
+ const goneTag = fileIsGone(sym.filePath) ? ` ${DELETED_TAG}` : "";
521
+ const staleTag = stillStale.has(sym.filePath) ? ` ${STALE_TAG}` : "";
522
+ const header = `# ${sym.name} (${sym.kind}) \u2014 ${formatSymbolLocation(toDisplayPath(symbolDisplayRoot, sym.filePath), sym.lineStart, sym.lineEnd)}${statsStr}${goneTag}${staleTag}`;
523
+ const body = resolveBody(sym);
524
+ const bodyLines = body.split(/\r?\n/);
525
+ const preview = bodyLines.slice(0, SYMBOL_PREVIEW_LINES).join("\n");
526
+ const dropped = bodyLines.length - SYMBOL_PREVIEW_LINES;
527
+ const elided = dropped > 0 ? `
528
+ ...(${countNoun(dropped, "more line")}; full body: token-goat read "${toDisplayPath(symbolDisplayRoot, sym.filePath)}::${sym.name}")` : "";
529
+ return preview.trim() !== "" ? `${header}
530
+ ${preview}${elided}` : header;
531
+ });
532
+ const warning = opts.file !== void 0 ? staleWarning(resolveSpecPath(opts.file, opts.projectRoot ?? process.cwd()), "symbol") : "";
533
+ const text = guardText(warning + blocks.join("\n\n"), "symbol");
534
+ recordReadStat("symbol_lookup", fullSourceBytes, text, opts.name ?? opts.file ?? opts.grep);
535
+ let cachedTotal;
536
+ const symbolTotal = () => {
537
+ cachedTotal ??= anyClientFilter ? { count: sweep.keptCount, exact: true } : { count: countSymbols(queryOpts), exact: true };
538
+ return cachedTotal;
539
+ };
540
+ const footer = truncationFooter(results.length, effectiveLimit, symbolTotal, "matches", "--limit");
541
+ const mineTotal = footer !== "" && preferRoot !== void 0 ? anyClientFilter ? sweep.mineKept : countSymbols({ ...queryOpts, rootDir: preferRoot }) : 0;
542
+ const someElsewhere = mineTotal > 0 && mineTotal < symbolTotal().count;
543
+ const mineNote = someElsewhere ? `
544
+ token-goat: ${mineTotal} of the matches are in this project and listed first; pass -p to search only it` : "";
545
+ return { text: text + footer + mineNote, code: 0 };
546
+ }
547
+
548
+ // src/read_brief.ts
549
+ init_define_import_meta_env();
550
+ function runBriefCore(opts) {
551
+ const resolution = resolveSymbolSpec(opts.spec, void 0, opts.projectRoot);
552
+ if (resolution.kind === "confined") return { text: resolution.message, code: 1 };
553
+ if (resolution.kind === "ambiguous") {
554
+ return {
555
+ // Name the command explicitly: formatAmbiguity defaults to 'read', so brief's retry lines would otherwise tell the user to run `token-goat read`, which answers a different question than the one they asked.
556
+ text: formatAmbiguity(
557
+ resolution.symbol,
558
+ resolution.file,
559
+ resolution.candidates,
560
+ opts.projectRoot,
561
+ "brief"
562
+ ),
563
+ code: 1
564
+ };
565
+ }
566
+ if (resolution.kind === "none") {
567
+ if (findSpecSeparator(opts.spec) === -1) {
568
+ return { text: formatBareNameSpecError("brief", opts.spec, opts.projectRoot), code: 1 };
569
+ }
570
+ if (opts.json !== true) {
571
+ const rootDir2 = resolveProjectRoot({ project: opts.projectRoot ?? process.cwd() });
572
+ if (isIndexEmptyForProject(globalDbPath(), rootDir2)) {
573
+ return { text: `Symbol not found: ${opts.spec}
574
+ ${emptyIndexMessage(rootDir2)}`, code: 1 };
575
+ }
576
+ }
577
+ return { text: `Symbol not found: ${opts.spec}`, code: 1 };
578
+ }
579
+ const match = resolution.entry;
580
+ const rootDir = specScopeRoot(opts.spec, resolveProjectRoot({ project: opts.projectRoot ?? process.cwd() })).root;
581
+ const excludeTests = opts.excludeTests === true;
582
+ const allCallers = resolveCallers(match.name, void 0, match.filePath, rootDir, true, ...match.kind === "method" ? [match] : []);
583
+ const testFiltered = excludeTests ? allCallers.filter((c) => !isTestFile(c.file)) : allCallers;
584
+ const hiddenByExcludeTests = excludeTests ? allCallers.length - testFiltered.length : 0;
585
+ const preGrepCount = testFiltered.length;
586
+ const matchesGrep = opts.grep !== void 0 ? compileGrepMatcher(opts.grep) : void 0;
587
+ const callers = matchesGrep !== void 0 ? testFiltered.filter((c) => matchesGrep(c.caller)) : testFiltered;
588
+ const hiddenByGrep = matchesGrep !== void 0 ? preGrepCount - callers.length : 0;
589
+ const totalCallers = callers.length;
590
+ const section = findContainingSection(match.filePath, match.lineStart, match.lineEnd, readFileText);
591
+ const limit = opts.limit ?? 20;
592
+ const shown = callers.slice(0, limit);
593
+ const truncated = totalCallers > shown.length;
594
+ const fullSourceBytes = sumFileSizes([match.filePath]);
595
+ if (opts.json === true) {
596
+ const callersWithContext = (opts.context ?? 0) > 0 ? shown.map((c) => ({ ...c, contextLines: buildContextWindow(c.file, c.line, opts.context ?? 0) ?? [] })) : shown;
597
+ const result = {
598
+ symbol: { ...match, body: resolveBody(match), filePath: toDisplayPath(rootDir, match.filePath) },
599
+ callers: callersWithContext.map((c) => ({ ...c, file: toDisplayPath(rootDir, c.file) })),
600
+ totalCallers,
601
+ truncated,
602
+ ...hiddenByExcludeTests > 0 ? { hiddenByExcludeTests } : {},
603
+ ...hiddenByGrep > 0 ? { hiddenByGrep } : {},
604
+ // Mirrors runRead's json branch: emitting the row verbatim would hand a JSON consumer a deleted file's stale body with no signal it is no longer live.
605
+ ...fileIsGone(match.filePath) ? { deleted: true } : {},
606
+ section
607
+ };
608
+ const jsonText = displaySafeJson(result);
609
+ recordStaleServed("brief", indexFreshness(match.filePath));
610
+ if (opts.suppressStat !== true) recordReadStat("brief_view", fullSourceBytes, jsonText, opts.spec);
611
+ return { text: jsonText, code: 0 };
612
+ }
613
+ const body = resolveBody(match);
614
+ const bodyLen = match.lineEnd - match.lineStart + 1;
615
+ const lines = [
616
+ // This header is token-goat's own line quoting a repo-chosen name, kind and path, so it is escaped. `body` below it is the source the reader asked for and stays byte-for-byte.
617
+ `# ${displaySafeText(match.name)} ${displaySafeText(match.kind)} ${formatSymbolLocation(displaySafeText(toDisplayPath(rootDir, match.filePath)), match.lineStart, match.lineEnd)}`,
618
+ `# ${countNoun(bodyLen, "line")} (~${Math.ceil(body.length / 4)} tok)`,
619
+ body,
620
+ ""
621
+ ];
622
+ const warning = staleWarning(match.filePath, "brief");
623
+ const hiddenNote = excludeTests && hiddenByExcludeTests > 0 ? ` (${excludeTestsHiddenNote(hiddenByExcludeTests)})` : "";
624
+ if (callers.length === 0 && matchesGrep !== void 0 && preGrepCount > 0) {
625
+ lines.push(`Callers (0): ${grepFilteredToEmptyNotice(preGrepCount, opts.grep ?? "", "caller", "callers").trim()}`);
626
+ } else {
627
+ lines.push(callers.length === 0 && hiddenNote !== "" ? `Callers (0): no non-test callers${hiddenNote}` : `Callers (${totalCallers}):${hiddenNote}`);
628
+ }
629
+ for (const c of shown) {
630
+ const callerDisplayPath = toDisplayPath(rootDir, c.file);
631
+ lines.push(` ${c.caller} ${callerDisplayPath}:${c.line}`);
632
+ const window = buildContextWindow(c.file, c.line, opts.context ?? 0);
633
+ if (window !== null) lines.push(...renderContextWindow(callerDisplayPath, c.line, window, "", " "));
634
+ }
635
+ if (truncated) {
636
+ lines.push(` ...(${totalCallers - shown.length} more elided)`);
637
+ }
638
+ if (section !== null) {
639
+ lines.push("");
640
+ lines.push(`Section: ${section.heading} (lines ${section.lineStart}-${section.lineEnd})`);
641
+ }
642
+ const text = guardText(warning + trimBlankLines(lines).join("\n"), "symbol");
643
+ if (opts.suppressStat !== true) recordReadStat("brief_view", fullSourceBytes, text, opts.spec);
644
+ return { text, code: 0 };
645
+ }
646
+ function runBriefMulti(file, symbols, opts) {
647
+ let anyFound = false;
648
+ const jsonOut = {};
649
+ const textBlocks = [];
650
+ for (const sym of symbols) {
651
+ const sub = runBriefCore({ ...opts, spec: `${file}::${sym}`, suppressStat: true });
652
+ if (sub.code === 0) anyFound = true;
653
+ if (opts.json === true) {
654
+ jsonOut[sym] = sub.code === 0 ? JSON.parse(sub.text) : { error: sub.text };
655
+ continue;
656
+ }
657
+ textBlocks.push(`${sym}:
658
+ ${sub.text}`);
659
+ }
660
+ const fullSourceBytes = sumFileSizes([resolveSpecPath(file, opts.projectRoot ?? process.cwd())]);
661
+ const text = opts.json === true ? displaySafeJson(jsonOut) : textBlocks.join("\n\n");
662
+ if (anyFound) recordReadStat("brief_view", fullSourceBytes, text, opts.spec);
663
+ return { text, code: anyFound ? 0 : 1 };
664
+ }
665
+ function runBriefCrossFile(pairs, opts) {
666
+ const distinctFiles = new Set(pairs.map((p) => p.file));
667
+ const keyFor = (p) => distinctFiles.size === 1 ? p.symbol : `${p.file}::${p.symbol}`;
668
+ let anyFound = false;
669
+ const jsonOut = {};
670
+ const textBlocks = [];
671
+ for (const { file, symbol } of pairs) {
672
+ const key = keyFor({ file, symbol });
673
+ const sub = runBriefCore({ ...opts, spec: `${file}::${symbol}`, suppressStat: true });
674
+ if (sub.code === 0) anyFound = true;
675
+ if (opts.json === true) {
676
+ jsonOut[key] = sub.code === 0 ? JSON.parse(sub.text) : { error: sub.text };
677
+ continue;
678
+ }
679
+ textBlocks.push(`${key}:
680
+ ${sub.text}`);
681
+ }
682
+ const fullSourceBytes = sumFileSizes([...distinctFiles].map((f) => resolveSpecPath(f, opts.projectRoot ?? process.cwd())));
683
+ const text = opts.json === true ? displaySafeJson(jsonOut) : textBlocks.join("\n\n");
684
+ if (anyFound) recordReadStat("brief_view", fullSourceBytes, text, opts.spec);
685
+ return { text, code: anyFound ? 0 : 1 };
686
+ }
687
+ function runBrief(opts) {
688
+ if (opts.limit !== void 0 && opts.limit <= 0) {
689
+ emitErr(`--limit must be a positive number, got: ${opts.limit}`);
690
+ return 1;
691
+ }
692
+ const crossFilePairs = parseCrossFileMultiSpec(opts.spec);
693
+ if (crossFilePairs !== null) {
694
+ const { text: text2, code: code2 } = runBriefCrossFile(crossFilePairs, opts);
695
+ if (code2 === 0) emit(text2);
696
+ else emitErr(text2);
697
+ return code2;
698
+ }
699
+ const { file, symbol } = parseReadSpec(opts.spec);
700
+ if (symbol !== void 0 && symbol !== "" && symbol.includes(",")) {
701
+ const multiSymbols = symbol.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
702
+ if (multiSymbols.length > 1) {
703
+ const { text: text2, code: code2 } = runBriefMulti(file, multiSymbols, opts);
704
+ if (code2 === 0) emit(text2);
705
+ else emitErr(text2);
706
+ return code2;
707
+ }
708
+ }
709
+ const { text, code } = runBriefCore(opts);
710
+ if (code === 0) emit(text);
711
+ else emitErr(text);
712
+ return code;
713
+ }
714
+
715
+ // src/read_section.ts
716
+ init_define_import_meta_env();
717
+ import * as fs from "node:fs";
718
+ function htmlIdSectionFallback(filePath, heading) {
719
+ if (detectLanguage(filePath) !== "html") return null;
720
+ healStaleIndex(filePath);
721
+ const hit = querySymbols({ name: stripHtmlIdSpelling(heading, filePath), filePath, kind: "html_id", limit: 1 })[0];
722
+ if (hit === void 0) return null;
723
+ const text = readFileText(filePath);
724
+ if (text === null) return null;
725
+ const lines = text.split("\n");
726
+ return { heading: hit.name, content: lines.slice(hit.lineStart - 1, hit.lineEnd).join("\n"), lineStart: hit.lineStart, lineEnd: hit.lineEnd };
727
+ }
728
+ var AMBIGUOUS_HEADING_LIMIT = 10;
729
+ function filterBodyByGrep(content, pattern) {
730
+ const matches = compileGrepMatcher(pattern);
731
+ const lines = content.split("\n");
732
+ const out = [];
733
+ let matched = 0;
734
+ let pendingHeading = null;
735
+ const unfenced = new Set(Array.from(eachUnfencedLine(lines), ([i]) => i));
736
+ for (const [i, line] of lines.entries()) {
737
+ const isHeading = unfenced.has(i) && matchAtxHeading(line, ATX_CLASS_BREAK_RE, true) !== null;
738
+ if (i === 0 && isHeading) out.push(line);
739
+ else if (isHeading) pendingHeading = line;
740
+ const hit = matches(line);
741
+ if (hit) matched++;
742
+ if (!hit || i === 0 && isHeading) continue;
743
+ if (pendingHeading !== null && pendingHeading !== line) out.push(pendingHeading);
744
+ pendingHeading = null;
745
+ out.push(line);
746
+ }
747
+ return { content: out.join("\n"), matched, total: lines.length };
748
+ }
749
+ function literalHeadingExists(filePath, heading) {
750
+ const ordinalMatch = /^([^#\r\n]+)#(\d+)$/.exec(heading);
751
+ const base = (ordinalMatch?.[1] ?? heading).trim().toLowerCase();
752
+ if (base.length === 0) return false;
753
+ return listSections(filePath, readFileText).some((h) => h.trim().toLowerCase() === base);
754
+ }
755
+ function findSectionSeparator(spec, projectRoot) {
756
+ const last = spec.lastIndexOf("::");
757
+ if (last <= 0) return last;
758
+ const isFile = (idx) => {
759
+ try {
760
+ return fs.statSync(resolveAgainstProjectRoot(spec.slice(0, idx), projectRoot)).isFile();
761
+ } catch {
762
+ return false;
763
+ }
764
+ };
765
+ if (isFile(last)) return last;
766
+ for (let idx = spec.indexOf("::"); idx !== -1 && idx < last; idx = spec.indexOf("::", idx + 1)) {
767
+ if (idx > 0 && isFile(idx)) return idx;
768
+ }
769
+ return last;
770
+ }
771
+ function runSection(opts) {
772
+ const crossFilePairs = parseCrossFileMultiSpec(opts.spec);
773
+ if (crossFilePairs !== null) return runSectionCrossFile(crossFilePairs, opts);
774
+ const colonIdx = findSectionSeparator(opts.spec, opts.projectRoot);
775
+ if (colonIdx === -1) {
776
+ return { text: `Invalid section spec \u2014 expected "file::Heading", got: ${opts.spec}`, code: 1 };
777
+ }
778
+ const specFilePath = opts.spec.slice(0, colonIdx);
779
+ const filePath = resolveAgainstProjectRoot(specFilePath, opts.projectRoot);
780
+ const heading = opts.spec.slice(colonIdx + 2);
781
+ if (heading.includes(",") && !literalHeadingExists(filePath, heading)) {
782
+ const multiHeadings = heading.split(",").map((h) => h.trim()).filter((h) => h.length > 0);
783
+ if (multiHeadings.length > 1) return runSectionMulti(specFilePath, filePath, multiHeadings, opts);
784
+ }
785
+ const result = readSection(filePath, heading, readFileText) ?? htmlIdSectionFallback(filePath, heading);
786
+ if (result === null) {
787
+ if (!fs.existsSync(filePath)) {
788
+ return { text: `File not found: '${filePath}'`, code: 1 };
789
+ }
790
+ const ordSpec = /^(.*?)#(\d+)$/.exec(heading);
791
+ const ordBase = ordSpec?.[1]?.trim();
792
+ if (ordBase !== void 0 && ordBase.length > 0) {
793
+ const baseResult = readSection(filePath, ordBase, readFileText);
794
+ if (baseResult !== null) {
795
+ const total = baseResult.occurrences?.length ?? 1;
796
+ return {
797
+ text: `Heading '${ordBase}' has ${countNoun(total, "occurrence")} in '${specFilePath}'; valid ordinals are #1 to #${total}`,
798
+ code: 1
799
+ };
800
+ }
801
+ }
802
+ const messages = [`Section '${heading}' not found in '${filePath}'`];
803
+ const allHeadings = listSections(filePath, readFileText);
804
+ const available = filterSimilarHeadings(allHeadings, heading);
805
+ if (available.length > 0) messages.push(didYouMean(available));
806
+ else if (allHeadings.length === 0) messages.push(`'${specFilePath}' has no headings`);
807
+ else messages.push(`Try: token-goat outline ${specFilePath}`);
808
+ return { text: messages.join("\n"), code: 1 };
809
+ }
810
+ if (result.occurrences !== void 0) {
811
+ const lines = [
812
+ `Ambiguous heading '${heading}' in '${specFilePath}': ${countNoun(result.occurrences.length, "heading")} match. Retry with one of the qualified commands below to pick one:`
813
+ ];
814
+ for (const [i, line] of result.occurrences.slice(0, AMBIGUOUS_HEADING_LIMIT).entries()) {
815
+ lines.push(` - line ${line} -> token-goat section "${specFilePath}::${heading}#${i + 1}"`);
816
+ }
817
+ if (result.occurrences.length > AMBIGUOUS_HEADING_LIMIT) {
818
+ lines.push(` (${result.occurrences.length - AMBIGUOUS_HEADING_LIMIT} more not shown)`);
819
+ }
820
+ return { text: lines.join("\n"), code: 1 };
821
+ }
822
+ const kind = result.redirectedFrom !== void 0 ? "section_replacement" : "section_read";
823
+ const fullSourceBytes = sumFileSizes([filePath]);
824
+ let content = result.content;
825
+ let lineEnd = result.lineEnd;
826
+ let truncatedNotice = "";
827
+ let grepNote = "";
828
+ let grepStats = null;
829
+ if (opts.grep !== void 0) {
830
+ const filtered = filterBodyByGrep(content, opts.grep);
831
+ grepStats = { matched: filtered.matched, total: filtered.total };
832
+ grepNote = filtered.matched === 0 ? `
833
+ ${grepFilteredToEmptyNotice(filtered.total, opts.grep, "line", "lines")}` : `
834
+ ${filtered.matched} of ${filtered.total} lines matched`;
835
+ content = filtered.matched === 0 ? "" : filtered.content;
836
+ }
837
+ if (opts.maxLines !== void 0 && opts.maxLines > 0) {
838
+ const lines = content.split("\n");
839
+ if (lines.length > opts.maxLines) {
840
+ const remaining = lines.length - opts.maxLines;
841
+ content = lines.slice(0, opts.maxLines).join("\n");
842
+ if (opts.grep === void 0) lineEnd = Math.min(result.lineEnd, result.lineStart + opts.maxLines - 1);
843
+ truncatedNotice = `
844
+ ... (${countNoun(remaining, "more line")} not shown; pass a larger --max-lines or omit to read full section)`;
845
+ }
846
+ }
847
+ if (opts.json === true) {
848
+ const jsonPayload = {
849
+ ...result,
850
+ content,
851
+ lineEnd,
852
+ ...grepStats !== null ? { grep: opts.grep, matchedLines: grepStats.matched, sectionLines: grepStats.total } : {},
853
+ ...opts.maxLines !== void 0 && opts.maxLines > 0 ? {
854
+ totalLines: result.content.split("\n").length,
855
+ shownLines: content.split("\n").length,
856
+ truncated: truncatedNotice.length > 0
857
+ } : {}
858
+ };
859
+ const text2 = displaySafeJson(jsonPayload);
860
+ if (opts.suppressStat !== true) recordReadStat(kind, fullSourceBytes, text2, heading);
861
+ return { text: text2, code: 0 };
862
+ }
863
+ const redirectNote = result.redirectedFrom !== void 0 ? ` (redirected from: '${result.redirectedFrom}')` : "";
864
+ const text = guardText(
865
+ `# ${result.heading} \u2014 ${filePath}:${result.lineStart}-${lineEnd}${redirectNote}${content.length > 0 ? `
866
+ ${content}` : ""}${truncatedNotice}${grepNote}`,
867
+ "heading"
868
+ );
869
+ if (opts.suppressStat !== true) recordReadStat(kind, fullSourceBytes, text, heading);
870
+ return { text, code: 0 };
871
+ }
872
+ function runSectionMulti(specFilePath, resolvedFilePath, headings, opts) {
873
+ let anyFound = false;
874
+ const jsonOut = {};
875
+ const textBlocks = [];
876
+ const includedSections = [];
877
+ for (const heading of headings) {
878
+ const sectionResult = readSection(resolvedFilePath, heading, readFileText);
879
+ if (sectionResult !== null) {
880
+ const parent = includedSections.find(
881
+ (p) => sectionResult.lineStart >= p.lineStart && sectionResult.lineEnd <= p.lineEnd
882
+ );
883
+ if (parent !== void 0) {
884
+ anyFound = true;
885
+ const notice = `(already included in section '${parent.heading}', lines ${sectionResult.lineStart}-${sectionResult.lineEnd})`;
886
+ if (opts.json === true) {
887
+ jsonOut[heading] = {
888
+ heading: sectionResult.heading,
889
+ subsumedBy: parent.heading,
890
+ lineStart: sectionResult.lineStart,
891
+ lineEnd: sectionResult.lineEnd,
892
+ notice
893
+ };
894
+ continue;
895
+ }
896
+ textBlocks.push(
897
+ `${heading}:
898
+ # ${sectionResult.heading} \u2014 ${specFilePath}:${sectionResult.lineStart}-${sectionResult.lineEnd}
899
+ ${notice}`
900
+ );
901
+ continue;
902
+ }
903
+ includedSections.push({
904
+ heading: sectionResult.heading,
905
+ lineStart: sectionResult.lineStart,
906
+ lineEnd: sectionResult.lineEnd
907
+ });
908
+ }
909
+ const sub = runSection({ ...opts, spec: `${specFilePath}::${heading}`, suppressStat: true });
910
+ if (sub.code === 0) anyFound = true;
911
+ if (opts.json === true) {
912
+ jsonOut[heading] = sub.code === 0 ? JSON.parse(sub.text) : { error: sub.text };
913
+ continue;
914
+ }
915
+ textBlocks.push(`${heading}:
916
+ ${sub.text}`);
917
+ }
918
+ const fullSourceBytes = sumFileSizes([resolvedFilePath]);
919
+ const text = opts.json === true ? displaySafeJson(jsonOut) : textBlocks.join("\n\n");
920
+ if (anyFound) recordReadStat("section_read", fullSourceBytes, text, opts.spec);
921
+ return { text, code: anyFound ? 0 : 1 };
922
+ }
923
+ function runSectionCrossFile(pairs, opts) {
924
+ let anyFound = false;
925
+ const jsonOut = {};
926
+ const textBlocks = [];
927
+ const includedByFile = /* @__PURE__ */ new Map();
928
+ const distinctFiles = new Set(pairs.map((p) => p.file));
929
+ const keyFor = (p) => distinctFiles.size === 1 ? p.symbol : `${p.file}::${p.symbol}`;
930
+ for (const { file, symbol: heading } of pairs) {
931
+ const resolved = resolveAgainstProjectRoot(file, opts.projectRoot);
932
+ const sectionResult = readSection(resolved, heading, readFileText);
933
+ const key = keyFor({ file, symbol: heading });
934
+ const fileIncluded = includedByFile.get(resolved) ?? [];
935
+ if (sectionResult !== null) {
936
+ const parent = fileIncluded.find(
937
+ (p) => sectionResult.lineStart >= p.lineStart && sectionResult.lineEnd <= p.lineEnd
938
+ );
939
+ if (parent !== void 0) {
940
+ anyFound = true;
941
+ const notice = `(already included in section '${parent.heading}', lines ${sectionResult.lineStart}-${sectionResult.lineEnd})`;
942
+ if (opts.json === true) {
943
+ jsonOut[key] = {
944
+ heading: sectionResult.heading,
945
+ subsumedBy: parent.heading,
946
+ lineStart: sectionResult.lineStart,
947
+ lineEnd: sectionResult.lineEnd,
948
+ notice
949
+ };
950
+ continue;
951
+ }
952
+ textBlocks.push(
953
+ `${key}:
954
+ # ${sectionResult.heading} \u2014 ${file}:${sectionResult.lineStart}-${sectionResult.lineEnd}
955
+ ${notice}`
956
+ );
957
+ continue;
958
+ }
959
+ fileIncluded.push({
960
+ heading: sectionResult.heading,
961
+ lineStart: sectionResult.lineStart,
962
+ lineEnd: sectionResult.lineEnd
963
+ });
964
+ includedByFile.set(resolved, fileIncluded);
965
+ }
966
+ const sub = runSection({ ...opts, spec: `${file}::${heading}`, suppressStat: true });
967
+ if (sub.code === 0) anyFound = true;
968
+ if (opts.json === true) {
969
+ jsonOut[key] = sub.code === 0 ? JSON.parse(sub.text) : { error: sub.text };
970
+ continue;
971
+ }
972
+ textBlocks.push(`${key}:
973
+ ${sub.text}`);
974
+ }
975
+ const text = opts.json === true ? displaySafeJson(jsonOut) : textBlocks.join("\n\n");
976
+ if (anyFound) {
977
+ const fullSourceBytes = sumFileSizes(Array.from(distinctFiles, (f) => resolveAgainstProjectRoot(f, opts.projectRoot)));
978
+ recordReadStat("section_read", fullSourceBytes, text, opts.spec);
979
+ }
980
+ return { text, code: anyFound ? 0 : 1 };
981
+ }
982
+
983
+ // src/read_inspect.ts
984
+ init_define_import_meta_env();
985
+ import * as path from "node:path";
986
+
987
+ // src/archive_query.ts
988
+ init_define_import_meta_env();
989
+ var loadFflate = createLazyModuleLoader(
990
+ async () => await import("fflate"),
991
+ "archive reading disabled (fflate unavailable)"
992
+ );
993
+ var ArchiveDependencyMissingError = class extends Error {
994
+ constructor() {
995
+ super("fflate is not installed; run `npm install fflate` to enable zip-list/zip-read");
996
+ this.name = "ArchiveDependencyMissingError";
997
+ }
998
+ };
999
+ async function requireFflate() {
1000
+ const fflate = await loadFflate();
1001
+ if (!fflate) throw new ArchiveDependencyMissingError();
1002
+ return fflate;
1003
+ }
1004
+ async function listZipEntries(data) {
1005
+ const fflate = await requireFflate();
1006
+ const entries = [];
1007
+ fflate.unzipSync(data, {
1008
+ filter: (file) => {
1009
+ entries.push({
1010
+ path: file.name,
1011
+ size: file.originalSize,
1012
+ compressedSize: file.size,
1013
+ isDirectory: file.name.endsWith("/")
1014
+ });
1015
+ return false;
1016
+ }
1017
+ });
1018
+ entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
1019
+ return entries;
1020
+ }
1021
+ function formatZipList(entries) {
1022
+ if (entries.length === 0) return "(no entries found)";
1023
+ return entries.map((e) => `${String(e.size).padStart(10)} ${displaySafeText(e.path)}`).join("\n");
1024
+ }
1025
+ async function extractZipEntry(data, entryPath) {
1026
+ const fflate = await requireFflate();
1027
+ const result = unzipBounded(fflate, data, { limitBytes: MAX_ZIP_OUTPUT_BYTES, shouldExtract: (name) => name === entryPath });
1028
+ return result[entryPath];
1029
+ }
1030
+
1031
+ // src/coverage_query.ts
1032
+ init_define_import_meta_env();
1033
+ function num(v) {
1034
+ return typeof v === "number" && Number.isFinite(v) ? v : 0;
1035
+ }
1036
+ function collapseLineRanges(sortedLines) {
1037
+ const ranges = [];
1038
+ for (const n of sortedLines) {
1039
+ const last = ranges[ranges.length - 1];
1040
+ if (last !== void 0 && n === last.end + 1) {
1041
+ last.end = n;
1042
+ } else {
1043
+ ranges.push({ start: n, end: n });
1044
+ }
1045
+ }
1046
+ return ranges;
1047
+ }
1048
+ function hasGap(f) {
1049
+ return f.uncoveredLineCount > 0 || f.uncoveredFunctions.length > 0 || f.uncoveredBranches.length > 0 || f.functionsHit < f.functionsTotal || f.branchesHit < f.branchesTotal;
1050
+ }
1051
+ function rankAndFilter(files) {
1052
+ const withGaps = files.filter(hasGap);
1053
+ withGaps.sort((a, b) => {
1054
+ const byCount = b.uncoveredLineCount - a.uncoveredLineCount;
1055
+ if (byCount !== 0) return byCount;
1056
+ return a.filePath < b.filePath ? -1 : a.filePath > b.filePath ? 1 : 0;
1057
+ });
1058
+ return withGaps;
1059
+ }
1060
+ function detectCoverageFormat(text) {
1061
+ let parsed;
1062
+ let isJson = true;
1063
+ try {
1064
+ parsed = JSON.parse(text);
1065
+ } catch {
1066
+ isJson = false;
1067
+ }
1068
+ if (isJson) {
1069
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1070
+ throw new Error("not a recognized coverage report (JSON root is not an object of per-file entries)");
1071
+ }
1072
+ const entries = Object.entries(parsed).filter(([k]) => k !== "total");
1073
+ const looksFinal = entries.some(
1074
+ ([, v]) => typeof v === "object" && v !== null && "statementMap" in v && "s" in v
1075
+ );
1076
+ if (looksFinal) return "istanbul-final";
1077
+ const looksSummary = entries.some(
1078
+ ([, v]) => typeof v === "object" && v !== null && "lines" in v && "statements" in v && "functions" in v && "branches" in v
1079
+ );
1080
+ if (looksSummary) return "istanbul-summary";
1081
+ const total = parsed["total"];
1082
+ if (entries.length === 0 && typeof total === "object" && total !== null && "lines" in total && "statements" in total && "functions" in total && "branches" in total) {
1083
+ return "istanbul-summary";
1084
+ }
1085
+ if (entries.length === 0) return "istanbul-final";
1086
+ throw new Error("not a recognized coverage report (JSON does not match Istanbul coverage-final or coverage-summary shape)");
1087
+ }
1088
+ const trimmedStart = text.replace(/^\s+/, "");
1089
+ if (/^(TN:|SF:)/.test(trimmedStart)) return "lcov";
1090
+ throw new Error("could not detect coverage report format (not valid LCOV, and not valid JSON)");
1091
+ }
1092
+ function buildLcovFileGaps(acc) {
1093
+ const linesTotal = acc.daLines.size;
1094
+ const uncoveredLineNums = [...acc.daLines.entries()].filter(([, hits]) => hits === 0).map(([ln]) => ln).sort((a, b) => a - b);
1095
+ const linesHit = linesTotal - uncoveredLineNums.length;
1096
+ const functionsTotal = acc.fnRecords.size;
1097
+ const uncoveredFunctions = [];
1098
+ for (const { name, line } of acc.fnRecords.values()) {
1099
+ if ((acc.fnHits.get(name) ?? 0) === 0) uncoveredFunctions.push({ name, line });
1100
+ }
1101
+ uncoveredFunctions.sort((a, b) => a.line - b.line);
1102
+ const functionsHit = functionsTotal - uncoveredFunctions.length;
1103
+ const branchesTotal = acc.brda.length;
1104
+ const uncoveredBranches = acc.brda.filter((b) => b.hits === null || b.hits === 0).map((b) => ({ line: b.line }));
1105
+ const branchesHit = branchesTotal - uncoveredBranches.length;
1106
+ return {
1107
+ filePath: acc.filePath,
1108
+ linesTotal,
1109
+ linesHit,
1110
+ functionsTotal,
1111
+ functionsHit,
1112
+ branchesTotal,
1113
+ branchesHit,
1114
+ uncoveredLineRanges: collapseLineRanges(uncoveredLineNums),
1115
+ uncoveredLineCount: uncoveredLineNums.length,
1116
+ uncoveredFunctions,
1117
+ uncoveredBranches,
1118
+ summaryOnly: false
1119
+ };
1120
+ }
1121
+ function parseLcov(text) {
1122
+ const files = [];
1123
+ let cur = null;
1124
+ const closeCurrent = () => {
1125
+ if (cur === null) return;
1126
+ files.push(buildLcovFileGaps(cur));
1127
+ cur = null;
1128
+ };
1129
+ for (const rawLine of text.split(/\r?\n/)) {
1130
+ const line = rawLine.trim();
1131
+ if (line === "") continue;
1132
+ if (line.startsWith("SF:")) {
1133
+ closeCurrent();
1134
+ cur = { filePath: line.slice(3).trim(), daLines: /* @__PURE__ */ new Map(), fnRecords: /* @__PURE__ */ new Map(), fnHits: /* @__PURE__ */ new Map(), brda: [] };
1135
+ continue;
1136
+ }
1137
+ if (cur === null) continue;
1138
+ if (line === "end_of_record") {
1139
+ closeCurrent();
1140
+ continue;
1141
+ }
1142
+ if (line.startsWith("DA:")) {
1143
+ const [rawLineNo, rawHits] = line.slice(3).split(",");
1144
+ const lineNo = Number.parseInt(rawLineNo ?? "", 10);
1145
+ const hits = Number.parseInt(rawHits ?? "", 10);
1146
+ if (Number.isFinite(lineNo) && Number.isFinite(hits)) cur.daLines.set(lineNo, hits);
1147
+ continue;
1148
+ }
1149
+ if (line.startsWith("FN:")) {
1150
+ const rest = line.slice(3);
1151
+ const comma = rest.indexOf(",");
1152
+ if (comma !== -1) {
1153
+ const lineNo = Number.parseInt(rest.slice(0, comma), 10);
1154
+ let name = rest.slice(comma + 1);
1155
+ const comma2 = name.indexOf(",");
1156
+ if (comma2 !== -1 && /^\d+$/.test(name.slice(0, comma2))) {
1157
+ name = name.slice(comma2 + 1);
1158
+ }
1159
+ if (Number.isFinite(lineNo)) cur.fnRecords.set(`${name}
1160
+ ${lineNo}`, { name, line: lineNo });
1161
+ }
1162
+ continue;
1163
+ }
1164
+ if (line.startsWith("FNDA:")) {
1165
+ const rest = line.slice(5);
1166
+ const comma = rest.indexOf(",");
1167
+ if (comma !== -1) {
1168
+ const hits = Number.parseInt(rest.slice(0, comma), 10);
1169
+ const name = rest.slice(comma + 1);
1170
+ if (Number.isFinite(hits)) cur.fnHits.set(name, (cur.fnHits.get(name) ?? 0) + hits);
1171
+ }
1172
+ continue;
1173
+ }
1174
+ if (line.startsWith("BRDA:")) {
1175
+ const parts = line.slice(5).split(",");
1176
+ const lineNo = Number.parseInt(parts[0] ?? "", 10);
1177
+ const rawHits = parts[3];
1178
+ if (Number.isFinite(lineNo)) {
1179
+ const hits = rawHits === "-" ? null : Number.parseInt(rawHits ?? "", 10);
1180
+ cur.brda.push({ line: lineNo, hits: hits !== null && Number.isFinite(hits) ? hits : null });
1181
+ }
1182
+ continue;
1183
+ }
1184
+ }
1185
+ closeCurrent();
1186
+ return { format: "lcov", files: rankAndFilter(files) };
1187
+ }
1188
+ function parseIstanbulFinal(data) {
1189
+ const files = [];
1190
+ for (const [filePath, entryRaw] of Object.entries(data)) {
1191
+ if (typeof entryRaw !== "object" || entryRaw === null) continue;
1192
+ const entry = entryRaw;
1193
+ const statementMap = entry["statementMap"] ?? {};
1194
+ const s = entry["s"] ?? {};
1195
+ const fnMap = entry["fnMap"] ?? {};
1196
+ const f = entry["f"] ?? {};
1197
+ const branchMap = entry["branchMap"] ?? {};
1198
+ const b = entry["b"] ?? {};
1199
+ const lineCovered = /* @__PURE__ */ new Map();
1200
+ for (const [id, hits] of Object.entries(s)) {
1201
+ const stmt = statementMap[id];
1202
+ if (stmt === void 0 || stmt.start === void 0) continue;
1203
+ const ln = stmt.start.line;
1204
+ lineCovered.set(ln, (lineCovered.get(ln) ?? false) || num(hits) > 0);
1205
+ }
1206
+ const linesTotal = lineCovered.size;
1207
+ const uncoveredLineNums = [...lineCovered.entries()].filter(([, covered]) => !covered).map(([ln]) => ln).sort((a, b2) => a - b2);
1208
+ const linesHit = linesTotal - uncoveredLineNums.length;
1209
+ let functionsTotal = 0;
1210
+ const uncoveredFunctions = [];
1211
+ for (const [id, fnInfo] of Object.entries(fnMap)) {
1212
+ functionsTotal++;
1213
+ if (num(f[id]) === 0) {
1214
+ const line = (fnInfo.decl?.start !== void 0 ? fnInfo.decl.start.line : void 0) ?? (fnInfo.loc?.start !== void 0 ? fnInfo.loc.start.line : void 0) ?? 0;
1215
+ uncoveredFunctions.push({ name: fnInfo.name !== void 0 && fnInfo.name !== "" ? fnInfo.name : "(anonymous)", line });
1216
+ }
1217
+ }
1218
+ uncoveredFunctions.sort((a, b2) => a.line - b2.line);
1219
+ const functionsHit = functionsTotal - uncoveredFunctions.length;
1220
+ let branchesTotal = 0;
1221
+ const uncoveredBranches = [];
1222
+ for (const [id, hitsArrRaw] of Object.entries(b)) {
1223
+ const branchInfo = branchMap[id];
1224
+ const hitsArr = Array.isArray(hitsArrRaw) ? hitsArrRaw : [];
1225
+ branchesTotal += hitsArr.length;
1226
+ hitsArr.forEach((hits, idx) => {
1227
+ if (num(hits) === 0) {
1228
+ const locStart = branchInfo?.locations?.[idx]?.start;
1229
+ const branchStart = branchInfo?.loc?.start;
1230
+ const line = (locStart !== void 0 ? locStart.line : void 0) ?? (branchStart !== void 0 ? branchStart.line : void 0) ?? 0;
1231
+ uncoveredBranches.push({ line });
1232
+ }
1233
+ });
1234
+ }
1235
+ const branchesHit = branchesTotal - uncoveredBranches.length;
1236
+ files.push({
1237
+ filePath,
1238
+ linesTotal,
1239
+ linesHit,
1240
+ functionsTotal,
1241
+ functionsHit,
1242
+ branchesTotal,
1243
+ branchesHit,
1244
+ uncoveredLineRanges: collapseLineRanges(uncoveredLineNums),
1245
+ uncoveredLineCount: uncoveredLineNums.length,
1246
+ uncoveredFunctions,
1247
+ uncoveredBranches,
1248
+ summaryOnly: false
1249
+ });
1250
+ }
1251
+ return { format: "istanbul-final", files: rankAndFilter(files) };
1252
+ }
1253
+ function parseIstanbulSummary(data) {
1254
+ const files = [];
1255
+ for (const [filePath, entryRaw] of Object.entries(data)) {
1256
+ if (filePath === "total") continue;
1257
+ if (typeof entryRaw !== "object" || entryRaw === null) continue;
1258
+ const entry = entryRaw;
1259
+ const lines = entry["lines"] ?? {};
1260
+ const functions = entry["functions"] ?? {};
1261
+ const branches = entry["branches"] ?? {};
1262
+ const linesTotal = num(lines.total);
1263
+ const linesHit = num(lines.covered);
1264
+ const functionsTotal = num(functions.total);
1265
+ const functionsHit = num(functions.covered);
1266
+ const branchesTotal = num(branches.total);
1267
+ const branchesHit = num(branches.covered);
1268
+ files.push({
1269
+ filePath,
1270
+ linesTotal,
1271
+ linesHit,
1272
+ functionsTotal,
1273
+ functionsHit,
1274
+ branchesTotal,
1275
+ branchesHit,
1276
+ uncoveredLineRanges: [],
1277
+ uncoveredLineCount: Math.max(0, linesTotal - linesHit),
1278
+ uncoveredFunctions: [],
1279
+ uncoveredBranches: [],
1280
+ summaryOnly: true
1281
+ });
1282
+ }
1283
+ return { format: "istanbul-summary", files: rankAndFilter(files) };
1284
+ }
1285
+ function parseCoverageReport(text) {
1286
+ text = stripBom(text);
1287
+ const format = detectCoverageFormat(text);
1288
+ if (format === "lcov") return parseLcov(text);
1289
+ const data = JSON.parse(text);
1290
+ return format === "istanbul-final" ? parseIstanbulFinal(data) : parseIstanbulSummary(data);
1291
+ }
1292
+ function endsWithPathBoundaryLocal(full, suffix) {
1293
+ if (!full.endsWith(suffix)) return false;
1294
+ if (full.length === suffix.length) return true;
1295
+ return full[full.length - suffix.length - 1] === "/";
1296
+ }
1297
+ function filterCoverageGapsByFile(report, filePathQuery) {
1298
+ const query = foldPath(normalizePath(filePathQuery));
1299
+ const files = report.files.filter((f) => {
1300
+ const candidate = foldPath(normalizePath(f.filePath));
1301
+ return candidate === query || endsWithPathBoundaryLocal(candidate, query) || endsWithPathBoundaryLocal(query, candidate);
1302
+ });
1303
+ return { format: report.format, files };
1304
+ }
1305
+ function formatRanges(ranges) {
1306
+ return ranges.map((r) => r.start === r.end ? String(r.start) : `${r.start}-${r.end}`).join(", ");
1307
+ }
1308
+ function formatFileGaps(f) {
1309
+ const lines = [displaySafeText(f.filePath)];
1310
+ lines.push(` lines: ${f.linesHit}/${f.linesTotal} functions: ${f.functionsHit}/${f.functionsTotal} branches: ${f.branchesHit}/${f.branchesTotal}`);
1311
+ if (f.summaryOnly) {
1312
+ lines.push(" (summary-only report -- no per-line/function/branch detail available)");
1313
+ return lines.join("\n");
1314
+ }
1315
+ if (f.uncoveredLineRanges.length > 0) {
1316
+ lines.push(` uncovered lines: ${formatRanges(f.uncoveredLineRanges)}`);
1317
+ }
1318
+ if (f.uncoveredFunctions.length > 0) {
1319
+ lines.push(" uncovered functions:");
1320
+ for (const fn of f.uncoveredFunctions) lines.push(` ${displaySafeText(fn.name)} (line ${fn.line})`);
1321
+ }
1322
+ if (f.uncoveredBranches.length > 0) {
1323
+ const branchLines = [...new Set(f.uncoveredBranches.map((br) => br.line))].sort((a, b) => a - b);
1324
+ lines.push(` uncovered branches at line${branchLines.length === 1 ? "" : "s"}: ${branchLines.join(", ")}`);
1325
+ }
1326
+ return lines.join("\n");
1327
+ }
1328
+ function formatCoverageGaps(report) {
1329
+ if (report.files.length === 0) return "No coverage gaps found -- 100% coverage.";
1330
+ return report.files.map(formatFileGaps).join("\n\n");
1331
+ }
1332
+
1333
+ // src/read_inspect.ts
1334
+ function archiveReadFailure(err, file) {
1335
+ if (err instanceof ArchiveDependencyMissingError || err instanceof ZipOutputTooLargeError) return err.message;
1336
+ return `Failed to read archive (not a valid zip-format file): ${file}`;
1337
+ }
1338
+ async function runZipList(opts) {
1339
+ let data;
1340
+ try {
1341
+ data = readFileBytes(opts.file);
1342
+ } catch (err) {
1343
+ if (err instanceof ZipInputTooLargeError) {
1344
+ emitErr(err.message);
1345
+ return 1;
1346
+ }
1347
+ throw err;
1348
+ }
1349
+ if (data === null) {
1350
+ emitErr(`Could not read: ${opts.file}`);
1351
+ return 1;
1352
+ }
1353
+ let entries;
1354
+ try {
1355
+ entries = await listZipEntries(data);
1356
+ } catch (err) {
1357
+ emitErr(archiveReadFailure(err, opts.file));
1358
+ return 1;
1359
+ }
1360
+ const fullSourceBytes = sumFileSizes([opts.file]);
1361
+ if (opts.json === true) {
1362
+ const jsonText = displaySafeJson(entries, 0);
1363
+ emit(jsonText);
1364
+ recordReadStat("zip_list", fullSourceBytes, jsonText, opts.file);
1365
+ } else {
1366
+ const text = formatZipList(entries);
1367
+ emitGuarded(text, "zip-list");
1368
+ recordReadStat("zip_list", fullSourceBytes, text, opts.file);
1369
+ }
1370
+ return 0;
1371
+ }
1372
+ async function runZipRead(opts) {
1373
+ let data;
1374
+ try {
1375
+ data = readFileBytes(opts.file);
1376
+ } catch (err) {
1377
+ if (err instanceof ZipInputTooLargeError) {
1378
+ emitErr(err.message);
1379
+ return 1;
1380
+ }
1381
+ throw err;
1382
+ }
1383
+ if (data === null) {
1384
+ emitErr(`Could not read: ${opts.file}`);
1385
+ return 1;
1386
+ }
1387
+ let entries;
1388
+ let content;
1389
+ try {
1390
+ entries = await listZipEntries(data);
1391
+ content = await extractZipEntry(data, opts.entry);
1392
+ } catch (err) {
1393
+ emitErr(archiveReadFailure(err, opts.file));
1394
+ return 1;
1395
+ }
1396
+ const matchedEntry = entries.find((e) => e.path === opts.entry);
1397
+ if (matchedEntry?.isDirectory === true) {
1398
+ emitErr(`Entry '${opts.entry}' is a directory, not a file, in '${opts.file}'`);
1399
+ return 1;
1400
+ }
1401
+ if (content === void 0) {
1402
+ const messages = [`Entry '${opts.entry}' not found in '${opts.file}'`];
1403
+ const closes = rankSimilarNames(entries.map((e) => e.path), opts.entry);
1404
+ if (closes.length > 0) messages.push(didYouMean(closes));
1405
+ else if (entries.length > 0) messages.push(`Try: token-goat zip-list ${opts.file}`);
1406
+ emitErr(messages.join("\n"));
1407
+ return 1;
1408
+ }
1409
+ const buf = Buffer.from(content);
1410
+ const text = isValidUtf8(buf) ? buf.toString("utf-8") : "[binary content elided by token-goat]";
1411
+ const fullSourceBytes = sumFileSizes([opts.file]);
1412
+ if (opts.json === true) {
1413
+ const jsonText = displaySafeJson({ path: opts.entry, text }, 0);
1414
+ emit(jsonText);
1415
+ recordReadStat("zip_read", fullSourceBytes, jsonText, opts.entry);
1416
+ } else {
1417
+ emitGuarded(text, "zip-read");
1418
+ recordReadStat("zip_read", fullSourceBytes, text, opts.entry);
1419
+ }
1420
+ return 0;
1421
+ }
1422
+ function runSqliteSchema(opts) {
1423
+ try {
1424
+ const schema = getSqliteSchema(opts.file);
1425
+ const fullSourceBytes = sumFileSizes([opts.file]);
1426
+ if (opts.json === true) {
1427
+ const jsonText = displaySafeJson(schema, 0);
1428
+ emit(jsonText);
1429
+ recordReadStat("sqlite_schema", fullSourceBytes, jsonText, opts.file);
1430
+ } else {
1431
+ const text = formatSqliteSchema(schema);
1432
+ emit(text);
1433
+ recordReadStat("sqlite_schema", fullSourceBytes, text, opts.file);
1434
+ }
1435
+ return 0;
1436
+ } catch (e) {
1437
+ emitErr(extractErrorMessage(e));
1438
+ return 1;
1439
+ }
1440
+ }
1441
+ function runSqliteTables(opts) {
1442
+ try {
1443
+ const tables = getSqliteTables(opts.file);
1444
+ const fullSourceBytes = sumFileSizes([opts.file]);
1445
+ if (opts.json === true) {
1446
+ const jsonText = displaySafeJson(tables, 0);
1447
+ emit(jsonText);
1448
+ recordReadStat("sqlite_tables", fullSourceBytes, jsonText, opts.file);
1449
+ } else {
1450
+ const text = formatSqliteTables(tables);
1451
+ emit(text);
1452
+ recordReadStat("sqlite_tables", fullSourceBytes, text, opts.file);
1453
+ }
1454
+ return 0;
1455
+ } catch (e) {
1456
+ emitErr(extractErrorMessage(e));
1457
+ return 1;
1458
+ }
1459
+ }
1460
+ function runSqliteQuery(opts) {
1461
+ let head;
1462
+ try {
1463
+ head = opts.head !== void 0 ? requireNonNegativeStrictInt("--head", opts.head) : void 0;
1464
+ } catch (e) {
1465
+ emitErr(extractErrorMessage(e));
1466
+ return 1;
1467
+ }
1468
+ try {
1469
+ const result = runReadOnlySqliteQuery(opts.file, opts.sql);
1470
+ const totalCount = result.rows.length;
1471
+ const headTruncated = head !== void 0 && result.rows.length > head;
1472
+ const rows = head !== void 0 ? result.rows.slice(0, head) : result.rows;
1473
+ if (opts.json === true) {
1474
+ const capped = guardJsonRows(rows);
1475
+ const jsonText = displaySafeJson({
1476
+ columns: result.columns,
1477
+ items: capped.items,
1478
+ truncated: capped.truncated || headTruncated || result.rowCapped,
1479
+ totalCount,
1480
+ rowCapped: result.rowCapped
1481
+ }, 0);
1482
+ emit(jsonText);
1483
+ const uncappedFull = guardJsonRows(result.rows);
1484
+ const baselineJsonText = displaySafeJson({
1485
+ columns: result.columns,
1486
+ items: uncappedFull.items,
1487
+ truncated: uncappedFull.truncated || result.rowCapped,
1488
+ totalCount,
1489
+ rowCapped: result.rowCapped
1490
+ }, 0);
1491
+ recordReadStat("sqlite_query", Buffer.byteLength(baselineJsonText, "utf8"), jsonText, opts.file);
1492
+ } else {
1493
+ const text = formatSqliteQueryTable({ ...result, rows }, { headTruncated });
1494
+ emit(text);
1495
+ const baselineText = formatSqliteQueryTable({ ...result, rows: result.rows }, { headTruncated: false });
1496
+ recordReadStat("sqlite_query", Buffer.byteLength(baselineText, "utf8"), text, opts.file);
1497
+ }
1498
+ return 0;
1499
+ } catch (e) {
1500
+ emitErr(extractErrorMessage(e));
1501
+ return 1;
1502
+ }
1503
+ }
1504
+ function runCoverageReportGaps(opts) {
1505
+ const text = readFileText(opts.file);
1506
+ if (text === null) {
1507
+ emitErr(`Could not read: ${opts.file}`);
1508
+ return 1;
1509
+ }
1510
+ let report;
1511
+ try {
1512
+ report = parseCoverageReport(text);
1513
+ } catch (e) {
1514
+ emitErr(`Failed to parse coverage report (not valid LCOV or Istanbul JSON): ${opts.file}
1515
+ ${extractErrorMessage(e)}`);
1516
+ return 1;
1517
+ }
1518
+ const scoped = opts.fileFilter !== void 0 ? filterCoverageGapsByFile(report, opts.fileFilter) : report;
1519
+ const fullSourceBytes = sumFileSizes([opts.file]);
1520
+ if (opts.json === true) {
1521
+ const jsonText = displaySafeJson(scoped, 0);
1522
+ emit(jsonText);
1523
+ recordReadStat("coverage_report_gaps", fullSourceBytes, jsonText, opts.file);
1524
+ } else {
1525
+ const text2 = formatCoverageGaps(scoped);
1526
+ emitGuarded(text2, "coverage-report-gaps");
1527
+ recordReadStat("coverage_report_gaps", fullSourceBytes, text2, opts.file);
1528
+ }
1529
+ return 0;
1530
+ }
1531
+ function stripInlineComment(s) {
1532
+ let inQuote = null;
1533
+ for (let i = 0; i < s.length; i++) {
1534
+ const ch = s[i];
1535
+ if (inQuote !== null) {
1536
+ if (ch === "\\" && i + 1 < s.length) {
1537
+ i++;
1538
+ continue;
1539
+ }
1540
+ if (ch === inQuote) inQuote = null;
1541
+ continue;
1542
+ }
1543
+ if (ch === '"' || ch === "'") {
1544
+ inQuote = ch;
1545
+ continue;
1546
+ }
1547
+ if (ch === "#" || ch === ";") {
1548
+ return s.slice(0, i);
1549
+ }
1550
+ }
1551
+ return s;
1552
+ }
1553
+ function stripPairedQuotes(s) {
1554
+ if (s.length >= 2) {
1555
+ const first = s[0];
1556
+ const last = s[s.length - 1];
1557
+ if ((first === '"' || first === "'") && first === last) {
1558
+ return s.slice(1, -1);
1559
+ }
1560
+ }
1561
+ return s;
1562
+ }
1563
+ function lookupYaml(lines, key) {
1564
+ const parts = key.split(".");
1565
+ let depth = 0;
1566
+ let parentIndent = -1;
1567
+ let childIndent = -1;
1568
+ for (const line of lines) {
1569
+ const trimmed = line.trim();
1570
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
1571
+ const indent = line.length - line.trimStart().length;
1572
+ if (depth > 0 && indent <= parentIndent) return null;
1573
+ if (childIndent !== -1 && indent !== childIndent) continue;
1574
+ const colon = trimmed.indexOf(":");
1575
+ if (colon < 0) continue;
1576
+ if (childIndent === -1) childIndent = indent;
1577
+ const k = trimmed.slice(0, colon).trim();
1578
+ if (k !== parts[depth]) continue;
1579
+ if (depth === parts.length - 1) {
1580
+ return stripPairedQuotes(trimmed.slice(colon + 1).trim());
1581
+ }
1582
+ parentIndent = indent;
1583
+ childIndent = -1;
1584
+ depth++;
1585
+ }
1586
+ return null;
1587
+ }
1588
+ function extractFrontmatter(lines) {
1589
+ if (lines[0]?.trim() !== "---") return null;
1590
+ let j = 1;
1591
+ while (j < lines.length && lines[j]?.trim() !== "---") {
1592
+ j++;
1593
+ }
1594
+ if (j >= lines.length) return null;
1595
+ return lines.slice(1, j);
1596
+ }
1597
+ function runConfigGet(opts) {
1598
+ const text = readFileText(opts.file);
1599
+ if (text === null) {
1600
+ emitErr(`Could not read: ${opts.file}`);
1601
+ return 1;
1602
+ }
1603
+ const frontmatterLines = extractFrontmatter(text.split(/\r?\n/));
1604
+ if (frontmatterLines !== null) {
1605
+ const value = lookupYaml(frontmatterLines, opts.key);
1606
+ if (value === null) {
1607
+ emitErr(`Key '${opts.key}' not found in ${opts.file}`);
1608
+ return 1;
1609
+ }
1610
+ emit(value);
1611
+ return 0;
1612
+ }
1613
+ const ext = path.extname(opts.file).toLowerCase();
1614
+ if (ext === ".json" || ext === ".jsonc") {
1615
+ try {
1616
+ let obj = parseJsonOrJsonc(text);
1617
+ for (const part of opts.key.split(".")) {
1618
+ if (typeof obj !== "object" || obj === null) {
1619
+ emitErr(`Key '${opts.key}' not found in ${opts.file}`);
1620
+ return 1;
1621
+ }
1622
+ obj = obj[part];
1623
+ if (obj === void 0) {
1624
+ emitErr(`Key '${opts.key}' not found in ${opts.file}`);
1625
+ return 1;
1626
+ }
1627
+ }
1628
+ emit(displaySafeJson(obj, 0));
1629
+ return 0;
1630
+ } catch {
1631
+ emitErr(`Failed to parse JSON: ${opts.file}`);
1632
+ return 1;
1633
+ }
1634
+ }
1635
+ if (ext === ".yaml" || ext === ".yml") {
1636
+ const value = lookupYaml(text.split(/\r?\n/), opts.key);
1637
+ if (value === null) {
1638
+ emitErr(`Key '${opts.key}' not found in ${opts.file}`);
1639
+ return 1;
1640
+ }
1641
+ emit(value);
1642
+ return 0;
1643
+ }
1644
+ if (ext === ".toml") {
1645
+ const toml = lookupToml(text, opts.key);
1646
+ if (toml !== void 0) {
1647
+ if (toml === null) {
1648
+ emitErr(`Key '${opts.key}' not found in ${opts.file}`);
1649
+ return 1;
1650
+ }
1651
+ emit(toml);
1652
+ return 0;
1653
+ }
1654
+ }
1655
+ const flavour = flatKeyFlavour(opts.file);
1656
+ const lines = text.split("\n");
1657
+ const keyParts = opts.key.split(".");
1658
+ const attempts = [{ section: "", leaf: opts.key }];
1659
+ if (keyParts.length > 1) attempts.push({ section: keyParts.slice(0, -1).join("."), leaf: keyParts.at(-1) ?? opts.key });
1660
+ for (const attempt of attempts) {
1661
+ const value = lookupFlatKey(lines, attempt.section, attempt.leaf, flavour);
1662
+ if (value !== null) {
1663
+ if (ext === ".toml" && isTomlFragment(value)) {
1664
+ emitErr(`Key '${opts.key}' in ${opts.file} spans several lines and the file is not valid TOML, so it cannot be read whole`);
1665
+ return 1;
1666
+ }
1667
+ emit(value);
1668
+ return 0;
1669
+ }
1670
+ }
1671
+ emitErr(`Key '${opts.key}' not found in ${opts.file}`);
1672
+ return 1;
1673
+ }
1674
+ function isTomlFragment(v) {
1675
+ if (v === '"' || v === "'" || v.startsWith('"""') || v.startsWith("'''")) return true;
1676
+ return v.startsWith("[") && !v.endsWith("]") || v.startsWith("{") && !v.endsWith("}");
1677
+ }
1678
+ var TOML_BARE_KEY = /^[A-Za-z0-9_-]+$/;
1679
+ function splitTomlKeyPath(key) {
1680
+ const zero = tomlProbePath(key, 0);
1681
+ const one = tomlProbePath(key, 1);
1682
+ if (zero === null || one === null || JSON.stringify(zero) !== JSON.stringify(one)) return key.split(".").map((name) => ({ name, joinable: true }));
1683
+ return zero.map((name) => ({ name, joinable: TOML_BARE_KEY.test(name) }));
1684
+ }
1685
+ function tomlProbePath(key, value) {
1686
+ const parts = [];
1687
+ try {
1688
+ let node = parse(`${key} = ${value}`);
1689
+ while (typeof node === "object" && node !== null && !Array.isArray(node)) {
1690
+ const keys = Object.keys(node);
1691
+ if (keys.length !== 1) return null;
1692
+ const k = keys[0];
1693
+ parts.push(k);
1694
+ node = node[k];
1695
+ }
1696
+ return node === value && parts.length > 0 ? parts : null;
1697
+ } catch {
1698
+ return null;
1699
+ }
1700
+ }
1701
+ function resolveTomlKey(node, parts) {
1702
+ if (parts.length === 0) return node;
1703
+ if (typeof node !== "object" || node === null || Array.isArray(node) || node instanceof Date) return void 0;
1704
+ const table = node;
1705
+ for (let n = 1; n <= parts.length; n++) {
1706
+ if (n > 1 && !(parts[0].joinable && parts[n - 1].joinable)) break;
1707
+ const head = parts.slice(0, n).map((p) => p.name).join(".");
1708
+ if (!Object.hasOwn(table, head)) continue;
1709
+ const found = resolveTomlKey(table[head], parts.slice(n));
1710
+ if (found !== void 0) return found;
1711
+ }
1712
+ return void 0;
1713
+ }
1714
+ function lookupToml(text, key) {
1715
+ let doc;
1716
+ try {
1717
+ doc = parse(text);
1718
+ } catch {
1719
+ return void 0;
1720
+ }
1721
+ const found = resolveTomlKey(doc, splitTomlKeyPath(key));
1722
+ if (found === void 0) return null;
1723
+ if (typeof found === "string") return found;
1724
+ if (typeof found === "object" && found !== null && !(found instanceof Date)) {
1725
+ return displaySafeJson(JSON.parse(JSON.stringify(found, (_k, x) => typeof x === "bigint" ? x.toString() : x)), 0);
1726
+ }
1727
+ return String(found);
1728
+ }
1729
+ function flatKeyFlavour(file) {
1730
+ const base = (file.split(/[\\/]/).pop() ?? "").toLowerCase();
1731
+ if (base.endsWith(".properties")) return "properties";
1732
+ if (/^\.env(?:\.|$)/.test(base) || base.endsWith(".env")) return "env";
1733
+ return "ini";
1734
+ }
1735
+ function lookupFlatKey(lines, section, leaf, flavour) {
1736
+ const prefix = flavour === "env" ? "(?:export[ \\t]+)?" : "";
1737
+ const separator = flavour === "properties" ? "(?:[ \\t]*[=:][ \\t]*|[ \\t]+)" : "\\s*=\\s*";
1738
+ const lineRe = new RegExp(`^${prefix}${escapeRegExp(leaf)}${separator}(.*)$`);
1739
+ let currentSection = "";
1740
+ for (const line of lines) {
1741
+ const trimmed = line.trim();
1742
+ const headerMatch = flavour === "properties" ? null : /^\[([^\]\r\n]+)\]\s*(?:[;#].*)?$/.exec(trimmed);
1743
+ if (headerMatch) {
1744
+ currentSection = (headerMatch[1] ?? "").trim();
1745
+ continue;
1746
+ }
1747
+ if (currentSection !== section) continue;
1748
+ const m = lineRe.exec(trimmed);
1749
+ if (m === null) continue;
1750
+ const raw = m[1] ?? "";
1751
+ return stripPairedQuotes((flavour === "properties" ? raw : stripInlineComment(raw)).trim());
1752
+ }
1753
+ return null;
1754
+ }
1755
+ function runPerFileEmitting(files, label, run) {
1756
+ let anyOk = false;
1757
+ files.forEach((file, i) => {
1758
+ if (i > 0) emit("");
1759
+ emit(`# ${label}: ${file}`);
1760
+ if (run(file) === 0) anyOk = true;
1761
+ });
1762
+ return anyOk ? 0 : 1;
1763
+ }
1764
+ function runExports(opts) {
1765
+ const multiFiles = opts.file.split(",").map((f) => f.trim()).filter(Boolean);
1766
+ if (multiFiles.length > 1) return runPerFileEmitting(multiFiles, "Exports", (file) => runExports({ ...opts, file }));
1767
+ const confined = fileConfinementRefusal("This file", opts.file, opts.projectRoot);
1768
+ if (confined !== null) {
1769
+ emitErr(confined);
1770
+ return 1;
1771
+ }
1772
+ const diskPath = resolveAgainstProjectRoot(opts.file, opts.projectRoot);
1773
+ const symbols = querySymbols({ filePath: resolveSpecPath(diskPath), limit: -1 });
1774
+ const kindOf = (name) => symbols.find((s) => s.name === name)?.kind ?? "export";
1775
+ const locOf = (name) => {
1776
+ const s = symbols.find((sym) => sym.name === name);
1777
+ return s === void 0 ? null : { lineStart: s.lineStart, lineEnd: s.lineEnd };
1778
+ };
1779
+ const names = [];
1780
+ for (const s of symbols) {
1781
+ if (/^(?:export|pub\b|public\b)/.test(s.body.trimStart()) && !names.includes(s.name)) {
1782
+ names.push(s.name);
1783
+ }
1784
+ }
1785
+ const ext = path.extname(opts.file).toLowerCase();
1786
+ const text = readFileText(diskPath);
1787
+ if (text === null && symbols.length === 0) {
1788
+ emitErr(`Could not read: ${opts.file}`);
1789
+ return 1;
1790
+ }
1791
+ if (text !== null) {
1792
+ if (ext === ".go") {
1793
+ for (const s of symbols) if (/^[A-Z]/.test(s.name) && !names.includes(s.name)) names.push(s.name);
1794
+ }
1795
+ for (const n of extractExportNames(text, ext)) if (!names.includes(n)) names.push(n);
1796
+ }
1797
+ if (names.length === 0) {
1798
+ emit(`No exported symbols found in '${displaySafeText(opts.file)}'`);
1799
+ return 0;
1800
+ }
1801
+ const preFilterCount = names.length;
1802
+ const matchesGrep = opts.grep !== void 0 ? compileGrepMatcher(opts.grep) : void 0;
1803
+ const filteredNames = matchesGrep !== void 0 ? names.filter((n) => matchesGrep(n)) : names;
1804
+ const fullSourceBytes = sumFileSizes([diskPath]);
1805
+ if (filteredNames.length === 0) {
1806
+ if (opts.json === true) {
1807
+ const jsonText = displaySafeJson([]);
1808
+ emit(jsonText);
1809
+ recordReadStat("exports", fullSourceBytes, jsonText, opts.file);
1810
+ return 0;
1811
+ }
1812
+ const textOut = grepFilteredToEmptyNotice(preFilterCount, opts.grep ?? "", "exported symbol", "exported symbols");
1813
+ emit(textOut);
1814
+ recordReadStat("exports", fullSourceBytes, textOut, opts.file);
1815
+ return 0;
1816
+ }
1817
+ if (opts.json === true) {
1818
+ const jsonText = displaySafeJson(
1819
+ filteredNames.map((n) => {
1820
+ const loc = locOf(n);
1821
+ return { name: n, kind: kindOf(n), lineStart: loc?.lineStart ?? null, lineEnd: loc?.lineEnd ?? null };
1822
+ })
1823
+ );
1824
+ emit(jsonText);
1825
+ recordReadStat("exports", fullSourceBytes, jsonText, opts.file);
1826
+ return 0;
1827
+ }
1828
+ const outLines = filteredNames.map((n) => {
1829
+ const loc = locOf(n);
1830
+ const locSuffix = loc === null ? "" : ` (${loc.lineStart}-${loc.lineEnd})`;
1831
+ return `${kindOf(n).padEnd(10)} ${n}${locSuffix}`;
1832
+ });
1833
+ for (const line of outLines) {
1834
+ emit(line);
1835
+ }
1836
+ recordReadStat("exports", fullSourceBytes, outLines.join("\n"), opts.file);
1837
+ return 0;
1838
+ }
1839
+ function runImports(opts) {
1840
+ const multiFiles = opts.file.split(",").map((f) => f.trim()).filter(Boolean);
1841
+ if (multiFiles.length > 1) return runPerFileEmitting(multiFiles, "Imports", (file) => runImports({ ...opts, file }));
1842
+ const diskPath = resolveAgainstProjectRoot(opts.file, opts.projectRoot);
1843
+ const text = readFileText(diskPath);
1844
+ if (text === null) {
1845
+ emitErr(`Could not read: ${opts.file}`);
1846
+ return 1;
1847
+ }
1848
+ const imports = extractImports(text, importsExtensionFor(opts.file));
1849
+ if (imports.length === 0) {
1850
+ emit(`No imports found in '${displaySafeText(opts.file)}'`);
1851
+ return 0;
1852
+ }
1853
+ const preFilterCount = imports.length;
1854
+ const matchesGrep = opts.grep !== void 0 ? compileGrepMatcher(opts.grep) : void 0;
1855
+ const filteredImports = matchesGrep !== void 0 ? imports.filter((i) => matchesGrep(i)) : imports;
1856
+ const fullSourceBytes = sumFileSizes([diskPath]);
1857
+ if (filteredImports.length === 0) {
1858
+ if (opts.json === true) {
1859
+ const jsonText = displaySafeJson({ items: [], truncated: false, totalCount: 0 });
1860
+ emit(jsonText);
1861
+ recordReadStat("imports", fullSourceBytes, jsonText, opts.file);
1862
+ return 0;
1863
+ }
1864
+ const text2 = grepFilteredToEmptyNotice(preFilterCount, opts.grep ?? "", "import", "imports");
1865
+ emit(text2);
1866
+ recordReadStat("imports", fullSourceBytes, text2, opts.file);
1867
+ return 0;
1868
+ }
1869
+ if (opts.json === true) {
1870
+ const capped = guardJsonRows(filteredImports);
1871
+ const jsonText = displaySafeJson({ items: capped.items, truncated: capped.truncated, totalCount: capped.totalCount });
1872
+ emit(jsonText);
1873
+ recordReadStat("imports", fullSourceBytes, jsonText, opts.file);
1874
+ return 0;
1875
+ }
1876
+ const outLines = filteredImports.map((imp) => `import ${imp}`);
1877
+ for (const line of outLines) {
1878
+ emit(line);
1879
+ }
1880
+ recordReadStat("imports", fullSourceBytes, outLines.join("\n"), opts.file);
1881
+ return 0;
1882
+ }
1883
+ function extractTranscriptText(jsonl) {
1884
+ const collected = [];
1885
+ for (const rawLine of jsonl.split(/\r?\n/)) {
1886
+ const line = rawLine.trim();
1887
+ if (line.length === 0) continue;
1888
+ let obj;
1889
+ try {
1890
+ obj = JSON.parse(line);
1891
+ } catch {
1892
+ continue;
1893
+ }
1894
+ if (typeof obj !== "object" || obj === null) continue;
1895
+ const rec = obj;
1896
+ if (rec["type"] !== "assistant") continue;
1897
+ const msg = rec["message"];
1898
+ if (typeof msg !== "object" || msg === null) continue;
1899
+ const content = msg["content"];
1900
+ if (typeof content === "string") {
1901
+ if (content.length > 0) collected.push(content);
1902
+ continue;
1903
+ }
1904
+ if (!Array.isArray(content)) continue;
1905
+ for (const block of content) {
1906
+ if (typeof block !== "object" || block === null) continue;
1907
+ const b = block;
1908
+ if (b["type"] === "text" && typeof b["text"] === "string" && b["text"].length > 0) {
1909
+ collected.push(b["text"]);
1910
+ }
1911
+ }
1912
+ }
1913
+ return collected.join("\n");
1914
+ }
1915
+ function runNoteGet(opts) {
1916
+ const resolvedPath = resolveSpecPath(opts.file, opts.projectRoot ?? process.cwd());
1917
+ healStaleIndex(resolvedPath);
1918
+ const symbol = opts.symbol ?? WHOLE_FILE_NOTE_SYMBOL;
1919
+ const note = getNote(resolvedPath, symbol);
1920
+ if (note === null) {
1921
+ const where = opts.symbol !== void 0 ? ` for symbol '${opts.symbol}'` : " (whole-file note)";
1922
+ return { text: `No note found for '${opts.file}'${where}`, code: 1 };
1923
+ }
1924
+ const stale = isNoteStale(note);
1925
+ if (opts.json === true) {
1926
+ const payload = {
1927
+ filePath: note.filePath,
1928
+ symbol: note.symbol === WHOLE_FILE_NOTE_SYMBOL ? null : note.symbol,
1929
+ content: note.content,
1930
+ stale,
1931
+ createdAt: note.createdAt,
1932
+ updatedAt: note.updatedAt
1933
+ };
1934
+ const text2 = displaySafeJson(payload);
1935
+ recordStat("note_read");
1936
+ return { text: text2, code: 0 };
1937
+ }
1938
+ const target = note.symbol === WHOLE_FILE_NOTE_SYMBOL ? opts.file : `${opts.file}::${note.symbol}`;
1939
+ const staleTag = stale ? " [STALE \u2014 code changed since this note was written]" : "";
1940
+ const text = `# note \u2014 ${target}${staleTag}
1941
+ ${note.content}`;
1942
+ recordStat("note_read");
1943
+ return { text, code: 0 };
1944
+ }
1945
+ function runNoteList(opts = {}) {
1946
+ const withStale = listNotes().map((note) => ({ note, stale: isNoteStale(note) }));
1947
+ const filtered = opts.staleOnly === true ? withStale.filter((n) => n.stale) : withStale;
1948
+ if (opts.json === true) {
1949
+ const items = filtered.map(({ note, stale }) => ({
1950
+ filePath: note.filePath,
1951
+ symbol: note.symbol === WHOLE_FILE_NOTE_SYMBOL ? null : note.symbol,
1952
+ stale,
1953
+ updatedAt: note.updatedAt
1954
+ }));
1955
+ recordStat("note_list");
1956
+ return { text: displaySafeJson(items), code: 0 };
1957
+ }
1958
+ recordStat("note_list");
1959
+ if (filtered.length === 0) {
1960
+ if (opts.staleOnly === true && withStale.length > 0) {
1961
+ const noun = withStale.length === 1 ? "note" : "notes";
1962
+ return { text: `No stale notes (${withStale.length} ${noun} recorded, none stale).`, code: 0 };
1963
+ }
1964
+ return { text: opts.staleOnly === true ? "No stale notes." : "No notes recorded.", code: 0 };
1965
+ }
1966
+ const noteListDisplayRoot = getDisplayRoot();
1967
+ const lines = filtered.map(({ note, stale }) => {
1968
+ const displayFile = toDisplayPath(noteListDisplayRoot, note.filePath);
1969
+ const target = note.symbol === WHOLE_FILE_NOTE_SYMBOL ? displayFile : `${displayFile}::${note.symbol}`;
1970
+ return `${stale ? "[STALE] " : ""}${target}`;
1971
+ });
1972
+ return { text: lines.join("\n"), code: 0 };
1973
+ }
1974
+ function runFind(opts) {
1975
+ if (opts.limit !== void 0 && opts.limit <= 0) {
1976
+ emitErr(`--limit must be a positive number, got: ${opts.limit}`);
1977
+ return 1;
1978
+ }
1979
+ const rootDir = resolveProjectRoot({ project: process.cwd() });
1980
+ const patternLower = opts.pattern.toLowerCase();
1981
+ const matchedFiles = /* @__PURE__ */ new Set();
1982
+ const allNames = /* @__PURE__ */ new Set();
1983
+ forEachSymbol({ rootDir }, (s) => {
1984
+ allNames.add(s.name);
1985
+ if (s.name.toLowerCase().includes(patternLower)) matchedFiles.add(s.filePath);
1986
+ });
1987
+ let fuzzyNames = [];
1988
+ let allFiles = [...matchedFiles].sort(compareBinary);
1989
+ if (matchedFiles.size === 0) {
1990
+ fuzzyNames = rankSimilarNames([...allNames], opts.pattern);
1991
+ if (fuzzyNames.length > 0) {
1992
+ const byName = new Map(fuzzyNames.map((n) => [n, /* @__PURE__ */ new Set()]));
1993
+ forEachSymbol({ rootDir }, (s) => {
1994
+ byName.get(s.name)?.add(s.filePath);
1995
+ });
1996
+ for (const n of fuzzyNames) for (const f of [...byName.get(n) ?? []].sort(compareBinary)) matchedFiles.add(f);
1997
+ allFiles = [...matchedFiles];
1998
+ }
1999
+ }
2000
+ const files = allFiles.slice(0, opts.limit ?? 50);
2001
+ const limitDropped = allFiles.length - files.length;
2002
+ const truncated = limitDropped > 0;
2003
+ if (files.length === 0) {
2004
+ emitErr(`No indexed files match '${opts.pattern}'`);
2005
+ return 1;
2006
+ }
2007
+ if (opts.json === true) {
2008
+ const fuzzyPayload = fuzzyNames.length > 0 ? { fuzzy: true, matchedNames: fuzzyNames } : {};
2009
+ emit(displaySafeJson({ files, truncated, totalCount: allFiles.length, ...fuzzyPayload }));
2010
+ return 0;
2011
+ }
2012
+ if (fuzzyNames.length > 0) {
2013
+ emitErr(`No symbol name contains '${opts.pattern}'; showing files for the nearest indexed ${fuzzyNames.length === 1 ? "name" : "names"}: ${fuzzyNames.join(", ")}`);
2014
+ }
2015
+ for (const f of files) {
2016
+ emit(toDisplayPath(rootDir, f));
2017
+ }
2018
+ if (limitDropped > 0) {
2019
+ emitErr(`Showing ${files.length} of ${allFiles.length} matching files; rerun with --limit ${allFiles.length} to see them all`);
2020
+ }
2021
+ return 0;
2022
+ }
2023
+ function runLocate(opts) {
2024
+ if (opts.limit !== void 0 && opts.limit <= 0) {
2025
+ emitErr(`--limit must be a positive number, got: ${opts.limit}`);
2026
+ return 1;
2027
+ }
2028
+ const rootDir = opts.projectRoot ?? resolveProjectRoot({ project: process.cwd() });
2029
+ let targetSpec = opts.spec;
2030
+ let targetFile = opts.file;
2031
+ if (targetSpec.includes("::")) {
2032
+ const colonIdx = targetSpec.indexOf("::");
2033
+ targetFile = targetSpec.slice(0, colonIdx);
2034
+ targetSpec = targetSpec.slice(colonIdx + 2);
2035
+ }
2036
+ const scanOpts = {};
2037
+ if (targetFile !== void 0) {
2038
+ scanOpts.filePath = resolveSpecPath(targetFile, rootDir);
2039
+ healStaleIndex(scanOpts.filePath);
2040
+ } else {
2041
+ scanOpts.rootDir = rootDir;
2042
+ }
2043
+ const limit = opts.limit ?? 25;
2044
+ const specLower = targetSpec.toLowerCase();
2045
+ const scan = () => {
2046
+ const found2 = { exact: new FirstRows(limit), exactCount: 0, partial: new FirstRows(limit), partialCount: 0, names: /* @__PURE__ */ new Set(), files: /* @__PURE__ */ new Set() };
2047
+ forEachSymbol(scanOpts, (s) => {
2048
+ found2.names.add(s.name);
2049
+ found2.files.add(s.filePath);
2050
+ const nameLower = s.name.toLowerCase();
2051
+ if (nameLower === specLower) {
2052
+ found2.exactCount++;
2053
+ found2.exact.offer(s);
2054
+ } else if (nameLower.includes(specLower)) {
2055
+ found2.partialCount++;
2056
+ found2.partial.offer(s);
2057
+ }
2058
+ });
2059
+ return found2;
2060
+ };
2061
+ let found = scan();
2062
+ if (targetFile === void 0) {
2063
+ const heal = healStaleResultFiles([...found.files]);
2064
+ if (heal.healed) found = scan();
2065
+ }
2066
+ let matchCount = found.exactCount + found.partialCount;
2067
+ let combined = [...found.exact.rows(), ...found.partial.rows()];
2068
+ let fuzzyNames = [];
2069
+ if (matchCount === 0) {
2070
+ fuzzyNames = rankSimilarNames([...found.names], targetSpec);
2071
+ if (fuzzyNames.length > 0) {
2072
+ const byName = new Map(fuzzyNames.map((n) => [n, new FirstRows(limit)]));
2073
+ forEachSymbol(scanOpts, (s) => {
2074
+ const bucket = byName.get(s.name);
2075
+ if (bucket === void 0) return;
2076
+ matchCount++;
2077
+ bucket.offer(s);
2078
+ });
2079
+ combined = fuzzyNames.flatMap((n) => byName.get(n)?.rows() ?? []);
2080
+ }
2081
+ }
2082
+ const shown = sinkGoneRows(combined, (s) => s.filePath).slice(0, limit);
2083
+ if (shown.length === 0) {
2084
+ emitErr(`No landmark or symbol located for '${targetSpec}'`);
2085
+ return 1;
2086
+ }
2087
+ warnIfFilesStale(shown.map((s) => s.filePath), "locate");
2088
+ const hits = shown.map((s) => ({
2089
+ filePath: toDisplayPath(rootDir, s.filePath),
2090
+ name: s.name,
2091
+ kind: s.kind,
2092
+ lineStart: s.lineStart,
2093
+ lineEnd: s.lineEnd,
2094
+ span: `${s.lineStart}-${s.lineEnd}`,
2095
+ ...fileIsGone(s.filePath) ? { deleted: true } : {}
2096
+ }));
2097
+ if (opts.json === true) {
2098
+ emit(displaySafeJson({
2099
+ items: hits,
2100
+ totalCount: matchCount,
2101
+ truncated: matchCount > limit,
2102
+ ...fuzzyNames.length > 0 ? { fuzzy: true, matchedNames: fuzzyNames } : {}
2103
+ }));
2104
+ return 0;
2105
+ }
2106
+ if (fuzzyNames.length > 0) {
2107
+ emitErr(`No exact landmark for '${targetSpec}'; nearest matches: ${fuzzyNames.join(", ")}`);
2108
+ }
2109
+ for (const hit of hits) {
2110
+ emit(`${displaySafeText(hit.filePath)}:${hit.span} [${displaySafeText(hit.kind)}] ${displaySafeText(hit.name)}${hit.deleted === true ? ` ${DELETED_TAG}` : ""}`);
2111
+ }
2112
+ if (matchCount > limit) {
2113
+ emitErr(`Showing ${limit} of ${matchCount} locations; rerun with --limit ${matchCount} to see them all`);
2114
+ }
2115
+ return 0;
2116
+ }
2117
+ function runListSections(opts) {
2118
+ const sections = listSections(opts.file);
2119
+ if (sections.length === 0) {
2120
+ if (!fileExists(opts.file)) {
2121
+ emitErr(`Could not read: ${opts.file}`);
2122
+ return 1;
2123
+ }
2124
+ emitErr(`No sections found in '${opts.file}'`);
2125
+ return 1;
2126
+ }
2127
+ const preFilterCount = sections.length;
2128
+ const matchesGrep = opts.grep !== void 0 ? compileGrepMatcher(opts.grep) : void 0;
2129
+ const totals = /* @__PURE__ */ new Map();
2130
+ for (const heading of sections) totals.set(heading, (totals.get(heading) ?? 0) + 1);
2131
+ const seen = /* @__PURE__ */ new Map();
2132
+ const labelled = sections.map((heading) => {
2133
+ const nth = (seen.get(heading) ?? 0) + 1;
2134
+ seen.set(heading, nth);
2135
+ return (totals.get(heading) ?? 1) > 1 ? `${heading}#${nth}` : heading;
2136
+ });
2137
+ const filtered = matchesGrep !== void 0 ? labelled.filter((_, i) => matchesGrep(sections[i] ?? "")) : labelled;
2138
+ if (filtered.length === 0) {
2139
+ if (opts.json === true) {
2140
+ emit(displaySafeJson({ items: [], truncated: false, totalCount: 0 }));
2141
+ return 0;
2142
+ }
2143
+ emit(grepFilteredToEmptyNotice(preFilterCount, opts.grep ?? "", "section", "sections"));
2144
+ return 0;
2145
+ }
2146
+ if (opts.json === true) {
2147
+ const capped = guardJsonRows(filtered);
2148
+ emit(displaySafeJson({ items: capped.items, truncated: capped.truncated, totalCount: capped.totalCount }));
2149
+ return 0;
2150
+ }
2151
+ for (const heading of filtered) {
2152
+ emit(heading);
2153
+ }
2154
+ return 0;
2155
+ }
2156
+
2157
+ // src/read_outline.ts
2158
+ init_define_import_meta_env();
2159
+ var DOC_SUMMARY_MAX_CHARS = 140;
2160
+ function firstSentenceEnd(line) {
2161
+ const MIN_SENTENCE_CHARS = 30;
2162
+ const ABBREV = new RegExp("(?:\\b(?:e\\.g|i\\.e|vs|cf|etc|approx|al|Dr|Mr|Ms|St|Fig|No)\\.|\\b\\p{L}\\.)$", "u");
2163
+ for (const m of line.matchAll(/[.!?](?=\s|$)/gu)) {
2164
+ const end = m.index + 1;
2165
+ if (end < MIN_SENTENCE_CHARS) continue;
2166
+ const head = line.slice(0, end);
2167
+ if (ABBREV.test(head)) continue;
2168
+ return end;
2169
+ }
2170
+ return null;
2171
+ }
2172
+ function clipDocSummary(firstLine) {
2173
+ const sentence = firstSentenceEnd(firstLine);
2174
+ if (sentence !== null && sentence <= DOC_SUMMARY_MAX_CHARS) {
2175
+ return sentence === firstLine.length ? firstLine : `${firstLine.slice(0, sentence)}\u2026`;
2176
+ }
2177
+ if (firstLine.length <= DOC_SUMMARY_MAX_CHARS) return firstLine;
2178
+ const cut = firstLine.lastIndexOf(" ", DOC_SUMMARY_MAX_CHARS);
2179
+ return `${firstLine.slice(0, cut > 40 ? cut : DOC_SUMMARY_MAX_CHARS).trimEnd()}\u2026`;
2180
+ }
2181
+ function filteredToEmptyNotice(preFilterCount, minLines, grep) {
2182
+ const parts = [];
2183
+ if (minLines !== void 0) parts.push(`--min-lines ${minLines}`);
2184
+ if (grep !== void 0) parts.push(`--grep ${grep}`);
2185
+ return filtersFilteredToEmptyNotice(preFilterCount, parts, "indexed symbol", "indexed symbols", "the file is indexed");
2186
+ }
2187
+ function prepareSymbolListing(file, opts) {
2188
+ const resolved = resolveSpecPath(file, opts.projectRoot ?? process.cwd());
2189
+ const confined = fileConfinementRefusal("This file", file, opts.projectRoot);
2190
+ if (confined !== null) return { kind: "confined", text: confined };
2191
+ if (opts.forceRefresh === true) {
2192
+ indexFileSyncPinned(resolved, globalDbPath());
2193
+ enqueueDirtyPathSafe(resolved, { alreadyResolved: true });
2194
+ } else {
2195
+ healStaleIndex(resolved);
2196
+ }
2197
+ const matchesGrep = opts.grep !== void 0 ? compileGrepMatcher(opts.grep) : void 0;
2198
+ const matches = (s) => (opts.minLines === void 0 || s.lineEnd - s.lineStart + 1 >= opts.minLines) && (matchesGrep === void 0 || matchesGrep(s.name));
2199
+ const filtered = [];
2200
+ let scanned = 0;
2201
+ let matchesTruncated = false;
2202
+ let totalLines = 0;
2203
+ for (let offset = 0; ; offset += SKELETON_SYMBOL_CAP) {
2204
+ const page = querySymbols({ filePath: resolved, limit: SKELETON_SYMBOL_CAP, offset });
2205
+ if (page.length === 0) break;
2206
+ scanned += page.length;
2207
+ for (const s of page) {
2208
+ if (s.lineEnd > totalLines) totalLines = s.lineEnd;
2209
+ if (!matches(s)) continue;
2210
+ if (filtered.length === SKELETON_SYMBOL_CAP) {
2211
+ matchesTruncated = true;
2212
+ break;
2213
+ }
2214
+ filtered.push(s);
2215
+ }
2216
+ if (matchesTruncated || page.length < SKELETON_SYMBOL_CAP) break;
2217
+ }
2218
+ if (scanned === 0) {
2219
+ return { kind: "empty", text: noSymbolsMessage(file, resolved) };
2220
+ }
2221
+ const trueSymbolCount = matchesTruncated ? countSymbols({ filePath: resolved }) : void 0;
2222
+ const symbolsTruncated = matchesTruncated;
2223
+ const refCounts = opts.stats === true ? queryRefCounts(filtered.map((s) => s.name), globalDbPath(), resolveProjectRoot({ project: opts.projectRoot ?? process.cwd() })) : void 0;
2224
+ const fullSourceBytes = sumFileSizes([resolved]);
2225
+ return { kind: "ok", resolved, displayRoot: getDisplayRoot(opts.projectRoot), filtered, preFilterCount: scanned, refCounts, fullSourceBytes, symbolsTruncated, trueSymbolCount, totalLines };
2226
+ }
2227
+ function runPerFileListing(files, run, json = false) {
2228
+ const blocks = [];
2229
+ let anyOk = false;
2230
+ for (const file of files) {
2231
+ const r = run(file);
2232
+ if (r.code === 0) anyOk = true;
2233
+ blocks.push(r.text);
2234
+ }
2235
+ if (json) return mergeListingJson(files, blocks, anyOk);
2236
+ return { text: blocks.join("\n\n"), code: anyOk ? 0 : 1 };
2237
+ }
2238
+ function mergeListingJson(files, blocks, anyOk) {
2239
+ const items = [];
2240
+ const errors = [];
2241
+ let truncated = false;
2242
+ let totalCount = 0;
2243
+ for (const [i, block] of blocks.entries()) {
2244
+ const file = files[i] ?? "";
2245
+ let parsed;
2246
+ try {
2247
+ parsed = JSON.parse(block);
2248
+ } catch {
2249
+ parsed = void 0;
2250
+ }
2251
+ if (parsed === void 0 || !Array.isArray(parsed.items)) {
2252
+ errors.push({ file, message: block.trim() });
2253
+ continue;
2254
+ }
2255
+ items.push(...parsed.items);
2256
+ if (parsed.truncated === true) truncated = true;
2257
+ totalCount += parsed.totalCount ?? parsed.items.length;
2258
+ }
2259
+ const payload = { items, truncated, totalCount, ...errors.length > 0 ? { errors } : {} };
2260
+ return { text: displaySafeJson(payload), code: anyOk ? 0 : 1 };
2261
+ }
2262
+ function symbolCountLabel(shown, truncated, trueCount) {
2263
+ if (!truncated || trueCount === void 0 || trueCount <= shown) return countNoun(shown, "symbol");
2264
+ return `${shown} of ${countNoun(trueCount, "symbol")}`;
2265
+ }
2266
+ function runSkeleton(opts) {
2267
+ const multiFiles = parseMultiFileSpec(opts.file);
2268
+ if (multiFiles !== null) return runPerFileListing(multiFiles, (file) => runSkeleton({ ...opts, file, includeFilePath: true }), opts.json === true);
2269
+ const prep = prepareSymbolListing(opts.file, opts);
2270
+ if (prep.kind === "confined" || prep.kind === "empty") {
2271
+ return { text: prep.text, code: 1 };
2272
+ }
2273
+ const { resolved, displayRoot, filtered, preFilterCount, refCounts, fullSourceBytes, symbolsTruncated, trueSymbolCount, totalLines } = prep;
2274
+ if (opts.json === true) {
2275
+ const rows = filtered.map((s) => ({
2276
+ ...opts.includeFilePath === true ? { filePath: toDisplayPath(displayRoot, s.filePath) } : {},
2277
+ name: s.name,
2278
+ kind: s.kind,
2279
+ lineStart: s.lineStart,
2280
+ lineEnd: s.lineEnd,
2281
+ ...refCounts !== void 0 ? { refCount: refCounts.get(s.name) ?? 0, hasDoc: hasRealDocstring(s.docstring) } : {}
2282
+ }));
2283
+ const capped = guardJsonRows(rows);
2284
+ const payload = {
2285
+ items: capped.items,
2286
+ truncated: capped.truncated || symbolsTruncated,
2287
+ totalCount: symbolsTruncated ? Math.max(trueSymbolCount ?? 0, capped.totalCount) : capped.totalCount,
2288
+ ...fileIsGone(resolved) ? { deleted: true } : {}
2289
+ };
2290
+ const text2 = displaySafeJson(payload);
2291
+ recordStaleServed("skeleton", indexFreshness(resolved));
2292
+ recordReadStat("stub_view", fullSourceBytes, text2, opts.file);
2293
+ return { text: text2, code: 0 };
2294
+ }
2295
+ const lines = [`# Skeleton: ${opts.file} (${symbolCountLabel(filtered.length, symbolsTruncated, trueSymbolCount)}, ${countNoun(totalLines, "line")})`];
2296
+ if (filtered.length === 0 && preFilterCount > 0) lines.push(filteredToEmptyNotice(preFilterCount, opts.minLines, opts.grep));
2297
+ for (const sym of filtered) {
2298
+ const lineStr = sym.lineStart.toString().padStart(6);
2299
+ const statsStr = formatStatsSuffix(refCounts, sym);
2300
+ lines.push(` ${lineStr} ${sym.kind.padEnd(10)} ${sym.name} ${firstBodyLine(sym.body)}${statsStr}`);
2301
+ }
2302
+ const text = guardText(staleWarning(resolved, "skeleton") + lines.join("\n"), "symbol");
2303
+ recordReadStat("stub_view", fullSourceBytes, text, opts.file);
2304
+ return { text, code: 0 };
2305
+ }
2306
+ function runOutline(opts) {
2307
+ const multiFiles = parseMultiFileSpec(opts.file);
2308
+ if (multiFiles !== null) return runPerFileListing(multiFiles, (file) => runOutline({ ...opts, file, includeFilePath: true }), opts.json === true);
2309
+ const prep = prepareSymbolListing(opts.file, opts);
2310
+ if (prep.kind === "confined" || prep.kind === "empty") {
2311
+ return { text: prep.text, code: 1 };
2312
+ }
2313
+ const { resolved, displayRoot, filtered, preFilterCount, refCounts, fullSourceBytes, symbolsTruncated, trueSymbolCount } = prep;
2314
+ if (opts.json === true) {
2315
+ const rows = filtered.map((s) => ({
2316
+ ...opts.includeFilePath === true ? { filePath: toDisplayPath(displayRoot, s.filePath) } : {},
2317
+ name: s.name,
2318
+ kind: s.kind,
2319
+ lineStart: s.lineStart,
2320
+ lineEnd: s.lineEnd,
2321
+ docstring: s.docstring,
2322
+ parent: s.parent,
2323
+ ...refCounts !== void 0 ? { refCount: refCounts.get(s.name) ?? 0, hasDoc: hasRealDocstring(s.docstring) } : {}
2324
+ }));
2325
+ const capped = guardJsonRows(rows);
2326
+ const payload = {
2327
+ items: capped.items,
2328
+ truncated: capped.truncated || symbolsTruncated,
2329
+ totalCount: symbolsTruncated ? Math.max(trueSymbolCount ?? 0, capped.totalCount) : capped.totalCount,
2330
+ ...fileIsGone(resolved) ? { deleted: true } : {}
2331
+ };
2332
+ const text2 = displaySafeJson(payload);
2333
+ recordStaleServed("outline", indexFreshness(resolved));
2334
+ recordReadStat("outline", fullSourceBytes, text2, opts.file);
2335
+ return { text: text2, code: 0 };
2336
+ }
2337
+ const lines = [`# Outline: ${opts.file} (${symbolCountLabel(filtered.length, symbolsTruncated, trueSymbolCount)})`];
2338
+ if (filtered.length === 0 && preFilterCount > 0) lines.push(filteredToEmptyNotice(preFilterCount, opts.minLines, opts.grep));
2339
+ for (const sym of filtered) {
2340
+ const rangeStr = `${sym.lineStart.toString().padStart(4)}-${sym.lineEnd.toString().padEnd(6)}`;
2341
+ const kindStr = sym.kind.padEnd(14);
2342
+ const bodyLen = sym.lineEnd - sym.lineStart + 1;
2343
+ const docFirst = hasRealDocstring(sym.docstring) ? ` # ${clipDocSummary(sym.docstring.split("\n")[0] ?? "")}` : "";
2344
+ const statsStr = formatStatsSuffix(refCounts, sym);
2345
+ const notebookSuffix = isVirtualIndexedPath(sym.filePath) ? NOTEBOOK_CELL_LINES_SUFFIX : "";
2346
+ lines.push(` ${rangeStr} ${kindStr} ${sym.name} (${bodyLen}\u2113)${docFirst}${statsStr}${notebookSuffix}`);
2347
+ }
2348
+ const text = guardText(staleWarning(resolved, "outline") + lines.join("\n"), "symbol");
2349
+ recordReadStat("outline", fullSourceBytes, text, opts.file);
2350
+ return { text, code: 0 };
2351
+ }
2352
+
2353
+ // src/semantic_distances.ts
2354
+ init_define_import_meta_env();
2355
+ var MIN_QUERIES_FOR_ADVICE = 20;
2356
+ var BAND_LOW = 0.7;
2357
+ var BAND_HIGH = 0.85;
2358
+ function percentile(sorted, p) {
2359
+ const idx = p * (sorted.length - 1);
2360
+ const lo = Math.floor(idx);
2361
+ const hi = Math.ceil(idx);
2362
+ return sorted[lo] + (sorted[hi] - sorted[lo]) * (idx - lo);
2363
+ }
2364
+ function summarizeDistances(rows, weakDistance) {
2365
+ const distances = rows.flatMap((r) => r.closest_distance === null ? [] : [r.closest_distance]).sort((a, b) => a - b);
2366
+ const counts = [0, 0, 0, rows.length - distances.length];
2367
+ for (const d of distances) counts[d < BAND_LOW ? 0 : d < BAND_HIGH ? 1 : 2]++;
2368
+ const ranges = [`< ${BAND_LOW.toFixed(2)}`, `${BAND_LOW.toFixed(2)} - ${BAND_HIGH.toFixed(2)}`, `>= ${BAND_HIGH.toFixed(2)}`, "none returned"];
2369
+ const weak = distances.filter((d) => d > weakDistance).length;
2370
+ return {
2371
+ total: rows.length,
2372
+ percentiles: distances.length === 0 ? null : { p10: percentile(distances, 0.1), p25: percentile(distances, 0.25), p50: percentile(distances, 0.5), p75: percentile(distances, 0.75), p90: percentile(distances, 0.9) },
2373
+ bands: ranges.map((range, i) => ({ range, count: counts[i], pct: rows.length === 0 ? 0 : counts[i] / rows.length * 100 })),
2374
+ weakShare: distances.length === 0 ? null : weak / distances.length
2375
+ };
2376
+ }
2377
+ function formatDistanceReport(summary, thresholds, scope = "this project") {
2378
+ const lines = [`Recorded semantic queries (${scope}): ${summary.total} queries`, `Current semantic.weak_distance: ${thresholds.weakDistance} semantic.max_distance: ${thresholds.maxDistance}`];
2379
+ if (summary.total < MIN_QUERIES_FOR_ADVICE) {
2380
+ lines.push(`Only ${summary.total} recorded, which is fewer than ${MIN_QUERIES_FOR_ADVICE}: too few to judge a threshold from. Run more semantic queries and check again.`);
2381
+ return lines.join("\n");
2382
+ }
2383
+ if (summary.percentiles !== null) {
2384
+ const p = summary.percentiles;
2385
+ lines.push(`Closest distance: p10 ${p.p10.toFixed(3)} p25 ${p.p25.toFixed(3)} p50 ${p.p50.toFixed(3)} p75 ${p.p75.toFixed(3)} p90 ${p.p90.toFixed(3)}`);
2386
+ }
2387
+ lines.push("Band Queries Share");
2388
+ for (const b of summary.bands) lines.push(`${b.range.padEnd(15)} ${String(b.count).padStart(7)} ${b.pct.toFixed(1)}%`);
2389
+ if (summary.weakShare !== null) lines.push(`Labelled weak at the current weak_distance: ${(summary.weakShare * 100).toFixed(1)}% of queries that returned a match`);
2390
+ return lines.join("\n");
2391
+ }
2392
+ function recordSemanticQuery(row) {
2393
+ try {
2394
+ withTelemetryWriteDb((db) => {
2395
+ db.prepare("INSERT INTO semantic_queries (ts, project_hash, closest_distance, floor_rejected_min, weak) VALUES (?, ?, ?, ?, ?)").run(Math.floor(Date.now() / 1e3), projectHash(row.projectRoot), row.closestDistance, row.floorRejectedMin, row.weak ? 1 : 0);
2396
+ });
2397
+ } catch {
2398
+ }
2399
+ }
2400
+ function runSemanticDistances(opts) {
2401
+ const { weak_distance: weakDistance, max_distance: maxDistance } = loadConfig().semantic;
2402
+ const db = getGlobalDb();
2403
+ const rows = opts.all === true ? db.prepare("SELECT closest_distance FROM semantic_queries").all() : db.prepare("SELECT closest_distance FROM semantic_queries WHERE project_hash = ?").all(projectHash(resolveProjectRoot({ project: process.cwd() })));
2404
+ const scope = opts.all === true ? "all projects" : "this project";
2405
+ return { text: formatDistanceReport(summarizeDistances(rows, weakDistance), { weakDistance, maxDistance }, scope), code: 0 };
2406
+ }
2407
+
2408
+ // src/read_semantic.ts
2409
+ init_define_import_meta_env();
2410
+ import * as fs2 from "node:fs";
2411
+ import * as path2 from "node:path";
2412
+
2413
+ // src/semantic_merge.ts
2414
+ init_define_import_meta_env();
2415
+ var STRUCTURAL_CHUNK_KINDS = /* @__PURE__ */ new Set(["symbol", "section"]);
2416
+ function mergeNearbyHits(hits, proximity = 20) {
2417
+ if (hits.length <= 1) {
2418
+ return hits;
2419
+ }
2420
+ const byFile = /* @__PURE__ */ new Map();
2421
+ for (const hit of hits) {
2422
+ const fileHits = byFile.get(hit.filePath);
2423
+ if (fileHits) {
2424
+ fileHits.push(hit);
2425
+ } else {
2426
+ byFile.set(hit.filePath, [hit]);
2427
+ }
2428
+ }
2429
+ const merged = [];
2430
+ for (const fileHits of byFile.values()) {
2431
+ const bestFirst = fileHits.map((hit, index) => ({ hit, index })).sort((a, b) => (a.hit.adjustedDistance ?? a.hit.distance) - (b.hit.adjustedDistance ?? b.hit.distance) || a.index - b.index).map((entry) => entry.hit);
2432
+ const claimed = /* @__PURE__ */ new Set();
2433
+ for (const anchor of bestFirst) {
2434
+ if (claimed.has(anchor)) continue;
2435
+ claimed.add(anchor);
2436
+ const members = [];
2437
+ for (const hit of bestFirst) {
2438
+ if (claimed.has(hit)) continue;
2439
+ const gap = hit.startLine > anchor.endLine ? hit.startLine - anchor.endLine - 1 : anchor.startLine > hit.endLine ? anchor.startLine - hit.endLine - 1 : -1;
2440
+ const distinctUnits = STRUCTURAL_CHUNK_KINDS.has(anchor.kind) || STRUCTURAL_CHUNK_KINDS.has(hit.kind);
2441
+ if (gap < 0 || !distinctUnits && gap <= proximity) {
2442
+ claimed.add(hit);
2443
+ members.push(hit);
2444
+ }
2445
+ }
2446
+ members.sort((a, b) => a.startLine - b.startLine);
2447
+ let startLine = anchor.startLine;
2448
+ let endLine = anchor.endLine;
2449
+ let distance = anchor.distance;
2450
+ let adjusted = anchor.adjustedDistance ?? anchor.distance;
2451
+ for (const m of members) {
2452
+ startLine = Math.min(startLine, m.startLine);
2453
+ endLine = Math.max(endLine, m.endLine);
2454
+ distance = Math.min(distance, m.distance);
2455
+ adjusted = Math.min(adjusted, m.adjustedDistance ?? m.distance);
2456
+ }
2457
+ merged.push({
2458
+ filePath: anchor.filePath,
2459
+ startLine,
2460
+ endLine,
2461
+ kind: anchor.kind,
2462
+ distance,
2463
+ adjustedDistance: adjusted,
2464
+ text: [anchor.text, ...members.map((m) => m.text)].join("\n---\n")
2465
+ });
2466
+ }
2467
+ }
2468
+ merged.sort((a, b) => (a.adjustedDistance ?? a.distance) - (b.adjustedDistance ?? b.distance));
2469
+ return merged;
2470
+ }
2471
+
2472
+ // src/read_semantic.ts
2473
+ function resolveEnclosingSymbol(filePath, chunkStartLine, chunkEndLine, linesOf) {
2474
+ const symbols = querySymbols({ filePath, limit: -1 }, globalDbPath());
2475
+ const related = (s, t) => t.lineStart >= s.lineStart && t.lineEnd <= s.lineEnd || t.lineStart <= s.lineStart && t.lineEnd >= s.lineEnd;
2476
+ const holdsChunk = (s) => {
2477
+ if (symbols.some((t) => t !== s && !related(s, t) && t.lineStart >= chunkStartLine && t.lineStart <= chunkEndLine)) return false;
2478
+ if (chunkEndLine <= s.lineEnd) return true;
2479
+ const lines = linesOf(filePath);
2480
+ return lines === null || onlyBlankOrComment(lines.slice(s.lineEnd, chunkEndLine), commentSyntaxFor(filePath));
2481
+ };
2482
+ let best = null;
2483
+ for (const s of symbols) {
2484
+ if (s.lineStart <= chunkStartLine && chunkStartLine <= s.lineEnd) {
2485
+ if ((best === null || s.lineEnd - s.lineStart < best.lineEnd - best.lineStart) && holdsChunk(s)) best = s;
2486
+ }
2487
+ }
2488
+ return best === null ? null : { name: best.name, kind: best.kind, lineStart: best.lineStart };
2489
+ }
2490
+ function onlyBlankOrComment(lines, syntax) {
2491
+ let inBlock = false;
2492
+ for (const line of lines) {
2493
+ const trimmed = line.trim();
2494
+ if (inBlock) {
2495
+ if (syntax?.close !== void 0 && trimmed.includes(syntax.close)) inBlock = false;
2496
+ continue;
2497
+ }
2498
+ if (trimmed === "") continue;
2499
+ if (syntax === null) return false;
2500
+ if (syntax.open !== void 0 && trimmed.startsWith(syntax.open)) {
2501
+ const rest = trimmed.slice(syntax.open.length);
2502
+ if (syntax.close === void 0 || !rest.includes(syntax.close)) inBlock = true;
2503
+ else if (rest.slice(rest.indexOf(syntax.close) + syntax.close.length).trim() !== "") return false;
2504
+ continue;
2505
+ }
2506
+ if (!syntax.line.some((p) => trimmed.startsWith(p))) return false;
2507
+ }
2508
+ return true;
2509
+ }
2510
+ var RRF_K = 60;
2511
+ function applyRelevanceFloor(hits, floor) {
2512
+ const kept = [];
2513
+ let nearestRejected = null;
2514
+ for (const h of hits) {
2515
+ if (h.distance <= floor) {
2516
+ kept.push(h);
2517
+ } else if (nearestRejected === null || h.distance < nearestRejected) {
2518
+ nearestRejected = h.distance;
2519
+ }
2520
+ }
2521
+ return { kept, nearestRejected };
2522
+ }
2523
+ function semanticDegradedFields(preflight, searchSemanticError) {
2524
+ if (preflight.status !== "ready") {
2525
+ return {
2526
+ preflightStatus: preflight.status,
2527
+ warning: preflight.summary,
2528
+ ...preflight.actionRequired ? { actionRequired: preflight.actionRequired } : {}
2529
+ };
2530
+ }
2531
+ if (searchSemanticError !== null) {
2532
+ return { preflightStatus: "degraded", warning: `Matching on meaning failed (${searchSemanticError}); results come from keyword search alone.` };
2533
+ }
2534
+ return {};
2535
+ }
2536
+ async function runSemantic(query, opts) {
2537
+ if (opts.limit !== void 0 && opts.limit <= 0) {
2538
+ const message = `--limit must be a positive number, got: ${opts.limit}`;
2539
+ if (opts.json === true) {
2540
+ return { text: displaySafeJson({ error: message }), code: 1 };
2541
+ }
2542
+ return { text: message, code: 1 };
2543
+ }
2544
+ const n = opts.limit !== void 0 && Number.isFinite(opts.limit) ? opts.limit : 20;
2545
+ if (opts.projectRoot !== void 0) {
2546
+ if (!path2.isAbsolute(opts.projectRoot) || !fs2.existsSync(opts.projectRoot) || !fs2.statSync(opts.projectRoot).isDirectory()) {
2547
+ const message = `token-goat: projectRoot must be an absolute, existing directory, got '${opts.projectRoot}'`;
2548
+ if (opts.json === true) {
2549
+ return { text: displaySafeJson({ error: message }), code: 1 };
2550
+ }
2551
+ return { text: message, code: 1 };
2552
+ }
2553
+ }
2554
+ const projectDenial = resolveProjectConfinement(opts.projectRoot).denial;
2555
+ if (projectDenial !== null) {
2556
+ return { text: opts.json === true ? displaySafeJson({ error: projectDenial }) : projectDenial, code: 1 };
2557
+ }
2558
+ const rootDir = opts.projectRoot ?? resolveProjectRoot({ project: process.cwd() });
2559
+ let projectCoverage;
2560
+ try {
2561
+ projectCoverage = getEmbeddingCoverage(globalDbPath(), rootDir);
2562
+ } catch {
2563
+ }
2564
+ const readiness = () => {
2565
+ const check = () => checkSemanticReadiness({
2566
+ ...opts.warm !== void 0 ? { warm: opts.warm } : {},
2567
+ projectRoot: rootDir,
2568
+ ...projectCoverage !== void 0 ? { coverage: projectCoverage } : {}
2569
+ });
2570
+ return opts.warm === true ? withExplicitDownload(check) : check();
2571
+ };
2572
+ if (opts.preflight === true || opts.warm === true && query === "") {
2573
+ const preflight2 = await readiness();
2574
+ if (opts.json === true) {
2575
+ return { text: displaySafeJson(preflight2), code: preflight2.status === "ready" ? 0 : 1 };
2576
+ }
2577
+ const lines = [
2578
+ `Semantic embedding status: ${preflight2.status.toUpperCase()}`,
2579
+ ` Summary: ${preflight2.summary}`,
2580
+ ` Config (indexing.embeddings_enabled): ${preflight2.configEnabled ? "enabled" : "disabled"}`,
2581
+ ` ONNX runtime (${preflight2.runtime}): ${preflight2.runtimeAvailable ? `available (${preflight2.runtimeVersion})` : "unavailable"}`,
2582
+ ...preflight2.runtimeBinaryPresent === null ? [] : [` Runtime binary (${ORT_WEB_WASM.name}, ~14 MB): ${preflight2.runtimeBinaryPresent ? "downloaded" : "not downloaded yet, fetched once on first use"}`],
2583
+ ` Model files (~35 MB): ${preflight2.modelFilesPresent ? "present" : "missing"}`,
2584
+ ` In-memory session: ${preflight2.modelWarmed ? "ready / warmed" : "not loaded"}`,
2585
+ ` Project coverage: ${preflight2.embeddedFiles}/${countNoun(preflight2.indexedFiles, "file")} (${preflight2.coveragePercent}%)`
2586
+ ];
2587
+ if (preflight2.actionRequired) {
2588
+ lines.push(` Action: ${preflight2.actionRequired}`);
2589
+ }
2590
+ return { text: lines.join("\n"), code: preflight2.status === "ready" ? 0 : 1 };
2591
+ }
2592
+ const preflight = await readiness();
2593
+ const embeddingsEnabled = loadConfig().indexing?.embeddings_enabled ?? true;
2594
+ if (embeddingsEnabled && !isAvailable()) {
2595
+ console.warn(
2596
+ `Matching on meaning is off (${embeddingBackendLoadError()?.message ?? "the inference runtime could not start"}); these results come from keyword search alone. ${RUNTIME_UNAVAILABLE_ADVICE}`
2597
+ );
2598
+ } else if (!embeddingsEnabled) {
2599
+ console.warn(
2600
+ "Matching on meaning is off (indexing.embeddings_enabled / TOKEN_GOAT_EMBEDDINGS_ENABLED is disabled); these results come from keyword search alone."
2601
+ );
2602
+ } else if (preflight.status !== "ready") {
2603
+ console.warn(
2604
+ `Matching on meaning is degraded (${preflight.summary}); these results come from keyword search alone.${preflight.actionRequired ? ` Action: ${preflight.actionRequired}` : ""}`
2605
+ );
2606
+ }
2607
+ const overFetchForMerge = Math.min(MAX_OVER_FETCH, n * OVER_FETCH_FACTOR);
2608
+ let rawHits = [];
2609
+ let searchSemanticError = null;
2610
+ const deferred = embeddingsEnabled && foregroundDownloadDeferred();
2611
+ if (deferred && !modelDownloadHeld()) {
2612
+ ensureWorkerAlive();
2613
+ console.warn(`The embedding model is not downloaded yet. This process would go around the proxy in HTTPS_PROXY to fetch it, so the background worker downloads it through the proxy instead; these results come from keyword search alone until then. To download it now, run \`${WARM_COMMAND}\`.`);
2614
+ }
2615
+ if (embeddingsEnabled && !deferred) {
2616
+ try {
2617
+ rawHits = await searchSemantic(
2618
+ getDb(globalDbPath()),
2619
+ query,
2620
+ overFetchForMerge,
2621
+ void 0,
2622
+ void 0,
2623
+ rootDir
2624
+ );
2625
+ } catch (e) {
2626
+ searchSemanticError = extractErrorMessage(e);
2627
+ if (preflight.status === "ready") {
2628
+ console.warn(
2629
+ `Matching on meaning is off (${searchSemanticError}); these results come from keyword search alone.`
2630
+ );
2631
+ }
2632
+ }
2633
+ }
2634
+ let floorNearestRejected = null;
2635
+ if (rawHits.length > 0) {
2636
+ const floor = loadConfig().semantic.max_distance;
2637
+ const { kept, nearestRejected } = applyRelevanceFloor(rawHits, floor);
2638
+ floorNearestRejected = nearestRejected;
2639
+ if (kept.length === 0 && nearestRejected !== null) {
2640
+ console.warn(
2641
+ `Matching on meaning found nothing within ${floor} (closest was ${nearestRejected.toFixed(3)}); these results come from keyword search alone. Raise semantic.max_distance to see weaker matches.`
2642
+ );
2643
+ }
2644
+ rawHits = kept;
2645
+ }
2646
+ const closestDense = rawHits.reduce((best, h) => best === null || h.distance < best ? h.distance : best, null);
2647
+ const weakDistance = loadConfig().semantic.weak_distance;
2648
+ const weakClosestDistance = closestDense !== null && closestDense > weakDistance ? closestDense : null;
2649
+ if (closestDense !== null || preflight.status === "ready" && !searchSemanticError) {
2650
+ recordSemanticQuery({ projectRoot: rootDir, closestDistance: closestDense, floorRejectedMin: floorNearestRejected, weak: weakClosestDistance !== null });
2651
+ }
2652
+ if (rawHits.length === 0 && floorNearestRejected === null && isAvailable() && preflight.status === "ready" && !searchSemanticError) {
2653
+ try {
2654
+ const enabled = loadConfig().indexing.embeddings_enabled;
2655
+ const { indexedFiles, embeddedFiles } = enabled ? getEmbeddingCoverage(globalDbPath(), rootDir) : { indexedFiles: 0, embeddedFiles: 0 };
2656
+ if (indexedFiles > 0 && embeddedFiles < indexedFiles) {
2657
+ console.warn(
2658
+ `Matching on meaning found nothing, and only ${embeddedFiles} of ${indexedFiles} indexed file(s) in this project have embeddings \u2014 these results come from keyword search alone. Run 'token-goat doctor' for why.`
2659
+ );
2660
+ }
2661
+ } catch {
2662
+ }
2663
+ }
2664
+ const mergedHits = mergeNearbyHits(rawHits);
2665
+ const overFetchFts = Math.min(MAX_OVER_FETCH, n * OVER_FETCH_FACTOR);
2666
+ const ftsRows = searchSymbolsFts(query, overFetchFts, void 0, rootDir);
2667
+ const fused = /* @__PURE__ */ new Map();
2668
+ const fileLines = /* @__PURE__ */ new Map();
2669
+ const linesOf = (p) => {
2670
+ if (!fileLines.has(p)) {
2671
+ const text2 = readFileText(p);
2672
+ fileLines.set(p, text2 === null ? null : text2.split(/\r?\n/));
2673
+ }
2674
+ return fileLines.get(p) ?? null;
2675
+ };
2676
+ mergedHits.forEach((h, denseRank) => {
2677
+ const enclosing = resolveEnclosingSymbol(h.filePath, h.startLine, h.endLine, linesOf);
2678
+ const key = enclosing !== null ? `${h.filePath}::${enclosing.name}@${enclosing.lineStart}` : `${h.filePath}::L${h.startLine}`;
2679
+ if (fused.has(key)) return;
2680
+ fused.set(key, {
2681
+ filePath: h.filePath,
2682
+ startLine: h.startLine,
2683
+ endLine: h.endLine,
2684
+ name: enclosing?.name ?? null,
2685
+ kind: enclosing?.kind ?? null,
2686
+ distance: h.distance,
2687
+ previewText: h.text,
2688
+ rrf: 1 / (RRF_K + denseRank),
2689
+ inDense: true,
2690
+ inLexical: false
2691
+ });
2692
+ });
2693
+ ftsRows.forEach((s, ftsRank) => {
2694
+ const key = `${s.filePath}::${s.name}@${s.lineStart}`;
2695
+ const existing = fused.get(key);
2696
+ if (existing !== void 0) {
2697
+ existing.rrf += 1 / (RRF_K + ftsRank);
2698
+ existing.inLexical = true;
2699
+ } else {
2700
+ fused.set(key, {
2701
+ filePath: s.filePath,
2702
+ startLine: s.lineStart,
2703
+ endLine: s.lineEnd,
2704
+ name: s.name,
2705
+ kind: s.kind,
2706
+ distance: null,
2707
+ previewText: s.body,
2708
+ rrf: 1 / (RRF_K + ftsRank),
2709
+ inDense: false,
2710
+ inLexical: true
2711
+ });
2712
+ }
2713
+ });
2714
+ const fusedHits = Array.from(fused.values()).sort((a, b) => b.rrf - a.rrf);
2715
+ const matchesGrep = opts.grep !== void 0 ? compileGrepMatcher(opts.grep) : void 0;
2716
+ const preFilterCount = fusedHits.length;
2717
+ const keepHit = (h) => {
2718
+ if (opts.excludeTests === true && isTestFile(h.filePath)) return false;
2719
+ return matchesGrep === void 0 || matchesGrep(toDisplayPath(rootDir, h.filePath));
2720
+ };
2721
+ const anyFilter = matchesGrep !== void 0 || opts.excludeTests === true;
2722
+ const filteredHits = anyFilter ? fusedHits.filter(keepHit) : fusedHits;
2723
+ const suppressedTotal = opts.excludeTests === true ? fusedHits.filter((h) => (matchesGrep === void 0 || matchesGrep(toDisplayPath(rootDir, h.filePath))) && isTestFile(h.filePath)).length : 0;
2724
+ const eligibleCount = filteredHits.length;
2725
+ const hits = filteredHits.slice(0, n);
2726
+ const candidatesClipped = rawHits.length >= overFetchForMerge || ftsRows.length >= overFetchFts;
2727
+ const hadDense = mergedHits.length > 0;
2728
+ const hadFts = ftsRows.length > 0;
2729
+ const source = hadDense && hadFts ? "hybrid" : hadDense ? "embeddings" : "fts";
2730
+ warnIfFilesStale(hits.map((h) => h.filePath), "semantic");
2731
+ if (hits.length > 0) {
2732
+ if (opts.json === true) {
2733
+ const items = hits.map((h, i) => ({
2734
+ filePath: toDisplayPath(rootDir, h.filePath),
2735
+ name: h.name,
2736
+ kind: h.kind,
2737
+ startLine: h.startLine,
2738
+ endLine: h.endLine,
2739
+ rank: i + 1,
2740
+ rrf: h.rrf,
2741
+ retrieval: h.inDense && h.inLexical ? "both" : h.inDense ? "dense" : "lexical",
2742
+ distance: h.distance,
2743
+ preview: previewLines(h.previewText, 3)
2744
+ }));
2745
+ const capped = guardJsonRows(items);
2746
+ const limitTruncated = hits.length < eligibleCount;
2747
+ const text3 = displaySafeJson({
2748
+ source,
2749
+ ...capped,
2750
+ truncated: capped.truncated || limitTruncated,
2751
+ totalCount: eligibleCount,
2752
+ ...candidatesClipped ? { totalCountIsFloor: true } : {},
2753
+ ...weakClosestDistance !== null ? { lowConfidence: { closestDistance: weakClosestDistance, threshold: weakDistance } } : {},
2754
+ ...semanticDegradedFields(preflight, searchSemanticError)
2755
+ });
2756
+ recordReadStat("semantic_search", largestFileSize(hits.map((h) => h.filePath)), text3, query);
2757
+ return { text: text3, code: 0 };
2758
+ }
2759
+ const blocks = hits.map((h, i) => {
2760
+ const mark = h.inDense && h.inLexical ? ", +keyword" : "";
2761
+ if (h.distance !== null) {
2762
+ const suffix = h.name !== null ? ` \u2014 inside ${h.name} (${h.kind})` : "";
2763
+ return `# ${i + 1}. ${toDisplayPath(rootDir, h.filePath)}:${h.startLine}-${h.endLine} (distance ${h.distance.toFixed(3)}${mark})${suffix}
2764
+ ${previewLines(h.previewText, 3)}`;
2765
+ }
2766
+ return `# ${i + 1}. ${h.name} (${h.kind}) \u2014 ${toDisplayPath(rootDir, h.filePath)}:${h.startLine}-${h.endLine} (keyword)
2767
+ ${previewLines(h.previewText, 3)}`;
2768
+ });
2769
+ const text2 = guardText(blocks.join("\n\n"), "semantic");
2770
+ if (hits.length < eligibleCount) {
2771
+ emitErr(`Showing ${hits.length} of ${candidatesClipped ? "at least " : ""}${countNoun(eligibleCount, "match", "matches")} (raise --limit to see the rest).`);
2772
+ }
2773
+ if (weakClosestDistance !== null) {
2774
+ console.warn(
2775
+ `Matching on meaning found nothing close for '${query}' (closest was ${weakClosestDistance.toFixed(3)}, weak above ${weakDistance}); these results may be unrelated. For a known name try token-goat symbol --grep <pattern> or rg, or rephrase the query.`
2776
+ );
2777
+ }
2778
+ recordReadStat("semantic_search", largestFileSize(hits.map((h) => h.filePath)), text2, query);
2779
+ return { text: text2, code: 0 };
2780
+ }
2781
+ if (matchesGrep !== void 0 && preFilterCount > 0) {
2782
+ const notice = grepFilteredToEmptyNotice(preFilterCount, opts.grep ?? "", "match", "matches");
2783
+ if (opts.json === true) {
2784
+ const payload = { source: "fts", items: [], truncated: false, totalCount: 0, grepFilteredToEmpty: true, hint: notice.trim() };
2785
+ return { text: displaySafeJson(payload), code: 0 };
2786
+ }
2787
+ return { text: `token-goat: ${notice.trim()}`, code: 0 };
2788
+ }
2789
+ if (opts.excludeTests === true && suppressedTotal > 0) {
2790
+ const notice = `no non-test matches for '${query}' (${excludeTestsHiddenNote(suppressedTotal)})`;
2791
+ if (opts.json === true) {
2792
+ const payload = { source: "fts", items: [], truncated: false, totalCount: 0, excludeTestsFilteredToEmpty: true, hint: notice };
2793
+ return { text: displaySafeJson(payload), code: 0 };
2794
+ }
2795
+ return { text: `token-goat: ${notice}`, code: 0 };
2796
+ }
2797
+ if (!anyFilter) {
2798
+ const evidenceHits = await searchEvidenceSemantically(rootDir, query, n);
2799
+ if (evidenceHits.length > 0) {
2800
+ const evidenceFullBytes = evidenceHits.reduce((sum, entry) => sum + deliveredOutputBytes(Buffer.byteLength(entry.text, "utf8")), 0);
2801
+ if (opts.json === true) {
2802
+ const items = evidenceHits.map((entry) => ({
2803
+ source: toDisplayPath(rootDir, entry.source),
2804
+ representation: entry.representation,
2805
+ preview: previewLines(entry.text, 3),
2806
+ cachedAt: entry.createdAt
2807
+ }));
2808
+ const capped = guardJsonRows(items);
2809
+ const text3 = displaySafeJson({ source: "workspace-evidence", ...capped });
2810
+ recordReadStat("semantic_search", evidenceFullBytes, text3, query);
2811
+ return { text: text3, code: 0 };
2812
+ }
2813
+ const text2 = guardText(
2814
+ evidenceHits.map((entry) => `# cached ${entry.representation} evidence \u2014 ${toDisplayPath(rootDir, entry.source)}
2815
+ ${previewLines(entry.text, 3)}`).join("\n\n"),
2816
+ "semantic"
2817
+ );
2818
+ recordReadStat("semantic_search", evidenceFullBytes, text2, query);
2819
+ return { text: text2, code: 0 };
2820
+ }
2821
+ }
2822
+ const indexEmpty = isIndexEmptyForProject(globalDbPath(), rootDir);
2823
+ if (opts.json === true) {
2824
+ let symFound = false;
2825
+ const trimmedQuery2 = query.trim();
2826
+ const isIdentifier2 = /^[A-Za-z0-9_.:-]+$/.test(trimmedQuery2);
2827
+ if (!indexEmpty && isIdentifier2) {
2828
+ try {
2829
+ const symMatches = querySymbols({ name: trimmedQuery2, rootDir, limit: 1 });
2830
+ if (symMatches.length > 0) {
2831
+ symFound = true;
2832
+ }
2833
+ } catch {
2834
+ }
2835
+ }
2836
+ const payload = indexEmpty ? {
2837
+ source: "fts",
2838
+ items: [],
2839
+ truncated: false,
2840
+ totalCount: 0,
2841
+ indexEmpty: true,
2842
+ hint: emptyIndexMessage(rootDir),
2843
+ ...semanticDegradedFields(preflight, searchSemanticError)
2844
+ } : {
2845
+ source: "fts",
2846
+ items: [],
2847
+ truncated: false,
2848
+ totalCount: 0,
2849
+ ...semanticDegradedFields(preflight, searchSemanticError),
2850
+ ...!indexEmpty && isIdentifier2 ? {
2851
+ symbolSuggestion: `token-goat symbol "${trimmedQuery2}"`,
2852
+ ...symFound ? { indexedSymbolFound: true } : {}
2853
+ } : {}
2854
+ };
2855
+ const text2 = displaySafeJson(payload);
2856
+ return { text: text2, code: 1 };
2857
+ }
2858
+ let text = indexEmpty ? `token-goat: no matches for '${query}'
2859
+ ${emptyIndexMessage(rootDir)}` : `token-goat: no matches for '${query}'`;
2860
+ const trimmedQuery = query.trim();
2861
+ const isIdentifier = /^[A-Za-z0-9_.:-]+$/.test(trimmedQuery);
2862
+ if (!indexEmpty && isIdentifier) {
2863
+ let symFound = false;
2864
+ try {
2865
+ const symMatches = querySymbols({ name: trimmedQuery, rootDir, limit: 1 });
2866
+ if (symMatches.length > 0) {
2867
+ symFound = true;
2868
+ }
2869
+ } catch {
2870
+ }
2871
+ if (symFound) {
2872
+ text += `
2873
+ (note: '${trimmedQuery}' is an indexed symbol name; use: token-goat symbol "${trimmedQuery}")`;
2874
+ } else {
2875
+ text += `
2876
+ Try: token-goat symbol "${trimmedQuery}"`;
2877
+ }
2878
+ }
2879
+ if (preflight.status !== "ready") {
2880
+ text += `
2881
+ (note: semantic indexing is degraded [${preflight.status}]: ${preflight.summary}${preflight.actionRequired ? ` \u2014 ${preflight.actionRequired}` : ""})`;
2882
+ } else if (searchSemanticError !== null) {
2883
+ text += `
2884
+ (note: semantic matching degraded: ${searchSemanticError}; results come from keyword search alone)`;
2885
+ }
2886
+ return { text, code: 1 };
2887
+ }
2888
+ async function runSemanticMulti(queries, opts) {
2889
+ const laterOpts = { ...opts };
2890
+ delete laterOpts.warm;
2891
+ const blocks = [];
2892
+ const entries = [];
2893
+ let anyOk = false;
2894
+ for (const [i, query] of queries.entries()) {
2895
+ const sub = await runSemantic(query, i === 0 ? opts : laterOpts);
2896
+ if (sub.code === 0) anyOk = true;
2897
+ if (opts.json === true) {
2898
+ entries.push({ query, ...JSON.parse(sub.text) });
2899
+ continue;
2900
+ }
2901
+ blocks.push(`'${query}':
2902
+ ${sub.text}`);
2903
+ }
2904
+ return { text: opts.json === true ? displaySafeJson(entries) : blocks.join("\n\n"), code: anyOk ? 0 : 1 };
2905
+ }
2906
+
2907
+ // src/read_refs.ts
2908
+ init_define_import_meta_env();
2909
+ function refsDisplayPath(p) {
2910
+ return toDisplayPath(getDisplayRoot(), p);
2911
+ }
2912
+ function goneSuffix(filePath) {
2913
+ return fileIsGone(filePath) ? ` ${DELETED_TAG}` : "";
2914
+ }
2915
+ function refGrepFilter(grep) {
2916
+ if (grep === void 0) return void 0;
2917
+ const matches = compileGrepMatcher(grep);
2918
+ return (r) => matches(refsDisplayPath(r.filePath));
2919
+ }
2920
+ function refsJsonItems(items, contextLines) {
2921
+ return withContextLines(items, contextLines).map((r) => ({ ...r, filePath: refsDisplayPath(r.filePath), ...fileIsGone(r.filePath) ? { deleted: true } : {} }));
2922
+ }
2923
+ function renderRefLines(ref, contextLines, indent = " ") {
2924
+ const displayPath = refsDisplayPath(ref.filePath);
2925
+ const base = `${indent}${displaySafeText(displayPath)}:${ref.line}: ${displaySafeText(ref.context)}${goneSuffix(ref.filePath)}`;
2926
+ const window = buildContextWindow(ref.filePath, ref.line, contextLines);
2927
+ if (window === null) return [base];
2928
+ return [base, ...renderContextWindow(displayPath, ref.line, window, "", `${indent} `)];
2929
+ }
2930
+ function withContextLines(items, contextLines) {
2931
+ if (!(contextLines > 0)) return items;
2932
+ return items.map((r) => ({ ...r, contextLines: buildContextWindow(r.filePath, r.line, contextLines) ?? [] }));
2933
+ }
2934
+ function applyTypedRefsTier(symName, file, results, qualifiedDef) {
2935
+ if (results.length === 0) return results;
2936
+ try {
2937
+ const symbolQueryOpts = { name: symName, limit: 2 };
2938
+ if (file !== void 0) symbolQueryOpts.filePath = file;
2939
+ const defs = qualifiedDef !== void 0 ? [qualifiedDef] : querySymbols(symbolQueryOpts);
2940
+ if (defs.length !== 1) return results;
2941
+ return typedRefsForDef(results, defs[0]) ?? results;
2942
+ } catch {
2943
+ return results;
2944
+ }
2945
+ }
2946
+ function parseMultiRefsSpec(spec) {
2947
+ const colonIdx = findSpecSeparator(spec);
2948
+ const file = colonIdx === -1 ? void 0 : spec.slice(0, colonIdx);
2949
+ const symPart = colonIdx === -1 ? spec : spec.slice(colonIdx + 2);
2950
+ const symbols = symPart.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
2951
+ return { file, symbols };
2952
+ }
2953
+ function refsRootDir(opts) {
2954
+ return opts.projectRoot ?? confinedProjectRoot() ?? void 0;
2955
+ }
2956
+ function refsSpecFiles(spec) {
2957
+ const crossFile = parseCrossFileMultiSpec(spec);
2958
+ if (crossFile !== null) return [...new Set(crossFile.map((p) => p.file))];
2959
+ const { file } = parseMultiRefsSpec(spec);
2960
+ return file === void 0 ? [] : [file];
2961
+ }
2962
+ function collectRefs(requestedName, defFile, opts, resolvedDef) {
2963
+ const qualifiedDef = resolvedDef ?? (defFile !== void 0 && requestedName.includes(".") ? resolveQualifiedSpecDef(`${defFile}::${requestedName}`, opts.projectRoot ?? process.cwd()) : void 0);
2964
+ const symName = qualifiedDef?.name ?? requestedName;
2965
+ const queryOpts = { name: symName };
2966
+ const defFileHint = defFile !== void 0 ? resolveSpecPath(defFile, opts.projectRoot ?? process.cwd()) : void 0;
2967
+ if (opts.excludeTests === true || opts.grep !== void 0) queryOpts.limit = UNBOUNDED_QUERY_LIMIT;
2968
+ else if (opts.limit !== void 0) queryOpts.limit = opts.limit;
2969
+ else if (opts.top !== void 0) queryOpts.limit = UNBOUNDED_QUERY_LIMIT;
2970
+ const rootDir = refsRootDir(opts);
2971
+ if (rootDir !== void 0) queryOpts.rootDir = rootDir;
2972
+ if (defFileHint !== void 0 && !fileIsGone(defFileHint)) healStaleIndex(defFileHint);
2973
+ let scanned = queryRefs(queryOpts);
2974
+ if (healStaleResultFiles(scanned.map((r) => r.filePath)).healed) scanned = queryRefs(queryOpts);
2975
+ const preScanCount = scanned.length;
2976
+ const scanLimit = queryOpts.limit ?? DEFAULT_QUERY_LIMIT;
2977
+ let results = applyTypedRefsTier(symName, defFileHint, scanned, qualifiedDef);
2978
+ const typedFilterDropped = results.length < scanned.length;
2979
+ let suppressed = 0;
2980
+ if (opts.excludeTests === true) {
2981
+ const f = applyExcludeTestsFilter(results);
2982
+ suppressed = f.suppressed;
2983
+ results = f.refs;
2984
+ }
2985
+ const preGrepCount = results.length;
2986
+ const matchesGrep = refGrepFilter(opts.grep);
2987
+ if (matchesGrep !== void 0) results = results.filter(matchesGrep);
2988
+ const clientFiltered = opts.excludeTests === true || matchesGrep !== void 0 || typedFilterDropped;
2989
+ const filteredTotal = clientFiltered ? results.length : void 0;
2990
+ results = sinkGoneRows(results, (r) => r.filePath);
2991
+ if (clientFiltered && opts.top === void 0) results = results.slice(0, opts.limit ?? 100);
2992
+ return { queryOpts, defFileHint, results, preScanCount, scanLimit, suppressed, preGrepCount, clientFiltered, filteredTotal };
2993
+ }
2994
+ function refsTotal(clientFiltered, filteredTotal, shown, countExact, preScanCount, scanLimit) {
2995
+ if (!clientFiltered) return { count: countExact(), exact: true };
2996
+ if (scanLimit < 0) return { count: filteredTotal ?? shown, exact: true };
2997
+ return { count: filteredTotal ?? shown, exact: preScanCount < scanLimit };
2998
+ }
2999
+ function refsSearchBaselineBytes(rows) {
3000
+ let total = 0;
3001
+ for (const ref of rows) total += Buffer.byteLength(`${refsDisplayPath(ref.filePath)}:${ref.line}: ${ref.context}
3002
+ `, "utf8");
3003
+ return deliveredOutputBytes(total);
3004
+ }
3005
+ function renderRefsTargets(targets, opts, {
3006
+ annotateHiddenByGrep
3007
+ }) {
3008
+ for (const { file, symbol } of targets) {
3009
+ if (file === void 0 || !symbol.includes(".")) continue;
3010
+ const qualified = resolveQualifiedSpec(`${file}::${symbol}`, opts.projectRoot ?? process.cwd());
3011
+ if (qualified?.kind !== "ambiguous") continue;
3012
+ emitErr(formatAmbiguity(qualified.symbol, qualified.file, qualified.candidates, opts.projectRoot, "refs"));
3013
+ return 1;
3014
+ }
3015
+ const jsonOut = {};
3016
+ let anyFound = false;
3017
+ const lines = [];
3018
+ const refRows = [];
3019
+ for (const { file, symbol, key } of targets) {
3020
+ const { queryOpts, results, preScanCount, scanLimit, suppressed, preGrepCount, clientFiltered, filteredTotal } = collectRefs(symbol, file, opts);
3021
+ if (results.length > 0) anyFound = true;
3022
+ refRows.push(...results);
3023
+ if (opts.json === true) {
3024
+ const hiddenByGrep = opts.grep !== void 0 ? preGrepCount - (filteredTotal ?? results.length) : 0;
3025
+ const withHidden = (payload) => ({ ...payload, ...annotateHiddenByGrep && hiddenByGrep > 0 ? { hiddenByGrep } : {} });
3026
+ if (opts.top !== void 0) {
3027
+ jsonOut[key] = withHidden(topFilesJsonPayload(results, opts.top));
3028
+ } else {
3029
+ const capped = guardJsonRows(results);
3030
+ const trueTotal = clientFiltered ? filteredTotal ?? results.length : countRefs(queryOpts);
3031
+ jsonOut[key] = withHidden({ items: refsJsonItems(capped.items, opts.context ?? 0), truncated: capped.truncated || trueTotal > results.length, totalCount: trueTotal });
3032
+ }
3033
+ continue;
3034
+ }
3035
+ if (results.length === 0) {
3036
+ if (opts.grep !== void 0 && preGrepCount > 0) {
3037
+ lines.push(`${key}: ${grepFilteredToEmptyNotice(preGrepCount, opts.grep ?? "", "reference", "references").trim()}`);
3038
+ continue;
3039
+ }
3040
+ lines.push(opts.excludeTests === true && suppressed > 0 ? `${key}: (no non-test references found; ${excludeTestsHiddenNote(suppressed)})` : `${key}: (no references found)`);
3041
+ continue;
3042
+ }
3043
+ lines.push(`${key}:`);
3044
+ if (opts.top !== void 0) {
3045
+ lines.push(...renderTopFilesSummary(results, opts.top, suppressed));
3046
+ } else if (opts.callers === true) {
3047
+ if (opts.excludeTests === true && suppressed > 0) lines.push(` ${countNoun(results.length, "reference")} (${excludeTestsHiddenNote(suppressed)})`);
3048
+ lines.push(...renderCallerGroups(results, opts.context ?? 0));
3049
+ } else {
3050
+ if (opts.excludeTests === true && suppressed > 0) lines.push(` ${countNoun(results.length, "reference")} (${excludeTestsHiddenNote(suppressed)})`);
3051
+ for (const ref of results) lines.push(...renderRefLines(ref, opts.context ?? 0));
3052
+ }
3053
+ if (opts.top === void 0) {
3054
+ const notice = truncationNotice(
3055
+ results.length,
3056
+ opts.limit ?? 100,
3057
+ () => refsTotal(clientFiltered, filteredTotal, results.length, () => countRefs(queryOpts), preScanCount, scanLimit),
3058
+ "references",
3059
+ "--limit"
3060
+ );
3061
+ if (notice !== null) lines.push(` token-goat: ${notice}`);
3062
+ }
3063
+ }
3064
+ warnIfFilesStale(refRows.map((r) => r.filePath), "refs");
3065
+ const fullSourceBytes = refsSearchBaselineBytes(refRows);
3066
+ if (opts.json === true) {
3067
+ const text2 = displaySafeJson(jsonOut);
3068
+ emit(text2);
3069
+ if (anyFound) recordReadStat("symbol_read", fullSourceBytes, text2, opts.spec);
3070
+ return anyFound ? 0 : 1;
3071
+ }
3072
+ const text = lines.join("\n");
3073
+ emitGuarded(text, "symbol");
3074
+ if (anyFound) recordReadStat("symbol_read", fullSourceBytes, text, opts.spec);
3075
+ return anyFound ? 0 : 1;
3076
+ }
3077
+ function runRefs(opts) {
3078
+ if (opts.limit !== void 0 && opts.limit <= 0) {
3079
+ emitErr(`--limit must be a positive number, got: ${opts.limit}`);
3080
+ return 1;
3081
+ }
3082
+ if (opts.top !== void 0 && opts.top <= 0) {
3083
+ emitErr(`--top must be a positive number, got: ${opts.top}`);
3084
+ return 1;
3085
+ }
3086
+ const { root: confinedRoot, denial: projectDenial } = resolveProjectConfinement(opts.projectRoot);
3087
+ if (projectDenial !== null) {
3088
+ emitErr(projectDenial);
3089
+ return 1;
3090
+ }
3091
+ if (confinedRoot !== null) {
3092
+ for (const file2 of refsSpecFiles(opts.spec)) {
3093
+ const denial = confinementRefusal("This file", resolveSpecPath(file2, opts.projectRoot ?? process.cwd()), confinedRoot);
3094
+ if (denial !== null) {
3095
+ emitErr(denial);
3096
+ return 1;
3097
+ }
3098
+ }
3099
+ }
3100
+ const crossFilePairs = parseCrossFileMultiSpec(opts.spec);
3101
+ if (crossFilePairs !== null) return runRefsCrossFile(crossFilePairs, opts);
3102
+ const { file, symbols } = parseMultiRefsSpec(opts.spec);
3103
+ if (symbols.length <= 1) return runRefsSingle(opts);
3104
+ return renderRefsTargets(
3105
+ symbols.map((symbol) => ({ file, symbol, key: symbol })),
3106
+ opts,
3107
+ { annotateHiddenByGrep: true }
3108
+ );
3109
+ }
3110
+ function runRefsCrossFile(pairs, opts) {
3111
+ const distinctFiles = new Set(pairs.map((p) => p.file));
3112
+ const keyFor = (p) => distinctFiles.size === 1 ? p.symbol : `${p.file}::${p.symbol}`;
3113
+ return renderRefsTargets(
3114
+ pairs.map((p) => ({ file: p.file, symbol: p.symbol, key: keyFor(p) })),
3115
+ opts,
3116
+ // Not annotated today; renderRefsTargets's own option doc explains why that is a preserved divergence rather than a decision.
3117
+ { annotateHiddenByGrep: false }
3118
+ );
3119
+ }
3120
+ function runRefsSingle(opts) {
3121
+ const { file, symbol } = parseReadSpec(opts.spec);
3122
+ const qualified = resolveQualifiedSpec(opts.spec, opts.projectRoot ?? process.cwd());
3123
+ if (qualified?.kind === "ambiguous") {
3124
+ emitErr(formatAmbiguity(qualified.symbol, qualified.file, qualified.candidates, opts.projectRoot, "refs"));
3125
+ return 1;
3126
+ }
3127
+ const qualifiedDef = qualified?.kind === "ok" ? qualified.entry : void 0;
3128
+ const symName = qualifiedDef?.name ?? symbol ?? file;
3129
+ const { queryOpts, defFileHint, results, preScanCount, scanLimit, suppressed, preGrepCount, clientFiltered, filteredTotal } = collectRefs(symName, symbol !== void 0 ? file : void 0, opts, qualifiedDef);
3130
+ if (results.length === 0) {
3131
+ if (opts.grep !== void 0 && preGrepCount > 0) {
3132
+ if (opts.json === true) {
3133
+ emit(displaySafeJson({ items: [], truncated: false, totalCount: 0, hiddenByGrep: preGrepCount }));
3134
+ return 0;
3135
+ }
3136
+ emit(grepFilteredToEmptyNotice(preGrepCount, opts.grep ?? "", "reference", "references"));
3137
+ return 0;
3138
+ }
3139
+ if (opts.excludeTests === true && suppressed > 0) {
3140
+ emitErr(`No non-test references found for '${symName}' (${excludeTestsHiddenNote(suppressed)})`);
3141
+ return 1;
3142
+ }
3143
+ const rootDir = opts.projectRoot ?? resolveProjectRoot({ project: process.cwd() });
3144
+ const defRows = querySymbols({ name: symName, rootDir, limit: REF_BLIND_DEF_PROBE_LIMIT });
3145
+ if (defRows.length === 0) {
3146
+ emitErr(`Symbol not found: ${symName}${unknownSymbolSuggestion(symName, rootDir)}`);
3147
+ if (opts.json !== true && isIndexEmptyForProject(globalDbPath(), rootDir)) emitErr(emptyIndexMessage(rootDir));
3148
+ return 1;
3149
+ }
3150
+ const defPaths = defFileHint !== void 0 ? [defFileHint] : defRows.map((r) => r.filePath);
3151
+ const firstDefPath = defPaths[0];
3152
+ if (firstDefPath !== void 0 && defPaths.every((fp) => !isRefIndexedFile(fp))) {
3153
+ emitErr(refBlindLanguageNotice(symName, detectLanguageOfFile(firstDefPath), refsDisplayPath(firstDefPath)));
3154
+ return 1;
3155
+ }
3156
+ const kindRows = defFileHint !== void 0 ? querySymbols({ name: symName, filePath: defFileHint, rootDir, limit: REF_BLIND_DEF_PROBE_LIMIT }) : defRows;
3157
+ const kindVerdict = refBlindKindVerdict(kindRows);
3158
+ if (kindVerdict.allBlind) {
3159
+ emitErr(refBlindKindNotice(symName, kindVerdict.blindKinds));
3160
+ return 1;
3161
+ }
3162
+ emitErr(`No references found for '${symName}'`);
3163
+ if (kindVerdict.blindCount > 0) emitErr(refBlindKindPartialNote(symName, kindVerdict.blindKinds, kindVerdict.blindCount, kindRows.length));
3164
+ if (opts.json !== true) {
3165
+ if (isIndexEmptyForProject(globalDbPath(), rootDir)) emitErr(emptyIndexMessage(rootDir));
3166
+ }
3167
+ return 1;
3168
+ }
3169
+ warnIfFilesStale(results.map((r) => r.filePath), "refs");
3170
+ const fullSourceBytes = refsSearchBaselineBytes(results);
3171
+ if (opts.json === true) {
3172
+ let payload;
3173
+ if (opts.top !== void 0) {
3174
+ payload = topFilesJsonPayload(results, opts.top);
3175
+ } else {
3176
+ const capped = guardJsonRows(results);
3177
+ const trueTotal = clientFiltered ? filteredTotal ?? results.length : countRefs(queryOpts);
3178
+ payload = { items: refsJsonItems(capped.items, opts.context ?? 0), truncated: capped.truncated || trueTotal > results.length, totalCount: trueTotal };
3179
+ }
3180
+ const hiddenByGrep = opts.grep !== void 0 ? preGrepCount - (filteredTotal ?? results.length) : 0;
3181
+ const text2 = displaySafeJson({ ...payload, ...hiddenByGrep > 0 ? { hiddenByGrep } : {} });
3182
+ emit(text2);
3183
+ recordReadStat("symbol_read", fullSourceBytes, text2, symName);
3184
+ return 0;
3185
+ }
3186
+ const lines = opts.top !== void 0 ? renderTopFilesSummary(results, opts.top, suppressed) : opts.callers === true ? [...opts.excludeTests === true && suppressed > 0 ? [`${countNoun(results.length, "reference")} (${excludeTestsHiddenNote(suppressed)})`] : [], ...renderCallerGroups(results, opts.context ?? 0)] : [...opts.excludeTests === true && suppressed > 0 ? [`${countNoun(results.length, "reference")} (${excludeTestsHiddenNote(suppressed)})`] : [], ...results.flatMap((ref) => renderRefLines(ref, opts.context ?? 0, ""))];
3187
+ const refsFooter = opts.top !== void 0 ? "" : truncationFooter(results.length, opts.limit ?? 100, () => refsTotal(clientFiltered, filteredTotal, results.length, () => countRefs(queryOpts), preScanCount, scanLimit), "references", "--limit");
3188
+ const text = lines.join("\n");
3189
+ emit(guardText(text, "symbol") + refsFooter);
3190
+ recordReadStat("symbol_read", fullSourceBytes, text + refsFooter, symName);
3191
+ return 0;
3192
+ }
3193
+ function applyExcludeTestsFilter(refs) {
3194
+ const filtered = refs.filter((r) => !isTestFile(r.filePath));
3195
+ return { refs: filtered, suppressed: refs.length - filtered.length };
3196
+ }
3197
+ function groupRefsByFile(refs) {
3198
+ const byFile = /* @__PURE__ */ new Map();
3199
+ for (const ref of refs) byFile.set(ref.filePath, (byFile.get(ref.filePath) ?? 0) + 1);
3200
+ return [...byFile.entries()].map(([file, count]) => ({ file, count })).sort((a, b) => b.count - a.count || (a.file < b.file ? -1 : a.file > b.file ? 1 : 0));
3201
+ }
3202
+ function renderTopFilesSummary(refs, topN, suppressed) {
3203
+ const grouped = groupRefsByFile(refs);
3204
+ const shown = grouped.slice(0, topN);
3205
+ const suppressedNote = suppressed !== void 0 && suppressed > 0 ? ` (${excludeTestsHiddenNote(suppressed)})` : "";
3206
+ const lines = [`${countNoun(refs.length, "reference")} across ${countNoun(grouped.length, "file")} (showing top ${shown.length})${suppressedNote}`];
3207
+ for (const { file, count } of shown) lines.push(` ${count} ${refsDisplayPath(file)}${goneSuffix(file)}`);
3208
+ const omittedFiles = grouped.length - shown.length;
3209
+ if (omittedFiles > 0) {
3210
+ const shownRefs = shown.reduce((sum, g) => sum + g.count, 0);
3211
+ lines.push(` ...(${omittedFiles} more files, ${refs.length - shownRefs} more references elided; use a higher --top to see more)`);
3212
+ }
3213
+ return lines;
3214
+ }
3215
+ function topFilesJsonPayload(refs, topN) {
3216
+ const grouped = groupRefsByFile(refs);
3217
+ const shown = grouped.slice(0, topN);
3218
+ return { fileCounts: shown.map((g) => ({ ...g, file: refsDisplayPath(g.file), ...fileIsGone(g.file) ? { deleted: true } : {} })), totalFiles: grouped.length, totalRefs: refs.length, shown: shown.length };
3219
+ }
3220
+ function renderCallerGroups(refs, contextLines = 0) {
3221
+ const byFile = /* @__PURE__ */ new Map();
3222
+ for (const ref of refs) {
3223
+ const bucket = byFile.get(ref.filePath);
3224
+ if (bucket !== void 0) {
3225
+ bucket.push(ref);
3226
+ } else {
3227
+ byFile.set(ref.filePath, [ref]);
3228
+ }
3229
+ }
3230
+ const lines = [];
3231
+ for (const [file, fileRefs] of byFile) {
3232
+ const displayPath = refsDisplayPath(file);
3233
+ lines.push(`${displaySafeText(displayPath)}:${goneSuffix(file)}`);
3234
+ for (const ref of fileRefs) {
3235
+ lines.push(` :${ref.line} ${ref.context !== "" ? displaySafeText(ref.context) : "(module scope)"}`);
3236
+ const window = buildContextWindow(file, ref.line, contextLines);
3237
+ if (window !== null) lines.push(...renderContextWindow(displayPath, ref.line, window, "", " "));
3238
+ }
3239
+ }
3240
+ return lines;
3241
+ }
3242
+
3243
+ // src/read_git.ts
3244
+ init_define_import_meta_env();
3245
+ import * as fs3 from "node:fs";
3246
+ import * as path3 from "node:path";
3247
+
3248
+ // src/conflict_query.ts
3249
+ init_define_import_meta_env();
3250
+ var OURS_RE = /^<{7}(?:\s+(.*))?$/;
3251
+ var BASE_RE = /^\|{7}(?:\s+(.*))?$/;
3252
+ var SEP_RE = /^={7}\s*$/;
3253
+ var THEIRS_RE = /^>{7}(?:\s+(.*))?$/;
3254
+ function parseConflicts(filePath, text, contextLines = 3) {
3255
+ const lines = text.split(/\r?\n/);
3256
+ const regions = [];
3257
+ const warnings = [];
3258
+ let state = "none";
3259
+ let startLine = 0;
3260
+ let oursLabel = "";
3261
+ let oursLines = [];
3262
+ let baseLabel = "";
3263
+ let baseLines = [];
3264
+ let sawBase = false;
3265
+ let theirsLines = [];
3266
+ const resetRegion = () => {
3267
+ state = "none";
3268
+ oursLabel = "";
3269
+ oursLines = [];
3270
+ baseLabel = "";
3271
+ baseLines = [];
3272
+ sawBase = false;
3273
+ theirsLines = [];
3274
+ };
3275
+ for (let i = 0; i < lines.length; i++) {
3276
+ const line = lines[i] ?? "";
3277
+ const lineNo = i + 1;
3278
+ const oursMatch = OURS_RE.exec(line);
3279
+ if (oursMatch !== null) {
3280
+ if (state !== "none") {
3281
+ warnings.push({
3282
+ filePath,
3283
+ line: startLine,
3284
+ message: `Unterminated conflict marker starting at line ${startLine} -- found a new '<<<<<<<' at line ${lineNo} before a matching '=======' / '>>>>>>>'`
3285
+ });
3286
+ }
3287
+ startLine = lineNo;
3288
+ oursLabel = (oursMatch[1] ?? "").trim();
3289
+ oursLines = [];
3290
+ baseLabel = "";
3291
+ baseLines = [];
3292
+ sawBase = false;
3293
+ theirsLines = [];
3294
+ state = "ours";
3295
+ continue;
3296
+ }
3297
+ if (state === "none") continue;
3298
+ if (state === "ours") {
3299
+ const baseMatch = BASE_RE.exec(line);
3300
+ if (baseMatch !== null) {
3301
+ baseLabel = (baseMatch[1] ?? "").trim();
3302
+ baseLines = [];
3303
+ sawBase = true;
3304
+ state = "base";
3305
+ continue;
3306
+ }
3307
+ }
3308
+ if ((state === "ours" || state === "base") && SEP_RE.test(line)) {
3309
+ theirsLines = [];
3310
+ state = "theirs";
3311
+ continue;
3312
+ }
3313
+ if (state === "theirs") {
3314
+ const theirsMatch = THEIRS_RE.exec(line);
3315
+ if (theirsMatch !== null) {
3316
+ const theirsLabel = (theirsMatch[1] ?? "").trim();
3317
+ const beforeContext = [];
3318
+ if (contextLines > 0) {
3319
+ const ctxStart = Math.max(0, startLine - 1 - contextLines);
3320
+ for (let c = ctxStart; c < startLine - 1; c++) {
3321
+ beforeContext.push({ line: c + 1, text: lines[c] ?? "" });
3322
+ }
3323
+ }
3324
+ const afterContext = [];
3325
+ if (contextLines > 0) {
3326
+ const ctxEnd = Math.min(lines.length, lineNo + contextLines);
3327
+ for (let c = lineNo; c < ctxEnd; c++) {
3328
+ afterContext.push({ line: c + 1, text: lines[c] ?? "" });
3329
+ }
3330
+ }
3331
+ regions.push({
3332
+ filePath,
3333
+ lineStart: startLine,
3334
+ lineEnd: lineNo,
3335
+ ours: { label: oursLabel, content: oursLines.join("\n") },
3336
+ base: sawBase ? { label: baseLabel, content: baseLines.join("\n") } : null,
3337
+ theirs: { label: theirsLabel, content: theirsLines.join("\n") },
3338
+ ...beforeContext.length > 0 ? { beforeContext } : {},
3339
+ ...afterContext.length > 0 ? { afterContext } : {}
3340
+ });
3341
+ resetRegion();
3342
+ continue;
3343
+ }
3344
+ }
3345
+ if (state === "ours") oursLines.push(line);
3346
+ else if (state === "base") baseLines.push(line);
3347
+ else if (state === "theirs") theirsLines.push(line);
3348
+ }
3349
+ if (state !== "none") {
3350
+ warnings.push({
3351
+ filePath,
3352
+ line: startLine,
3353
+ message: `Unterminated conflict marker starting at line ${startLine} -- reached end of file without a matching '>>>>>>>'`
3354
+ });
3355
+ }
3356
+ return { filePath, regions, warnings };
3357
+ }
3358
+ function summarizeFileConflicts(result) {
3359
+ return {
3360
+ filePath: result.filePath,
3361
+ conflictCount: result.regions.length,
3362
+ regions: result.regions.map((r) => ({
3363
+ lineStart: r.lineStart,
3364
+ lineEnd: r.lineEnd,
3365
+ oursLabel: r.ours.label,
3366
+ baseLabel: r.base !== null ? r.base.label : null,
3367
+ theirsLabel: r.theirs.label
3368
+ })),
3369
+ warnings: result.warnings
3370
+ };
3371
+ }
3372
+ function indentBlock(content) {
3373
+ if (content === "") return "";
3374
+ return content.split("\n").map((l) => ` ${l}`).join("\n");
3375
+ }
3376
+ function formatSingleFileConflicts(result) {
3377
+ const lines = [];
3378
+ if (result.regions.length > 0) {
3379
+ lines.push(`${displaySafeText(result.filePath)} -- ${result.regions.length} conflict${result.regions.length === 1 ? "" : "s"}`);
3380
+ for (const r of result.regions) {
3381
+ lines.push(` lines ${r.lineStart}-${r.lineEnd}`);
3382
+ if (r.beforeContext && r.beforeContext.length > 0) {
3383
+ for (const c of r.beforeContext) {
3384
+ lines.push(` ${c.line} | ${displaySafeText(c.text)}`);
3385
+ }
3386
+ }
3387
+ lines.push(` <<<<<<< ${displaySafeText(r.ours.label || "(ours)")}`);
3388
+ lines.push(indentBlock(r.ours.content));
3389
+ if (r.base !== null) {
3390
+ lines.push(` ||||||| ${displaySafeText(r.base.label || "(base)")}`);
3391
+ lines.push(indentBlock(r.base.content));
3392
+ }
3393
+ lines.push(" =======");
3394
+ lines.push(indentBlock(r.theirs.content));
3395
+ lines.push(` >>>>>>> ${displaySafeText(r.theirs.label || "(theirs)")}`);
3396
+ if (r.afterContext && r.afterContext.length > 0) {
3397
+ for (const c of r.afterContext) {
3398
+ lines.push(` ${c.line} | ${displaySafeText(c.text)}`);
3399
+ }
3400
+ }
3401
+ }
3402
+ } else {
3403
+ lines.push(`${displaySafeText(result.filePath)} -- no conflicts`);
3404
+ }
3405
+ if (result.warnings.length > 0) {
3406
+ lines.push(" warnings:");
3407
+ for (const w of result.warnings) lines.push(` line ${w.line}: ${displaySafeText(w.message)}`);
3408
+ }
3409
+ return lines.join("\n");
3410
+ }
3411
+ function formatConflicts(results) {
3412
+ const withData = results.filter((r) => r.regions.length > 0 || r.warnings.length > 0);
3413
+ if (withData.length === 0) return "No conflicts found.";
3414
+ return withData.map(formatSingleFileConflicts).join("\n\n");
3415
+ }
3416
+ function formatSingleFileSummary(summary) {
3417
+ const lines = [];
3418
+ if (summary.conflictCount > 0) {
3419
+ lines.push(`${displaySafeText(summary.filePath)} -- ${summary.conflictCount} conflict${summary.conflictCount === 1 ? "" : "s"}`);
3420
+ for (const r of summary.regions) {
3421
+ const baseText = r.baseLabel !== null ? ` base=${displaySafeText(r.baseLabel || "(unlabeled)")}` : "";
3422
+ lines.push(` lines ${r.lineStart}-${r.lineEnd} ours=${displaySafeText(r.oursLabel || "(unlabeled)")}${baseText} theirs=${displaySafeText(r.theirsLabel || "(unlabeled)")}`);
3423
+ }
3424
+ } else {
3425
+ lines.push(`${displaySafeText(summary.filePath)} -- no conflicts`);
3426
+ }
3427
+ if (summary.warnings.length > 0) {
3428
+ lines.push(" warnings:");
3429
+ for (const w of summary.warnings) lines.push(` line ${w.line}: ${displaySafeText(w.message)}`);
3430
+ }
3431
+ return lines.join("\n");
3432
+ }
3433
+ function formatConflictSummaries(summaries) {
3434
+ const withData = summaries.filter((s) => s.conflictCount > 0 || s.warnings.length > 0);
3435
+ if (withData.length === 0) return "No conflicts found.";
3436
+ return withData.map(formatSingleFileSummary).join("\n");
3437
+ }
3438
+
3439
+ // src/read_git.ts
3440
+ function runConflicts(opts) {
3441
+ let files;
3442
+ if (opts.path === void 0) {
3443
+ files = walkProject(process.cwd()).files;
3444
+ } else {
3445
+ const abs = path3.resolve(opts.path);
3446
+ let stat;
3447
+ try {
3448
+ stat = fs3.statSync(abs);
3449
+ } catch {
3450
+ emitErr(`Could not read: ${opts.path}`);
3451
+ return 1;
3452
+ }
3453
+ files = stat.isDirectory() ? walkProject(abs).files : [abs];
3454
+ }
3455
+ const contextLines = opts.context !== void 0 ? opts.context : 3;
3456
+ const results = [];
3457
+ for (const f of files) {
3458
+ const text = readFileText(f);
3459
+ if (text === null) continue;
3460
+ const parsed = parseConflicts(f, text, contextLines);
3461
+ if (parsed.regions.length > 0 || parsed.warnings.length > 0) results.push(parsed);
3462
+ }
3463
+ const fullSourceBytes = sumFileSizes(files);
3464
+ const detail = opts.path ?? ".";
3465
+ if (opts.json === true) {
3466
+ const jsonText = displaySafeJson(opts.summary === true ? results.map(summarizeFileConflicts) : results, 0);
3467
+ emit(jsonText);
3468
+ recordReadStat("conflicts", fullSourceBytes, jsonText, detail);
3469
+ } else if (opts.summary === true) {
3470
+ const text = formatConflictSummaries(results.map(summarizeFileConflicts));
3471
+ emitGuarded(text, "conflicts");
3472
+ recordReadStat("conflicts", fullSourceBytes, text, detail);
3473
+ } else {
3474
+ const text = formatConflicts(results);
3475
+ emitGuarded(text, "conflicts");
3476
+ recordReadStat("conflicts", fullSourceBytes, text, detail);
3477
+ }
3478
+ return 0;
3479
+ }
3480
+ var HUNK_HEADER_RE = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/;
3481
+ function hunkHeaderRange(m) {
3482
+ const newStart = parseInt(m[1], 10);
3483
+ const newLines = m[2] !== void 0 ? parseInt(m[2], 10) : 1;
3484
+ return newLines === 0 ? { start: Math.max(newStart, 1), end: Math.max(newStart, 1) } : { start: newStart, end: newStart + newLines - 1 };
3485
+ }
3486
+ var C_ESCAPES = { a: 7, b: 8, t: 9, n: 10, v: 11, f: 12, r: 13, '"': 34, "\\": 92 };
3487
+ function unquoteGitPath(quoted) {
3488
+ const bytes = [];
3489
+ for (let i = 0; i < quoted.length; i++) {
3490
+ const ch = quoted.charAt(i);
3491
+ if (ch !== "\\") {
3492
+ bytes.push(...Buffer.from(ch, "utf8"));
3493
+ continue;
3494
+ }
3495
+ const next = quoted.charAt(i + 1);
3496
+ const octal = /^[0-7]{3}/.exec(quoted.slice(i + 1, i + 4));
3497
+ if (octal !== null) {
3498
+ bytes.push(parseInt(octal[0], 8));
3499
+ i += 3;
3500
+ } else if (C_ESCAPES[next] !== void 0) {
3501
+ bytes.push(C_ESCAPES[next]);
3502
+ i += 1;
3503
+ } else {
3504
+ bytes.push(92);
3505
+ }
3506
+ }
3507
+ return Buffer.from(bytes).toString("utf8");
3508
+ }
3509
+ function newSidePath(line) {
3510
+ const quoted = /^\+\+\+ "b\/(.*)"$/.exec(line);
3511
+ if (quoted !== null) return unquoteGitPath(quoted[1] ?? "");
3512
+ const plain = /^\+\+\+ b\/(.+?)\t?$/.exec(line);
3513
+ return plain === null ? null : plain[1] ?? null;
3514
+ }
3515
+ function parseDiffHunks(diffText) {
3516
+ const hunksByFile = /* @__PURE__ */ new Map();
3517
+ let currentFile = null;
3518
+ for (const line of diffText.split(/\r?\n/)) {
3519
+ if (line.startsWith("+++ ") && line !== "+++ /dev/null") {
3520
+ currentFile = newSidePath(line);
3521
+ continue;
3522
+ }
3523
+ if (line === "+++ /dev/null") {
3524
+ currentFile = null;
3525
+ continue;
3526
+ }
3527
+ const hunkMatch = HUNK_HEADER_RE.exec(line);
3528
+ if (hunkMatch !== null && currentFile !== null) {
3529
+ const range = hunkHeaderRange(hunkMatch);
3530
+ const existing = hunksByFile.get(currentFile);
3531
+ if (existing !== void 0) {
3532
+ existing.push(range);
3533
+ } else {
3534
+ hunksByFile.set(currentFile, [range]);
3535
+ }
3536
+ }
3537
+ }
3538
+ return hunksByFile;
3539
+ }
3540
+ function parseHunklessFiles(diffText) {
3541
+ const hunkless = /* @__PURE__ */ new Set();
3542
+ let path5 = null;
3543
+ let metadataOnly = false;
3544
+ let hasContent = false;
3545
+ const flush = () => {
3546
+ if (path5 !== null && metadataOnly && !hasContent) hunkless.add(path5);
3547
+ };
3548
+ for (const line of diffText.split(/\r?\n/)) {
3549
+ if (line.startsWith("diff --git ")) {
3550
+ flush();
3551
+ const header = /^diff --git "?a\/.*?"? "?b\/(.*?)"?$/.exec(line);
3552
+ path5 = header === null ? null : line.endsWith('"') ? unquoteGitPath(header[1] ?? "") : header[1] ?? null;
3553
+ metadataOnly = false;
3554
+ hasContent = false;
3555
+ } else if (line.startsWith("rename to ")) {
3556
+ const to = line.slice("rename to ".length);
3557
+ path5 = to.startsWith('"') && to.endsWith('"') && to.length > 1 ? unquoteGitPath(to.slice(1, -1)) : to;
3558
+ metadataOnly = true;
3559
+ } else if (line.startsWith("new mode ")) {
3560
+ metadataOnly = true;
3561
+ } else if (line.startsWith("+++ ") || line.startsWith("Binary files ") || line === "GIT binary patch") {
3562
+ hasContent = true;
3563
+ }
3564
+ }
3565
+ flush();
3566
+ return hunkless;
3567
+ }
3568
+ var EMPTY_TREE_HASH = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
3569
+ function buildChangedRefHint(cwd, ref) {
3570
+ const countResult = runGit(["rev-list", "--count", "HEAD"], { cwd });
3571
+ if (countResult.exitCode !== 0) {
3572
+ return null;
3573
+ }
3574
+ const commitCount = Number.parseInt(countResult.stdout.trim(), 10);
3575
+ if (!Number.isFinite(commitCount) || commitCount < 1) {
3576
+ return null;
3577
+ }
3578
+ const refResolves = runGit(["rev-parse", "--verify", "--quiet", ref], { cwd });
3579
+ if (refResolves.exitCode === 0) {
3580
+ return null;
3581
+ }
3582
+ let suggestedRef = null;
3583
+ for (let n = commitCount - 1; n >= 1; n--) {
3584
+ const candidate = `HEAD~${n}`;
3585
+ const candidateResolves = runGit(["rev-parse", "--verify", "--quiet", candidate], { cwd });
3586
+ if (candidateResolves.exitCode === 0) {
3587
+ suggestedRef = candidate;
3588
+ break;
3589
+ }
3590
+ }
3591
+ if (suggestedRef === null) {
3592
+ suggestedRef = EMPTY_TREE_HASH;
3593
+ }
3594
+ const commitWord = commitCount === 1 ? "1 commit" : `${commitCount} commits`;
3595
+ return `Hint: this repo has only ${commitWord}; '${ref}' does not exist. Try: token-goat changed --since ${suggestedRef}`;
3596
+ }
3597
+ function refIsSafe(ref) {
3598
+ return !ref.startsWith("-");
3599
+ }
3600
+ function emitUnsafeRef(ref) {
3601
+ emitErr(`Refusing a git ref that starts with '-': ${ref}`);
3602
+ }
3603
+ function literalPathspec(file) {
3604
+ return `:(literal)${file}`;
3605
+ }
3606
+ function withRenameSources(cwd, ref, files) {
3607
+ const wanted = new Set(files);
3608
+ const paths = [...files];
3609
+ try {
3610
+ const result = runGit(["diff", ref, "--name-status", "-M"], { cwd });
3611
+ if (result.exitCode !== 0) return paths;
3612
+ const unquote = (f) => f.startsWith('"') && f.endsWith('"') && f.length > 1 ? unquoteGitPath(f.slice(1, -1)) : f;
3613
+ for (const row of result.stdout.split(/\r?\n/)) {
3614
+ const [status, from, to] = row.split(" ");
3615
+ if (status?.startsWith("R") !== true || from === void 0 || to === void 0) continue;
3616
+ if (wanted.has(unquote(to))) paths.push(unquote(from));
3617
+ }
3618
+ } catch {
3619
+ }
3620
+ return paths;
3621
+ }
3622
+ function unifiedDiffOf(cwd, ref, files, extraArgs = []) {
3623
+ const run = (paths2) => runGit(["diff", ref, "-M", "--unified=0", ...extraArgs, "--", ...paths2.map(literalPathspec)], { cwd });
3624
+ const first = run(files);
3625
+ if (first.exitCode !== 0 || !first.stdout.includes("--- /dev/null")) return first;
3626
+ const paths = withRenameSources(cwd, ref, files);
3627
+ return paths.length > files.length ? run(paths) : first;
3628
+ }
3629
+ function changedDiffBaselineBytes(cwd, ref, files) {
3630
+ if (files.length === 0) return 0;
3631
+ try {
3632
+ const result = unifiedDiffOf(cwd, ref, files);
3633
+ if (result.exitCode === 0) return deliveredOutputBytes(Buffer.byteLength(result.stdout, "utf8"));
3634
+ } catch {
3635
+ }
3636
+ return 0;
3637
+ }
3638
+ function runChanged(opts = {}) {
3639
+ const ref = opts.ref ?? "HEAD~5";
3640
+ if (!refIsSafe(ref)) {
3641
+ emitUnsafeRef(ref);
3642
+ return 1;
3643
+ }
3644
+ const projectDenial = opts.symbolMode === true ? resolveProjectConfinement(opts.projectRoot).denial : null;
3645
+ if (projectDenial !== null) {
3646
+ emitErr(projectDenial);
3647
+ return 1;
3648
+ }
3649
+ const cwd = opts.projectRoot ?? process.cwd();
3650
+ const projectRoot = resolveProjectRoot({ project: cwd });
3651
+ let changedFiles;
3652
+ try {
3653
+ const result = runGit(["diff", ref, "--name-only"], { cwd });
3654
+ if (result.exitCode !== 0) {
3655
+ emitErr(`git diff failed: ${result.stderr}`);
3656
+ const hint = buildChangedRefHint(cwd, ref);
3657
+ if (hint !== null) {
3658
+ emitErr(hint);
3659
+ }
3660
+ return 1;
3661
+ }
3662
+ changedFiles = result.stdout.trim().split(/\r?\n/).filter(Boolean).map((f) => f.startsWith('"') && f.endsWith('"') && f.length > 1 ? unquoteGitPath(f.slice(1, -1)) : f);
3663
+ } catch {
3664
+ emitErr(`Could not run git diff against '${ref}'`);
3665
+ return 1;
3666
+ }
3667
+ const emptyEnvelope = () => {
3668
+ emit(displaySafeJson({ items: [], truncated: false, totalCount: 0 }));
3669
+ return 0;
3670
+ };
3671
+ if (changedFiles.length === 0) {
3672
+ if (opts.json === true) return emptyEnvelope();
3673
+ emit("No files changed.");
3674
+ return 0;
3675
+ }
3676
+ const preGrepFileCount = changedFiles.length;
3677
+ const matchesGrep = opts.grep !== void 0 ? compileGrepMatcher(opts.grep) : void 0;
3678
+ if (matchesGrep !== void 0) changedFiles = changedFiles.filter((f) => matchesGrep(f));
3679
+ if (matchesGrep !== void 0 && changedFiles.length === 0) {
3680
+ if (opts.json === true) return emptyEnvelope();
3681
+ emit(grepFilteredToEmptyNotice(preGrepFileCount, opts.grep ?? "", "changed file", "changed files"));
3682
+ return 0;
3683
+ }
3684
+ const postGrepFileCount = changedFiles.length;
3685
+ let hiddenTestFiles = 0;
3686
+ if (opts.excludeTests === true) {
3687
+ const kept = changedFiles.filter((f) => !isTestFile(f));
3688
+ hiddenTestFiles = changedFiles.length - kept.length;
3689
+ changedFiles = kept;
3690
+ }
3691
+ if (changedFiles.length === 0 && hiddenTestFiles > 0) {
3692
+ if (opts.json === true) return emptyEnvelope();
3693
+ const grepRemoved = preGrepFileCount - postGrepFileCount;
3694
+ if (matchesGrep !== void 0 && grepRemoved > 0) {
3695
+ emit(
3696
+ `No non-test files matched --grep ${opts.grep ?? ""} (${excludeTestsHiddenNote(hiddenTestFiles)}; ${countNoun(grepRemoved, "other changed file")} did not match the filter)`
3697
+ );
3698
+ return 0;
3699
+ }
3700
+ emit(`No non-test files changed (${excludeTestsHiddenNote(hiddenTestFiles)})`);
3701
+ return 0;
3702
+ }
3703
+ if (opts.symbolMode === true) {
3704
+ let hunksByFile = /* @__PURE__ */ new Map();
3705
+ let hunklessFiles = /* @__PURE__ */ new Set();
3706
+ let symbolDiffBaselineBytes = 0;
3707
+ try {
3708
+ const diffResult = unifiedDiffOf(projectRoot, ref, changedFiles, ["--src-prefix=a/", "--dst-prefix=b/"]);
3709
+ if (diffResult.exitCode === 0) {
3710
+ hunksByFile = parseDiffHunks(diffResult.stdout);
3711
+ hunklessFiles = parseHunklessFiles(diffResult.stdout);
3712
+ symbolDiffBaselineBytes = deliveredOutputBytes(Buffer.byteLength(diffResult.stdout, "utf8"));
3713
+ }
3714
+ } catch {
3715
+ }
3716
+ const allSymbols = [];
3717
+ for (const f of changedFiles) {
3718
+ if (hunklessFiles.has(f)) continue;
3719
+ const fileSymbols = querySymbols({ filePath: resolveIndexPath(f, projectRoot), limit: FIND_SCAN_LIMIT });
3720
+ const hunks = hunksByFile.get(f);
3721
+ const scoped = hunks === void 0 || isVirtualIndexedPath(f) ? fileSymbols : fileSymbols.filter((s) => hunks.some((h) => h.start <= s.lineEnd && h.end >= s.lineStart));
3722
+ allSymbols.push(...scoped);
3723
+ }
3724
+ if (allSymbols.length === 0) {
3725
+ if (opts.json === true) return emptyEnvelope();
3726
+ emit("No symbols changed.");
3727
+ return 0;
3728
+ }
3729
+ const symbolFullBytes = symbolDiffBaselineBytes;
3730
+ if (opts.json === true) {
3731
+ const capped = guardJsonRows(allSymbols);
3732
+ const text = displaySafeJson({ items: capped.items, truncated: capped.truncated, totalCount: capped.totalCount });
3733
+ emit(text);
3734
+ recordReadStat("changed_lookup", symbolFullBytes, text, ref);
3735
+ return 0;
3736
+ }
3737
+ const symbolText = allSymbols.map((s) => `${s.name} (${s.kind}) \u2014 ${formatSymbolLocation(toDisplayPath(projectRoot, s.filePath), s.lineStart)}`).join("\n");
3738
+ for (const s of allSymbols) {
3739
+ emit(`${displaySafeText(s.name)} (${displaySafeText(s.kind)}) \u2014 ${formatSymbolLocation(displaySafeText(toDisplayPath(projectRoot, s.filePath)), s.lineStart)}`);
3740
+ }
3741
+ recordReadStat("changed_lookup", symbolFullBytes, symbolText, ref);
3742
+ return 0;
3743
+ }
3744
+ const fullBytes = changedDiffBaselineBytes(projectRoot, ref, changedFiles);
3745
+ if (opts.json === true) {
3746
+ const capped = guardJsonRows(changedFiles);
3747
+ const text = displaySafeJson({ items: capped.items, truncated: capped.truncated, totalCount: capped.totalCount });
3748
+ emit(text);
3749
+ recordReadStat("changed_lookup", fullBytes, text, ref);
3750
+ return 0;
3751
+ }
3752
+ for (const f of changedFiles) {
3753
+ emit(f);
3754
+ }
3755
+ recordReadStat("changed_lookup", fullBytes, changedFiles.join("\n"), ref);
3756
+ return 0;
3757
+ }
3758
+ function splitDiffHunks(diffText) {
3759
+ const lines = diffText.split(/\r?\n/);
3760
+ let preambleEnd = lines.length;
3761
+ for (let i2 = 0; i2 < lines.length; i2++) {
3762
+ if (HUNK_HEADER_RE.test(lines[i2])) {
3763
+ preambleEnd = i2;
3764
+ break;
3765
+ }
3766
+ }
3767
+ const preamble = lines.slice(0, preambleEnd).join("\n");
3768
+ const hunks = [];
3769
+ let i = preambleEnd;
3770
+ while (i < lines.length) {
3771
+ const line = lines[i];
3772
+ const m = HUNK_HEADER_RE.exec(line);
3773
+ if (m === null) {
3774
+ i++;
3775
+ continue;
3776
+ }
3777
+ const range = hunkHeaderRange(m);
3778
+ const bodyLines = [line];
3779
+ let j = i + 1;
3780
+ while (j < lines.length && !HUNK_HEADER_RE.test(lines[j])) {
3781
+ bodyLines.push(lines[j]);
3782
+ j++;
3783
+ }
3784
+ if (j >= lines.length && bodyLines[bodyLines.length - 1] === "") bodyLines.pop();
3785
+ hunks.push({ text: bodyLines.join("\n"), start: range.start, end: range.end });
3786
+ i = j;
3787
+ }
3788
+ return { preamble, hunks };
3789
+ }
3790
+ function runDiff(opts) {
3791
+ if (opts.ref !== void 0 && !refIsSafe(opts.ref)) {
3792
+ emitUnsafeRef(opts.ref);
3793
+ return 1;
3794
+ }
3795
+ const match = resolveSymbolSpecOrEmitError("diff", opts.spec, opts.projectRoot);
3796
+ if (match === null) return 1;
3797
+ const cwd = opts.projectRoot ?? process.cwd();
3798
+ const diffArgs = opts.ref !== void 0 ? ["diff", opts.ref, "--unified=0", "--", match.filePath] : ["diff", "--unified=0", "--", match.filePath];
3799
+ let diffResult;
3800
+ try {
3801
+ diffResult = runGit(diffArgs, { cwd });
3802
+ } catch {
3803
+ emitErr(`Could not run git diff for '${match.filePath}'`);
3804
+ return 1;
3805
+ }
3806
+ if (diffResult.exitCode !== 0) {
3807
+ emitErr(`git diff failed: ${diffResult.stderr}`);
3808
+ return 1;
3809
+ }
3810
+ if (diffResult.stdout.trim() === "") {
3811
+ emit(`No changes to '${displaySafeText(match.name)}' in '${displaySafeText(toDisplayPath(getDisplayRoot(opts.projectRoot), match.filePath))}'.`);
3812
+ return 0;
3813
+ }
3814
+ const { hunks } = splitDiffHunks(diffResult.stdout);
3815
+ const diffFileScoped = isVirtualIndexedPath(match.filePath);
3816
+ const overlapping = diffFileScoped ? hunks : hunks.filter((h) => h.start <= match.lineEnd && h.end >= match.lineStart);
3817
+ if (overlapping.length === 0) {
3818
+ emit(`No changes to '${displaySafeText(match.name)}' (lines ${match.lineStart}-${match.lineEnd}) in '${displaySafeText(toDisplayPath(getDisplayRoot(opts.projectRoot), match.filePath))}'.`);
3819
+ return 0;
3820
+ }
3821
+ if (opts.json === true) {
3822
+ const capped = guardJsonRows(overlapping.map((h) => ({ start: h.start, end: h.end, text: h.text })));
3823
+ emit(
3824
+ displaySafeJson(
3825
+ {
3826
+ symbol: match.name,
3827
+ file: match.filePath,
3828
+ lineStart: match.lineStart,
3829
+ lineEnd: match.lineEnd,
3830
+ hunks: capped.items,
3831
+ truncated: capped.truncated,
3832
+ totalCount: capped.totalCount
3833
+ }
3834
+ )
3835
+ );
3836
+ return 0;
3837
+ }
3838
+ const header = `# ${displaySafeText(match.name)} (${displaySafeText(match.kind)}) \u2014 ${formatSymbolLocation(displaySafeText(toDisplayPath(getDisplayRoot(opts.projectRoot), match.filePath)), match.lineStart, match.lineEnd)}`;
3839
+ const diffNote = diffFileScoped ? [virtualIndexedScopeNote(displaySafeText(toDisplayPath(getDisplayRoot(opts.projectRoot), match.filePath)), "these hunks cover the whole file rather than this symbol alone")] : [];
3840
+ emit(guardText([header, ...diffNote, ...overlapping.map((h) => h.text)].join("\n"), "diff"));
3841
+ return 0;
3842
+ }
3843
+ var DEFAULT_LOG_MAX_COUNT = 20;
3844
+ function parseLogDashLOutput(stdout) {
3845
+ const lines = stdout.split(/\r?\n/);
3846
+ const commitHeaderRe = /^commit ([0-9a-f]{40})/;
3847
+ const entries = [];
3848
+ let i = 0;
3849
+ while (i < lines.length) {
3850
+ const headerMatch = commitHeaderRe.exec(lines[i]);
3851
+ if (headerMatch === null) {
3852
+ i++;
3853
+ continue;
3854
+ }
3855
+ const hash = headerMatch[1];
3856
+ let author = "";
3857
+ let date = "";
3858
+ let j = i + 1;
3859
+ while (j < lines.length && !commitHeaderRe.test(lines[j])) {
3860
+ const line = lines[j];
3861
+ if (line.startsWith("diff --git")) break;
3862
+ if (author === "" && line.startsWith("Author:")) author = line.slice("Author:".length).trim();
3863
+ else if (date === "" && line.startsWith("Date:")) date = line.slice("Date:".length).trim();
3864
+ j++;
3865
+ }
3866
+ const messageLines = [];
3867
+ for (let k2 = i + 1; k2 < j; k2++) {
3868
+ const line = lines[k2];
3869
+ if (line.startsWith("Author:") || line.startsWith("Date:")) continue;
3870
+ messageLines.push(line.startsWith(" ") ? line.slice(4) : line);
3871
+ }
3872
+ const message = trimBlankLines(messageLines).join("\n");
3873
+ let k = j;
3874
+ while (k < lines.length && !commitHeaderRe.test(lines[k])) k++;
3875
+ const diff = lines.slice(j, k).join("\n");
3876
+ entries.push({ hash, author, date, message, diff });
3877
+ i = k;
3878
+ }
3879
+ return entries;
3880
+ }
3881
+ function runLog(opts) {
3882
+ if (opts.ref !== void 0 && !refIsSafe(opts.ref)) {
3883
+ emitUnsafeRef(opts.ref);
3884
+ return 1;
3885
+ }
3886
+ const match = resolveSymbolSpecOrEmitError("log", opts.spec, opts.projectRoot);
3887
+ if (match === null) return 1;
3888
+ const cwd = opts.projectRoot ?? process.cwd();
3889
+ const maxCount = opts.maxCount ?? DEFAULT_LOG_MAX_COUNT;
3890
+ const fileScoped = isVirtualIndexedPath(match.filePath);
3891
+ const logArgs = fileScoped ? ["log", `--max-count=${maxCount}`, ...opts.ref !== void 0 ? [opts.ref] : [], "--", match.filePath] : [
3892
+ "log",
3893
+ `-L${match.lineStart},${match.lineEnd}:${match.filePath}`,
3894
+ `--max-count=${maxCount}`,
3895
+ ...opts.ref !== void 0 ? [opts.ref] : []
3896
+ ];
3897
+ let logResult;
3898
+ try {
3899
+ logResult = runGit(logArgs, { cwd });
3900
+ } catch {
3901
+ emitErr(`Could not run git log for '${match.filePath}'`);
3902
+ return 1;
3903
+ }
3904
+ if (logResult.exitCode !== 0) {
3905
+ emitErr(`git log failed: ${displaySafeText(logResult.stderr)}`);
3906
+ return 1;
3907
+ }
3908
+ if (logResult.stdout.trim() === "") {
3909
+ emit(`No history for '${displaySafeText(match.name)}' (lines ${match.lineStart}-${match.lineEnd}) in '${displaySafeText(toDisplayPath(getDisplayRoot(opts.projectRoot), match.filePath))}'.`);
3910
+ return 0;
3911
+ }
3912
+ if (opts.json === true) {
3913
+ const capped = guardJsonRows(parseLogDashLOutput(logResult.stdout));
3914
+ emit(
3915
+ displaySafeJson(
3916
+ {
3917
+ symbol: match.name,
3918
+ file: match.filePath,
3919
+ lineStart: match.lineStart,
3920
+ lineEnd: match.lineEnd,
3921
+ commits: capped.items,
3922
+ truncated: capped.truncated,
3923
+ totalCount: capped.totalCount
3924
+ }
3925
+ )
3926
+ );
3927
+ return 0;
3928
+ }
3929
+ const header = `# ${displaySafeText(match.name)} (${displaySafeText(match.kind)}) \u2014 ${formatSymbolLocation(displaySafeText(toDisplayPath(getDisplayRoot(opts.projectRoot), match.filePath)), match.lineStart, match.lineEnd)}`;
3930
+ const logNote = fileScoped ? [virtualIndexedScopeNote(displaySafeText(toDisplayPath(getDisplayRoot(opts.projectRoot), match.filePath)), "this history covers the whole file rather than this symbol alone")] : [];
3931
+ emit(guardText([header, ...logNote, logResult.stdout].join("\n"), "diff"));
3932
+ return 0;
3933
+ }
3934
+
3935
+ // src/handle_stat.ts
3936
+ init_define_import_meta_env();
3937
+ import * as fs4 from "node:fs";
3938
+ var OPEN_FOR_STAT = process.platform === "win32" ? fs4.constants.O_RDONLY : fs4.constants.O_RDONLY | fs4.constants.O_NONBLOCK;
3939
+ function statThroughHandle(p) {
3940
+ const fd = fs4.openSync(p, OPEN_FOR_STAT);
3941
+ try {
3942
+ return fs4.fstatSync(fd, { bigint: true });
3943
+ } finally {
3944
+ fs4.closeSync(fd);
3945
+ }
3946
+ }
3947
+
3948
+ // src/content_store.ts
3949
+ init_define_import_meta_env();
3950
+ import { deflateRawSync } from "node:zlib";
3951
+ import * as path4 from "node:path";
3952
+ var CONTENT_MAX_INPUT_CHARS = 512 * 1024;
3953
+ var CONTENT_MAX_ITEMS = 256;
3954
+ var CONTENT_MAX_BYTES = 32 * 1024 * 1024;
3955
+ var CONTENT_MAX_ITEM_BYTES = CONTENT_MAX_INPUT_CHARS * 7;
3956
+ var CONTENT_SUBDIR = "content";
3957
+ var TEXT_BYTES_PER_TOKEN = BYTES_PER_TOKEN.text;
3958
+ var BASE64URL_BYTES_PER_TOKEN = BYTES_PER_TOKEN.dense;
3959
+ function estimateTextTokens(bytes) {
3960
+ return savedTokensFromBytes(bytes, "text");
3961
+ }
3962
+ function estimateBase64urlTokens(bytes) {
3963
+ return savedTokensFromBytes(bytes, "dense");
3964
+ }
3965
+ function assertInput(text) {
3966
+ if (text.length > CONTENT_MAX_INPUT_CHARS) {
3967
+ throw new Error(`text exceeds the ${CONTENT_MAX_INPUT_CHARS}-character safety limit`);
3968
+ }
3969
+ }
3970
+ function contentId(text) {
3971
+ return `tg_${shortFingerprint(text)}`;
3972
+ }
3973
+ function normalizeProjectRoot(projectRoot) {
3974
+ const candidate = path4.resolve(projectRoot ?? process.cwd());
3975
+ return findProject(candidate)?.root ?? candidate;
3976
+ }
3977
+ function handoffId(name, projectRoot) {
3978
+ return `tg_handoff_${shortFingerprint(`${projectRoot}\0${name}`)}`;
3979
+ }
3980
+ function storeContent(text) {
3981
+ const id = contentId(text);
3982
+ const value = { kind: "content", id, text, createdAt: Date.now() };
3983
+ const ok = storeBlob(CONTENT_SUBDIR, id, value, {
3984
+ maxCount: CONTENT_MAX_ITEMS,
3985
+ maxBytes: CONTENT_MAX_BYTES,
3986
+ maxBytesPerItem: CONTENT_MAX_ITEM_BYTES
3987
+ });
3988
+ if (!ok) throw new Error("unable to persist compressed content within the local cache bounds");
3989
+ return id;
3990
+ }
3991
+ function readContent(id) {
3992
+ if (!/^tg_[0-9a-f]{16}$/.test(id)) return null;
3993
+ if (isBlobStale(CONTENT_SUBDIR, id)) return null;
3994
+ const value = loadBlob(CONTENT_SUBDIR, id);
3995
+ if (value === null || typeof value !== "object" || value === null) return null;
3996
+ const entry = value;
3997
+ return entry.kind === "content" && entry.id === id && typeof entry.text === "string" ? entry.text : null;
3998
+ }
3999
+ function compressText(text) {
4000
+ assertInput(text);
4001
+ const safeText = redactSecrets(text).text;
4002
+ const id = storeContent(safeText);
4003
+ const compact = deflateRawSync(Buffer.from(safeText, "utf8"), { level: 9 }).toString("base64url");
4004
+ const originalBytes = Buffer.byteLength(safeText, "utf8");
4005
+ const compactBytes = Buffer.byteLength(compact, "utf8");
4006
+ const bytesSaved = Math.max(0, originalBytes - compactBytes);
4007
+ const tokensSaved = estimateTextTokens(originalBytes) - estimateBase64urlTokens(compactBytes);
4008
+ const inlineWins = tokensSaved > 0;
4009
+ recordStat("content_compress", inlineWins ? bytesSaved : 0, inlineWins ? tokensSaved : 0);
4010
+ return {
4011
+ id,
4012
+ compact,
4013
+ encoding: "deflate-raw-base64url",
4014
+ originalBytes,
4015
+ compactBytes,
4016
+ bytesSaved,
4017
+ tokensSaved,
4018
+ inlineWins,
4019
+ recovery: `token-goat retrieve ${id}`
4020
+ };
4021
+ }
4022
+ function retrieveText(id) {
4023
+ const text = readContent(id);
4024
+ if (text === null) return null;
4025
+ recordStat("content_retrieve");
4026
+ return text;
4027
+ }
4028
+ function createHandoff(name, text, projectRoot) {
4029
+ if (!/^[A-Za-z0-9._-]{1,128}$/.test(name)) {
4030
+ throw new Error("handoff name must be 1-128 characters using letters, numbers, dot, underscore, or hyphen");
4031
+ }
4032
+ assertInput(text);
4033
+ const root = normalizeProjectRoot(projectRoot);
4034
+ const safeText = redactSecrets(text).text;
4035
+ const contentIdValue = storeContent(safeText);
4036
+ const id = handoffId(name, root);
4037
+ const value = {
4038
+ kind: "handoff",
4039
+ id,
4040
+ name,
4041
+ projectRoot: root,
4042
+ contentId: contentIdValue,
4043
+ // Keep the handoff independently resolvable if the generic content entry
4044
+ // is later evicted from the shared bounded cache.
4045
+ text: safeText,
4046
+ createdAt: Date.now()
4047
+ };
4048
+ const ok = storeBlob(CONTENT_SUBDIR, id, value, {
4049
+ maxCount: CONTENT_MAX_ITEMS,
4050
+ maxBytes: CONTENT_MAX_BYTES,
4051
+ maxBytesPerItem: CONTENT_MAX_ITEM_BYTES
4052
+ });
4053
+ if (!ok) throw new Error("unable to persist handoff within the local cache bounds");
4054
+ recordStat("handoff_create");
4055
+ return { id, name, contentId: contentIdValue, projectRoot: root, recovery: `token-goat handoff-resolve ${name}` };
4056
+ }
4057
+ function resolveHandoff(name, opts = {}) {
4058
+ const root = normalizeProjectRoot(opts.projectRoot);
4059
+ const id = handoffId(name, root);
4060
+ if (isBlobStale(CONTENT_SUBDIR, id)) return null;
4061
+ const value = loadBlob(CONTENT_SUBDIR, id);
4062
+ if (value === null || typeof value !== "object" || value === null) return null;
4063
+ const entry = value;
4064
+ if (entry.kind !== "handoff" || entry.projectRoot !== root || typeof entry.contentId !== "string" || typeof entry.text !== "string") return null;
4065
+ const text = entry.text;
4066
+ recordStat("handoff_resolve");
4067
+ if (opts.full === true) return text;
4068
+ return compressText(text);
4069
+ }
4070
+
4071
+ export {
4072
+ WARM_COMMAND,
4073
+ modelDownloadHeld,
4074
+ foregroundDownloadDeferred,
4075
+ downloadAdvice,
4076
+ runtimeDownloadAdvice,
4077
+ checkSemanticReadiness,
4078
+ offlineEmbedNotice,
4079
+ mergeNearbyHits,
4080
+ runSymbol,
4081
+ runBrief,
4082
+ AMBIGUOUS_HEADING_LIMIT,
4083
+ runSection,
4084
+ runZipList,
4085
+ runZipRead,
4086
+ runSqliteSchema,
4087
+ runSqliteTables,
4088
+ runSqliteQuery,
4089
+ runCoverageReportGaps,
4090
+ runConfigGet,
4091
+ runExports,
4092
+ runImports,
4093
+ extractTranscriptText,
4094
+ runNoteGet,
4095
+ runNoteList,
4096
+ runFind,
4097
+ runLocate,
4098
+ runListSections,
4099
+ runSkeleton,
4100
+ runOutline,
4101
+ runSemanticDistances,
4102
+ runSemantic,
4103
+ runSemanticMulti,
4104
+ runRefs,
4105
+ runConflicts,
4106
+ runChanged,
4107
+ runDiff,
4108
+ runLog,
4109
+ statThroughHandle,
4110
+ CONTENT_MAX_INPUT_CHARS,
4111
+ compressText,
4112
+ retrieveText,
4113
+ createHandoff,
4114
+ resolveHandoff
4115
+ };