tianshu-harness 3.23.0

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 (255) hide show
  1. package/LICENSE +189 -0
  2. package/README.en.md +1066 -0
  3. package/README.ja.md +1214 -0
  4. package/README.ko.md +1216 -0
  5. package/README.md +309 -0
  6. package/completions/rivet.bash +118 -0
  7. package/completions/rivet.fish +110 -0
  8. package/completions/rivet.ps1 +230 -0
  9. package/completions/rivet.zsh +187 -0
  10. package/dist/account-OAEKORZE.js +47 -0
  11. package/dist/account-login-V6FOLHJR.js +65 -0
  12. package/dist/account-status-KHNPPAFL.js +23 -0
  13. package/dist/agent/worker-process/child.js +407 -0
  14. package/dist/browser-cli-FLNY5ZHI.js +19 -0
  15. package/dist/browser-readiness-LWVGSBD5.js +12 -0
  16. package/dist/build-backends-5KXKR2S4.js +13 -0
  17. package/dist/build-options-66Y4ZE7C.js +9 -0
  18. package/dist/bundled-skills/brainstorming/SKILL.md +139 -0
  19. package/dist/bundled-skills/visual-acceptance/SKILL.md +64 -0
  20. package/dist/chain-7MAMFXZ3.js +10 -0
  21. package/dist/checkpoint-MND4DUIX.js +33 -0
  22. package/dist/chunk-2E6CK2RC.js +392 -0
  23. package/dist/chunk-2EBWGAXD.js +51 -0
  24. package/dist/chunk-2HDP7RMR.js +119 -0
  25. package/dist/chunk-2HKD2UFM.js +1000 -0
  26. package/dist/chunk-2VYRLULP.js +51 -0
  27. package/dist/chunk-2XN7PM4N.js +125 -0
  28. package/dist/chunk-35H7GVNR.js +66 -0
  29. package/dist/chunk-3C3R7JXY.js +77 -0
  30. package/dist/chunk-3VGD2FXB.js +118 -0
  31. package/dist/chunk-424AP7FM.js +534 -0
  32. package/dist/chunk-4J5TZGOE.js +3707 -0
  33. package/dist/chunk-4QJ3Y5JM.js +898 -0
  34. package/dist/chunk-4U2ET7ZY.js +1326 -0
  35. package/dist/chunk-4XPFLICE.js +876 -0
  36. package/dist/chunk-5B2OCU5P.js +31707 -0
  37. package/dist/chunk-5GGOGLSN.js +73 -0
  38. package/dist/chunk-5JRZPLI6.js +251 -0
  39. package/dist/chunk-5TQ3JYBU.js +397 -0
  40. package/dist/chunk-66JPZSGX.js +55 -0
  41. package/dist/chunk-6HSVXHCZ.js +764 -0
  42. package/dist/chunk-6S5FPUS7.js +21 -0
  43. package/dist/chunk-722LQO64.js +131 -0
  44. package/dist/chunk-73NJ7IT7.js +354 -0
  45. package/dist/chunk-7FW3I7WC.js +95 -0
  46. package/dist/chunk-7KUCBNLB.js +173 -0
  47. package/dist/chunk-7RDOV3DF.js +201 -0
  48. package/dist/chunk-A5MNH4ER.js +79 -0
  49. package/dist/chunk-ADIVZ2D5.js +15 -0
  50. package/dist/chunk-AE3P3F6W.js +366 -0
  51. package/dist/chunk-AERM3VGP.js +280 -0
  52. package/dist/chunk-AEWHC4WM.js +246 -0
  53. package/dist/chunk-AOVQ7VIF.js +169 -0
  54. package/dist/chunk-AOYHP3WI.js +12411 -0
  55. package/dist/chunk-B3YEBABJ.js +7160 -0
  56. package/dist/chunk-BCQXEZPJ.js +154 -0
  57. package/dist/chunk-BKTQAYO2.js +418 -0
  58. package/dist/chunk-BSECIPAQ.js +80 -0
  59. package/dist/chunk-BUIMGNDD.js +52 -0
  60. package/dist/chunk-BXHV3GSB.js +10320 -0
  61. package/dist/chunk-C6ZVZCPP.js +139 -0
  62. package/dist/chunk-CEZNQVWM.js +82 -0
  63. package/dist/chunk-CGFZGR7G.js +1492 -0
  64. package/dist/chunk-CGOADM2X.js +337 -0
  65. package/dist/chunk-CI4R6P2B.js +24 -0
  66. package/dist/chunk-D2TCGRSX.js +73 -0
  67. package/dist/chunk-D4PTYFOG.js +154 -0
  68. package/dist/chunk-D54ZWG4T.js +717 -0
  69. package/dist/chunk-DEBI37QB.js +145 -0
  70. package/dist/chunk-DWROHPXP.js +1051 -0
  71. package/dist/chunk-DZ6UOJ5D.js +92 -0
  72. package/dist/chunk-EBJTVRQN.js +81 -0
  73. package/dist/chunk-EGEI2RQA.js +1167 -0
  74. package/dist/chunk-EHHUASO5.js +1305 -0
  75. package/dist/chunk-EPKTIOED.js +41 -0
  76. package/dist/chunk-ER5JW2QZ.js +5681 -0
  77. package/dist/chunk-ETHFACAB.js +315 -0
  78. package/dist/chunk-EXUE5W2F.js +112 -0
  79. package/dist/chunk-EXXICUIW.js +73 -0
  80. package/dist/chunk-FFQSZJOO.js +169 -0
  81. package/dist/chunk-FHFKPGDO.js +85 -0
  82. package/dist/chunk-FW3QKMFX.js +158 -0
  83. package/dist/chunk-GD4ZNZ7W.js +30 -0
  84. package/dist/chunk-GHVMWF2I.js +581 -0
  85. package/dist/chunk-GMKAXSYQ.js +297 -0
  86. package/dist/chunk-HBVF6BWO.js +325 -0
  87. package/dist/chunk-HV4YGVMS.js +379 -0
  88. package/dist/chunk-ILBCKAV6.js +89 -0
  89. package/dist/chunk-J4U4AW46.js +440 -0
  90. package/dist/chunk-J6ESSM47.js +1791 -0
  91. package/dist/chunk-JBZHIC6T.js +2309 -0
  92. package/dist/chunk-JCFWOULR.js +245 -0
  93. package/dist/chunk-JRF5L6OD.js +131 -0
  94. package/dist/chunk-JSS75KCG.js +2014 -0
  95. package/dist/chunk-JSXWIWWJ.js +225 -0
  96. package/dist/chunk-JYV7I2QH.js +98 -0
  97. package/dist/chunk-KIOXTQQL.js +1596 -0
  98. package/dist/chunk-KWVTXT5B.js +89 -0
  99. package/dist/chunk-KX22RD6R.js +33 -0
  100. package/dist/chunk-LA6IPTGH.js +709 -0
  101. package/dist/chunk-LH5VCGLB.js +1796 -0
  102. package/dist/chunk-LQQ6XJ2Y.js +269 -0
  103. package/dist/chunk-MJQ55WZZ.js +65 -0
  104. package/dist/chunk-ML3UKMH3.js +40 -0
  105. package/dist/chunk-MOZPLHSU.js +5526 -0
  106. package/dist/chunk-NGEWJ4DF.js +89 -0
  107. package/dist/chunk-NH6VIOAR.js +211 -0
  108. package/dist/chunk-O5VZQKCB.js +26106 -0
  109. package/dist/chunk-OHSV42QB.js +748 -0
  110. package/dist/chunk-OO3TAHO5.js +4070 -0
  111. package/dist/chunk-OTNHSAGN.js +38282 -0
  112. package/dist/chunk-OUWAC7AQ.js +442 -0
  113. package/dist/chunk-OXMIV4WE.js +270 -0
  114. package/dist/chunk-P5KJURUR.js +229 -0
  115. package/dist/chunk-P6GJOVO6.js +307 -0
  116. package/dist/chunk-QARAGUXX.js +23 -0
  117. package/dist/chunk-R43HJCDX.js +2711 -0
  118. package/dist/chunk-RAXBAWRN.js +210 -0
  119. package/dist/chunk-RK3JHWPT.js +39 -0
  120. package/dist/chunk-RWA23GMM.js +24 -0
  121. package/dist/chunk-S4K4L6IM.js +179 -0
  122. package/dist/chunk-SAXK6LVF.js +124 -0
  123. package/dist/chunk-SEPGEJNK.js +1607 -0
  124. package/dist/chunk-SUJERP5I.js +285 -0
  125. package/dist/chunk-T3ED6BY2.js +913 -0
  126. package/dist/chunk-THKDVSEC.js +90 -0
  127. package/dist/chunk-TI4GSEL5.js +52 -0
  128. package/dist/chunk-TPI73L57.js +164 -0
  129. package/dist/chunk-TVHW77C7.js +338 -0
  130. package/dist/chunk-TWNHOSDT.js +1073 -0
  131. package/dist/chunk-UFMN76QD.js +347 -0
  132. package/dist/chunk-UHGLFRZE.js +205 -0
  133. package/dist/chunk-UTZYPKBF.js +15231 -0
  134. package/dist/chunk-UXNLELW6.js +25 -0
  135. package/dist/chunk-UXXS53EA.js +166 -0
  136. package/dist/chunk-V5DHFGI7.js +171 -0
  137. package/dist/chunk-V5YIPMHV.js +2353 -0
  138. package/dist/chunk-WMUWEO2C.js +754 -0
  139. package/dist/chunk-WOE3D7JM.js +945 -0
  140. package/dist/chunk-X2IK5LVR.js +2592 -0
  141. package/dist/chunk-XU2YXCDL.js +459 -0
  142. package/dist/chunk-YJG5HIX5.js +203 -0
  143. package/dist/chunk-Z3QE7TZM.js +72 -0
  144. package/dist/chunk-Z5Z3JHJY.js +64 -0
  145. package/dist/chunk-ZDIFBJFT.js +168 -0
  146. package/dist/chunk-ZGGCEXDC.js +176 -0
  147. package/dist/chunk-ZMZCZNKU.js +263 -0
  148. package/dist/chunk-ZNC5T65N.js +17 -0
  149. package/dist/chunk-ZO3I3A7O.js +308 -0
  150. package/dist/chunk-ZQKXBUB7.js +217 -0
  151. package/dist/chunk-ZTRINFM2.js +78 -0
  152. package/dist/chunk-ZU7JZCVP.js +193 -0
  153. package/dist/chunk-ZZAWNALS.js +350 -0
  154. package/dist/cli/entry.js +54 -0
  155. package/dist/connector-L5QQTR6Y.js +26 -0
  156. package/dist/context-62DC6EC3.js +31 -0
  157. package/dist/council-convene-NJWPNBDU.js +46 -0
  158. package/dist/create-agent-config-ODSQWNVC.js +55 -0
  159. package/dist/default-registry-2PU3BTAU.js +68 -0
  160. package/dist/deliver-task-KONV4C4M.js +57 -0
  161. package/dist/delivery-gate-v2-F2YVMVGK.js +27 -0
  162. package/dist/domain-knowledge-store-DDZ3F4J7.js +11 -0
  163. package/dist/fetch-core-ZPPYDSZH.js +14 -0
  164. package/dist/galaxy-K72BBDQN.js +91 -0
  165. package/dist/goal-criteria-SCALAU2D.js +141 -0
  166. package/dist/goal-persist-XWY2GDQX.js +19 -0
  167. package/dist/goal-tracker-FXMJ2GVK.js +11 -0
  168. package/dist/headless-LSB6I7PT.js +162 -0
  169. package/dist/headless-coordinator-VDPDMVUT.js +90 -0
  170. package/dist/inject-U55SMB6A.js +39 -0
  171. package/dist/login-flow-CD4ZGXOC.js +59 -0
  172. package/dist/logs-cli-SQVYFMMN.js +283 -0
  173. package/dist/loop-ULZYBDA3.js +76 -0
  174. package/dist/main.js +4064 -0
  175. package/dist/manager-ROZBCWPA.js +171 -0
  176. package/dist/manager-Z7XFFUOM.js +10670 -0
  177. package/dist/meridian-indexer-ZIW363L7.js +21 -0
  178. package/dist/native-resolver-PN34UIVV.js +13 -0
  179. package/dist/node_modules/better-sqlite3/lib/database.js +90 -0
  180. package/dist/node_modules/better-sqlite3/lib/index.js +3 -0
  181. package/dist/node_modules/better-sqlite3/lib/methods/aggregate.js +43 -0
  182. package/dist/node_modules/better-sqlite3/lib/methods/backup.js +67 -0
  183. package/dist/node_modules/better-sqlite3/lib/methods/function.js +31 -0
  184. package/dist/node_modules/better-sqlite3/lib/methods/inspect.js +7 -0
  185. package/dist/node_modules/better-sqlite3/lib/methods/pragma.js +12 -0
  186. package/dist/node_modules/better-sqlite3/lib/methods/serialize.js +16 -0
  187. package/dist/node_modules/better-sqlite3/lib/methods/table.js +189 -0
  188. package/dist/node_modules/better-sqlite3/lib/methods/transaction.js +78 -0
  189. package/dist/node_modules/better-sqlite3/lib/methods/wrappers.js +54 -0
  190. package/dist/node_modules/better-sqlite3/lib/sqlite-error.js +20 -0
  191. package/dist/node_modules/better-sqlite3/lib/util.js +12 -0
  192. package/dist/node_modules/better-sqlite3/package.json +59 -0
  193. package/dist/ownership-ledger-E3WIEKPL.js +9 -0
  194. package/dist/package.json +3 -0
  195. package/dist/path-grants-XRRXHXG7.js +40 -0
  196. package/dist/paths-LVV5IMQN.js +49 -0
  197. package/dist/plan-fact-anchors-LMVQZCW5.js +13 -0
  198. package/dist/plan-store-ODLTTPFB.js +17 -0
  199. package/dist/plan-templates-5FPFBOL6.js +109 -0
  200. package/dist/plugin-loader-ZZKX7ZE6.js +22 -0
  201. package/dist/prefix-budget-62OV5HQC.js +13 -0
  202. package/dist/project-trust-5WS5MOMU.js +31 -0
  203. package/dist/provider-cli-ALDQXIT7.js +37 -0
  204. package/dist/providers-XQYTAVPK.js +11 -0
  205. package/dist/proxy-resolver-V2Y6534D.js +15 -0
  206. package/dist/recovery-cli-KPGY6FY3.js +106 -0
  207. package/dist/review-discipline-MNSYCST5.js +55 -0
  208. package/dist/review-router-BVQZW3MX.js +18 -0
  209. package/dist/seed-capsules/seed-capsule-attack-methodology.md +62 -0
  210. package/dist/seed-capsules/seed-capsule-changgeng.md +31 -0
  211. package/dist/seed-capsules/seed-capsule-design-aesthetics.md +38 -0
  212. package/dist/seed-capsules/seed-capsule-diagnostic-ladder.md +54 -0
  213. package/dist/seed-capsules/seed-capsule-fu.md +39 -0
  214. package/dist/seed-capsules/seed-capsule-huagai.md +73 -0
  215. package/dist/seed-capsules/seed-capsule-kaiyang.md +65 -0
  216. package/dist/seed-capsules/seed-capsule-knowledge-work.md +40 -0
  217. package/dist/seed-capsules/seed-capsule-tanlang.md +47 -0
  218. package/dist/seed-capsules/seed-capsule-tianfu.md +23 -0
  219. package/dist/seed-capsules/seed-capsule-tianliang.md +63 -0
  220. package/dist/seed-capsules/seed-capsule-tianquan.md +32 -0
  221. package/dist/seed-capsules/seed-capsule-tianxuan-ccr.md +22 -0
  222. package/dist/seed-capsules/seed-capsule-tianxuan.md +64 -0
  223. package/dist/seed-capsules/seed-capsule-yaoguang.md +87 -0
  224. package/dist/serve-X2R2UTLF.js +170 -0
  225. package/dist/serve-agent-KHSU6RVV.js +965 -0
  226. package/dist/session-persist-HUUC772H.js +37 -0
  227. package/dist/session-registry-6XUD5C4Y.js +15 -0
  228. package/dist/skill-loader-CK3RMO47.js +48 -0
  229. package/dist/skills/cli-harness-sop.md +207 -0
  230. package/dist/spawn-git-4HFU65AH.js +29 -0
  231. package/dist/sse-EYUTBWUK.js +512 -0
  232. package/dist/starflow-NOFBMPFU.js +30 -0
  233. package/dist/streamableHttp-V67CNJMO.js +447 -0
  234. package/dist/task-contract-6XHBNJQK.js +32 -0
  235. package/dist/task-ledger-3K4TJIER.js +9 -0
  236. package/dist/team-orchestrate-S6NHZP7X.js +64 -0
  237. package/dist/title-extract-QX4XGCYN.js +50 -0
  238. package/dist/todo-QDXGRZZP.js +31 -0
  239. package/dist/todo-store-V3TTO5AH.js +12 -0
  240. package/dist/tool-preset-LU5BYEDZ.js +23 -0
  241. package/dist/tool-tiers-GD742YBN.js +23 -0
  242. package/dist/turndown.es-OI55K6C3.js +17373 -0
  243. package/dist/verification-attribution-EKG4CNUV.js +13 -0
  244. package/dist/volatile-git-MBTST7RA.js +15 -0
  245. package/dist/wave-checkpoint-XLIIHXQA.js +30 -0
  246. package/dist/web-cli-DUG4I35O.js +233 -0
  247. package/dist/web-search-3IK542AA.js +34 -0
  248. package/dist/workers/cpu-worker.js +45 -0
  249. package/dist/workflow-runner-HX7UBAYG.js +297 -0
  250. package/dist/worktree-baseline-O5NCU6M2.js +9 -0
  251. package/package.json +122 -0
  252. package/scripts/check-mobile-env.mjs +76 -0
  253. package/scripts/fetch-native-sqlite.js +392 -0
  254. package/scripts/prune-installed-wasms.js +45 -0
  255. package/scripts/tree-sitter-wasm-keep.js +40 -0
@@ -0,0 +1,1791 @@
1
+ #!/usr/bin/env -S node --expose-gc --max-old-space-size=4096
2
+ import { createRequire as __rivetCreateRequire } from 'node:module'; const require = __rivetCreateRequire(import.meta.url);
3
+ import {
4
+ analyzeImpact,
5
+ inferTestedByTargets
6
+ } from "./chunk-3C3R7JXY.js";
7
+ import {
8
+ classifyPath
9
+ } from "./chunk-7FW3I7WC.js";
10
+ import {
11
+ toPosixPath
12
+ } from "./chunk-GD4ZNZ7W.js";
13
+ import {
14
+ resolveBetterSqlite3
15
+ } from "./chunk-722LQO64.js";
16
+ import {
17
+ init_esm_shims
18
+ } from "./chunk-5GGOGLSN.js";
19
+
20
+ // src/repo/meridian-indexer.ts
21
+ init_esm_shims();
22
+ import { readFileSync, existsSync as existsSync2, realpathSync } from "fs";
23
+ import { resolve, dirname } from "path";
24
+ import { createHash as createHash2 } from "crypto";
25
+
26
+ // src/repo/meridian-db.ts
27
+ init_esm_shims();
28
+ import { join } from "path";
29
+ import { existsSync, mkdirSync } from "fs";
30
+ var SCHEMA = `
31
+ CREATE TABLE IF NOT EXISTS files (
32
+ path TEXT PRIMARY KEY,
33
+ content_hash TEXT NOT NULL,
34
+ indexed_at TEXT NOT NULL DEFAULT (datetime('now'))
35
+ );
36
+ CREATE TABLE IF NOT EXISTS symbols (
37
+ id TEXT PRIMARY KEY,
38
+ name TEXT NOT NULL,
39
+ kind TEXT NOT NULL,
40
+ file_path TEXT NOT NULL,
41
+ line INTEGER NOT NULL,
42
+ exported INTEGER NOT NULL DEFAULT 0,
43
+ content_hash TEXT NOT NULL
44
+ );
45
+ CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_path);
46
+ CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);
47
+
48
+ CREATE TABLE IF NOT EXISTS edges (
49
+ source_id TEXT NOT NULL,
50
+ target_id TEXT NOT NULL,
51
+ kind TEXT NOT NULL,
52
+ weight REAL NOT NULL DEFAULT 1.0,
53
+ confidence TEXT NOT NULL DEFAULT 'extracted',
54
+ PRIMARY KEY(source_id, target_id, kind)
55
+ );
56
+ CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);
57
+ CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);
58
+
59
+ CREATE TABLE IF NOT EXISTS access_log (
60
+ file_path TEXT NOT NULL,
61
+ accessed_at TEXT NOT NULL DEFAULT (datetime('now'))
62
+ );
63
+ CREATE INDEX IF NOT EXISTS idx_access_file ON access_log(file_path);
64
+
65
+ CREATE TABLE IF NOT EXISTS co_edits (
66
+ file_a TEXT NOT NULL,
67
+ file_b TEXT NOT NULL,
68
+ weight REAL NOT NULL DEFAULT 1.0,
69
+ last_turn INTEGER NOT NULL,
70
+ PRIMARY KEY(file_a, file_b)
71
+ );
72
+ CREATE INDEX IF NOT EXISTS idx_co_edits_a ON co_edits(file_a);
73
+ CREATE INDEX IF NOT EXISTS idx_co_edits_b ON co_edits(file_b);
74
+
75
+ CREATE TABLE IF NOT EXISTS physarum_edges (
76
+ file_a TEXT NOT NULL,
77
+ file_b TEXT NOT NULL,
78
+ weight REAL NOT NULL,
79
+ flow REAL NOT NULL DEFAULT 0,
80
+ consolidated INTEGER NOT NULL DEFAULT 0,
81
+ activation_count INTEGER NOT NULL DEFAULT 0,
82
+ last_activated_turn INTEGER NOT NULL DEFAULT 0,
83
+ direction REAL NOT NULL DEFAULT 0,
84
+ PRIMARY KEY(file_a, file_b)
85
+ );
86
+
87
+ CREATE TABLE IF NOT EXISTS physarum_prediction_observations (
88
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
89
+ source_file TEXT NOT NULL,
90
+ predicted_at_turn INTEGER NOT NULL,
91
+ predictions_json TEXT NOT NULL,
92
+ observed_file TEXT NOT NULL,
93
+ observed_at_turn INTEGER NOT NULL,
94
+ hit_rank INTEGER,
95
+ lead_turns INTEGER NOT NULL,
96
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
97
+ );
98
+ CREATE INDEX IF NOT EXISTS idx_physarum_prediction_source ON physarum_prediction_observations(source_file);
99
+ CREATE INDEX IF NOT EXISTS idx_physarum_prediction_observed ON physarum_prediction_observations(observed_file);
100
+
101
+ CREATE TABLE IF NOT EXISTS immune_memory (
102
+ id TEXT PRIMARY KEY,
103
+ pattern TEXT NOT NULL,
104
+ response_json TEXT NOT NULL,
105
+ affinity_score REAL NOT NULL DEFAULT 0.5,
106
+ hit_count INTEGER NOT NULL DEFAULT 0,
107
+ last_hit INTEGER NOT NULL DEFAULT 0,
108
+ created_at INTEGER NOT NULL DEFAULT 0
109
+ );
110
+ CREATE INDEX IF NOT EXISTS idx_immune_pattern ON immune_memory(pattern);
111
+
112
+ CREATE TABLE IF NOT EXISTS mistake_entries (
113
+ id TEXT PRIMARY KEY,
114
+ timestamp TEXT NOT NULL,
115
+ error TEXT NOT NULL,
116
+ context TEXT NOT NULL,
117
+ resolution TEXT NOT NULL,
118
+ tags_json TEXT NOT NULL DEFAULT '[]'
119
+ );
120
+ CREATE INDEX IF NOT EXISTS idx_mistake_error ON mistake_entries(error);
121
+
122
+ CREATE TABLE IF NOT EXISTS p3_state (
123
+ kind TEXT NOT NULL,
124
+ version INTEGER NOT NULL,
125
+ json TEXT NOT NULL,
126
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
127
+ PRIMARY KEY(kind, version)
128
+ );
129
+
130
+ CREATE TABLE IF NOT EXISTS sensorimotor_log (
131
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
132
+ context_hash TEXT NOT NULL,
133
+ tool_name TEXT NOT NULL,
134
+ success INTEGER NOT NULL,
135
+ duration_ms INTEGER,
136
+ turn INTEGER NOT NULL,
137
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
138
+ );
139
+ CREATE INDEX IF NOT EXISTS idx_sm_context ON sensorimotor_log(context_hash, tool_name);
140
+ CREATE INDEX IF NOT EXISTS idx_sm_tool ON sensorimotor_log(tool_name);
141
+
142
+ CREATE TABLE IF NOT EXISTS module_summaries (
143
+ dir_path TEXT PRIMARY KEY,
144
+ summary TEXT NOT NULL,
145
+ key_exports_json TEXT NOT NULL DEFAULT '[]',
146
+ file_count INTEGER NOT NULL DEFAULT 0,
147
+ status TEXT NOT NULL DEFAULT 'active',
148
+ content_hash TEXT NOT NULL DEFAULT '',
149
+ verified_at_commit TEXT,
150
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
151
+ );
152
+
153
+ CREATE TABLE IF NOT EXISTS cli_entries (
154
+ flag TEXT NOT NULL,
155
+ handler TEXT NOT NULL,
156
+ wired INTEGER NOT NULL DEFAULT 0,
157
+ verified_at_commit TEXT,
158
+ source_file TEXT NOT NULL,
159
+ PRIMARY KEY(flag, source_file)
160
+ );
161
+
162
+ CREATE TABLE IF NOT EXISTS meridian_meta (
163
+ key TEXT PRIMARY KEY,
164
+ value TEXT NOT NULL
165
+ );
166
+
167
+ CREATE INDEX IF NOT EXISTS idx_access_time ON access_log(accessed_at);
168
+ CREATE INDEX IF NOT EXISTS idx_sm_created ON sensorimotor_log(created_at);
169
+ CREATE INDEX IF NOT EXISTS idx_p3_updated ON p3_state(updated_at);
170
+ `;
171
+ var CLEANUP_MIN_INTERVAL_MS = 24 * 3600 * 1e3;
172
+ var P3_EVENT_RETENTION_DAYS = 30;
173
+ var LOG_RETENTION_DAYS = 90;
174
+ function globEscape(filePath) {
175
+ return filePath.replace(/[*?[]/g, "[$&]");
176
+ }
177
+ var MeridianDb = class {
178
+ conn = null;
179
+ stateDir;
180
+ _available = true;
181
+ constructor(stateDir) {
182
+ this.stateDir = stateDir;
183
+ }
184
+ get db() {
185
+ if (!this.conn) {
186
+ if (!existsSync(this.stateDir)) mkdirSync(this.stateDir, { recursive: true });
187
+ try {
188
+ const Database = resolveBetterSqlite3(import.meta.url);
189
+ if (!Database) throw new Error("better-sqlite3 not installed");
190
+ const dbPath = join(this.stateDir, "meridian.db");
191
+ this.conn = new Database(dbPath);
192
+ this.conn.pragma("journal_mode = WAL");
193
+ this.conn.pragma("busy_timeout = 3000");
194
+ this.conn.exec(SCHEMA);
195
+ migrateToV1(this.conn);
196
+ this.conn.pragma("journal_size_limit = 67108864");
197
+ migrateToV2(this.conn);
198
+ this.cleanupExpiredRows();
199
+ } catch (err) {
200
+ if (err?.code === "ESQLITE_BUNDLE_BROKEN") throw err;
201
+ const reason = err instanceof Error ? err.message : String(err);
202
+ const hint = process.platform === "win32" ? 'Run: cd "$(npm root -g)\\tianshu-harness" && node scripts\\fetch-native-sqlite.js' : 'Run: cd "$(npm root -g)/tianshu-harness" && node scripts/fetch-native-sqlite.js';
203
+ console.warn(`\u26A0 better-sqlite3 not available. Code index (MeridianDb) disabled \u2014 repo symbol search & cross-file analysis will be unavailable. Reason: ${reason}
204
+ Fix: ${hint}`);
205
+ this._available = false;
206
+ this.conn = createNullDb();
207
+ }
208
+ }
209
+ return this.conn;
210
+ }
211
+ /**
212
+ * Whether the index is actually backed by sqlite.
213
+ *
214
+ * Callers must consult this BEFORE doing expensive work whose only consumer is
215
+ * the index (reading a file, hashing it, running tree-sitter). Feeding the
216
+ * no-op DB is not merely wasted storage: `needsParse` can never observe a
217
+ * previous write, so every caller re-does the same work forever.
218
+ *
219
+ * Touching `this.db` first is load-bearing — `_available` starts out `true`
220
+ * and only becomes accurate once the lazy connection has been attempted.
221
+ */
222
+ get available() {
223
+ void this.db;
224
+ return this._available;
225
+ }
226
+ needsParse(filePath, contentHash) {
227
+ if (!this.available) return false;
228
+ const row = this.db.prepare("SELECT content_hash FROM files WHERE path = ?").get(filePath);
229
+ return !row || row.content_hash !== contentHash;
230
+ }
231
+ upsertFile(result) {
232
+ if (!this.available) return;
233
+ const tx = this.db.transaction(() => {
234
+ this.db.prepare("INSERT OR REPLACE INTO files (path, content_hash) VALUES (?, ?)").run(result.filePath, result.contentHash);
235
+ this.db.prepare("DELETE FROM symbols WHERE file_path = ?").run(result.filePath);
236
+ const escapedPath = globEscape(result.filePath);
237
+ this.db.prepare("DELETE FROM edges WHERE source_id GLOB ?").run(`${escapedPath}:*`);
238
+ const insertSym = this.db.prepare("INSERT OR REPLACE INTO symbols (id, name, kind, file_path, line, exported, content_hash) VALUES (?, ?, ?, ?, ?, ?, ?)");
239
+ for (const s of result.symbols) {
240
+ insertSym.run(s.id, s.name, s.kind, s.filePath, s.line, s.exported ? 1 : 0, s.contentHash);
241
+ }
242
+ const insertEdge = this.db.prepare("INSERT OR REPLACE INTO edges (source_id, target_id, kind, weight, confidence) VALUES (?, ?, ?, ?, ?)");
243
+ for (const e of result.edges) {
244
+ insertEdge.run(e.sourceId, e.targetId, e.kind, e.weight, e.confidence ?? "extracted");
245
+ }
246
+ for (const imp of result.imports) {
247
+ const firstSymbol = result.symbols[0];
248
+ if (firstSymbol) {
249
+ insertEdge.run(firstSymbol.id, `${imp}:*:0`, "imports", 1, "extracted");
250
+ }
251
+ }
252
+ });
253
+ tx();
254
+ }
255
+ getSymbolsForFile(filePath) {
256
+ return this.db.prepare("SELECT * FROM symbols WHERE file_path = ?").all(filePath).map((row) => ({
257
+ id: row.id,
258
+ name: row.name,
259
+ kind: row.kind,
260
+ filePath: row.file_path,
261
+ line: row.line,
262
+ exported: row.exported === 1,
263
+ contentHash: row.content_hash
264
+ }));
265
+ }
266
+ /** Get every indexed symbol — cross-file callee-name matching reads this. */
267
+ getAllSymbols() {
268
+ return this.db.prepare("SELECT * FROM symbols").all().map((row) => ({
269
+ id: row.id,
270
+ name: row.name,
271
+ kind: row.kind,
272
+ filePath: row.file_path,
273
+ line: row.line,
274
+ exported: row.exported === 1,
275
+ contentHash: row.content_hash
276
+ }));
277
+ }
278
+ /**
279
+ * 按 name 集合一次拉取符号(buildCallEdges 批量路径,2026-09-08)。
280
+ * 参数化 `WHERE name IN (?,...)` 走 idx_symbols_name,替代 getAllSymbols
281
+ * 全表物化 + 内存过滤。名字去重后按 500 个分片(SQLite 变量上限留足余量),
282
+ * 分片结果按 symbol id 去重。空集合/无命中返回 []。
283
+ */
284
+ getSymbolsByNames(names) {
285
+ const unique = [...new Set(names)];
286
+ if (unique.length === 0) return [];
287
+ const CHUNK_SIZE = 500;
288
+ const seen = /* @__PURE__ */ new Set();
289
+ const out = [];
290
+ for (let i = 0; i < unique.length; i += CHUNK_SIZE) {
291
+ const chunk = unique.slice(i, i + CHUNK_SIZE);
292
+ const placeholders = chunk.map(() => "?").join(",");
293
+ const rows = this.db.prepare(`SELECT * FROM symbols WHERE name IN (${placeholders})`).all(...chunk);
294
+ for (const row of rows) {
295
+ const symbol = {
296
+ id: row.id,
297
+ name: row.name,
298
+ kind: row.kind,
299
+ filePath: row.file_path,
300
+ line: row.line,
301
+ exported: row.exported === 1,
302
+ contentHash: row.content_hash
303
+ };
304
+ if (seen.has(symbol.id)) continue;
305
+ seen.add(symbol.id);
306
+ out.push(symbol);
307
+ }
308
+ }
309
+ return out;
310
+ }
311
+ getEdgesFrom(symbolId) {
312
+ return this.db.prepare("SELECT * FROM edges WHERE source_id = ?").all(symbolId).map((row) => ({
313
+ sourceId: row.source_id,
314
+ targetId: row.target_id,
315
+ kind: row.kind,
316
+ weight: row.weight,
317
+ confidence: row.confidence ?? "extracted"
318
+ }));
319
+ }
320
+ getEdgesTo(symbolId) {
321
+ return this.db.prepare("SELECT * FROM edges WHERE target_id = ?").all(symbolId).map((row) => ({
322
+ sourceId: row.source_id,
323
+ targetId: row.target_id,
324
+ kind: row.kind,
325
+ weight: row.weight,
326
+ confidence: row.confidence ?? "extracted"
327
+ }));
328
+ }
329
+ recordAccess(filePath) {
330
+ if (!this.available) return;
331
+ this.db.prepare("INSERT INTO access_log (file_path) VALUES (?)").run(filePath);
332
+ }
333
+ getAccessCount(filePath) {
334
+ const row = this.db.prepare("SELECT COUNT(*) as cnt FROM access_log WHERE file_path = ?").get(filePath);
335
+ return row.cnt;
336
+ }
337
+ getNeighborIds(startId, maxHops) {
338
+ const visited = /* @__PURE__ */ new Set();
339
+ let frontier = /* @__PURE__ */ new Set([startId]);
340
+ for (let hop = 0; hop < maxHops; hop++) {
341
+ const next = /* @__PURE__ */ new Set();
342
+ for (const id of frontier) {
343
+ const rows = this.db.prepare(
344
+ "SELECT target_id as nid FROM edges WHERE source_id = ? UNION SELECT source_id as nid FROM edges WHERE target_id = ?"
345
+ ).all(id, id);
346
+ for (const r of rows) {
347
+ if (!visited.has(r.nid) && r.nid !== startId) {
348
+ visited.add(r.nid);
349
+ next.add(r.nid);
350
+ }
351
+ }
352
+ }
353
+ frontier = next;
354
+ }
355
+ return visited;
356
+ }
357
+ getStats() {
358
+ const files = this.db.prepare("SELECT COUNT(*) as cnt FROM files").get().cnt;
359
+ const symbols = this.db.prepare("SELECT COUNT(*) as cnt FROM symbols").get().cnt;
360
+ const edges = this.db.prepare("SELECT COUNT(*) as cnt FROM edges").get().cnt;
361
+ return { files, symbols, edges };
362
+ }
363
+ recordCoEdit(fileA, fileB, turn) {
364
+ const [a, b] = fileA < fileB ? [fileA, fileB] : [fileB, fileA];
365
+ this.db.prepare(`
366
+ INSERT INTO co_edits (file_a, file_b, weight, last_turn)
367
+ VALUES (?, ?, 1.0, ?)
368
+ ON CONFLICT(file_a, file_b) DO UPDATE SET
369
+ weight = MIN(weight + 0.5, 5.0),
370
+ last_turn = excluded.last_turn
371
+ `).run(a, b, turn);
372
+ }
373
+ getCoEditNeighbors(filePath) {
374
+ return this.db.prepare(`
375
+ SELECT file_b as file, weight FROM co_edits WHERE file_a = ?
376
+ UNION ALL
377
+ SELECT file_a as file, weight FROM co_edits WHERE file_b = ?
378
+ `).all(filePath, filePath);
379
+ }
380
+ getAccessHeat(filePath, decayHalfLifeN = 10) {
381
+ const rows = this.db.prepare(
382
+ "SELECT accessed_at FROM access_log WHERE file_path = ? ORDER BY rowid DESC LIMIT 20"
383
+ ).all(filePath);
384
+ let heat = 0;
385
+ for (let i = 0; i < rows.length; i++) {
386
+ heat += Math.pow(0.5, i / decayHalfLifeN);
387
+ }
388
+ return heat;
389
+ }
390
+ /** Get files that depend on the given file (reverse edges: who imports/calls into this file) */
391
+ getReverseDependents(filePath) {
392
+ return this.db.prepare(`
393
+ SELECT DISTINCT
394
+ substr(e.source_id, 1, instr(e.source_id, ':') - 1) as file,
395
+ e.kind,
396
+ e.weight
397
+ FROM edges e
398
+ WHERE e.target_id GLOB ?
399
+ AND substr(e.source_id, 1, instr(e.source_id, ':') - 1) != ?
400
+ `).all(`${globEscape(filePath)}:*`, filePath);
401
+ }
402
+ /** Get files this file depends on via imports edges (P2-2 出边 API,与入边对称)。
403
+ * 导入边写入格式 target_id = <path>:*:0(240 行),source_id = <path>:<symbol>;
404
+ * source_id GLOB 前缀匹配该文件全部符号,排除 target 为自身的 self-import 边
405
+ * (方向与入边相反:排除对象是 target_id 一侧),globEscape 沿
406
+ * getReverseDependents 的既有约定(370 行)。悬边(target 未索引)保留。 */
407
+ getForwardDependencies(filePath) {
408
+ return this.db.prepare(`
409
+ SELECT DISTINCT
410
+ substr(e.target_id, 1, instr(e.target_id, ':') - 1) as file,
411
+ e.kind,
412
+ e.weight
413
+ FROM edges e
414
+ WHERE e.kind = 'imports'
415
+ AND e.source_id GLOB ?
416
+ AND substr(e.target_id, 1, instr(e.target_id, ':') - 1) != ?
417
+ `).all(`${globEscape(filePath)}:*`, filePath);
418
+ }
419
+ /** Get test files associated with a source file via tested_by edges */
420
+ getTestsFor(filePath) {
421
+ const rows = this.db.prepare(`
422
+ SELECT DISTINCT substr(e.source_id, 1, instr(e.source_id, ':') - 1) as file
423
+ FROM edges e
424
+ WHERE e.target_id GLOB ? AND e.kind = 'tested_by'
425
+ `).all(`${globEscape(filePath)}:*`);
426
+ return rows.map((r) => r.file);
427
+ }
428
+ /** Get all indexed file paths */
429
+ getAllFiles() {
430
+ return this.db.prepare("SELECT path FROM files").all().map((r) => r.path);
431
+ }
432
+ /**
433
+ * P1-2: cheap cold-start probe — whether the files table has any rows.
434
+ * LIMIT 1 探测,避免写工具热路径上每次全表扫(getAllFiles 5k 文件 ≈ 5k 行)。
435
+ */
436
+ hasFiles() {
437
+ const row = this.db.prepare("SELECT 1 AS one FROM files LIMIT 1").get();
438
+ return row !== void 0;
439
+ }
440
+ /** Insert or update a single edge */
441
+ upsertEdge(sourceId, targetId, kind, weight, confidence = "extracted") {
442
+ this.db.prepare(
443
+ "INSERT OR REPLACE INTO edges (source_id, target_id, kind, weight, confidence) VALUES (?, ?, ?, ?, ?)"
444
+ ).run(sourceId, targetId, kind, weight, confidence);
445
+ }
446
+ // ─── Codebase index (module summaries + CLI entries) ────────────────
447
+ upsertModuleSummary(entry) {
448
+ this.db.prepare(`INSERT OR REPLACE INTO module_summaries (dir_path, summary, key_exports_json, file_count, status, content_hash, verified_at_commit, updated_at)
449
+ VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))`).run(
450
+ entry.dirPath,
451
+ entry.summary,
452
+ JSON.stringify(entry.keyExports),
453
+ entry.fileCount,
454
+ entry.status,
455
+ entry.contentHash,
456
+ entry.verifiedAtCommit ?? null
457
+ );
458
+ }
459
+ getModuleSummaries() {
460
+ const rows = this.db.prepare("SELECT * FROM module_summaries ORDER BY dir_path").all();
461
+ return rows.map((r) => ({
462
+ dirPath: r.dir_path,
463
+ summary: r.summary,
464
+ keyExports: JSON.parse(r.key_exports_json),
465
+ fileCount: r.file_count,
466
+ status: r.status,
467
+ contentHash: r.content_hash,
468
+ verifiedAtCommit: r.verified_at_commit ?? void 0
469
+ }));
470
+ }
471
+ upsertCliEntry(entry) {
472
+ this.db.prepare(`INSERT OR REPLACE INTO cli_entries (flag, handler, wired, verified_at_commit, source_file)
473
+ VALUES (?, ?, ?, ?, ?)`).run(
474
+ entry.flag,
475
+ entry.handler,
476
+ entry.wired ? 1 : 0,
477
+ entry.verifiedAtCommit ?? null,
478
+ entry.sourceFile
479
+ );
480
+ }
481
+ getCliEntries() {
482
+ const rows = this.db.prepare("SELECT * FROM cli_entries ORDER BY flag").all();
483
+ return rows.map((r) => ({
484
+ flag: r.flag,
485
+ handler: r.handler,
486
+ wired: r.wired === 1,
487
+ verifiedAtCommit: r.verified_at_commit ?? void 0,
488
+ sourceFile: r.source_file
489
+ }));
490
+ }
491
+ // ─── Physarum persistence ───────────────────────────────────────────
492
+ savePhysarumEdges(edges) {
493
+ const tx = this.db.transaction(() => {
494
+ this.db.prepare("DELETE FROM physarum_edges").run();
495
+ const stmt = this.db.prepare(
496
+ "INSERT INTO physarum_edges (file_a, file_b, weight, flow, consolidated, activation_count, last_activated_turn, direction) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
497
+ );
498
+ for (const e of edges) {
499
+ stmt.run(e.fileA, e.fileB, e.weight, e.flow, e.consolidated ? 1 : 0, e.activationCount, e.lastActivatedTurn, e.direction);
500
+ }
501
+ });
502
+ tx();
503
+ }
504
+ loadPhysarumEdges() {
505
+ const rows = this.db.prepare("SELECT * FROM physarum_edges").all();
506
+ return rows.map((r) => ({
507
+ fileA: r.file_a,
508
+ fileB: r.file_b,
509
+ weight: r.weight,
510
+ flow: r.flow,
511
+ consolidated: r.consolidated === 1,
512
+ activationCount: r.activation_count,
513
+ lastActivatedTurn: r.last_activated_turn,
514
+ direction: r.direction
515
+ }));
516
+ }
517
+ recordPhysarumPredictionObservation(observation) {
518
+ if (!this._available) return;
519
+ try {
520
+ this.db.prepare(`
521
+ INSERT INTO physarum_prediction_observations
522
+ (source_file, predicted_at_turn, predictions_json, observed_file, observed_at_turn, hit_rank, lead_turns)
523
+ VALUES (?, ?, ?, ?, ?, ?, ?)
524
+ `).run(
525
+ observation.sourceFile,
526
+ observation.predictedAtTurn,
527
+ JSON.stringify(observation.predictions),
528
+ observation.observedFile,
529
+ observation.observedAtTurn,
530
+ observation.hitRank,
531
+ observation.leadTurns
532
+ );
533
+ } catch {
534
+ }
535
+ }
536
+ getPhysarumPredictionObservations(limit = 100) {
537
+ if (!this._available) return [];
538
+ try {
539
+ const safeLimit = Math.max(1, Math.min(1e3, Math.floor(limit)));
540
+ const rows = this.db.prepare(`
541
+ SELECT * FROM physarum_prediction_observations
542
+ ORDER BY id DESC
543
+ LIMIT ${safeLimit}
544
+ `).all();
545
+ return rows.map((r) => ({
546
+ sourceFile: r.source_file,
547
+ predictedAtTurn: r.predicted_at_turn,
548
+ predictions: JSON.parse(r.predictions_json),
549
+ observedFile: r.observed_file,
550
+ observedAtTurn: r.observed_at_turn,
551
+ hitRank: r.hit_rank ?? null,
552
+ leadTurns: r.lead_turns
553
+ }));
554
+ } catch {
555
+ return [];
556
+ }
557
+ }
558
+ // ─── Immune memory persistence ───────────────────────────────────────
559
+ saveImmuneMemories(memories) {
560
+ const tx = this.db.transaction(() => {
561
+ this.db.prepare("DELETE FROM immune_memory").run();
562
+ const stmt = this.db.prepare(
563
+ "INSERT INTO immune_memory (id, pattern, response_json, affinity_score, hit_count, last_hit, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)"
564
+ );
565
+ for (const m of memories) {
566
+ stmt.run(
567
+ m.id,
568
+ m.pattern,
569
+ JSON.stringify(m.response),
570
+ m.affinityScore,
571
+ m.hitCount,
572
+ m.lastHit,
573
+ m.createdAt
574
+ );
575
+ }
576
+ });
577
+ tx();
578
+ }
579
+ loadImmuneMemories() {
580
+ const rows = this.db.prepare("SELECT * FROM immune_memory").all();
581
+ const result = [];
582
+ for (const r of rows) {
583
+ try {
584
+ const response = JSON.parse(r.response_json);
585
+ result.push({
586
+ id: r.id,
587
+ pattern: r.pattern,
588
+ response,
589
+ affinityScore: r.affinity_score,
590
+ hitCount: r.hit_count,
591
+ lastHit: r.last_hit,
592
+ createdAt: r.created_at
593
+ });
594
+ } catch {
595
+ }
596
+ }
597
+ return result;
598
+ }
599
+ // ─── Mistake notebook persistence ────────────────────────────────────
600
+ saveMistakeEntries(entries) {
601
+ const insert = this.db.prepare(
602
+ "INSERT INTO mistake_entries (id, timestamp, error, context, resolution, tags_json) VALUES (?, ?, ?, ?, ?, ?)"
603
+ );
604
+ const tx = this.db.transaction((items) => {
605
+ this.db.prepare("DELETE FROM mistake_entries").run();
606
+ for (const e of items) {
607
+ insert.run(e.id, e.timestamp, e.error, e.context, e.resolution, JSON.stringify(e.tags));
608
+ }
609
+ });
610
+ tx(entries);
611
+ }
612
+ /** Memory-epoch reset: drop all persisted mistake entries (see memory-epoch.ts). */
613
+ clearMistakeEntries() {
614
+ this.db.prepare("DELETE FROM mistake_entries").run();
615
+ }
616
+ loadMistakeEntries() {
617
+ const rows = this.db.prepare("SELECT * FROM mistake_entries").all();
618
+ const result = [];
619
+ for (const r of rows) {
620
+ try {
621
+ result.push({
622
+ id: r.id,
623
+ timestamp: r.timestamp,
624
+ error: r.error,
625
+ context: r.context,
626
+ resolution: r.resolution,
627
+ tags: JSON.parse(r.tags_json)
628
+ });
629
+ } catch {
630
+ }
631
+ }
632
+ return result;
633
+ }
634
+ // ─── P3 state persistence ───────────────────────────────────────────
635
+ saveToolPatternMinerSnapshot(snapshot) {
636
+ if (!this._available) return;
637
+ this.db.prepare(`
638
+ INSERT INTO p3_state (kind, version, json, updated_at)
639
+ VALUES ('tool_pattern_miner', ?, ?, datetime('now'))
640
+ ON CONFLICT(kind, version) DO UPDATE SET
641
+ json = excluded.json,
642
+ updated_at = excluded.updated_at
643
+ `).run(snapshot.version, JSON.stringify(snapshot));
644
+ }
645
+ loadToolPatternMinerSnapshot() {
646
+ if (!this._available) return null;
647
+ const row = this.db.prepare(`
648
+ SELECT json FROM p3_state
649
+ WHERE kind = 'tool_pattern_miner' AND version = 1
650
+ `).get();
651
+ if (!row) return null;
652
+ const parsed = JSON.parse(row.json);
653
+ return parsed.version === 1 ? parsed : null;
654
+ }
655
+ // ─── T2-02: Bandit state persistence ──────────────────────────────────
656
+ /**
657
+ * Periodic retention cleanup (anti-regression): runs at most once per 24h,
658
+ * tracked in meridian_meta. Idempotent; failures degrade silently — telemetry
659
+ * cleanup never blocks any path.
660
+ */
661
+ cleanupExpiredRows() {
662
+ if (!this._available) return;
663
+ try {
664
+ const row = this.db.prepare(`SELECT value FROM meridian_meta WHERE key = 'last_cleanup_at'`).get();
665
+ const last = row ? Number(row.value) : 0;
666
+ if (Number.isFinite(last) && Date.now() - last < CLEANUP_MIN_INTERVAL_MS) return;
667
+ purgeExpiredRows(this.db);
668
+ this.db.prepare(`INSERT OR REPLACE INTO meridian_meta (key, value) VALUES ('last_cleanup_at', ?)`).run(String(Date.now()));
669
+ } catch {
670
+ }
671
+ }
672
+ saveBanditState(kind, json) {
673
+ if (!this._available) return;
674
+ try {
675
+ this.db.prepare(`
676
+ INSERT INTO p3_state (kind, version, json, updated_at)
677
+ VALUES (?, 1, ?, datetime('now'))
678
+ ON CONFLICT(kind, version) DO UPDATE SET
679
+ json = excluded.json,
680
+ updated_at = excluded.updated_at
681
+ `).run(kind, json);
682
+ } catch {
683
+ }
684
+ }
685
+ loadBanditState(kind) {
686
+ if (!this._available) return null;
687
+ try {
688
+ const row = this.db.prepare(`
689
+ SELECT json FROM p3_state
690
+ WHERE kind = ? AND version = 1
691
+ `).get(kind);
692
+ return row?.json ?? null;
693
+ } catch {
694
+ return null;
695
+ }
696
+ }
697
+ loadBanditStatesByPrefix(prefix, limit = 100) {
698
+ if (!this._available) return [];
699
+ try {
700
+ const safeLimit = Math.max(1, Math.min(1e3, Math.floor(limit)));
701
+ return this.db.prepare(`
702
+ SELECT kind, json, updated_at as updatedAt FROM p3_state
703
+ WHERE kind LIKE ?
704
+ ORDER BY updated_at DESC
705
+ LIMIT ${safeLimit}
706
+ `).all(`${prefix}%`);
707
+ } catch {
708
+ return [];
709
+ }
710
+ }
711
+ // ─── Sensorimotor ─────────────────────────────────────────────────────
712
+ /**
713
+ * Record a sensorimotor experience: (context, tool, outcome).
714
+ * Gracefully degrades when DB is unavailable.
715
+ */
716
+ recordSensorimotorExperience(contextHash, toolName, success, durationMs, turn) {
717
+ if (!this._available) return;
718
+ try {
719
+ this.db.prepare(
720
+ `INSERT INTO sensorimotor_log (context_hash, tool_name, success, duration_ms, turn)
721
+ VALUES (?, ?, ?, ?, ?)`
722
+ ).run(contextHash, toolName, success ? 1 : 0, durationMs, turn);
723
+ } catch {
724
+ }
725
+ }
726
+ /**
727
+ * Get the success rate of a tool from recent sensorimotor history.
728
+ * Returns null if no data exists.
729
+ */
730
+ getToolSuccessRate(toolName, recentWindow) {
731
+ if (!this._available) return null;
732
+ try {
733
+ const limit = recentWindow && recentWindow > 0 ? `LIMIT ${recentWindow}` : "";
734
+ const rows = this.db.prepare(
735
+ `SELECT success FROM sensorimotor_log
736
+ WHERE tool_name = ?
737
+ ORDER BY id DESC
738
+ ${limit}`
739
+ ).all(toolName);
740
+ if (rows.length === 0) return null;
741
+ const successes = rows.filter((r) => r.success === 1).length;
742
+ return successes / rows.length;
743
+ } catch {
744
+ return null;
745
+ }
746
+ }
747
+ /** Current Meridian schema version (PRAGMA user_version), 0 when unavailable. */
748
+ schemaVersion() {
749
+ if (!this._available) return 0;
750
+ try {
751
+ return this.db.pragma("user_version", { simple: true }) ?? 0;
752
+ } catch {
753
+ return 0;
754
+ }
755
+ }
756
+ close() {
757
+ if (this.conn) {
758
+ try {
759
+ this.conn.pragma("wal_checkpoint(TRUNCATE)");
760
+ } catch {
761
+ }
762
+ this.conn.close();
763
+ this.conn = null;
764
+ }
765
+ }
766
+ };
767
+ function createNullDb() {
768
+ const noopStmt = { run: () => ({ changes: 0, lastInsertRowid: 0 }), all: () => [], get: () => void 0 };
769
+ return new Proxy(/* @__PURE__ */ Object.create(null), {
770
+ get: (_target, prop) => {
771
+ if (prop === "prepare") return () => noopStmt;
772
+ if (prop === "exec") return () => {
773
+ };
774
+ if (prop === "pragma") return () => {
775
+ };
776
+ if (prop === "close") return () => {
777
+ };
778
+ if (prop === "transaction") return (fn) => fn;
779
+ return () => {
780
+ };
781
+ }
782
+ });
783
+ }
784
+ function migrateToV1(db) {
785
+ try {
786
+ if ((db.pragma("user_version", { simple: true }) ?? 0) >= 1) return;
787
+ const tx = db.transaction(() => {
788
+ db.prepare("DELETE FROM files WHERE substr(path, 1, 1) = '/' OR (substr(path, 2, 1) = ':' AND path GLOB '[A-Za-z]:*')").run();
789
+ db.prepare("DELETE FROM symbols WHERE substr(file_path, 1, 1) = '/' OR (substr(file_path, 2, 1) = ':' AND file_path GLOB '[A-Za-z]:*')").run();
790
+ db.prepare("DELETE FROM edges WHERE kind = 'imports' AND NOT EXISTS (SELECT 1 FROM files f WHERE edges.target_id = f.path || ':*:0')").run();
791
+ db.pragma("user_version = 1");
792
+ });
793
+ tx();
794
+ } catch {
795
+ }
796
+ }
797
+ function migrateToV2(db) {
798
+ try {
799
+ if ((db.pragma("user_version", { simple: true }) ?? 0) >= 2) return;
800
+ const tx = db.transaction(() => {
801
+ purgeExpiredRows(db);
802
+ db.pragma("user_version = 2");
803
+ });
804
+ tx();
805
+ try {
806
+ db.pragma("wal_checkpoint(TRUNCATE)");
807
+ db.exec("VACUUM");
808
+ } catch {
809
+ }
810
+ } catch {
811
+ }
812
+ }
813
+ function purgeExpiredRows(db) {
814
+ db.prepare(`DELETE FROM p3_state
815
+ WHERE updated_at < datetime('now', ?)
816
+ AND kind NOT LIKE 'bandit:%' AND kind NOT LIKE 'p3:%'
817
+ AND kind NOT LIKE 'team\\_plan\\_cache:%' ESCAPE '\\'
818
+ AND kind <> 'tool_pattern_miner'`).run(`-${P3_EVENT_RETENTION_DAYS} days`);
819
+ db.prepare(`DELETE FROM access_log WHERE accessed_at < datetime('now', ?)`).run(`-${LOG_RETENTION_DAYS} days`);
820
+ db.prepare(`DELETE FROM sensorimotor_log WHERE created_at < datetime('now', ?)`).run(`-${LOG_RETENTION_DAYS} days`);
821
+ }
822
+
823
+ // src/repo/meridian-behavior.ts
824
+ init_esm_shims();
825
+ var CO_EDIT_BLACKLIST = [
826
+ "package.json",
827
+ "package-lock.json",
828
+ "tsconfig.json",
829
+ ".eslintrc",
830
+ ".prettierrc",
831
+ "yarn.lock",
832
+ "pnpm-lock.yaml"
833
+ ];
834
+ function isBlacklisted(filePath) {
835
+ return CO_EDIT_BLACKLIST.some((p) => filePath.endsWith(p));
836
+ }
837
+ var DEFAULT_WEIGHTS = {
838
+ structural: 1,
839
+ coEdit: 0.6,
840
+ accessHeat: 0.3,
841
+ pheromone: 0.2
842
+ };
843
+ var MeridianBehavior = class {
844
+ constructor(db, stigmergy, weights = DEFAULT_WEIGHTS) {
845
+ this.db = db;
846
+ this.stigmergy = stigmergy;
847
+ this.weights = weights;
848
+ }
849
+ db;
850
+ stigmergy;
851
+ weights;
852
+ editBuffer = /* @__PURE__ */ new Set();
853
+ currentTurn = 0;
854
+ /** Pre-loaded pheromone cache for sync access during graph queries */
855
+ pheromoneCache = /* @__PURE__ */ new Map();
856
+ /** Load pheromone signals into cache (call before sync graph queries) */
857
+ async refreshPheromoneCache() {
858
+ if (!this.stigmergy) return;
859
+ const all = await this.stigmergy.query();
860
+ this.pheromoneCache.clear();
861
+ for (const p of all) {
862
+ const existing = this.pheromoneCache.get(p.path) ?? 0;
863
+ this.pheromoneCache.set(p.path, existing + p.currentStrength);
864
+ }
865
+ }
866
+ recordEdit(filePath, turn) {
867
+ if (isBlacklisted(filePath)) return;
868
+ if (turn !== this.currentTurn) {
869
+ this.flushCoEdits();
870
+ this.currentTurn = turn;
871
+ this.editBuffer.clear();
872
+ }
873
+ this.editBuffer.add(filePath);
874
+ }
875
+ flushCoEdits() {
876
+ const files = [...this.editBuffer];
877
+ for (let i = 0; i < files.length; i++) {
878
+ for (let j = i + 1; j < files.length; j++) {
879
+ this.db.recordCoEdit(files[i], files[j], this.currentTurn);
880
+ }
881
+ }
882
+ this.editBuffer.clear();
883
+ }
884
+ getFileBoost(filePath) {
885
+ let boost = 0;
886
+ const coNeighbors = this.db.getCoEditNeighbors(filePath);
887
+ const coEditScore = coNeighbors.reduce((sum, n) => sum + n.weight, 0);
888
+ boost += Math.min(coEditScore, 5) * this.weights.coEdit;
889
+ const heat = this.db.getAccessHeat(filePath);
890
+ boost += Math.min(heat, 3) * this.weights.accessHeat;
891
+ const pheromoneScore = this.pheromoneCache.get(filePath) ?? 0;
892
+ boost += Math.min(pheromoneScore, 2) * this.weights.pheromone;
893
+ return boost;
894
+ }
895
+ getCoEditEdges(seedFile) {
896
+ if (isBlacklisted(seedFile)) return [];
897
+ return this.db.getCoEditNeighbors(seedFile).filter((n) => !isBlacklisted(n.file)).map((n) => ({ targetFile: n.file, weight: n.weight * this.weights.coEdit }));
898
+ }
899
+ };
900
+
901
+ // src/repo/meridian-parser.ts
902
+ init_esm_shims();
903
+ import { createHash } from "crypto";
904
+ import { createRequire } from "module";
905
+ var parserModule = null;
906
+ var parsers = /* @__PURE__ */ new Map();
907
+ var parseCount = 0;
908
+ var MAX_PARSES_BEFORE_RESET = 250;
909
+ var LANG_WASM = {
910
+ typescript: "tree-sitter-typescript.wasm",
911
+ python: "tree-sitter-python.wasm",
912
+ go: "tree-sitter-go.wasm"
913
+ };
914
+ var EXT_TO_LANG = {
915
+ ".ts": "typescript",
916
+ ".tsx": "typescript",
917
+ ".js": "typescript",
918
+ ".jsx": "typescript",
919
+ ".py": "python",
920
+ ".go": "go"
921
+ };
922
+ function detectLang(filePath) {
923
+ const ext = filePath.slice(filePath.lastIndexOf("."));
924
+ return EXT_TO_LANG[ext] ?? null;
925
+ }
926
+ async function initParser() {
927
+ const TreeSitter = (await import("web-tree-sitter")).default;
928
+ await TreeSitter.init();
929
+ parserModule = TreeSitter;
930
+ parsers.clear();
931
+ parseCount = 0;
932
+ }
933
+ async function getParser(lang) {
934
+ if (!parserModule || parseCount >= MAX_PARSES_BEFORE_RESET) {
935
+ await initParser();
936
+ }
937
+ if (!parsers.has(lang)) {
938
+ const p = new parserModule();
939
+ const require2 = createRequire(import.meta.url);
940
+ const wasmPath = require2.resolve(`tree-sitter-wasms/out/${LANG_WASM[lang]}`);
941
+ const language = await parserModule.Language.load(wasmPath);
942
+ p.setLanguage(language);
943
+ parsers.set(lang, p);
944
+ }
945
+ return parsers.get(lang);
946
+ }
947
+ function makeId(filePath, name, line) {
948
+ return `${filePath}:${name}:${line}`;
949
+ }
950
+ async function parseFile(filePath, source) {
951
+ const lang = detectLang(filePath);
952
+ if (!lang) throw new Error(`Unsupported language for: ${filePath}`);
953
+ switch (lang) {
954
+ case "typescript":
955
+ return parseTypeScriptFile(filePath, source);
956
+ case "python":
957
+ return parsePythonFile(filePath, source);
958
+ case "go":
959
+ return parseGoFile(filePath, source);
960
+ }
961
+ }
962
+ async function parseTypeScriptFile(filePath, source) {
963
+ const p = await getParser("typescript");
964
+ const tree = p.parse(source);
965
+ parseCount++;
966
+ const symbols = [];
967
+ const edges = [];
968
+ const imports = [];
969
+ const calls = [];
970
+ const contentHash = createHash("sha256").update(source).digest("hex").slice(0, 16);
971
+ function walkSymbols(node, parentId) {
972
+ const row = node.startPosition.row + 1;
973
+ const isExported = node.parent?.type === "export_statement";
974
+ if (node.type === "import_statement") {
975
+ const sourceNode = node.childForFieldName("source");
976
+ if (sourceNode) {
977
+ const raw = sourceNode.text.replace(/['"]/g, "");
978
+ if (raw.startsWith(".")) imports.push(raw);
979
+ }
980
+ return;
981
+ }
982
+ const info = tsSymbolInfo(node);
983
+ if (info) {
984
+ const id = makeId(filePath, info.name, row);
985
+ symbols.push({ id, name: info.name, kind: info.kind, filePath, line: row, exported: isExported, contentHash });
986
+ if (parentId) {
987
+ edges.push({ sourceId: parentId, targetId: id, kind: "contains", weight: 1, confidence: "extracted" });
988
+ }
989
+ for (const child of node.namedChildren) {
990
+ walkSymbols(child, id);
991
+ }
992
+ return;
993
+ }
994
+ for (const child of node.namedChildren) {
995
+ walkSymbols(child, parentId);
996
+ }
997
+ }
998
+ walkSymbols(tree.rootNode);
999
+ const localByName = /* @__PURE__ */ new Map();
1000
+ for (const s of symbols) {
1001
+ const arr = localByName.get(s.name) ?? [];
1002
+ arr.push(s);
1003
+ localByName.set(s.name, arr);
1004
+ }
1005
+ function walkCalls(node, ownerId) {
1006
+ const info = tsSymbolInfo(node);
1007
+ if (info) ownerId = makeId(filePath, info.name, node.startPosition.row + 1);
1008
+ if (node.type === "call_expression" && ownerId) {
1009
+ const name = calleeName(node);
1010
+ if (name) {
1011
+ const locals = localByName.get(name);
1012
+ if (locals && locals.length > 0) {
1013
+ for (const l of locals) {
1014
+ edges.push({ sourceId: ownerId, targetId: l.id, kind: "calls", weight: 1, confidence: "extracted" });
1015
+ }
1016
+ } else {
1017
+ calls.push({ sourceId: ownerId, name, line: node.startPosition.row + 1 });
1018
+ }
1019
+ }
1020
+ }
1021
+ for (const child of node.namedChildren) {
1022
+ walkCalls(child, ownerId);
1023
+ }
1024
+ }
1025
+ walkCalls(tree.rootNode);
1026
+ tree.delete();
1027
+ return { filePath, contentHash, symbols, edges, imports, calls };
1028
+ }
1029
+ function tsSymbolInfo(node) {
1030
+ let kind = null;
1031
+ let name = null;
1032
+ switch (node.type) {
1033
+ case "function_declaration":
1034
+ kind = "function";
1035
+ name = node.childForFieldName("name")?.text ?? null;
1036
+ break;
1037
+ case "class_declaration":
1038
+ kind = "class";
1039
+ name = node.childForFieldName("name")?.text ?? null;
1040
+ break;
1041
+ case "interface_declaration":
1042
+ kind = "interface";
1043
+ name = node.childForFieldName("name")?.text ?? null;
1044
+ break;
1045
+ case "type_alias_declaration":
1046
+ kind = "type";
1047
+ name = node.childForFieldName("name")?.text ?? null;
1048
+ break;
1049
+ case "enum_declaration":
1050
+ kind = "enum";
1051
+ name = node.childForFieldName("name")?.text ?? null;
1052
+ break;
1053
+ case "method_definition":
1054
+ kind = "method";
1055
+ name = node.childForFieldName("name")?.text ?? null;
1056
+ break;
1057
+ case "lexical_declaration":
1058
+ case "variable_declaration": {
1059
+ const declarator = node.namedChildren.find((c) => c.type === "variable_declarator");
1060
+ if (declarator) {
1061
+ const init = declarator.childForFieldName("value");
1062
+ if (init && (init.type === "arrow_function" || init.type === "function")) {
1063
+ kind = "function";
1064
+ } else {
1065
+ kind = "variable";
1066
+ }
1067
+ name = declarator.childForFieldName("name")?.text ?? null;
1068
+ }
1069
+ break;
1070
+ }
1071
+ }
1072
+ if (kind && name) return { kind, name };
1073
+ return null;
1074
+ }
1075
+ function calleeName(node) {
1076
+ const fn = node.childForFieldName("function");
1077
+ if (!fn) return null;
1078
+ if (fn.type === "identifier") return fn.text;
1079
+ if (fn.type === "member_expression") {
1080
+ const prop = fn.childForFieldName("property");
1081
+ if (prop && prop.type === "property_identifier") return prop.text;
1082
+ }
1083
+ return null;
1084
+ }
1085
+ async function parsePythonFile(filePath, source) {
1086
+ const p = await getParser("python");
1087
+ const tree = p.parse(source);
1088
+ parseCount++;
1089
+ const symbols = [];
1090
+ const edges = [];
1091
+ const imports = [];
1092
+ const contentHash = createHash("sha256").update(source).digest("hex").slice(0, 16);
1093
+ function walk(node, parentId) {
1094
+ const row = node.startPosition.row + 1;
1095
+ let kind = null;
1096
+ let name = null;
1097
+ switch (node.type) {
1098
+ case "function_definition":
1099
+ kind = "function";
1100
+ name = node.childForFieldName("name")?.text ?? null;
1101
+ break;
1102
+ case "class_definition":
1103
+ kind = "class";
1104
+ name = node.childForFieldName("name")?.text ?? null;
1105
+ break;
1106
+ case "import_statement": {
1107
+ const modNode = node.namedChildren.find((c) => c.type === "dotted_name");
1108
+ if (modNode) imports.push(modNode.text);
1109
+ return;
1110
+ }
1111
+ case "import_from_statement": {
1112
+ const modName = node.childForFieldName("module_name");
1113
+ if (modName) {
1114
+ const raw = modName.text;
1115
+ if (raw.startsWith(".")) imports.push(raw);
1116
+ else imports.push(raw);
1117
+ }
1118
+ return;
1119
+ }
1120
+ }
1121
+ if (kind && name) {
1122
+ const id = makeId(filePath, name, row);
1123
+ const isExported = node.parent?.type === "module";
1124
+ symbols.push({ id, name, kind, filePath, line: row, exported: isExported ?? false, contentHash });
1125
+ if (parentId) {
1126
+ edges.push({ sourceId: parentId, targetId: id, kind: "contains", weight: 1, confidence: "extracted" });
1127
+ }
1128
+ for (const child of node.namedChildren) {
1129
+ walk(child, id);
1130
+ }
1131
+ return;
1132
+ }
1133
+ for (const child of node.namedChildren) {
1134
+ walk(child, parentId);
1135
+ }
1136
+ }
1137
+ walk(tree.rootNode);
1138
+ tree.delete();
1139
+ return { filePath, contentHash, symbols, edges, imports, calls: [] };
1140
+ }
1141
+ async function parseGoFile(filePath, source) {
1142
+ const p = await getParser("go");
1143
+ const tree = p.parse(source);
1144
+ parseCount++;
1145
+ const symbols = [];
1146
+ const edges = [];
1147
+ const imports = [];
1148
+ const contentHash = createHash("sha256").update(source).digest("hex").slice(0, 16);
1149
+ function walk(node) {
1150
+ const row = node.startPosition.row + 1;
1151
+ let kind = null;
1152
+ let name = null;
1153
+ switch (node.type) {
1154
+ case "function_declaration":
1155
+ kind = "function";
1156
+ name = node.childForFieldName("name")?.text ?? null;
1157
+ break;
1158
+ case "method_declaration":
1159
+ kind = "method";
1160
+ name = node.childForFieldName("name")?.text ?? null;
1161
+ break;
1162
+ case "type_declaration": {
1163
+ const spec = node.namedChildren.find((c) => c.type === "type_spec");
1164
+ if (spec) {
1165
+ name = spec.childForFieldName("name")?.text ?? null;
1166
+ const typeNode = spec.childForFieldName("type");
1167
+ kind = typeNode?.type === "interface_type" ? "interface" : "type";
1168
+ }
1169
+ break;
1170
+ }
1171
+ case "import_declaration": {
1172
+ for (const child of node.namedChildren) {
1173
+ if (child.type === "import_spec" || child.type === "interpreted_string_literal") {
1174
+ const raw = child.text.replace(/['"]/g, "");
1175
+ if (raw && !raw.startsWith("//")) imports.push(raw);
1176
+ }
1177
+ if (child.type === "import_spec_list") {
1178
+ for (const spec of child.namedChildren) {
1179
+ const pathNode = spec.childForFieldName("path") ?? spec.namedChildren.find((c) => c.type === "interpreted_string_literal");
1180
+ if (pathNode) imports.push(pathNode.text.replace(/['"]/g, ""));
1181
+ }
1182
+ }
1183
+ }
1184
+ return;
1185
+ }
1186
+ }
1187
+ if (kind && name) {
1188
+ const id = makeId(filePath, name, row);
1189
+ const isExported = /^[A-Z]/.test(name);
1190
+ symbols.push({ id, name, kind, filePath, line: row, exported: isExported, contentHash });
1191
+ }
1192
+ for (const child of node.namedChildren) {
1193
+ walk(child);
1194
+ }
1195
+ }
1196
+ walk(tree.rootNode);
1197
+ tree.delete();
1198
+ return { filePath, contentHash, symbols, edges, imports, calls: [] };
1199
+ }
1200
+
1201
+ // src/repo/meridian-graph.ts
1202
+ init_esm_shims();
1203
+
1204
+ // src/repo/meridian-types.ts
1205
+ init_esm_shims();
1206
+ var CONFIDENCE_MULTIPLIER = {
1207
+ extracted: 1,
1208
+ inferred: 0.7,
1209
+ ambiguous: 0.4
1210
+ };
1211
+
1212
+ // src/repo/meridian-graph.ts
1213
+ function spreadingActivation(db, seedFile, opts) {
1214
+ const scores = /* @__PURE__ */ new Map();
1215
+ scores.set(seedFile, 1);
1216
+ const seedSymbols = db.getSymbolsForFile(seedFile);
1217
+ let frontier = seedSymbols.map((s) => s.id);
1218
+ for (let hop = 0; hop < opts.maxHops; hop++) {
1219
+ const decayFactor = Math.pow(opts.decay, hop + 1);
1220
+ const nextFrontier = [];
1221
+ for (const symbolId of frontier) {
1222
+ const edges = db.getEdgesFrom(symbolId);
1223
+ for (const edge of edges) {
1224
+ const targetFile = edge.targetId.split(":")[0];
1225
+ if (targetFile && !targetFile.includes("*")) {
1226
+ const confMult = CONFIDENCE_MULTIPLIER[edge.confidence ?? "extracted"];
1227
+ const addition = decayFactor * edge.weight * confMult;
1228
+ const existing = scores.get(targetFile) ?? 0;
1229
+ scores.set(targetFile, Math.max(existing, addition));
1230
+ nextFrontier.push(edge.targetId);
1231
+ }
1232
+ }
1233
+ const reverseEdges = db.getEdgesTo(symbolId);
1234
+ for (const edge of reverseEdges) {
1235
+ const sourceFile = edge.sourceId.split(":")[0];
1236
+ if (sourceFile && !sourceFile.includes("*")) {
1237
+ const confMult = CONFIDENCE_MULTIPLIER[edge.confidence ?? "extracted"];
1238
+ const addition = decayFactor * edge.weight * 0.7 * confMult;
1239
+ const existing = scores.get(sourceFile) ?? 0;
1240
+ scores.set(sourceFile, Math.max(existing, addition));
1241
+ nextFrontier.push(edge.sourceId);
1242
+ }
1243
+ }
1244
+ }
1245
+ frontier = nextFrontier;
1246
+ }
1247
+ if (opts.behavior) {
1248
+ const coEdges = opts.behavior.getCoEditEdges(seedFile);
1249
+ for (const { targetFile, weight } of coEdges) {
1250
+ const existing = scores.get(targetFile) ?? 0;
1251
+ scores.set(targetFile, Math.max(existing, weight));
1252
+ }
1253
+ }
1254
+ return scores;
1255
+ }
1256
+ var TOKENS_PER_SYMBOL_LINE = 25;
1257
+ function buildRepoMap(db, seedFile, opts) {
1258
+ const scores = spreadingActivation(db, seedFile, opts);
1259
+ const stats = db.getStats();
1260
+ const entries = [];
1261
+ for (const [filePath, score] of scores) {
1262
+ const symbols = db.getSymbolsForFile(filePath);
1263
+ const boost = opts.behavior ? opts.behavior.getFileBoost(filePath) : 0;
1264
+ entries.push({
1265
+ filePath,
1266
+ symbols: symbols.map((s) => ({ name: s.name, kind: s.kind, line: s.line })),
1267
+ score: score + boost
1268
+ });
1269
+ }
1270
+ entries.sort((a, b) => b.score - a.score);
1271
+ let tokenCount = 0;
1272
+ let cutoff = entries.length;
1273
+ for (let i = 0; i < entries.length; i++) {
1274
+ const entryTokens = entries[i].symbols.length * TOKENS_PER_SYMBOL_LINE + 10;
1275
+ if (tokenCount + entryTokens > opts.maxTokens) {
1276
+ cutoff = i;
1277
+ break;
1278
+ }
1279
+ tokenCount += entryTokens;
1280
+ }
1281
+ return {
1282
+ entries: entries.slice(0, Math.max(cutoff, 1)),
1283
+ // always include at least seed
1284
+ totalSymbols: stats.symbols,
1285
+ graphSize: stats.files
1286
+ };
1287
+ }
1288
+
1289
+ // src/repo/meridian-framework.ts
1290
+ init_esm_shims();
1291
+ function stripComments(source) {
1292
+ let out = "";
1293
+ let i = 0;
1294
+ let inStr = null;
1295
+ while (i < source.length) {
1296
+ const ch = source[i];
1297
+ if (inStr) {
1298
+ out += ch;
1299
+ if (ch === "\\" && i + 1 < source.length) {
1300
+ out += source[i + 1];
1301
+ i += 2;
1302
+ continue;
1303
+ }
1304
+ if (ch === inStr) inStr = null;
1305
+ i++;
1306
+ continue;
1307
+ }
1308
+ if (ch === '"' || ch === "'" || ch === "`") {
1309
+ inStr = ch;
1310
+ out += ch;
1311
+ i++;
1312
+ continue;
1313
+ }
1314
+ if (ch === "/" && source[i + 1] === "/") {
1315
+ while (i < source.length && source[i] !== "\n") i++;
1316
+ continue;
1317
+ }
1318
+ if (ch === "/" && source[i + 1] === "*") {
1319
+ i += 2;
1320
+ while (i < source.length && !(source[i] === "*" && source[i + 1] === "/")) {
1321
+ if (source[i] === "\n") out += "\n";
1322
+ i++;
1323
+ }
1324
+ i += 2;
1325
+ continue;
1326
+ }
1327
+ out += ch;
1328
+ i++;
1329
+ }
1330
+ return out;
1331
+ }
1332
+ var ROUTE_HEAD_RE = /\b(app|router)\.(get|post|put|patch|delete|all|use)\s*\(\s*['"]([^'"]+)['"]\s*,/g;
1333
+ function tailIdent(expr) {
1334
+ const cleaned = expr.replace(/\s+/g, "").replace(/\(\)$/, "");
1335
+ const m = cleaned.match(/(?:\.|^)([A-Za-z_][A-Za-z0-9_]*)$/);
1336
+ return m ? m[1] : null;
1337
+ }
1338
+ function extractExpressRoutes(filePath, source, knownSymbols) {
1339
+ const symbols = [];
1340
+ const edges = [];
1341
+ const safe = stripComments(source);
1342
+ const knownByName = /* @__PURE__ */ new Map();
1343
+ for (const s of knownSymbols) {
1344
+ const arr = knownByName.get(s.name) ?? [];
1345
+ arr.push(s);
1346
+ knownByName.set(s.name, arr);
1347
+ }
1348
+ let m;
1349
+ while ((m = ROUTE_HEAD_RE.exec(safe)) !== null) {
1350
+ const method = m[2].toUpperCase();
1351
+ const routePath = m[3];
1352
+ if (method === "USE" && !routePath.startsWith("/")) continue;
1353
+ const line = safe.slice(0, m.index).split("\n").length;
1354
+ const name = `${method} ${routePath}`;
1355
+ const routeId = `${filePath}:${name}:${line}`;
1356
+ symbols.push({ id: routeId, name, kind: "route", filePath, line, exported: false, contentHash: "" });
1357
+ const openParen = safe.indexOf("(", m.index);
1358
+ const closeParen = openParen >= 0 ? matchDelim(safe, openParen, "(", ")") : -1;
1359
+ const args = closeParen > openParen ? safe.slice(openParen + 1, closeParen) : "";
1360
+ if (args.includes("=>")) continue;
1361
+ const parts = args.split(",").map((s) => s.trim()).filter(Boolean);
1362
+ const last = parts[parts.length - 1];
1363
+ const handlerName = last ? tailIdent(last) : null;
1364
+ if (!handlerName) continue;
1365
+ const matches = knownByName.get(handlerName);
1366
+ if (matches && matches.length === 1) {
1367
+ edges.push({
1368
+ sourceId: routeId,
1369
+ targetId: matches[0].id,
1370
+ kind: "route_handles",
1371
+ weight: 1,
1372
+ confidence: "inferred"
1373
+ });
1374
+ }
1375
+ }
1376
+ return { symbols, edges };
1377
+ }
1378
+ var JSX_TAG_RE = /<([A-Z][A-Za-z0-9_]*)[\s/>]/g;
1379
+ function extractJsxChildren(filePath, source, fileSymbols, knownSymbols) {
1380
+ const edges = [];
1381
+ const safe = stripComments(source);
1382
+ const knownByName = /* @__PURE__ */ new Map();
1383
+ for (const s of knownSymbols) {
1384
+ const arr = knownByName.get(s.name) ?? [];
1385
+ arr.push(s);
1386
+ knownByName.set(s.name, arr);
1387
+ }
1388
+ const enclosing = [...fileSymbols].sort((a, b) => a.line - b.line);
1389
+ let m;
1390
+ while ((m = JSX_TAG_RE.exec(safe)) !== null) {
1391
+ const tag = m[1];
1392
+ const line = safe.slice(0, m.index).split("\n").length;
1393
+ const matches = knownByName.get(tag);
1394
+ if (!matches || matches.length !== 1) continue;
1395
+ const target = matches[0];
1396
+ let parent = null;
1397
+ for (const s of enclosing) {
1398
+ if (s.line <= line) parent = s;
1399
+ else break;
1400
+ }
1401
+ if (!parent) continue;
1402
+ edges.push({
1403
+ sourceId: parent.id,
1404
+ targetId: target.id,
1405
+ kind: "jsx_children",
1406
+ weight: 1,
1407
+ confidence: "inferred"
1408
+ });
1409
+ }
1410
+ return { symbols: [], edges };
1411
+ }
1412
+ function matchDelim(s, open, oc, cc) {
1413
+ let depth = 0;
1414
+ for (let i = open; i < s.length; i++) {
1415
+ const ch = s[i];
1416
+ if (ch === '"' || ch === "'" || ch === "`") {
1417
+ i++;
1418
+ while (i < s.length && s[i] !== ch) {
1419
+ if (s[i] === "\\") i++;
1420
+ i++;
1421
+ }
1422
+ continue;
1423
+ }
1424
+ if (ch === oc) depth++;
1425
+ else if (ch === cc) {
1426
+ depth--;
1427
+ if (depth === 0) return i;
1428
+ }
1429
+ }
1430
+ return -1;
1431
+ }
1432
+
1433
+ // src/repo/meridian-indexer.ts
1434
+ var TS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
1435
+ var ALL_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".py", ".go"];
1436
+ var IGNORE_PATTERNS = ["node_modules", "dist", ".git", ".rivet"];
1437
+ function isMeridianIndexablePath(rel) {
1438
+ if (IGNORE_PATTERNS.some((p) => rel.includes(p))) return false;
1439
+ if (classifyPath(rel).silent) return false;
1440
+ return ALL_EXTENSIONS.some((ext) => rel.endsWith(ext));
1441
+ }
1442
+ function isUnnamedSymbolId(id) {
1443
+ return id.endsWith(":*:0");
1444
+ }
1445
+ function queryFlow(db, seedId, opts) {
1446
+ const maxHops = opts?.maxHops ?? 4;
1447
+ const maxBridges = opts?.maxBridges ?? 1;
1448
+ if (isUnnamedSymbolId(seedId)) return [];
1449
+ const seedFile = seedId.split(":")[0];
1450
+ const seed = db.getSymbolsForFile(seedFile).find((s) => s.id === seedId);
1451
+ if (!seed) return [];
1452
+ const hits = /* @__PURE__ */ new Map();
1453
+ const visited = /* @__PURE__ */ new Set();
1454
+ let frontier = [{ id: seedId, bridges: 0, hops: 0 }];
1455
+ visited.add(`${seedId}:0`);
1456
+ for (let hop = 0; hop < maxHops && frontier.length > 0; hop++) {
1457
+ const next = [];
1458
+ for (const cur of frontier) {
1459
+ const neighbors = [
1460
+ ...db.getEdgesFrom(cur.id).map((e) => e.targetId),
1461
+ ...db.getEdgesTo(cur.id).map((e) => e.sourceId)
1462
+ ];
1463
+ for (const nid of neighbors) {
1464
+ const unnamed = isUnnamedSymbolId(nid);
1465
+ const bridges = cur.bridges + (unnamed ? 1 : 0);
1466
+ if (bridges > maxBridges) continue;
1467
+ const key = `${nid}:${bridges}`;
1468
+ if (visited.has(key)) continue;
1469
+ visited.add(key);
1470
+ if (unnamed) {
1471
+ next.push({ id: nid, bridges, hops: cur.hops + 1 });
1472
+ } else {
1473
+ const file = nid.split(":")[0];
1474
+ const sym = db.getSymbolsForFile(file).find((s) => s.id === nid);
1475
+ if (sym) {
1476
+ const existing = hits.get(nid);
1477
+ if (!existing || bridges < existing.bridges) {
1478
+ hits.set(nid, { symbolId: nid, name: sym.name, kind: sym.kind, filePath: sym.filePath, line: sym.line, hops: cur.hops + 1, bridges });
1479
+ }
1480
+ next.push({ id: nid, bridges, hops: cur.hops + 1 });
1481
+ }
1482
+ }
1483
+ }
1484
+ }
1485
+ frontier = next;
1486
+ }
1487
+ return [...hits.values()].sort((a, b) => a.hops - b.hops || a.symbolId.localeCompare(b.symbolId));
1488
+ }
1489
+ function reviveDeletedFile(db, rel) {
1490
+ const symbols = db.getSymbolsForFile(rel);
1491
+ let revived = 0;
1492
+ for (const sym of symbols) {
1493
+ for (const edge of db.getEdgesTo(sym.id)) {
1494
+ const sourceFile = edge.sourceId.split(":")[0];
1495
+ if (sourceFile && sourceFile !== rel) {
1496
+ db.upsertEdge(edge.sourceId, `${rel}:*:0`, edge.kind, edge.weight, edge.confidence ?? "extracted");
1497
+ revived++;
1498
+ }
1499
+ }
1500
+ }
1501
+ db.upsertFile({ filePath: rel, contentHash: "", symbols: [], edges: [], imports: [], calls: [] });
1502
+ return revived;
1503
+ }
1504
+ var MeridianIndexer = class {
1505
+ constructor(cwd, stateDir, stigmergy) {
1506
+ this.cwd = cwd;
1507
+ const dir = stateDir ?? resolve(cwd, ".rivet");
1508
+ this.db = new MeridianDb(dir);
1509
+ this.behavior = new MeridianBehavior(this.db, stigmergy);
1510
+ }
1511
+ cwd;
1512
+ db;
1513
+ behavior;
1514
+ initialized = false;
1515
+ indexing = /* @__PURE__ */ new Set();
1516
+ /** invalidateFile 并发守卫(共享 indexer 场景):同 rel 只跑一个解析,
1517
+ * 并发到达标 pending,首个完成后重跑一轮,保证最终落到最新内容。 */
1518
+ invalidating = /* @__PURE__ */ new Set();
1519
+ pendingInvalidations = /* @__PURE__ */ new Set();
1520
+ /** 后台全量索引(meridian-backfill)每实例只调度一次的 flag。 */
1521
+ backfillScheduled = false;
1522
+ getDb() {
1523
+ return this.db;
1524
+ }
1525
+ /**
1526
+ * 廉价探针(meridian-hook read_file 热路径,2026-09-08 阻塞修复):
1527
+ * 与 indexFile 前缀完全同序地完成 path 校验 → indexable → db available →
1528
+ * 读文件 → sha256 → needsParse。hash 未变时写 recordAccess 并返回
1529
+ * 'unchanged';需要重解析时返回 'parse',由 hook 将完整 indexFile 入队,
1530
+ * 不在 postTool 链上执行 tree-sitter/事务。此方法只做同步 IO + 一次
1531
+ * SELECT,不触碰 parser,不写库(unchanged 的 access_log 除外)。
1532
+ */
1533
+ probeFile(filePath) {
1534
+ const rel = this.toRepoRelative(filePath);
1535
+ if (rel === null) return "skip";
1536
+ if (this.indexing.has(rel)) return "skip";
1537
+ if (!this.isIndexable(rel)) return "skip";
1538
+ if (!this.db.available) return "skip";
1539
+ const absPath = resolve(this.cwd, rel);
1540
+ if (!existsSync2(absPath)) return "skip";
1541
+ const source = readFileSync(absPath, "utf-8");
1542
+ const hash = createHash2("sha256").update(source).digest("hex").slice(0, 16);
1543
+ if (!this.db.needsParse(rel, hash)) {
1544
+ this.db.recordAccess(rel);
1545
+ return "unchanged";
1546
+ }
1547
+ return "parse";
1548
+ }
1549
+ async ensureInit() {
1550
+ if (!this.initialized) {
1551
+ await initParser();
1552
+ this.initialized = true;
1553
+ }
1554
+ }
1555
+ async indexFile(filePath, options = {}) {
1556
+ const rel = this.toRepoRelative(filePath);
1557
+ if (rel === null) return;
1558
+ if (this.indexing.has(rel)) return;
1559
+ if (!this.isIndexable(rel)) return;
1560
+ if (!this.db.available) return;
1561
+ const absPath = resolve(this.cwd, rel);
1562
+ if (!existsSync2(absPath)) return;
1563
+ const source = readFileSync(absPath, "utf-8");
1564
+ const hash = createHash2("sha256").update(source).digest("hex").slice(0, 16);
1565
+ if (!this.db.needsParse(rel, hash)) {
1566
+ this.db.recordAccess(rel);
1567
+ return;
1568
+ }
1569
+ await this.ensureInit();
1570
+ this.indexing.add(rel);
1571
+ try {
1572
+ const result = await parseFile(rel, source);
1573
+ const resolvedImports = this.resolveImports(rel, result.imports);
1574
+ for (const resolved of resolvedImports) {
1575
+ if (!this.indexing.has(resolved)) {
1576
+ if (options.yieldBetweenImports) {
1577
+ await new Promise((resolve2) => setTimeout(resolve2, 0));
1578
+ }
1579
+ await this.indexFile(resolved, options);
1580
+ }
1581
+ }
1582
+ const known = [...result.symbols, ...resolvedImports.flatMap((imp) => this.db.getSymbolsForFile(imp))];
1583
+ const fw = this.extractFrameworkEdges(rel, source, result.symbols, known);
1584
+ this.db.upsertFile({ ...result, imports: resolvedImports, symbols: [...result.symbols, ...fw.symbols] });
1585
+ this.db.recordAccess(rel);
1586
+ if (this.isTestFile(rel)) {
1587
+ this.buildTestEdges(rel);
1588
+ }
1589
+ for (const e of fw.edges) {
1590
+ this.db.upsertEdge(e.sourceId, e.targetId, e.kind, e.weight, e.confidence ?? "extracted");
1591
+ }
1592
+ this.buildCallEdges(rel, result.calls);
1593
+ } finally {
1594
+ this.indexing.delete(rel);
1595
+ }
1596
+ }
1597
+ async invalidateFile(filePath) {
1598
+ const rel = this.toRepoRelative(filePath);
1599
+ if (rel === null) return;
1600
+ if (!this.isIndexable(rel)) return;
1601
+ if (this.invalidating.has(rel)) {
1602
+ this.pendingInvalidations.add(rel);
1603
+ return;
1604
+ }
1605
+ this.invalidating.add(rel);
1606
+ try {
1607
+ do {
1608
+ this.pendingInvalidations.delete(rel);
1609
+ await this.invalidateFileCore(rel);
1610
+ } while (this.pendingInvalidations.has(rel));
1611
+ } finally {
1612
+ this.invalidating.delete(rel);
1613
+ }
1614
+ }
1615
+ async invalidateFileCore(rel) {
1616
+ const absPath = resolve(this.cwd, rel);
1617
+ if (!existsSync2(absPath)) return;
1618
+ await this.ensureInit();
1619
+ const source = readFileSync(absPath, "utf-8");
1620
+ const result = await parseFile(rel, source);
1621
+ const resolvedImports = this.resolveImports(rel, result.imports);
1622
+ const known = [...result.symbols, ...resolvedImports.flatMap((imp) => this.db.getSymbolsForFile(imp))];
1623
+ const fw = this.extractFrameworkEdges(rel, source, result.symbols, known);
1624
+ this.db.upsertFile({ ...result, imports: resolvedImports, symbols: [...result.symbols, ...fw.symbols] });
1625
+ if (this.isTestFile(rel)) {
1626
+ this.buildTestEdges(rel);
1627
+ }
1628
+ for (const e of fw.edges) {
1629
+ this.db.upsertEdge(e.sourceId, e.targetId, e.kind, e.weight, e.confidence ?? "extracted");
1630
+ }
1631
+ this.buildCallEdges(rel, result.calls);
1632
+ }
1633
+ /** Shared framework-edge extraction (route_handles/jsx_children) for the
1634
+ * indexFile and invalidateFile paths — keeps the two production paths in
1635
+ * lockstep so hot updates never silently drop framework edges.
1636
+ * fileSymbols must stay this-file-only (JSX enclosing selection compares
1637
+ * line numbers); knownSymbols may include cross-file symbols (route handler /
1638
+ * component name matching). */
1639
+ extractFrameworkEdges(rel, source, fileSymbols, knownSymbols) {
1640
+ const out = { symbols: [], edges: [] };
1641
+ const fw = extractExpressRoutes(rel, source, knownSymbols);
1642
+ out.symbols.push(...fw.symbols);
1643
+ out.edges.push(...fw.edges);
1644
+ const jsx = extractJsxChildren(rel, source, fileSymbols, knownSymbols);
1645
+ out.edges.push(...jsx.edges);
1646
+ return out;
1647
+ }
1648
+ /** 删除文件处理(wave4 T9):跨文件入边复活为 pending 而非静默断裂。
1649
+ * 已删除/不可索引的文件直接忽略。返回复活(pending)的入边条数。 */
1650
+ removeFile(filePath) {
1651
+ const rel = this.toRepoRelative(filePath);
1652
+ if (rel === null) return 0;
1653
+ if (!this.isIndexable(rel)) return 0;
1654
+ return reviveDeletedFile(this.db, rel);
1655
+ }
1656
+ async query(seedFile, opts) {
1657
+ await this.behavior.refreshPheromoneCache();
1658
+ return buildRepoMap(this.db, seedFile, {
1659
+ maxHops: opts?.maxHops ?? 3,
1660
+ decay: opts?.decay ?? 0.5,
1661
+ maxTokens: opts?.maxTokens ?? 2e3,
1662
+ behavior: this.behavior
1663
+ });
1664
+ }
1665
+ recordEdit(filePath, turn) {
1666
+ const rel = this.toRepoRelative(filePath);
1667
+ if (rel === null) return;
1668
+ this.behavior.recordEdit(rel, turn);
1669
+ }
1670
+ flushTurn() {
1671
+ this.behavior.flushCoEdits();
1672
+ }
1673
+ /** Analyze impact radius for changed files */
1674
+ impact(changedFiles, opts) {
1675
+ return analyzeImpact(this.db, changedFiles, opts);
1676
+ }
1677
+ /** Build tested_by edges for a test file based on naming + imports */
1678
+ buildTestEdges(testFilePath) {
1679
+ const allFiles = this.db.getAllFiles();
1680
+ const targets = inferTestedByTargets(testFilePath, allFiles);
1681
+ for (const target of targets) {
1682
+ const sourceId = `${testFilePath}:*:0`;
1683
+ const targetId = `${target}:*:0`;
1684
+ this.db.upsertEdge(sourceId, targetId, "tested_by", 0.7, "inferred");
1685
+ }
1686
+ }
1687
+ /** Resolve same-file-unresolved call sites against cross-file symbols by name.
1688
+ * Unique match → inferred; multiple matches → ambiguous on every candidate.
1689
+ * Confidence drives the CONFIDENCE_MULTIPLIER discount in the graph layer.
1690
+ *
1691
+ * 2026-09-08:不再 getAllSymbols 全表物化 + 逐 call 内存 filter——收集本次
1692
+ * 全部 callee name 后一次 getSymbolsByNames 批量拉取(走 idx_symbols_name),
1693
+ * 内存只建 name → symbols Map;同名多命中 ambiguous 语义不变。 */
1694
+ buildCallEdges(fromFile, calls) {
1695
+ if (calls.length === 0) return;
1696
+ const names = [...new Set(calls.map((c) => c.name))];
1697
+ const symbolsByName = /* @__PURE__ */ new Map();
1698
+ for (const symbol of this.db.getSymbolsByNames(names)) {
1699
+ if (symbol.filePath === fromFile) continue;
1700
+ const list = symbolsByName.get(symbol.name);
1701
+ if (list) list.push(symbol);
1702
+ else symbolsByName.set(symbol.name, [symbol]);
1703
+ }
1704
+ for (const call of calls) {
1705
+ const matches = symbolsByName.get(call.name) ?? [];
1706
+ if (matches.length === 0) continue;
1707
+ if (matches.length === 1) {
1708
+ const target = matches[0];
1709
+ if (target) this.db.upsertEdge(call.sourceId, target.id, "calls", 1, "inferred");
1710
+ } else {
1711
+ for (const m of matches) {
1712
+ this.db.upsertEdge(call.sourceId, m.id, "calls", 1, "ambiguous");
1713
+ }
1714
+ }
1715
+ }
1716
+ }
1717
+ getStats() {
1718
+ return this.db.getStats();
1719
+ }
1720
+ close() {
1721
+ this.db.close();
1722
+ }
1723
+ /** Normalize to repo-relative path for classification & DB keys.
1724
+ * Returns null for any path that resolves outside the repo root —
1725
+ * covers both absolute paths and relative `../` traversal.
1726
+ * Fail-closed: the indexer must never read/parse/store files
1727
+ * outside the project boundary. */
1728
+ toRepoRelative(filePath) {
1729
+ const absCwd = resolve(this.cwd);
1730
+ let realCwd;
1731
+ try {
1732
+ realCwd = realpathSync(this.cwd);
1733
+ } catch {
1734
+ realCwd = absCwd;
1735
+ }
1736
+ const absFile = resolve(absCwd, filePath);
1737
+ const posixCwd = toPosixPath(absCwd);
1738
+ const posixFile = toPosixPath(absFile);
1739
+ if (!posixFile.startsWith(posixCwd + "/")) return null;
1740
+ let realFile;
1741
+ try {
1742
+ realFile = realpathSync(absFile);
1743
+ } catch {
1744
+ realFile = resolve(realCwd, "." + absFile.slice(absCwd.length));
1745
+ }
1746
+ if (!toPosixPath(realFile).startsWith(toPosixPath(realCwd) + "/")) return null;
1747
+ return posixFile.slice(posixCwd.length + 1);
1748
+ }
1749
+ isIndexable(filePath) {
1750
+ const rel = this.toRepoRelative(filePath);
1751
+ if (rel === null) return false;
1752
+ return isMeridianIndexablePath(rel);
1753
+ }
1754
+ isTestFile(filePath) {
1755
+ return filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("__tests__/") || filePath.includes("test/");
1756
+ }
1757
+ /** Resolve a list of raw import strings to deduped repo-relative paths.
1758
+ * External packages (zod, node:fs) and tsconfig path aliases (@/, ~) fail
1759
+ * resolution and are dropped — they carry no reverse-dependency value here. */
1760
+ resolveImports(fromFile, imports) {
1761
+ const seen = /* @__PURE__ */ new Set();
1762
+ for (const imp of imports) {
1763
+ const resolved = this.resolveImport(fromFile, imp);
1764
+ if (resolved) seen.add(resolved);
1765
+ }
1766
+ return [...seen];
1767
+ }
1768
+ resolveImport(fromFile, importPath) {
1769
+ const posixRoot = toPosixPath(resolve(this.cwd));
1770
+ const baseDir = dirname(resolve(this.cwd, fromFile));
1771
+ for (const ext of TS_EXTENSIONS) {
1772
+ const withExt = resolve(baseDir, importPath.replace(/\.[jt]sx?$/, "") + ext);
1773
+ if (existsSync2(withExt)) {
1774
+ return toPosixPath(withExt).slice(posixRoot.length + 1);
1775
+ }
1776
+ const indexFile = resolve(baseDir, importPath, "index" + ext);
1777
+ if (existsSync2(indexFile)) {
1778
+ return toPosixPath(indexFile).slice(posixRoot.length + 1);
1779
+ }
1780
+ }
1781
+ return null;
1782
+ }
1783
+ };
1784
+
1785
+ export {
1786
+ isMeridianIndexablePath,
1787
+ isUnnamedSymbolId,
1788
+ queryFlow,
1789
+ reviveDeletedFile,
1790
+ MeridianIndexer
1791
+ };