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.
- package/README.md +24 -7
- package/SECURITY.md +10 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/dist/native/linux-arm64/tg-hook +0 -0
- package/dist/native/linux-x64/tg-hook +0 -0
- package/dist/native/win32-arm64/tg-hook.exe +0 -0
- package/dist/native/win32-x64/tg-hook.exe +0 -0
- package/dist/{token-goat-chunk-QSBRTGM2.mjs → token-goat-chunk-25TRAWET.mjs} +17 -7
- package/dist/token-goat-chunk-2KXO6V2B.mjs +203 -0
- package/dist/{token-goat-chunk-IVKIZWAB.mjs → token-goat-chunk-2M7E4PF7.mjs} +41 -12
- package/dist/{token-goat-chunk-SMVRN2HU.mjs → token-goat-chunk-2UPZGKKY.mjs} +2 -2
- package/dist/token-goat-chunk-2WLPWKHV.mjs +135 -0
- package/dist/token-goat-chunk-2XBMMDBD.mjs +126 -0
- package/dist/token-goat-chunk-32ZOY4N5.mjs +23 -0
- package/dist/token-goat-chunk-3EZRNKTK.mjs +188 -0
- package/dist/token-goat-chunk-4ADZHI7J.mjs +63 -0
- package/dist/token-goat-chunk-4GBVPH3D.mjs +242 -0
- package/dist/{token-goat-chunk-KWPNJBDH.mjs → token-goat-chunk-4GILLLWY.mjs} +7 -4
- package/dist/{token-goat-chunk-67TQJ76B.mjs → token-goat-chunk-4NAPX7BA.mjs} +36 -3
- package/dist/{token-goat-chunk-PU7V3FD6.mjs → token-goat-chunk-4SC7UJYV.mjs} +3354 -2553
- package/dist/token-goat-chunk-4SMRO4TO.mjs +366 -0
- package/dist/{token-goat-chunk-AFLYLHDH.mjs → token-goat-chunk-4ZBFWLXF.mjs} +283 -427
- package/dist/{token-goat-chunk-JDWCRD6M.mjs → token-goat-chunk-57BIAXJL.mjs} +73 -40
- package/dist/token-goat-chunk-5AC66HNP.mjs +115 -0
- package/dist/token-goat-chunk-5IMLKSOP.mjs +145 -0
- package/dist/token-goat-chunk-646E6U44.mjs +398 -0
- package/dist/token-goat-chunk-65N4RHP4.mjs +33 -0
- package/dist/{token-goat-chunk-JN4XMLSD.mjs → token-goat-chunk-6DG6HWP3.mjs} +8 -2
- package/dist/token-goat-chunk-6ENQSNRZ.mjs +573 -0
- package/dist/token-goat-chunk-6ZY4GNF2.mjs +91 -0
- package/dist/{token-goat-chunk-VPBBTSJR.mjs → token-goat-chunk-76FGDLRO.mjs} +262 -255
- package/dist/{token-goat-chunk-7IN2MKDS.mjs → token-goat-chunk-7EZTZOMC.mjs} +121 -289
- package/dist/{token-goat-chunk-OJBNYTJP.mjs → token-goat-chunk-7MAOA6X4.mjs} +640 -80
- package/dist/token-goat-chunk-7R2ZVVAX.mjs +218 -0
- package/dist/{token-goat-chunk-PD5VKH46.mjs → token-goat-chunk-AUNGYYQX.mjs} +2 -2
- package/dist/{token-goat-chunk-HG45AF2B.mjs → token-goat-chunk-AUQYRMMH.mjs} +794 -37
- package/dist/token-goat-chunk-B3PEKBYG.mjs +128 -0
- package/dist/{token-goat-chunk-A3CK6N5Z.mjs → token-goat-chunk-BA4HI6BW.mjs} +1309 -110
- package/dist/{token-goat-chunk-JB6QN6VF.mjs → token-goat-chunk-BFCWY73P.mjs} +59 -23
- package/dist/{token-goat-chunk-B45VMIIH.mjs → token-goat-chunk-BWGFUZZE.mjs} +7 -5
- package/dist/{token-goat-chunk-P4QMM4WK.mjs → token-goat-chunk-C75NA7PL.mjs} +50 -23
- package/dist/token-goat-chunk-CBIQ7WTD.mjs +87 -0
- package/dist/token-goat-chunk-CETM74KC.mjs +307 -0
- package/dist/{token-goat-chunk-H2PHHGM5.mjs → token-goat-chunk-CIMQ2BB3.mjs} +57 -12
- package/dist/{token-goat-chunk-N2NMER56.mjs → token-goat-chunk-CJLS3CDL.mjs} +46 -4
- package/dist/{token-goat-chunk-VES7EL7O.mjs → token-goat-chunk-CO3UYX6G.mjs} +1 -1
- package/dist/token-goat-chunk-COPNXRJM.mjs +154 -0
- package/dist/token-goat-chunk-CSJ5L3NL.mjs +479 -0
- package/dist/{token-goat-chunk-4LCCNRTG.mjs → token-goat-chunk-CVCEVUGK.mjs} +8 -6
- package/dist/{token-goat-chunk-B2RZZCSS.mjs → token-goat-chunk-CXHOFN7R.mjs} +37 -20
- package/dist/token-goat-chunk-D735C4QM.mjs +603 -0
- package/dist/{token-goat-chunk-APGZXSPS.mjs → token-goat-chunk-DGS2LVNA.mjs} +47 -30
- package/dist/{token-goat-chunk-54K3EUV3.mjs → token-goat-chunk-DOWRSMWO.mjs} +1 -1
- package/dist/token-goat-chunk-E45INMY3.mjs +71 -0
- package/dist/token-goat-chunk-EC5F37F4.mjs +244 -0
- package/dist/{token-goat-chunk-XTQAOTSO.mjs → token-goat-chunk-EDBDRVH5.mjs} +4 -1
- package/dist/{token-goat-chunk-5BCIJXMO.mjs → token-goat-chunk-EELTBKDM.mjs} +14 -10
- package/dist/{token-goat-chunk-W3QCJS6G.mjs → token-goat-chunk-F4HYL64R.mjs} +8 -6
- package/dist/token-goat-chunk-F6PAUIEK.mjs +624 -0
- package/dist/token-goat-chunk-FDSREWLX.mjs +692 -0
- package/dist/token-goat-chunk-GGGG7RWB.mjs +236 -0
- package/dist/{token-goat-chunk-C63JQV5K.mjs → token-goat-chunk-GL3ETKSC.mjs} +233 -15
- package/dist/token-goat-chunk-GVB2MUST.mjs +1298 -0
- package/dist/token-goat-chunk-GWFLAUMQ.mjs +307 -0
- package/dist/{token-goat-chunk-ZEJVMUQH.mjs → token-goat-chunk-HLPW63A7.mjs} +2 -2
- package/dist/{token-goat-chunk-GOLEOXZP.mjs → token-goat-chunk-HU4TORC5.mjs} +44 -26
- package/dist/{token-goat-chunk-Z57YODA2.mjs → token-goat-chunk-I7VKZZAW.mjs} +2 -2
- package/dist/{token-goat-chunk-NYKZLXMK.mjs → token-goat-chunk-IA6XSPE3.mjs} +6 -6
- package/dist/{token-goat-chunk-E3OQX2XT.mjs → token-goat-chunk-IAGXMHIK.mjs} +81 -42
- package/dist/{token-goat-chunk-HYBFA5X3.mjs → token-goat-chunk-IK2DRMFX.mjs} +24 -68
- package/dist/{token-goat-chunk-COBQGL5S.mjs → token-goat-chunk-INNPTRUX.mjs} +249 -299
- package/dist/{token-goat-chunk-7NADEDKJ.mjs → token-goat-chunk-JJ5BX4F4.mjs} +6 -1
- package/dist/{token-goat-chunk-PEPTHRFF.mjs → token-goat-chunk-JVFSHVFJ.mjs} +6 -6
- package/dist/token-goat-chunk-KGEFFSHD.mjs +50 -0
- package/dist/token-goat-chunk-KQBLRPBN.mjs +433 -0
- package/dist/{token-goat-chunk-Q5VTGRH7.mjs → token-goat-chunk-LCNQ2CNN.mjs} +55 -319
- package/dist/token-goat-chunk-M3LFJDPU.mjs +58 -0
- package/dist/token-goat-chunk-M52KPT3C.mjs +87 -0
- package/dist/token-goat-chunk-MBEG42H7.mjs +150 -0
- package/dist/{token-goat-chunk-EVC4TOLE.mjs → token-goat-chunk-METECDOH.mjs} +3 -12
- package/dist/{token-goat-chunk-T4FK7VOV.mjs → token-goat-chunk-MHX7XCC3.mjs} +2 -1
- package/dist/token-goat-chunk-N5LSOVSU.mjs +137 -0
- package/dist/{token-goat-chunk-LOBGHXPB.mjs → token-goat-chunk-N5MMR2IW.mjs} +1 -1
- package/dist/token-goat-chunk-N7B3JQ2C.mjs +160 -0
- package/dist/{token-goat-chunk-RHXKCZX3.mjs → token-goat-chunk-NISNMD7T.mjs} +2182 -2959
- package/dist/token-goat-chunk-NMD42SFQ.mjs +434 -0
- package/dist/token-goat-chunk-NOSS5VWY.mjs +174 -0
- package/dist/{token-goat-chunk-KQNZDCBD.mjs → token-goat-chunk-NSNSPPBG.mjs} +25 -3
- package/dist/token-goat-chunk-OBOFIHKN.mjs +38 -0
- package/dist/token-goat-chunk-OBRFJ5O5.mjs +76 -0
- package/dist/{token-goat-chunk-JTQQGXZH.mjs → token-goat-chunk-ODBT2USM.mjs} +124 -57
- package/dist/token-goat-chunk-OS5EM5PP.mjs +107 -0
- package/dist/token-goat-chunk-OUKDTVNU.mjs +1178 -0
- package/dist/token-goat-chunk-OXFMP75N.mjs +566 -0
- package/dist/{token-goat-chunk-DZX2YDPT.mjs → token-goat-chunk-OYIYKDDT.mjs} +2 -2
- package/dist/{token-goat-chunk-QCLDAQTS.mjs → token-goat-chunk-P6HQSWQ3.mjs} +50 -53
- package/dist/token-goat-chunk-P6VGNTQT.mjs +71 -0
- package/dist/{token-goat-chunk-7BXSNBY5.mjs → token-goat-chunk-PFWIMH2E.mjs} +23 -15
- package/dist/token-goat-chunk-PJZXIS3G.mjs +39 -0
- package/dist/token-goat-chunk-PUO5INXP.mjs +606 -0
- package/dist/{token-goat-chunk-GFBSN7BD.mjs → token-goat-chunk-PX5PQWTY.mjs} +22 -250
- package/dist/token-goat-chunk-Q5QDBC6O.mjs +111 -0
- package/dist/token-goat-chunk-QFNQXSDT.mjs +40 -0
- package/dist/{token-goat-chunk-FBNQNSJK.mjs → token-goat-chunk-QH7YVYOY.mjs} +2198 -2782
- package/dist/{token-goat-chunk-64FBPDTK.mjs → token-goat-chunk-QLMUHEPB.mjs} +12 -9
- package/dist/{token-goat-chunk-RZRTDST7.mjs → token-goat-chunk-QQOJIHL3.mjs} +37 -11
- package/dist/{token-goat-chunk-XKI4SEMX.mjs → token-goat-chunk-QQTWJY2O.mjs} +19 -0
- package/dist/{token-goat-chunk-U4N3BN2W.mjs → token-goat-chunk-QYHO5CPH.mjs} +3 -2
- package/dist/token-goat-chunk-RELAVWAD.mjs +27 -0
- package/dist/{token-goat-chunk-ETBHVDB6.mjs → token-goat-chunk-RSLPOBKQ.mjs} +22 -11
- package/dist/token-goat-chunk-SAMFDIS2.mjs +712 -0
- package/dist/token-goat-chunk-SDELXLDM.mjs +48 -0
- package/dist/{token-goat-chunk-4JJS54TR.mjs → token-goat-chunk-SOAFZNNZ.mjs} +161 -38
- package/dist/{token-goat-chunk-664RTTUV.mjs → token-goat-chunk-SRTHT7KV.mjs} +21 -16
- package/dist/{token-goat-chunk-4FVIAHSI.mjs → token-goat-chunk-T5P5ACNQ.mjs} +7 -6
- package/dist/token-goat-chunk-T5YGG4V3.mjs +45 -0
- package/dist/token-goat-chunk-TAUZ4OFS.mjs +110 -0
- package/dist/{token-goat-chunk-COTXZHTS.mjs → token-goat-chunk-TM6MC754.mjs} +4 -3
- package/dist/token-goat-chunk-TWUKBOMP.mjs +72 -0
- package/dist/{token-goat-chunk-3CHUNJ2O.mjs → token-goat-chunk-U2KSB4SG.mjs} +8 -5
- package/dist/{token-goat-chunk-3Z5S4KGM.mjs → token-goat-chunk-U4WHMG6Y.mjs} +29 -49
- package/dist/{token-goat-chunk-PY533T7F.mjs → token-goat-chunk-UGJHGR5A.mjs} +21 -15
- package/dist/{token-goat-chunk-CYCEYAEO.mjs → token-goat-chunk-UOUDVA5U.mjs} +1 -1
- package/dist/{token-goat-chunk-JDHE26RP.mjs → token-goat-chunk-URRJ33GR.mjs} +16 -5
- package/dist/{token-goat-chunk-56EIMS3E.mjs → token-goat-chunk-UZOROKIS.mjs} +23 -17
- package/dist/{token-goat-chunk-CZ3LUXYZ.mjs → token-goat-chunk-VIHGGGEI.mjs} +427 -308
- package/dist/token-goat-chunk-VTJLAA7P.mjs +21 -0
- package/dist/token-goat-chunk-VWDXLT4F.mjs +678 -0
- package/dist/{token-goat-chunk-YARZFCZS.mjs → token-goat-chunk-VWZA2H4P.mjs} +62 -53
- package/dist/{token-goat-chunk-SQSSNQPE.mjs → token-goat-chunk-VXEXMJDL.mjs} +3979 -4745
- package/dist/token-goat-chunk-WBA5SKT4.mjs +547 -0
- package/dist/{token-goat-chunk-SJHWVNDQ.mjs → token-goat-chunk-WDH44VAU.mjs} +7 -4
- package/dist/token-goat-chunk-X2SUP3EI.mjs +107 -0
- package/dist/token-goat-chunk-X3BELZSJ.mjs +125 -0
- package/dist/{token-goat-chunk-FZ3NEW5O.mjs → token-goat-chunk-XLRBNZCC.mjs} +13 -8
- package/dist/{token-goat-chunk-Z44YO3QZ.mjs → token-goat-chunk-XRKUX6CM.mjs} +1 -1
- package/dist/token-goat-chunk-XX6O25RN.mjs +82 -0
- package/dist/token-goat-chunk-XZSUHBVS.mjs +65 -0
- package/dist/{token-goat-chunk-WY3L4DVT.mjs → token-goat-chunk-Y2ZTQOCL.mjs} +13 -8
- package/dist/{token-goat-chunk-UQ5UK2T7.mjs → token-goat-chunk-YB4VMGK2.mjs} +1 -1
- package/dist/token-goat-chunk-YBIIY3HR.mjs +51 -0
- package/dist/{token-goat-chunk-ZQSJMO2R.mjs → token-goat-chunk-YHVDKQBR.mjs} +105 -105
- package/dist/{token-goat-chunk-H2K3QP6H.mjs → token-goat-chunk-YJB5XLWI.mjs} +8 -6
- package/dist/{token-goat-chunk-ZCDTCVIS.mjs → token-goat-chunk-YKB6AQG3.mjs} +5 -2
- package/dist/token-goat-chunk-YMTG2YA5.mjs +35 -0
- package/dist/token-goat-chunk-YPBZYQRV.mjs +40 -0
- package/dist/{token-goat-chunk-HOJLCGDP.mjs → token-goat-chunk-YY6AVUFL.mjs} +2033 -2251
- package/dist/token-goat-chunk-Z7N5OMMP.mjs +98 -0
- package/dist/token-goat-chunk-ZNMNUPAZ.mjs +938 -0
- package/dist/token-goat-hook-client.cjs +1 -1
- package/dist/token-goat-hook-client.mjs +3 -3
- package/dist/token-goat-hook.mjs +67 -38
- package/dist/token-goat.core.mjs +4 -4
- package/docs/C4_RUNTIME_ARCHITECTURE.md +1 -1
- package/docs/cli.md +40 -21
- package/docs/install.md +19 -9
- package/docs/security.md +5 -3
- package/package.json +2 -1
- package/dist/token-goat-chunk-5KDMCMF2.mjs +0 -362
- package/dist/token-goat-chunk-6QMAS7AB.mjs +0 -42
- package/dist/token-goat-chunk-7F2UBPD3.mjs +0 -397
- package/dist/token-goat-chunk-ARDNEC2U.mjs +0 -248
- package/dist/token-goat-chunk-ATCPY7EQ.mjs +0 -134
- package/dist/token-goat-chunk-EFHMMIGP.mjs +0 -68
- package/dist/token-goat-chunk-IVALLT3S.mjs +0 -48
- package/dist/token-goat-chunk-JDU3EXSU.mjs +0 -65
- package/dist/token-goat-chunk-MKPF4CL5.mjs +0 -37
- package/dist/token-goat-chunk-RC35RBUL.mjs +0 -312
- package/dist/token-goat-chunk-USILZ3T3.mjs +0 -53
- package/dist/token-goat-chunk-VL3YIFHZ.mjs +0 -2975
- package/dist/token-goat-chunk-XU4P6Z24.mjs +0 -133
- 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
|
+
};
|