token-goat 2.9.30 → 2.9.31

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 (172) hide show
  1. package/README.md +24 -7
  2. package/SECURITY.md +10 -2
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/dist/native/linux-arm64/tg-hook +0 -0
  5. package/dist/native/linux-x64/tg-hook +0 -0
  6. package/dist/native/win32-arm64/tg-hook.exe +0 -0
  7. package/dist/native/win32-x64/tg-hook.exe +0 -0
  8. package/dist/{token-goat-chunk-QSBRTGM2.mjs → token-goat-chunk-25TRAWET.mjs} +17 -7
  9. package/dist/token-goat-chunk-2KXO6V2B.mjs +203 -0
  10. package/dist/{token-goat-chunk-IVKIZWAB.mjs → token-goat-chunk-2M7E4PF7.mjs} +41 -12
  11. package/dist/{token-goat-chunk-SMVRN2HU.mjs → token-goat-chunk-2UPZGKKY.mjs} +2 -2
  12. package/dist/token-goat-chunk-2WLPWKHV.mjs +135 -0
  13. package/dist/token-goat-chunk-2XBMMDBD.mjs +126 -0
  14. package/dist/token-goat-chunk-32ZOY4N5.mjs +23 -0
  15. package/dist/token-goat-chunk-3EZRNKTK.mjs +188 -0
  16. package/dist/token-goat-chunk-4ADZHI7J.mjs +63 -0
  17. package/dist/token-goat-chunk-4GBVPH3D.mjs +242 -0
  18. package/dist/{token-goat-chunk-KWPNJBDH.mjs → token-goat-chunk-4GILLLWY.mjs} +7 -4
  19. package/dist/{token-goat-chunk-67TQJ76B.mjs → token-goat-chunk-4NAPX7BA.mjs} +36 -3
  20. package/dist/{token-goat-chunk-PU7V3FD6.mjs → token-goat-chunk-4SC7UJYV.mjs} +3354 -2553
  21. package/dist/token-goat-chunk-4SMRO4TO.mjs +366 -0
  22. package/dist/{token-goat-chunk-AFLYLHDH.mjs → token-goat-chunk-4ZBFWLXF.mjs} +283 -427
  23. package/dist/{token-goat-chunk-JDWCRD6M.mjs → token-goat-chunk-57BIAXJL.mjs} +73 -40
  24. package/dist/token-goat-chunk-5AC66HNP.mjs +115 -0
  25. package/dist/token-goat-chunk-5IMLKSOP.mjs +145 -0
  26. package/dist/token-goat-chunk-646E6U44.mjs +398 -0
  27. package/dist/token-goat-chunk-65N4RHP4.mjs +33 -0
  28. package/dist/{token-goat-chunk-JN4XMLSD.mjs → token-goat-chunk-6DG6HWP3.mjs} +8 -2
  29. package/dist/token-goat-chunk-6ENQSNRZ.mjs +573 -0
  30. package/dist/token-goat-chunk-6ZY4GNF2.mjs +91 -0
  31. package/dist/{token-goat-chunk-VPBBTSJR.mjs → token-goat-chunk-76FGDLRO.mjs} +262 -255
  32. package/dist/{token-goat-chunk-7IN2MKDS.mjs → token-goat-chunk-7EZTZOMC.mjs} +121 -289
  33. package/dist/{token-goat-chunk-OJBNYTJP.mjs → token-goat-chunk-7MAOA6X4.mjs} +640 -80
  34. package/dist/token-goat-chunk-7R2ZVVAX.mjs +218 -0
  35. package/dist/{token-goat-chunk-PD5VKH46.mjs → token-goat-chunk-AUNGYYQX.mjs} +2 -2
  36. package/dist/{token-goat-chunk-HG45AF2B.mjs → token-goat-chunk-AUQYRMMH.mjs} +794 -37
  37. package/dist/token-goat-chunk-B3PEKBYG.mjs +128 -0
  38. package/dist/{token-goat-chunk-A3CK6N5Z.mjs → token-goat-chunk-BA4HI6BW.mjs} +1309 -110
  39. package/dist/{token-goat-chunk-JB6QN6VF.mjs → token-goat-chunk-BFCWY73P.mjs} +59 -23
  40. package/dist/{token-goat-chunk-B45VMIIH.mjs → token-goat-chunk-BWGFUZZE.mjs} +7 -5
  41. package/dist/{token-goat-chunk-P4QMM4WK.mjs → token-goat-chunk-C75NA7PL.mjs} +50 -23
  42. package/dist/token-goat-chunk-CBIQ7WTD.mjs +87 -0
  43. package/dist/token-goat-chunk-CETM74KC.mjs +307 -0
  44. package/dist/{token-goat-chunk-H2PHHGM5.mjs → token-goat-chunk-CIMQ2BB3.mjs} +57 -12
  45. package/dist/{token-goat-chunk-N2NMER56.mjs → token-goat-chunk-CJLS3CDL.mjs} +46 -4
  46. package/dist/{token-goat-chunk-VES7EL7O.mjs → token-goat-chunk-CO3UYX6G.mjs} +1 -1
  47. package/dist/token-goat-chunk-COPNXRJM.mjs +154 -0
  48. package/dist/token-goat-chunk-CSJ5L3NL.mjs +479 -0
  49. package/dist/{token-goat-chunk-4LCCNRTG.mjs → token-goat-chunk-CVCEVUGK.mjs} +8 -6
  50. package/dist/{token-goat-chunk-B2RZZCSS.mjs → token-goat-chunk-CXHOFN7R.mjs} +37 -20
  51. package/dist/token-goat-chunk-D735C4QM.mjs +603 -0
  52. package/dist/{token-goat-chunk-APGZXSPS.mjs → token-goat-chunk-DGS2LVNA.mjs} +47 -30
  53. package/dist/{token-goat-chunk-54K3EUV3.mjs → token-goat-chunk-DOWRSMWO.mjs} +1 -1
  54. package/dist/token-goat-chunk-E45INMY3.mjs +71 -0
  55. package/dist/token-goat-chunk-EC5F37F4.mjs +244 -0
  56. package/dist/{token-goat-chunk-XTQAOTSO.mjs → token-goat-chunk-EDBDRVH5.mjs} +4 -1
  57. package/dist/{token-goat-chunk-5BCIJXMO.mjs → token-goat-chunk-EELTBKDM.mjs} +14 -10
  58. package/dist/{token-goat-chunk-W3QCJS6G.mjs → token-goat-chunk-F4HYL64R.mjs} +8 -6
  59. package/dist/token-goat-chunk-F6PAUIEK.mjs +624 -0
  60. package/dist/token-goat-chunk-FDSREWLX.mjs +692 -0
  61. package/dist/token-goat-chunk-GGGG7RWB.mjs +236 -0
  62. package/dist/{token-goat-chunk-C63JQV5K.mjs → token-goat-chunk-GL3ETKSC.mjs} +233 -15
  63. package/dist/token-goat-chunk-GVB2MUST.mjs +1298 -0
  64. package/dist/token-goat-chunk-GWFLAUMQ.mjs +307 -0
  65. package/dist/{token-goat-chunk-ZEJVMUQH.mjs → token-goat-chunk-HLPW63A7.mjs} +2 -2
  66. package/dist/{token-goat-chunk-GOLEOXZP.mjs → token-goat-chunk-HU4TORC5.mjs} +44 -26
  67. package/dist/{token-goat-chunk-Z57YODA2.mjs → token-goat-chunk-I7VKZZAW.mjs} +2 -2
  68. package/dist/{token-goat-chunk-NYKZLXMK.mjs → token-goat-chunk-IA6XSPE3.mjs} +6 -6
  69. package/dist/{token-goat-chunk-E3OQX2XT.mjs → token-goat-chunk-IAGXMHIK.mjs} +81 -42
  70. package/dist/{token-goat-chunk-HYBFA5X3.mjs → token-goat-chunk-IK2DRMFX.mjs} +24 -68
  71. package/dist/{token-goat-chunk-COBQGL5S.mjs → token-goat-chunk-INNPTRUX.mjs} +249 -299
  72. package/dist/{token-goat-chunk-7NADEDKJ.mjs → token-goat-chunk-JJ5BX4F4.mjs} +6 -1
  73. package/dist/{token-goat-chunk-PEPTHRFF.mjs → token-goat-chunk-JVFSHVFJ.mjs} +6 -6
  74. package/dist/token-goat-chunk-KGEFFSHD.mjs +50 -0
  75. package/dist/token-goat-chunk-KQBLRPBN.mjs +433 -0
  76. package/dist/{token-goat-chunk-Q5VTGRH7.mjs → token-goat-chunk-LCNQ2CNN.mjs} +55 -319
  77. package/dist/token-goat-chunk-M3LFJDPU.mjs +58 -0
  78. package/dist/token-goat-chunk-M52KPT3C.mjs +87 -0
  79. package/dist/token-goat-chunk-MBEG42H7.mjs +150 -0
  80. package/dist/{token-goat-chunk-EVC4TOLE.mjs → token-goat-chunk-METECDOH.mjs} +3 -12
  81. package/dist/{token-goat-chunk-T4FK7VOV.mjs → token-goat-chunk-MHX7XCC3.mjs} +2 -1
  82. package/dist/token-goat-chunk-N5LSOVSU.mjs +137 -0
  83. package/dist/{token-goat-chunk-LOBGHXPB.mjs → token-goat-chunk-N5MMR2IW.mjs} +1 -1
  84. package/dist/token-goat-chunk-N7B3JQ2C.mjs +160 -0
  85. package/dist/{token-goat-chunk-RHXKCZX3.mjs → token-goat-chunk-NISNMD7T.mjs} +2182 -2959
  86. package/dist/token-goat-chunk-NMD42SFQ.mjs +434 -0
  87. package/dist/token-goat-chunk-NOSS5VWY.mjs +174 -0
  88. package/dist/{token-goat-chunk-KQNZDCBD.mjs → token-goat-chunk-NSNSPPBG.mjs} +25 -3
  89. package/dist/token-goat-chunk-OBOFIHKN.mjs +38 -0
  90. package/dist/token-goat-chunk-OBRFJ5O5.mjs +76 -0
  91. package/dist/{token-goat-chunk-JTQQGXZH.mjs → token-goat-chunk-ODBT2USM.mjs} +124 -57
  92. package/dist/token-goat-chunk-OS5EM5PP.mjs +107 -0
  93. package/dist/token-goat-chunk-OUKDTVNU.mjs +1178 -0
  94. package/dist/token-goat-chunk-OXFMP75N.mjs +566 -0
  95. package/dist/{token-goat-chunk-DZX2YDPT.mjs → token-goat-chunk-OYIYKDDT.mjs} +2 -2
  96. package/dist/{token-goat-chunk-QCLDAQTS.mjs → token-goat-chunk-P6HQSWQ3.mjs} +50 -53
  97. package/dist/token-goat-chunk-P6VGNTQT.mjs +71 -0
  98. package/dist/{token-goat-chunk-7BXSNBY5.mjs → token-goat-chunk-PFWIMH2E.mjs} +23 -15
  99. package/dist/token-goat-chunk-PJZXIS3G.mjs +39 -0
  100. package/dist/token-goat-chunk-PUO5INXP.mjs +606 -0
  101. package/dist/{token-goat-chunk-GFBSN7BD.mjs → token-goat-chunk-PX5PQWTY.mjs} +22 -250
  102. package/dist/token-goat-chunk-Q5QDBC6O.mjs +111 -0
  103. package/dist/token-goat-chunk-QFNQXSDT.mjs +40 -0
  104. package/dist/{token-goat-chunk-FBNQNSJK.mjs → token-goat-chunk-QH7YVYOY.mjs} +2198 -2782
  105. package/dist/{token-goat-chunk-64FBPDTK.mjs → token-goat-chunk-QLMUHEPB.mjs} +12 -9
  106. package/dist/{token-goat-chunk-RZRTDST7.mjs → token-goat-chunk-QQOJIHL3.mjs} +37 -11
  107. package/dist/{token-goat-chunk-XKI4SEMX.mjs → token-goat-chunk-QQTWJY2O.mjs} +19 -0
  108. package/dist/{token-goat-chunk-U4N3BN2W.mjs → token-goat-chunk-QYHO5CPH.mjs} +3 -2
  109. package/dist/token-goat-chunk-RELAVWAD.mjs +27 -0
  110. package/dist/{token-goat-chunk-ETBHVDB6.mjs → token-goat-chunk-RSLPOBKQ.mjs} +22 -11
  111. package/dist/token-goat-chunk-SAMFDIS2.mjs +712 -0
  112. package/dist/token-goat-chunk-SDELXLDM.mjs +48 -0
  113. package/dist/{token-goat-chunk-4JJS54TR.mjs → token-goat-chunk-SOAFZNNZ.mjs} +161 -38
  114. package/dist/{token-goat-chunk-664RTTUV.mjs → token-goat-chunk-SRTHT7KV.mjs} +21 -16
  115. package/dist/{token-goat-chunk-4FVIAHSI.mjs → token-goat-chunk-T5P5ACNQ.mjs} +7 -6
  116. package/dist/token-goat-chunk-T5YGG4V3.mjs +45 -0
  117. package/dist/token-goat-chunk-TAUZ4OFS.mjs +110 -0
  118. package/dist/{token-goat-chunk-COTXZHTS.mjs → token-goat-chunk-TM6MC754.mjs} +4 -3
  119. package/dist/token-goat-chunk-TWUKBOMP.mjs +72 -0
  120. package/dist/{token-goat-chunk-3CHUNJ2O.mjs → token-goat-chunk-U2KSB4SG.mjs} +8 -5
  121. package/dist/{token-goat-chunk-3Z5S4KGM.mjs → token-goat-chunk-U4WHMG6Y.mjs} +29 -49
  122. package/dist/{token-goat-chunk-PY533T7F.mjs → token-goat-chunk-UGJHGR5A.mjs} +21 -15
  123. package/dist/{token-goat-chunk-CYCEYAEO.mjs → token-goat-chunk-UOUDVA5U.mjs} +1 -1
  124. package/dist/{token-goat-chunk-JDHE26RP.mjs → token-goat-chunk-URRJ33GR.mjs} +16 -5
  125. package/dist/{token-goat-chunk-56EIMS3E.mjs → token-goat-chunk-UZOROKIS.mjs} +23 -17
  126. package/dist/{token-goat-chunk-CZ3LUXYZ.mjs → token-goat-chunk-VIHGGGEI.mjs} +427 -308
  127. package/dist/token-goat-chunk-VTJLAA7P.mjs +21 -0
  128. package/dist/token-goat-chunk-VWDXLT4F.mjs +678 -0
  129. package/dist/{token-goat-chunk-YARZFCZS.mjs → token-goat-chunk-VWZA2H4P.mjs} +62 -53
  130. package/dist/{token-goat-chunk-SQSSNQPE.mjs → token-goat-chunk-VXEXMJDL.mjs} +3979 -4745
  131. package/dist/token-goat-chunk-WBA5SKT4.mjs +547 -0
  132. package/dist/{token-goat-chunk-SJHWVNDQ.mjs → token-goat-chunk-WDH44VAU.mjs} +7 -4
  133. package/dist/token-goat-chunk-X2SUP3EI.mjs +107 -0
  134. package/dist/token-goat-chunk-X3BELZSJ.mjs +125 -0
  135. package/dist/{token-goat-chunk-FZ3NEW5O.mjs → token-goat-chunk-XLRBNZCC.mjs} +13 -8
  136. package/dist/{token-goat-chunk-Z44YO3QZ.mjs → token-goat-chunk-XRKUX6CM.mjs} +1 -1
  137. package/dist/token-goat-chunk-XX6O25RN.mjs +82 -0
  138. package/dist/token-goat-chunk-XZSUHBVS.mjs +65 -0
  139. package/dist/{token-goat-chunk-WY3L4DVT.mjs → token-goat-chunk-Y2ZTQOCL.mjs} +13 -8
  140. package/dist/{token-goat-chunk-UQ5UK2T7.mjs → token-goat-chunk-YB4VMGK2.mjs} +1 -1
  141. package/dist/token-goat-chunk-YBIIY3HR.mjs +51 -0
  142. package/dist/{token-goat-chunk-ZQSJMO2R.mjs → token-goat-chunk-YHVDKQBR.mjs} +105 -105
  143. package/dist/{token-goat-chunk-H2K3QP6H.mjs → token-goat-chunk-YJB5XLWI.mjs} +8 -6
  144. package/dist/{token-goat-chunk-ZCDTCVIS.mjs → token-goat-chunk-YKB6AQG3.mjs} +5 -2
  145. package/dist/token-goat-chunk-YMTG2YA5.mjs +35 -0
  146. package/dist/token-goat-chunk-YPBZYQRV.mjs +40 -0
  147. package/dist/{token-goat-chunk-HOJLCGDP.mjs → token-goat-chunk-YY6AVUFL.mjs} +2033 -2251
  148. package/dist/token-goat-chunk-Z7N5OMMP.mjs +98 -0
  149. package/dist/token-goat-chunk-ZNMNUPAZ.mjs +938 -0
  150. package/dist/token-goat-hook-client.cjs +1 -1
  151. package/dist/token-goat-hook-client.mjs +3 -3
  152. package/dist/token-goat-hook.mjs +67 -38
  153. package/dist/token-goat.core.mjs +4 -4
  154. package/docs/C4_RUNTIME_ARCHITECTURE.md +1 -1
  155. package/docs/cli.md +40 -21
  156. package/docs/install.md +19 -9
  157. package/docs/security.md +5 -3
  158. package/package.json +2 -1
  159. package/dist/token-goat-chunk-5KDMCMF2.mjs +0 -362
  160. package/dist/token-goat-chunk-6QMAS7AB.mjs +0 -42
  161. package/dist/token-goat-chunk-7F2UBPD3.mjs +0 -397
  162. package/dist/token-goat-chunk-ARDNEC2U.mjs +0 -248
  163. package/dist/token-goat-chunk-ATCPY7EQ.mjs +0 -134
  164. package/dist/token-goat-chunk-EFHMMIGP.mjs +0 -68
  165. package/dist/token-goat-chunk-IVALLT3S.mjs +0 -48
  166. package/dist/token-goat-chunk-JDU3EXSU.mjs +0 -65
  167. package/dist/token-goat-chunk-MKPF4CL5.mjs +0 -37
  168. package/dist/token-goat-chunk-RC35RBUL.mjs +0 -312
  169. package/dist/token-goat-chunk-USILZ3T3.mjs +0 -53
  170. package/dist/token-goat-chunk-VL3YIFHZ.mjs +0 -2975
  171. package/dist/token-goat-chunk-XU4P6Z24.mjs +0 -133
  172. package/dist/token-goat-chunk-Y776AENC.mjs +0 -1597
@@ -0,0 +1,938 @@
1
+ import { createRequire as __cjsRequire } from 'node:module';
2
+ const require = __cjsRequire(import.meta.url);
3
+ import {
4
+ buildLineDiff,
5
+ formatSymbolLocation
6
+ } from "./token-goat-chunk-QH7YVYOY.mjs";
7
+ import {
8
+ claudeConfigDir
9
+ } from "./token-goat-chunk-BPGCUMR7.mjs";
10
+ import {
11
+ embedTexts,
12
+ hasSkipSegmentBelowRoot,
13
+ isAvailable,
14
+ isEmbeddableDocument
15
+ } from "./token-goat-chunk-NISNMD7T.mjs";
16
+ import {
17
+ detectLanguageOfFile,
18
+ getDb,
19
+ getReadDb,
20
+ projectScopeClause
21
+ } from "./token-goat-chunk-GVB2MUST.mjs";
22
+ import {
23
+ estimateTokens
24
+ } from "./token-goat-chunk-BFCWY73P.mjs";
25
+ import {
26
+ loadConfig
27
+ } from "./token-goat-chunk-7EZTZOMC.mjs";
28
+ import {
29
+ findProject,
30
+ resolveProjectRoot
31
+ } from "./token-goat-chunk-4GBVPH3D.mjs";
32
+ import {
33
+ atomicWriteText,
34
+ countNoun,
35
+ isTestFile
36
+ } from "./token-goat-chunk-HU4TORC5.mjs";
37
+ import {
38
+ echoedValue,
39
+ fencedCommand,
40
+ quotedArg
41
+ } from "./token-goat-chunk-GGGG7RWB.mjs";
42
+ import {
43
+ foldPath,
44
+ isCaseInsensitiveFs
45
+ } from "./token-goat-chunk-AM5V6GSX.mjs";
46
+ import {
47
+ displaySafeJson,
48
+ displaySafePath,
49
+ displaySafeText,
50
+ normalizePath,
51
+ toDisplayPath
52
+ } from "./token-goat-chunk-FNFBKLRD.mjs";
53
+ import {
54
+ globalDbPath
55
+ } from "./token-goat-chunk-CO3UYX6G.mjs";
56
+ import {
57
+ init_define_import_meta_env
58
+ } from "./token-goat-chunk-A37V4PBF.mjs";
59
+
60
+ // src/index_health.ts
61
+ init_define_import_meta_env();
62
+ import * as fs2 from "fs";
63
+
64
+ // src/walk_mode.ts
65
+ init_define_import_meta_env();
66
+ import * as fs from "fs";
67
+ import * as path from "path";
68
+ function detectWalkMode(cwd) {
69
+ const project = findProject(cwd);
70
+ if (project?.marker === ".git") return "git";
71
+ let cur = cwd;
72
+ while (true) {
73
+ if (fs.existsSync(path.join(cur, ".git"))) return "git";
74
+ const parent = path.dirname(cur);
75
+ if (parent === cur) break;
76
+ cur = parent;
77
+ }
78
+ return "non-git";
79
+ }
80
+
81
+ // src/index_health.ts
82
+ function getProjectIndexCounts(dbPath, rootDir) {
83
+ const db = getReadDb(dbPath);
84
+ const countScoped = (table, column) => {
85
+ if (rootDir === void 0) {
86
+ return db.prepare(`SELECT COUNT(*) as c FROM ${table}`).get().c;
87
+ }
88
+ const scope = projectScopeClause(column);
89
+ return db.prepare(`SELECT COUNT(*) as c FROM ${table} WHERE ${scope.clause}`).get(...scope.params(rootDir)).c;
90
+ };
91
+ return { fileCount: countScoped("files", "path"), symbolCount: countScoped("symbols", "file_path") };
92
+ }
93
+ function getEmbeddingCoverage(dbPath, rootDir) {
94
+ const db = getReadDb(dbPath);
95
+ const countScoped = (sql, column) => {
96
+ if (rootDir === void 0) {
97
+ return db.prepare(sql).get().c;
98
+ }
99
+ const scope = projectScopeClause(column);
100
+ return db.prepare(`${sql} WHERE ${scope.clause}`).get(...scope.params(rootDir)).c;
101
+ };
102
+ return {
103
+ indexedFiles: countScoped("SELECT COUNT(*) as c FROM files", "path"),
104
+ // DISTINCT file_path, not COUNT(*): the question is how many files are reachable by vector search at all, and one file contributes anywhere from 1 to 404 chunks (measured), so a raw chunk count would read as healthy coverage whenever a handful of large files chunked well.
105
+ embeddedFiles: countScoped("SELECT COUNT(DISTINCT file_path) as c FROM chunks", "file_path")
106
+ };
107
+ }
108
+ function getParserFreshness(dbPath, expectedFor, rootDir) {
109
+ const db = getReadDb(dbPath);
110
+ const scope = rootDir === void 0 ? void 0 : projectScopeClause("path");
111
+ const rows = db.prepare(`SELECT language, parser_sha, COUNT(*) as c FROM files${scope === void 0 ? "" : ` WHERE ${scope.clause}`} GROUP BY language, parser_sha`).all(...scope === void 0 || rootDir === void 0 ? [] : scope.params(rootDir));
112
+ let indexedFiles = 0;
113
+ let currentFiles = 0;
114
+ for (const row of rows) {
115
+ indexedFiles += row.c;
116
+ if (row.parser_sha !== null && row.parser_sha === expectedFor(row.language ?? "unknown")) currentFiles += row.c;
117
+ }
118
+ return { indexedFiles, currentFiles };
119
+ }
120
+ function isIndexEmptyForProject(dbPath, rootDir) {
121
+ if (!fs2.existsSync(dbPath)) return true;
122
+ try {
123
+ const { fileCount, symbolCount } = getProjectIndexCounts(dbPath, rootDir);
124
+ return fileCount === 0 && symbolCount === 0;
125
+ } catch {
126
+ return false;
127
+ }
128
+ }
129
+ function suggestedIndexCommand(rootDir) {
130
+ return detectWalkMode(rootDir) === "git" ? "token-goat index ." : "token-goat index . --walk";
131
+ }
132
+ function emptyIndexMessage(rootDir, projectRootWasOmitted = false) {
133
+ const omittedNote = projectRootWasOmitted ? ` (projectRoot was omitted and defaulted to server process cwd ${echoedValue(rootDir)}; pass projectRoot: "<workspace-path>" to target your workspace)` : "";
134
+ return `no files indexed for this project \u2014 every read command will return empty, which looks like a genuine "not found" rather than a missing index; run ${fencedCommand(suggestedIndexCommand(rootDir))} here${omittedNote}`;
135
+ }
136
+
137
+ // src/baseline.ts
138
+ init_define_import_meta_env();
139
+ import * as fs5 from "node:fs";
140
+ import * as path4 from "node:path";
141
+
142
+ // src/cli_context_stats.ts
143
+ init_define_import_meta_env();
144
+ import * as fs4 from "node:fs";
145
+ import * as os from "node:os";
146
+ import * as path3 from "node:path";
147
+
148
+ // src/confirm_apply.ts
149
+ init_define_import_meta_env();
150
+ import * as readline from "node:readline";
151
+ async function defaultConfirm(question) {
152
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
153
+ try {
154
+ const answer = await new Promise((resolve3) => rl.question(question, resolve3));
155
+ return /^y(es)?$/i.test(answer.trim());
156
+ } finally {
157
+ rl.close();
158
+ }
159
+ }
160
+ async function confirmAndApply(changes, opts = {}) {
161
+ const write = opts.write ?? ((text) => {
162
+ process.stdout.write(text);
163
+ });
164
+ const isTTY = opts.isTTY ?? process.stdin.isTTY === true;
165
+ const confirmFn = opts.confirm ?? defaultConfirm;
166
+ const dryRun = opts.yes !== true && !isTTY;
167
+ const applied = [];
168
+ const skipped = [];
169
+ for (const change of changes) {
170
+ const label = change.label ?? change.path;
171
+ const diff = buildLineDiff(change.before, change.after, label);
172
+ write(`
173
+ ${diff}
174
+ `);
175
+ if (opts.yes === true) {
176
+ atomicWriteText(change.path, change.after);
177
+ applied.push(change);
178
+ continue;
179
+ }
180
+ if (!isTTY) {
181
+ skipped.push(change);
182
+ continue;
183
+ }
184
+ const ok = await confirmFn(`Apply this change to ${label}? [y/N] `);
185
+ if (ok) {
186
+ atomicWriteText(change.path, change.after);
187
+ applied.push(change);
188
+ } else {
189
+ skipped.push(change);
190
+ }
191
+ }
192
+ if (dryRun) {
193
+ write(
194
+ "\nDry run: no files were written (stdin is not a TTY and --yes was not passed).\nRe-run with --yes to apply these changes non-interactively.\n"
195
+ );
196
+ }
197
+ return { applied, skipped, dryRun };
198
+ }
199
+
200
+ // src/memory_prune.ts
201
+ init_define_import_meta_env();
202
+ import * as fs3 from "node:fs";
203
+ import * as path2 from "node:path";
204
+ var ENTRY_RE = /^\s*-\s*\[(?<title>[^\]]+)\]\((?<target>[^)]+?\.md)\)/;
205
+ var URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
206
+ function parseIndex(text) {
207
+ const passthrough = [];
208
+ const entries = [];
209
+ if (!text) return [passthrough, entries];
210
+ const lines = [];
211
+ let current = "";
212
+ for (const char of text) {
213
+ current += char;
214
+ if (char === "\n") {
215
+ lines.push(current);
216
+ current = "";
217
+ }
218
+ }
219
+ if (current) lines.push(current);
220
+ for (let lineno = 0; lineno < lines.length; lineno++) {
221
+ const rawLine = lines[lineno];
222
+ const m = ENTRY_RE.exec(rawLine);
223
+ if (m?.groups) {
224
+ const title = m.groups["title"];
225
+ const target = m.groups["target"];
226
+ if (title && target) {
227
+ entries.push({
228
+ raw: rawLine,
229
+ title,
230
+ target,
231
+ lineno
232
+ });
233
+ continue;
234
+ }
235
+ }
236
+ passthrough.push([lineno, rawLine]);
237
+ }
238
+ return [passthrough, entries];
239
+ }
240
+ function pruneIndex(memoryDir, opts) {
241
+ const result = {
242
+ removedDead: [],
243
+ removedDup: [],
244
+ kept: 0,
245
+ changed: false,
246
+ tokensSaved: 0
247
+ };
248
+ const memoryMd = path2.join(memoryDir, "MEMORY.md");
249
+ let text;
250
+ try {
251
+ text = fs3.readFileSync(memoryMd, "utf-8");
252
+ } catch {
253
+ return result;
254
+ }
255
+ const [passthrough, entries] = parseIndex(text);
256
+ const seenTargets = /* @__PURE__ */ new Set();
257
+ const keep = [];
258
+ const dead = [];
259
+ const dups = [];
260
+ for (const entry of entries) {
261
+ const isUrl = URL_SCHEME_RE.test(entry.target);
262
+ const targetExists = isUrl ? true : path2.isAbsolute(entry.target) ? fs3.existsSync(entry.target) : fs3.existsSync(path2.join(memoryDir, entry.target));
263
+ const foldedTarget = foldPath(entry.target);
264
+ if (!targetExists) {
265
+ dead.push(entry);
266
+ } else if (seenTargets.has(foldedTarget)) {
267
+ dups.push(entry);
268
+ } else {
269
+ seenTargets.add(foldedTarget);
270
+ keep.push(entry);
271
+ }
272
+ }
273
+ result.removedDead = dead;
274
+ result.removedDup = dups;
275
+ result.kept = keep.length;
276
+ result.changed = dead.length > 0 || dups.length > 0;
277
+ result.tokensSaved = dead.reduce((sum, e) => sum + estimateTokens(e.raw), 0) + dups.reduce((sum, e) => sum + estimateTokens(e.raw), 0);
278
+ if (!result.changed) {
279
+ return result;
280
+ }
281
+ const lineMap = /* @__PURE__ */ new Map();
282
+ for (const [lineno, rawLine] of passthrough) {
283
+ lineMap.set(lineno, rawLine);
284
+ }
285
+ for (const entry of keep) {
286
+ lineMap.set(entry.lineno, entry.raw);
287
+ }
288
+ const sortedKeys = Array.from(lineMap.keys()).sort((a, b) => a - b);
289
+ let reconstructed = sortedKeys.map((k) => lineMap.get(k) ?? "").join("");
290
+ if (reconstructed && !reconstructed.endsWith("\n")) {
291
+ reconstructed += "\n";
292
+ }
293
+ result.after = reconstructed;
294
+ if (opts?.dryRun) {
295
+ return result;
296
+ }
297
+ try {
298
+ atomicWriteText(memoryMd, reconstructed);
299
+ } catch {
300
+ result.changed = false;
301
+ }
302
+ return result;
303
+ }
304
+ function jaccard(a, b) {
305
+ const ta = new Set(a.toLowerCase().split(/\s+/));
306
+ const tb = new Set(b.toLowerCase().split(/\s+/));
307
+ if (ta.size === 0 && tb.size === 0) return 1;
308
+ if (ta.size === 0 || tb.size === 0) return 0;
309
+ const intersection = new Set([...ta].filter((x) => tb.has(x)));
310
+ const union = /* @__PURE__ */ new Set([...ta, ...tb]);
311
+ return intersection.size / union.size;
312
+ }
313
+ function siblingSnippet(filePath) {
314
+ let text;
315
+ try {
316
+ text = fs3.readFileSync(filePath, { encoding: "utf-8" });
317
+ } catch {
318
+ return "";
319
+ }
320
+ if (text.startsWith("---")) {
321
+ const closeRe = /\n---(?:\n|$)/g;
322
+ closeRe.lastIndex = 3;
323
+ const closeMatch = closeRe.exec(text);
324
+ const end = closeMatch ? closeMatch.index : -1;
325
+ const closeLen = closeMatch ? closeMatch[0].length : 0;
326
+ if (end !== -1) {
327
+ const body = text.slice(end + closeLen).trimStart();
328
+ const fm = text.slice(3, end);
329
+ let desc = "";
330
+ for (const line of fm.split("\n")) {
331
+ if (line.startsWith("description:")) {
332
+ desc = line.slice(12).trim().replace(/^["']|["']$/g, "");
333
+ break;
334
+ }
335
+ }
336
+ return `${desc} ${body.slice(0, 500)}`.trim();
337
+ }
338
+ return text.slice(0, 500);
339
+ }
340
+ return text.slice(0, 500);
341
+ }
342
+ function cosineSimilarity(a, b) {
343
+ let dot = 0;
344
+ let na = 0;
345
+ let nb = 0;
346
+ for (let i = 0; i < a.length; i++) {
347
+ dot += a[i] * b[i];
348
+ na += a[i] * a[i];
349
+ nb += b[i] * b[i];
350
+ }
351
+ if (na === 0 || nb === 0) return 0;
352
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
353
+ }
354
+ function greedyCluster(siblings, snippets, matches, simFor, method) {
355
+ const clusters = [];
356
+ const used = /* @__PURE__ */ new Set();
357
+ for (let i = 0; i < siblings.length; i++) {
358
+ if (used.has(i)) continue;
359
+ const group = [i];
360
+ for (let j = i + 1; j < siblings.length; j++) {
361
+ if (used.has(j)) continue;
362
+ if (matches(i, j, group)) {
363
+ group.push(j);
364
+ }
365
+ }
366
+ if (group.length > 1) {
367
+ const members = group.map((k) => siblings[k]);
368
+ const tok2 = group.reduce((sum, k) => sum + estimateTokens(snippets[k]), 0);
369
+ let maxSim = 0;
370
+ for (let a = 0; a < group.length; a++) {
371
+ for (let b = a + 1; b < group.length; b++) {
372
+ maxSim = Math.max(maxSim, simFor(group[a], group[b]));
373
+ }
374
+ }
375
+ clusters.push({
376
+ members,
377
+ similarity: Math.round(maxSim * 1e3) / 1e3,
378
+ method,
379
+ tokens: tok2
380
+ });
381
+ for (const idx of group) used.add(idx);
382
+ }
383
+ }
384
+ return clusters;
385
+ }
386
+ async function tryEmbeddingClusters(siblings, snippets, threshold) {
387
+ if (!(loadConfig().indexing?.embeddings_enabled ?? true) || !isAvailable()) {
388
+ return null;
389
+ }
390
+ let vecs;
391
+ try {
392
+ vecs = await embedTexts(snippets);
393
+ } catch {
394
+ return null;
395
+ }
396
+ return greedyCluster(
397
+ siblings,
398
+ snippets,
399
+ (i, j) => cosineSimilarity(vecs[i], vecs[j]) >= threshold,
400
+ (a, b) => cosineSimilarity(vecs[a], vecs[b]),
401
+ "embedding"
402
+ );
403
+ }
404
+ async function findContentDuplicates(memoryDir, _opts) {
405
+ const threshold = _opts?.threshold ?? 0.92;
406
+ const siblings = fs3.readdirSync(memoryDir).filter((name) => name.toLowerCase().endsWith(".md") && name.toLowerCase() !== "memory.md").map((name) => path2.join(memoryDir, name)).sort();
407
+ if (siblings.length < 2) {
408
+ return [];
409
+ }
410
+ const snippets = siblings.map((p) => siblingSnippet(p));
411
+ const embeddingClusters = await tryEmbeddingClusters(siblings, snippets, threshold);
412
+ if (embeddingClusters !== null) {
413
+ return embeddingClusters;
414
+ }
415
+ const JACCARD_THRESHOLD = 0.6;
416
+ return greedyCluster(
417
+ siblings,
418
+ snippets,
419
+ (_seed, j, group) => group.some((gi) => jaccard(snippets[gi], snippets[j]) >= JACCARD_THRESHOLD),
420
+ (a, b) => jaccard(snippets[a], snippets[b]),
421
+ "jaccard"
422
+ );
423
+ }
424
+ function auditClaudeMd(files) {
425
+ const reports = [];
426
+ const allLines = [];
427
+ for (const filePath of files) {
428
+ let text;
429
+ try {
430
+ text = fs3.readFileSync(filePath, { encoding: "utf-8" });
431
+ } catch {
432
+ continue;
433
+ }
434
+ const lines = text.split("\n");
435
+ const tokens = estimateTokens(text);
436
+ const exactDups = [];
437
+ const dupSections = [];
438
+ const seenLines = /* @__PURE__ */ new Map();
439
+ for (let i = 0; i < lines.length; i++) {
440
+ const stripped = lines[i].trim();
441
+ if (!stripped) continue;
442
+ if (seenLines.has(stripped)) {
443
+ const firstLine = seenLines.get(stripped);
444
+ exactDups.push([firstLine, i, stripped]);
445
+ } else {
446
+ seenLines.set(stripped, i);
447
+ }
448
+ }
449
+ const seenHeadings = /* @__PURE__ */ new Map();
450
+ for (let i = 0; i < lines.length; i++) {
451
+ const line = lines[i];
452
+ if (line.startsWith("##")) {
453
+ const heading = line.trim();
454
+ if (!seenHeadings.has(heading)) {
455
+ seenHeadings.set(heading, []);
456
+ }
457
+ seenHeadings.get(heading).push(i);
458
+ }
459
+ }
460
+ for (const [heading, lnos] of seenHeadings) {
461
+ if (lnos.length > 1) {
462
+ dupSections.push([heading, lnos]);
463
+ }
464
+ }
465
+ reports.push({
466
+ path: filePath,
467
+ tokens,
468
+ exactDupLines: exactDups,
469
+ dupSections,
470
+ crossFileOverlaps: []
471
+ });
472
+ for (let i = 0; i < lines.length; i++) {
473
+ const stripped = lines[i].trim();
474
+ if (stripped) {
475
+ allLines.push([filePath, i, stripped]);
476
+ }
477
+ }
478
+ }
479
+ const lineToFiles = /* @__PURE__ */ new Map();
480
+ for (const [filePath, _i, stripped] of allLines) {
481
+ if (!lineToFiles.has(stripped)) {
482
+ lineToFiles.set(stripped, /* @__PURE__ */ new Set());
483
+ }
484
+ lineToFiles.get(stripped).add(filePath);
485
+ }
486
+ for (const report of reports) {
487
+ const overlaps = [];
488
+ for (const [stripped, filesSet] of lineToFiles) {
489
+ if (filesSet.has(report.path) && filesSet.size > 1) {
490
+ const others = Array.from(filesSet).filter((p) => p !== report.path).map((p) => path2.basename(p));
491
+ if (others.length > 0) {
492
+ if (stripped.length > 60) {
493
+ overlaps.push(
494
+ `${JSON.stringify(stripped.slice(0, 60))}\u2026 also in ${others.join(", ")}`
495
+ );
496
+ } else {
497
+ overlaps.push(`${JSON.stringify(stripped)} also in ${others.join(", ")}`);
498
+ }
499
+ }
500
+ }
501
+ }
502
+ report.crossFileOverlaps = overlaps.slice(0, 10);
503
+ }
504
+ return reports;
505
+ }
506
+
507
+ // src/cli_context_stats.ts
508
+ function tok(filePath) {
509
+ try {
510
+ const size = fs4.statSync(filePath).size;
511
+ return Math.floor(size / 4);
512
+ } catch {
513
+ return 0;
514
+ }
515
+ }
516
+ function findClaudeMdFiles(projectRoot, homeDir = os.homedir()) {
517
+ const found = [];
518
+ const seen = /* @__PURE__ */ new Set();
519
+ let current = path3.resolve(projectRoot);
520
+ while (true) {
521
+ const candidate = path3.join(current, "CLAUDE.md");
522
+ if (!seen.has(candidate) && fs4.existsSync(candidate)) {
523
+ found.push(candidate);
524
+ seen.add(candidate);
525
+ }
526
+ const parent = path3.dirname(current);
527
+ if (parent === current) break;
528
+ current = parent;
529
+ }
530
+ const globalMd = path3.join(claudeConfigDir(homeDir), "CLAUDE.md");
531
+ if (!seen.has(globalMd) && fs4.existsSync(globalMd)) {
532
+ found.push(globalMd);
533
+ }
534
+ return found;
535
+ }
536
+ function findMemoryMd(projectRoot, homeDir = os.homedir(), alternateRoots = []) {
537
+ try {
538
+ const projectsDir = path3.join(claudeConfigDir(homeDir), "projects");
539
+ if (!fs4.existsSync(projectsDir)) return null;
540
+ const rootStr = path3.resolve(projectRoot);
541
+ const candidateRoots = [rootStr];
542
+ try {
543
+ const realRoot = fs4.realpathSync.native(rootStr);
544
+ if (realRoot !== rootStr) candidateRoots.push(realRoot);
545
+ } catch {
546
+ }
547
+ for (const alternate of alternateRoots) {
548
+ const resolved = path3.resolve(alternate);
549
+ if (!candidateRoots.includes(resolved)) candidateRoots.push(resolved);
550
+ }
551
+ for (const root of candidateRoots) {
552
+ const expectedSlug = root.replace(/[^A-Za-z0-9]/g, "-");
553
+ const candidate = path3.join(projectsDir, expectedSlug, "memory", "MEMORY.md");
554
+ if (fs4.existsSync(candidate)) return candidate;
555
+ }
556
+ return null;
557
+ } catch {
558
+ return null;
559
+ }
560
+ }
561
+ function buildStats(projectRoot, homeDir = os.homedir(), alternateRoots = []) {
562
+ const claudeMds = findClaudeMdFiles(projectRoot, homeDir);
563
+ const claudeMdRows = [];
564
+ let claudeMdTotal = 0;
565
+ for (const p of claudeMds) {
566
+ const t = tok(p);
567
+ claudeMdTotal += t;
568
+ const parentDir = path3.basename(path3.dirname(p));
569
+ const label = parentDir === ".claude" ? "~/.claude/CLAUDE.md" : (() => {
570
+ try {
571
+ return path3.relative(projectRoot, p);
572
+ } catch {
573
+ return p;
574
+ }
575
+ })();
576
+ claudeMdRows.push({ label, tokens: t, path: p });
577
+ }
578
+ const memPath = findMemoryMd(projectRoot, homeDir, alternateRoots);
579
+ const memTok = memPath !== null ? tok(memPath) : 0;
580
+ const total = claudeMdTotal + memTok;
581
+ return {
582
+ claude_md_rows: claudeMdRows,
583
+ claude_md_total: claudeMdTotal,
584
+ memory_md_path: memPath,
585
+ memory_md_tokens: memTok,
586
+ total_tokens: total
587
+ };
588
+ }
589
+ async function runContextStats(opts = {}) {
590
+ const projectRoot = resolveProjectRoot(opts.project !== void 0 ? { project: opts.project } : {});
591
+ const result = buildStats(projectRoot);
592
+ if (opts.json === true) {
593
+ process.stdout.write(displaySafeJson(result) + "\n");
594
+ return;
595
+ }
596
+ process.stdout.write(`
597
+ # token-goat context-stats
598
+ `);
599
+ process.stdout.write(`Project: ${projectRoot}
600
+
601
+ `);
602
+ if (result.claude_md_rows.length === 0) {
603
+ process.stdout.write("No CLAUDE.md files found.\n");
604
+ } else {
605
+ process.stdout.write("## CLAUDE.md files\n");
606
+ for (const row of result.claude_md_rows) {
607
+ process.stdout.write(` ${row.tokens.toString().padStart(6)} tok ${displaySafeText(row.label)}
608
+ `);
609
+ }
610
+ process.stdout.write(
611
+ ` ${"\u2500".repeat(6)} total
612
+ ${result.claude_md_total.toString().padStart(6)} tok
613
+
614
+ `
615
+ );
616
+ }
617
+ if (result.memory_md_path !== null) {
618
+ process.stdout.write("## MEMORY.md\n");
619
+ process.stdout.write(` ${result.memory_md_tokens.toString().padStart(6)} tok ${displaySafeText(result.memory_md_path)}
620
+
621
+ `);
622
+ } else {
623
+ process.stdout.write("## MEMORY.md\n (not found)\n\n");
624
+ }
625
+ process.stdout.write(`## Total estimated context overhead
626
+ `);
627
+ process.stdout.write(` ${result.total_tokens.toString().padStart(6)} tok
628
+
629
+ `);
630
+ if (opts.fix === true) {
631
+ if (result.memory_md_path === null) {
632
+ process.stdout.write("[--fix] No MEMORY.md found; nothing to prune.\n");
633
+ } else {
634
+ const memPath = result.memory_md_path;
635
+ const pruneResult = pruneIndex(path3.dirname(memPath), { dryRun: true });
636
+ if (!pruneResult.changed || pruneResult.after === void 0) {
637
+ process.stdout.write("[--fix] MEMORY.md already clean; nothing to prune.\n");
638
+ } else {
639
+ const before = fs4.readFileSync(memPath, "utf-8");
640
+ const applyResult = await confirmAndApply(
641
+ [{ path: memPath, before, after: pruneResult.after, label: "MEMORY.md" }],
642
+ opts.yes === true ? { yes: true } : {}
643
+ );
644
+ if (applyResult.applied.length > 0) {
645
+ process.stdout.write("[--fix] Pruned MEMORY.md\n");
646
+ process.stdout.write(` removed ${countNoun(pruneResult.removedDead.length, "dead-link entry", "dead-link entries")}
647
+ `);
648
+ process.stdout.write(` removed ${countNoun(pruneResult.removedDup.length, "duplicate entry", "duplicate entries")}
649
+ `);
650
+ process.stdout.write(` kept ${countNoun(pruneResult.kept, "entry", "entries")}
651
+ `);
652
+ process.stdout.write(` ${pruneResult.tokensSaved} tok saved
653
+ `);
654
+ } else {
655
+ process.stdout.write(
656
+ `[--fix] ${applyResult.dryRun ? "Dry run" : "Skipped"} -- MEMORY.md not written. Re-run with --yes to apply without prompting.
657
+ `
658
+ );
659
+ }
660
+ }
661
+ }
662
+ }
663
+ }
664
+
665
+ // src/baseline.ts
666
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
667
+ "node_modules",
668
+ ".git",
669
+ ".hg",
670
+ ".svn",
671
+ "dist",
672
+ "build",
673
+ "out",
674
+ "coverage",
675
+ ".next",
676
+ ".nuxt",
677
+ ".venv",
678
+ "venv",
679
+ // Installed Python packages live under <any-venv>/Lib/site-packages or lib/pythonX.Y/site-packages. Skip by this exact name so a non-standard venv directory name (e.g. tmptg-py313-venv) cannot smuggle dependency code into the symbol index — the enclosing venv dir name varies, but site-packages does not.
680
+ "site-packages",
681
+ "__pycache__",
682
+ ".mypy_cache",
683
+ ".pytest_cache",
684
+ ".ruff_cache",
685
+ "target",
686
+ ".idea",
687
+ ".vscode"
688
+ ]);
689
+ function isIgnoredIndexPath(p, projectRoot) {
690
+ return hasSkipSegmentBelowRoot(p, projectRoot, (s) => SKIP_DIRS.has(foldPath(s)));
691
+ }
692
+ var MAX_FILES_SCANNED = 2e4;
693
+ function walkProject(rootDir, opts = {}) {
694
+ const files = [];
695
+ const languages = {};
696
+ const stack = [rootDir];
697
+ const excludeTests = opts.excludeTests === true;
698
+ const includeEmbeddableDocuments = opts.includeEmbeddableDocuments === true;
699
+ const extraSkipDirs = loadConfig().indexing.skip_dirs;
700
+ const maxFiles = opts.maxFiles ?? MAX_FILES_SCANNED;
701
+ while (stack.length > 0 && files.length < maxFiles) {
702
+ const dir = stack.pop();
703
+ if (dir === void 0) break;
704
+ let entries;
705
+ try {
706
+ entries = fs5.readdirSync(dir, { withFileTypes: true });
707
+ } catch {
708
+ continue;
709
+ }
710
+ for (const entry of entries) {
711
+ const full = path4.join(dir, entry.name);
712
+ if (entry.isDirectory()) {
713
+ if (SKIP_DIRS.has(entry.name) || extraSkipDirs.includes(entry.name)) continue;
714
+ if (entry.name.startsWith(".") && entry.name !== ".") {
715
+ continue;
716
+ }
717
+ stack.push(full);
718
+ } else if (entry.isFile()) {
719
+ const lang = detectLanguageOfFile(full);
720
+ if (lang === "unknown" && !(includeEmbeddableDocuments && isEmbeddableDocument(full))) continue;
721
+ if (excludeTests && isTestFile(full)) continue;
722
+ files.push(full);
723
+ languages[lang] = (languages[lang] ?? 0) + 1;
724
+ if (files.length >= maxFiles) break;
725
+ }
726
+ }
727
+ }
728
+ return { files, languages, truncated: files.length >= maxFiles };
729
+ }
730
+ function buildTopSymbolsSql() {
731
+ const { clause } = projectScopeClause("s.file_path");
732
+ const refScope = projectScopeClause("r.file_path");
733
+ const symbolsPathIndex = isCaseInsensitiveFs() ? "idx_symbols_file_folded" : "idx_symbols_file";
734
+ const family = (col) => `CASE WHEN ${col} IN ('typescript', 'javascript') THEN 'js' ELSE COALESCE(${col}, '') END`;
735
+ return `SELECT s.file_path, s.name, s.kind, s.line_start, s.line_end, s.body, s.docstring, s.parent
736
+ FROM (
737
+ SELECT rid, kind, score, body_len
738
+ FROM (
739
+ SELECT t.rid, t.name, t.kind, t.body_len, t.file_path,
740
+ t.ref_count * 1.0 / COUNT(*) OVER (PARTITION BY t.name) AS score,
741
+ ROW_NUMBER() OVER (
742
+ PARTITION BY t.name
743
+ ORDER BY t.body_len DESC, t.file_path
744
+ ) AS rn
745
+ FROM (
746
+ SELECT d.rid, d.file_path, d.name, d.kind, d.body_len, COUNT(DISTINCT rr.file_path) AS ref_count
747
+ FROM (
748
+ SELECT s.rowid AS rid, s.file_path, s.name, s.kind, LENGTH(COALESCE(s.body, '')) AS body_len,
749
+ ${family("(SELECT f.language FROM files f WHERE f.path = s.file_path)")} AS fam
750
+ FROM symbols s INDEXED BY ${symbolsPathIndex}
751
+ WHERE s.kind IN ('class', 'function', 'interface') AND ${clause}
752
+ AND LENGTH(s.name) >= 4
753
+ AND s.file_path NOT LIKE '%/tests/%' ESCAPE '\\'
754
+ AND s.file_path NOT LIKE '%/test/%' ESCAPE '\\'
755
+ AND s.file_path NOT LIKE '%/__tests__/%' ESCAPE '\\'
756
+ AND s.file_path NOT LIKE '%/spec/%' ESCAPE '\\'
757
+ AND s.file_path NOT LIKE '%.test.%'
758
+ AND s.file_path NOT LIKE '%.spec.%'
759
+ ) d
760
+ LEFT JOIN (
761
+ SELECT DISTINCT r.name AS name, r.file_path AS file_path, ${family("(SELECT rf.language FROM files rf WHERE rf.path = r.file_path)")} AS fam
762
+ FROM refs r
763
+ WHERE ${refScope.clause}
764
+ ) rr ON rr.name = d.name AND rr.fam = d.fam AND rr.file_path <> d.file_path
765
+ GROUP BY d.rid
766
+ ) t
767
+ )
768
+ WHERE rn = 1
769
+ ORDER BY score DESC,
770
+ CASE kind WHEN 'class' THEN 0 WHEN 'interface' THEN 1 ELSE 2 END,
771
+ body_len DESC
772
+ LIMIT ?
773
+ ) top
774
+ JOIN symbols s ON s.rowid = top.rid
775
+ ORDER BY top.score DESC,
776
+ CASE top.kind WHEN 'class' THEN 0 WHEN 'interface' THEN 1 ELSE 2 END,
777
+ top.body_len DESC`;
778
+ }
779
+ function fetchTopSymbols(limit, dbPath, rootDir) {
780
+ try {
781
+ const db = getDb(dbPath);
782
+ const bounds = projectScopeClause("file_path").params(rootDir);
783
+ const rows = db.prepare(buildTopSymbolsSql()).all(...bounds, ...bounds, limit);
784
+ return rows.map((r) => ({
785
+ filePath: r.file_path,
786
+ name: r.name,
787
+ kind: r.kind,
788
+ lineStart: r.line_start,
789
+ lineEnd: r.line_end,
790
+ body: r.body ?? "",
791
+ docstring: r.docstring ?? "",
792
+ parent: r.parent ?? ""
793
+ }));
794
+ } catch {
795
+ return [];
796
+ }
797
+ }
798
+ function buildProjectMap(rootDir = process.cwd(), opts = {}) {
799
+ const root = path4.resolve(rootDir);
800
+ const config = loadConfig();
801
+ const { files, languages, truncated } = walkProject(root, { excludeTests: config.repomap.exclude_tests });
802
+ const compact = opts.compact === true || files.length > config.repomap.compact_file_threshold;
803
+ const symbolLimit = compact ? 10 : 30;
804
+ const topSymbols = fetchTopSymbols(symbolLimit, globalDbPath(), root);
805
+ const recentFiles = compact && opts.emitsJson !== true ? [] : files.map((f) => {
806
+ let mtime;
807
+ try {
808
+ mtime = fs5.statSync(f).mtimeMs;
809
+ } catch {
810
+ mtime = 0;
811
+ }
812
+ return { f, mtime };
813
+ }).sort((a, b) => b.mtime - a.mtime).slice(0, compact ? 5 : 15).map((x) => path4.relative(root, x.f).split(path4.sep).join("/"));
814
+ return {
815
+ rootDir: root,
816
+ fileCount: files.length,
817
+ fileCountTruncated: truncated,
818
+ languages,
819
+ topSymbols,
820
+ recentFiles,
821
+ compact
822
+ };
823
+ }
824
+ function formatProjectMap(map, compact = false) {
825
+ const lines = [];
826
+ const rel = displaySafeText(path4.basename(map.rootDir));
827
+ lines.push(`# Project map: ${rel}`);
828
+ lines.push(map.fileCountTruncated === true ? `Files: at least ${map.fileCount} (walk stopped at the ${MAX_FILES_SCANNED}-file cap)` : `Files: ${map.fileCount}`);
829
+ const langPairs = Object.entries(map.languages).sort((a, b) => b[1] - a[1]);
830
+ const langSummary = langPairs.map(([lang, n]) => `${lang} ${n}`).join(", ");
831
+ const langSuffix = map.fileCountTruncated === true ? " (counted over a truncated walk; each count is a lower bound)" : "";
832
+ lines.push(`Languages: ${langSummary || "(none)"}${langSuffix}`);
833
+ if (map.topSymbols.length > 0) {
834
+ lines.push("");
835
+ lines.push("## Top symbols");
836
+ for (const s of map.topSymbols) {
837
+ const loc = formatSymbolLocation(displaySafeText(toDisplayPath(map.rootDir, s.filePath)), s.lineStart, s.lineEnd);
838
+ lines.push(`- ${displaySafeText(s.name)} (${displaySafeText(s.kind)}) \u2014 ${loc}`);
839
+ }
840
+ } else {
841
+ lines.push("");
842
+ lines.push(`## Top symbols: none \u2014 no files indexed for this project; run ${fencedCommand(suggestedIndexCommand(map.rootDir))}`);
843
+ }
844
+ if (!compact && map.recentFiles.length > 0) {
845
+ lines.push("");
846
+ lines.push("## Recent files");
847
+ for (const f of map.recentFiles) {
848
+ lines.push(`- ${displaySafeText(f)}`);
849
+ }
850
+ }
851
+ return lines.join(String.fromCharCode(10));
852
+ }
853
+ function mapLookupBytesSaved(map, emittedText) {
854
+ const referencedFiles = /* @__PURE__ */ new Set([
855
+ ...map.recentFiles.map((f) => normalizePath(path4.resolve(map.rootDir, f))),
856
+ ...map.topSymbols.map((s) => normalizePath(s.filePath))
857
+ ]);
858
+ const listingText = Array.from(referencedFiles).sort().join("\n");
859
+ const fullSourceBytes = Buffer.byteLength(listingText, "utf8");
860
+ const emittedBytes = Buffer.byteLength(emittedText, "utf8");
861
+ return Math.max(1, fullSourceBytes - emittedBytes);
862
+ }
863
+ var BULLET_LINE_RE = /^[-*]\s+\S.*$/;
864
+ var STRUCTURAL_HEADING_RE = /\b(architecture|file structure|directory structure|repository structure|project structure|folder structure)\b/i;
865
+ var CRITICAL_HEADING_RE = /\b(MANDATORY|REQUIRED|CRITICAL|NEVER|ALWAYS)\b/;
866
+ function findMemSuggestionCandidates(projectRoot) {
867
+ const claudeMdFiles = findClaudeMdFiles(projectRoot);
868
+ const candidateFiles = new Set(claudeMdFiles);
869
+ for (const claudeMd of claudeMdFiles) {
870
+ const agentsMd = path4.join(path4.dirname(claudeMd), "AGENTS.md");
871
+ if (fs5.existsSync(agentsMd)) candidateFiles.add(agentsMd);
872
+ }
873
+ const suggestions = [];
874
+ for (const filePath of candidateFiles) {
875
+ let text;
876
+ try {
877
+ text = fs5.readFileSync(filePath, { encoding: "utf-8" });
878
+ } catch {
879
+ continue;
880
+ }
881
+ let count = 0;
882
+ let skipSection = false;
883
+ let skipLevel = 0;
884
+ for (const rawLine of text.split(String.fromCharCode(10))) {
885
+ const line = rawLine.trim();
886
+ if (line.startsWith("#")) {
887
+ const level = /^#+/.exec(line)?.[0].length ?? 0;
888
+ if (skipSection && level > skipLevel) continue;
889
+ const heading = line.replace(/^#+\s*/, "");
890
+ skipSection = STRUCTURAL_HEADING_RE.test(heading) || CRITICAL_HEADING_RE.test(heading);
891
+ skipLevel = level;
892
+ continue;
893
+ }
894
+ if (skipSection) continue;
895
+ if (BULLET_LINE_RE.test(line)) count++;
896
+ }
897
+ if (count > 0) suggestions.push({ path: filePath, count });
898
+ }
899
+ return suggestions;
900
+ }
901
+ function formatMemSuggestions(projectRoot) {
902
+ const suggestions = findMemSuggestionCandidates(projectRoot);
903
+ if (suggestions.length === 0) return "";
904
+ const lines = ["", "## mem suggestions"];
905
+ for (const s of suggestions) {
906
+ const basename4 = path4.basename(s.path);
907
+ lines.push(
908
+ "Consider: mem import --from-md " + quotedArg(displaySafePath(s.path)) + " # migrates " + s.count + " preference-shaped lines from " + basename4 + " as pending facts for review"
909
+ );
910
+ }
911
+ return lines.join(String.fromCharCode(10));
912
+ }
913
+
914
+ export {
915
+ detectWalkMode,
916
+ getProjectIndexCounts,
917
+ getEmbeddingCoverage,
918
+ getParserFreshness,
919
+ isIndexEmptyForProject,
920
+ suggestedIndexCommand,
921
+ emptyIndexMessage,
922
+ confirmAndApply,
923
+ findContentDuplicates,
924
+ auditClaudeMd,
925
+ findClaudeMdFiles,
926
+ findMemoryMd,
927
+ buildStats,
928
+ runContextStats,
929
+ SKIP_DIRS,
930
+ isIgnoredIndexPath,
931
+ MAX_FILES_SCANNED,
932
+ walkProject,
933
+ buildProjectMap,
934
+ formatProjectMap,
935
+ mapLookupBytesSaved,
936
+ findMemSuggestionCandidates,
937
+ formatMemSuggestions
938
+ };