@co-engram/core 0.1.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 (531) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +167 -0
  3. package/dist/config/defaults.d.ts +45 -0
  4. package/dist/config/defaults.d.ts.map +1 -0
  5. package/dist/config/defaults.js +70 -0
  6. package/dist/config/defaults.js.map +1 -0
  7. package/dist/config/index.d.ts +84 -0
  8. package/dist/config/index.d.ts.map +1 -0
  9. package/dist/config/index.js +230 -0
  10. package/dist/config/index.js.map +1 -0
  11. package/dist/config/types.d.ts +145 -0
  12. package/dist/config/types.d.ts.map +1 -0
  13. package/dist/config/types.js +14 -0
  14. package/dist/config/types.js.map +1 -0
  15. package/dist/contradiction/arbiter.d.ts +51 -0
  16. package/dist/contradiction/arbiter.d.ts.map +1 -0
  17. package/dist/contradiction/arbiter.js +112 -0
  18. package/dist/contradiction/arbiter.js.map +1 -0
  19. package/dist/contradiction/auto-degrade.d.ts +53 -0
  20. package/dist/contradiction/auto-degrade.d.ts.map +1 -0
  21. package/dist/contradiction/auto-degrade.js +79 -0
  22. package/dist/contradiction/auto-degrade.js.map +1 -0
  23. package/dist/contradiction/detector.d.ts +33 -0
  24. package/dist/contradiction/detector.d.ts.map +1 -0
  25. package/dist/contradiction/detector.js +76 -0
  26. package/dist/contradiction/detector.js.map +1 -0
  27. package/dist/contradiction/index.d.ts +11 -0
  28. package/dist/contradiction/index.d.ts.map +1 -0
  29. package/dist/contradiction/index.js +11 -0
  30. package/dist/contradiction/index.js.map +1 -0
  31. package/dist/contradiction/resolver.d.ts +45 -0
  32. package/dist/contradiction/resolver.d.ts.map +1 -0
  33. package/dist/contradiction/resolver.js +196 -0
  34. package/dist/contradiction/resolver.js.map +1 -0
  35. package/dist/contradiction/types.d.ts +73 -0
  36. package/dist/contradiction/types.d.ts.map +1 -0
  37. package/dist/contradiction/types.js +7 -0
  38. package/dist/contradiction/types.js.map +1 -0
  39. package/dist/dedup/dedupe.d.ts +49 -0
  40. package/dist/dedup/dedupe.d.ts.map +1 -0
  41. package/dist/dedup/dedupe.js +111 -0
  42. package/dist/dedup/dedupe.js.map +1 -0
  43. package/dist/dedup/hash.d.ts +27 -0
  44. package/dist/dedup/hash.d.ts.map +1 -0
  45. package/dist/dedup/hash.js +43 -0
  46. package/dist/dedup/hash.js.map +1 -0
  47. package/dist/dedup/index.d.ts +12 -0
  48. package/dist/dedup/index.d.ts.map +1 -0
  49. package/dist/dedup/index.js +12 -0
  50. package/dist/dedup/index.js.map +1 -0
  51. package/dist/dedup/llm-triage.d.ts +48 -0
  52. package/dist/dedup/llm-triage.d.ts.map +1 -0
  53. package/dist/dedup/llm-triage.js +100 -0
  54. package/dist/dedup/llm-triage.js.map +1 -0
  55. package/dist/dedup/merge.d.ts +41 -0
  56. package/dist/dedup/merge.d.ts.map +1 -0
  57. package/dist/dedup/merge.js +55 -0
  58. package/dist/dedup/merge.js.map +1 -0
  59. package/dist/dedup/similar.d.ts +45 -0
  60. package/dist/dedup/similar.d.ts.map +1 -0
  61. package/dist/dedup/similar.js +103 -0
  62. package/dist/dedup/similar.js.map +1 -0
  63. package/dist/dedup/types.d.ts +72 -0
  64. package/dist/dedup/types.d.ts.map +1 -0
  65. package/dist/dedup/types.js +18 -0
  66. package/dist/dedup/types.js.map +1 -0
  67. package/dist/disclosure/adaptive.d.ts +86 -0
  68. package/dist/disclosure/adaptive.d.ts.map +1 -0
  69. package/dist/disclosure/adaptive.js +196 -0
  70. package/dist/disclosure/adaptive.js.map +1 -0
  71. package/dist/disclosure/budget.d.ts +51 -0
  72. package/dist/disclosure/budget.d.ts.map +1 -0
  73. package/dist/disclosure/budget.js +91 -0
  74. package/dist/disclosure/budget.js.map +1 -0
  75. package/dist/disclosure/index.d.ts +11 -0
  76. package/dist/disclosure/index.d.ts.map +1 -0
  77. package/dist/disclosure/index.js +11 -0
  78. package/dist/disclosure/index.js.map +1 -0
  79. package/dist/disclosure/tier-loader.d.ts +58 -0
  80. package/dist/disclosure/tier-loader.d.ts.map +1 -0
  81. package/dist/disclosure/tier-loader.js +205 -0
  82. package/dist/disclosure/tier-loader.js.map +1 -0
  83. package/dist/dreaming/decay.d.ts +46 -0
  84. package/dist/dreaming/decay.d.ts.map +1 -0
  85. package/dist/dreaming/decay.js +69 -0
  86. package/dist/dreaming/decay.js.map +1 -0
  87. package/dist/dreaming/deep.d.ts +48 -0
  88. package/dist/dreaming/deep.d.ts.map +1 -0
  89. package/dist/dreaming/deep.js +41 -0
  90. package/dist/dreaming/deep.js.map +1 -0
  91. package/dist/dreaming/index.d.ts +12 -0
  92. package/dist/dreaming/index.d.ts.map +1 -0
  93. package/dist/dreaming/index.js +12 -0
  94. package/dist/dreaming/index.js.map +1 -0
  95. package/dist/dreaming/light.d.ts +54 -0
  96. package/dist/dreaming/light.d.ts.map +1 -0
  97. package/dist/dreaming/light.js +114 -0
  98. package/dist/dreaming/light.js.map +1 -0
  99. package/dist/dreaming/rem.d.ts +128 -0
  100. package/dist/dreaming/rem.d.ts.map +1 -0
  101. package/dist/dreaming/rem.js +228 -0
  102. package/dist/dreaming/rem.js.map +1 -0
  103. package/dist/dreaming/scheduler.d.ts +75 -0
  104. package/dist/dreaming/scheduler.d.ts.map +1 -0
  105. package/dist/dreaming/scheduler.js +138 -0
  106. package/dist/dreaming/scheduler.js.map +1 -0
  107. package/dist/dreaming/trash.d.ts +174 -0
  108. package/dist/dreaming/trash.d.ts.map +1 -0
  109. package/dist/dreaming/trash.js +466 -0
  110. package/dist/dreaming/trash.js.map +1 -0
  111. package/dist/evolution/category.d.ts +127 -0
  112. package/dist/evolution/category.d.ts.map +1 -0
  113. package/dist/evolution/category.js +271 -0
  114. package/dist/evolution/category.js.map +1 -0
  115. package/dist/evolution/index.d.ts +8 -0
  116. package/dist/evolution/index.d.ts.map +1 -0
  117. package/dist/evolution/index.js +8 -0
  118. package/dist/evolution/index.js.map +1 -0
  119. package/dist/evolution/triggered.d.ts +107 -0
  120. package/dist/evolution/triggered.d.ts.map +1 -0
  121. package/dist/evolution/triggered.js +278 -0
  122. package/dist/evolution/triggered.js.map +1 -0
  123. package/dist/generative/cross-pollinate.d.ts +144 -0
  124. package/dist/generative/cross-pollinate.d.ts.map +1 -0
  125. package/dist/generative/cross-pollinate.js +390 -0
  126. package/dist/generative/cross-pollinate.js.map +1 -0
  127. package/dist/generative/gap-detector.d.ts +80 -0
  128. package/dist/generative/gap-detector.d.ts.map +1 -0
  129. package/dist/generative/gap-detector.js +250 -0
  130. package/dist/generative/gap-detector.js.map +1 -0
  131. package/dist/generative/hypothesis.d.ts +161 -0
  132. package/dist/generative/hypothesis.d.ts.map +1 -0
  133. package/dist/generative/hypothesis.js +356 -0
  134. package/dist/generative/hypothesis.js.map +1 -0
  135. package/dist/generative/index.d.ts +9 -0
  136. package/dist/generative/index.d.ts.map +1 -0
  137. package/dist/generative/index.js +9 -0
  138. package/dist/generative/index.js.map +1 -0
  139. package/dist/graph/clustering.d.ts +117 -0
  140. package/dist/graph/clustering.d.ts.map +1 -0
  141. package/dist/graph/clustering.js +196 -0
  142. package/dist/graph/clustering.js.map +1 -0
  143. package/dist/graph/index.d.ts +13 -0
  144. package/dist/graph/index.d.ts.map +1 -0
  145. package/dist/graph/index.js +13 -0
  146. package/dist/graph/index.js.map +1 -0
  147. package/dist/graph/layout.d.ts +46 -0
  148. package/dist/graph/layout.d.ts.map +1 -0
  149. package/dist/graph/layout.js +269 -0
  150. package/dist/graph/layout.js.map +1 -0
  151. package/dist/graph/presets.d.ts +134 -0
  152. package/dist/graph/presets.d.ts.map +1 -0
  153. package/dist/graph/presets.js +190 -0
  154. package/dist/graph/presets.js.map +1 -0
  155. package/dist/graph/registry.d.ts +42 -0
  156. package/dist/graph/registry.d.ts.map +1 -0
  157. package/dist/graph/registry.js +158 -0
  158. package/dist/graph/registry.js.map +1 -0
  159. package/dist/graph/snapshot.d.ts +113 -0
  160. package/dist/graph/snapshot.d.ts.map +1 -0
  161. package/dist/graph/snapshot.js +194 -0
  162. package/dist/graph/snapshot.js.map +1 -0
  163. package/dist/graph/traverse.d.ts +84 -0
  164. package/dist/graph/traverse.d.ts.map +1 -0
  165. package/dist/graph/traverse.js +190 -0
  166. package/dist/graph/traverse.js.map +1 -0
  167. package/dist/graph/view-config.d.ts +99 -0
  168. package/dist/graph/view-config.d.ts.map +1 -0
  169. package/dist/graph/view-config.js +208 -0
  170. package/dist/graph/view-config.js.map +1 -0
  171. package/dist/host/detect-git-author.d.ts +26 -0
  172. package/dist/host/detect-git-author.d.ts.map +1 -0
  173. package/dist/host/detect-git-author.js +53 -0
  174. package/dist/host/detect-git-author.js.map +1 -0
  175. package/dist/host/index.d.ts +7 -0
  176. package/dist/host/index.d.ts.map +1 -0
  177. package/dist/host/index.js +7 -0
  178. package/dist/host/index.js.map +1 -0
  179. package/dist/i18n/en.d.ts +8 -0
  180. package/dist/i18n/en.d.ts.map +1 -0
  181. package/dist/i18n/en.js +211 -0
  182. package/dist/i18n/en.js.map +1 -0
  183. package/dist/i18n/field-names.d.ts +95 -0
  184. package/dist/i18n/field-names.d.ts.map +1 -0
  185. package/dist/i18n/field-names.js +329 -0
  186. package/dist/i18n/field-names.js.map +1 -0
  187. package/dist/i18n/index.d.ts +92 -0
  188. package/dist/i18n/index.d.ts.map +1 -0
  189. package/dist/i18n/index.js +135 -0
  190. package/dist/i18n/index.js.map +1 -0
  191. package/dist/i18n/types.d.ts +92 -0
  192. package/dist/i18n/types.d.ts.map +1 -0
  193. package/dist/i18n/types.js +7 -0
  194. package/dist/i18n/types.js.map +1 -0
  195. package/dist/i18n/zh.d.ts +197 -0
  196. package/dist/i18n/zh.d.ts.map +1 -0
  197. package/dist/i18n/zh.js +216 -0
  198. package/dist/i18n/zh.js.map +1 -0
  199. package/dist/importance/index.d.ts +7 -0
  200. package/dist/importance/index.d.ts.map +1 -0
  201. package/dist/importance/index.js +7 -0
  202. package/dist/importance/index.js.map +1 -0
  203. package/dist/importance/vector.d.ts +113 -0
  204. package/dist/importance/vector.d.ts.map +1 -0
  205. package/dist/importance/vector.js +202 -0
  206. package/dist/importance/vector.js.map +1 -0
  207. package/dist/index/digest-builder.d.ts +59 -0
  208. package/dist/index/digest-builder.d.ts.map +1 -0
  209. package/dist/index/digest-builder.js +195 -0
  210. package/dist/index/digest-builder.js.map +1 -0
  211. package/dist/index/graph-builder.d.ts +32 -0
  212. package/dist/index/graph-builder.d.ts.map +1 -0
  213. package/dist/index/graph-builder.js +104 -0
  214. package/dist/index/graph-builder.js.map +1 -0
  215. package/dist/index/incremental.d.ts +31 -0
  216. package/dist/index/incremental.d.ts.map +1 -0
  217. package/dist/index/incremental.js +47 -0
  218. package/dist/index/incremental.js.map +1 -0
  219. package/dist/index/index.d.ts +11 -0
  220. package/dist/index/index.d.ts.map +1 -0
  221. package/dist/index/index.js +11 -0
  222. package/dist/index/index.js.map +1 -0
  223. package/dist/index/orchestrator.d.ts +60 -0
  224. package/dist/index/orchestrator.d.ts.map +1 -0
  225. package/dist/index/orchestrator.js +99 -0
  226. package/dist/index/orchestrator.js.map +1 -0
  227. package/dist/index/types.d.ts +116 -0
  228. package/dist/index/types.d.ts.map +1 -0
  229. package/dist/index/types.js +10 -0
  230. package/dist/index/types.js.map +1 -0
  231. package/dist/index.d.ts +37 -0
  232. package/dist/index.d.ts.map +1 -0
  233. package/dist/index.js +37 -0
  234. package/dist/index.js.map +1 -0
  235. package/dist/learning/index.d.ts +7 -0
  236. package/dist/learning/index.d.ts.map +1 -0
  237. package/dist/learning/index.js +7 -0
  238. package/dist/learning/index.js.map +1 -0
  239. package/dist/learning/loop.d.ts +79 -0
  240. package/dist/learning/loop.d.ts.map +1 -0
  241. package/dist/learning/loop.js +116 -0
  242. package/dist/learning/loop.js.map +1 -0
  243. package/dist/lifecycle/freshness.d.ts +35 -0
  244. package/dist/lifecycle/freshness.d.ts.map +1 -0
  245. package/dist/lifecycle/freshness.js +60 -0
  246. package/dist/lifecycle/freshness.js.map +1 -0
  247. package/dist/lifecycle/index.d.ts +7 -0
  248. package/dist/lifecycle/index.d.ts.map +1 -0
  249. package/dist/lifecycle/index.js +7 -0
  250. package/dist/lifecycle/index.js.map +1 -0
  251. package/dist/lineage/index.d.ts +10 -0
  252. package/dist/lineage/index.d.ts.map +1 -0
  253. package/dist/lineage/index.js +10 -0
  254. package/dist/lineage/index.js.map +1 -0
  255. package/dist/lineage/trace.d.ts +142 -0
  256. package/dist/lineage/trace.d.ts.map +1 -0
  257. package/dist/lineage/trace.js +316 -0
  258. package/dist/lineage/trace.js.map +1 -0
  259. package/dist/maintenance/engine.d.ts +78 -0
  260. package/dist/maintenance/engine.d.ts.map +1 -0
  261. package/dist/maintenance/engine.js +291 -0
  262. package/dist/maintenance/engine.js.map +1 -0
  263. package/dist/maintenance/index.d.ts +8 -0
  264. package/dist/maintenance/index.d.ts.map +1 -0
  265. package/dist/maintenance/index.js +8 -0
  266. package/dist/maintenance/index.js.map +1 -0
  267. package/dist/maintenance/types.d.ts +111 -0
  268. package/dist/maintenance/types.d.ts.map +1 -0
  269. package/dist/maintenance/types.js +24 -0
  270. package/dist/maintenance/types.js.map +1 -0
  271. package/dist/observability/audit-log.d.ts +77 -0
  272. package/dist/observability/audit-log.d.ts.map +1 -0
  273. package/dist/observability/audit-log.js +101 -0
  274. package/dist/observability/audit-log.js.map +1 -0
  275. package/dist/observability/effectiveness-tracker.d.ts +162 -0
  276. package/dist/observability/effectiveness-tracker.d.ts.map +1 -0
  277. package/dist/observability/effectiveness-tracker.js +251 -0
  278. package/dist/observability/effectiveness-tracker.js.map +1 -0
  279. package/dist/observability/index.d.ts +16 -0
  280. package/dist/observability/index.d.ts.map +1 -0
  281. package/dist/observability/index.js +16 -0
  282. package/dist/observability/index.js.map +1 -0
  283. package/dist/observability/necessity-evaluator.d.ts +116 -0
  284. package/dist/observability/necessity-evaluator.d.ts.map +1 -0
  285. package/dist/observability/necessity-evaluator.js +487 -0
  286. package/dist/observability/necessity-evaluator.js.map +1 -0
  287. package/dist/observability/proposal-engine.d.ts +224 -0
  288. package/dist/observability/proposal-engine.d.ts.map +1 -0
  289. package/dist/observability/proposal-engine.js +651 -0
  290. package/dist/observability/proposal-engine.js.map +1 -0
  291. package/dist/perspectives/index.d.ts +7 -0
  292. package/dist/perspectives/index.d.ts.map +1 -0
  293. package/dist/perspectives/index.js +7 -0
  294. package/dist/perspectives/index.js.map +1 -0
  295. package/dist/perspectives/multi-view.d.ts +81 -0
  296. package/dist/perspectives/multi-view.d.ts.map +1 -0
  297. package/dist/perspectives/multi-view.js +221 -0
  298. package/dist/perspectives/multi-view.js.map +1 -0
  299. package/dist/prompt-builder/builder.d.ts +47 -0
  300. package/dist/prompt-builder/builder.d.ts.map +1 -0
  301. package/dist/prompt-builder/builder.js +136 -0
  302. package/dist/prompt-builder/builder.js.map +1 -0
  303. package/dist/prompt-builder/index.d.ts +8 -0
  304. package/dist/prompt-builder/index.d.ts.map +1 -0
  305. package/dist/prompt-builder/index.js +8 -0
  306. package/dist/prompt-builder/index.js.map +1 -0
  307. package/dist/prompt-builder/types.d.ts +42 -0
  308. package/dist/prompt-builder/types.d.ts.map +1 -0
  309. package/dist/prompt-builder/types.js +10 -0
  310. package/dist/prompt-builder/types.js.map +1 -0
  311. package/dist/prompt-signals/cache.d.ts +36 -0
  312. package/dist/prompt-signals/cache.d.ts.map +1 -0
  313. package/dist/prompt-signals/cache.js +70 -0
  314. package/dist/prompt-signals/cache.js.map +1 -0
  315. package/dist/prompt-signals/index.d.ts +12 -0
  316. package/dist/prompt-signals/index.d.ts.map +1 -0
  317. package/dist/prompt-signals/index.js +11 -0
  318. package/dist/prompt-signals/index.js.map +1 -0
  319. package/dist/prompt-signals/stats.d.ts +45 -0
  320. package/dist/prompt-signals/stats.d.ts.map +1 -0
  321. package/dist/prompt-signals/stats.js +119 -0
  322. package/dist/prompt-signals/stats.js.map +1 -0
  323. package/dist/prompt-signals/types.d.ts +53 -0
  324. package/dist/prompt-signals/types.d.ts.map +1 -0
  325. package/dist/prompt-signals/types.js +34 -0
  326. package/dist/prompt-signals/types.js.map +1 -0
  327. package/dist/provenance/index.d.ts +7 -0
  328. package/dist/provenance/index.d.ts.map +1 -0
  329. package/dist/provenance/index.js +7 -0
  330. package/dist/provenance/index.js.map +1 -0
  331. package/dist/provenance/reliability.d.ts +133 -0
  332. package/dist/provenance/reliability.d.ts.map +1 -0
  333. package/dist/provenance/reliability.js +213 -0
  334. package/dist/provenance/reliability.js.map +1 -0
  335. package/dist/reinforcement/config.d.ts +34 -0
  336. package/dist/reinforcement/config.d.ts.map +1 -0
  337. package/dist/reinforcement/config.js +43 -0
  338. package/dist/reinforcement/config.js.map +1 -0
  339. package/dist/reinforcement/index.d.ts +12 -0
  340. package/dist/reinforcement/index.d.ts.map +1 -0
  341. package/dist/reinforcement/index.js +12 -0
  342. package/dist/reinforcement/index.js.map +1 -0
  343. package/dist/reinforcement/ltd.d.ts +59 -0
  344. package/dist/reinforcement/ltd.d.ts.map +1 -0
  345. package/dist/reinforcement/ltd.js +90 -0
  346. package/dist/reinforcement/ltd.js.map +1 -0
  347. package/dist/reinforcement/ltp.d.ts +63 -0
  348. package/dist/reinforcement/ltp.d.ts.map +1 -0
  349. package/dist/reinforcement/ltp.js +99 -0
  350. package/dist/reinforcement/ltp.js.map +1 -0
  351. package/dist/reinforcement/related.d.ts +39 -0
  352. package/dist/reinforcement/related.d.ts.map +1 -0
  353. package/dist/reinforcement/related.js +92 -0
  354. package/dist/reinforcement/related.js.map +1 -0
  355. package/dist/retrieval/filter.d.ts +18 -0
  356. package/dist/retrieval/filter.d.ts.map +1 -0
  357. package/dist/retrieval/filter.js +68 -0
  358. package/dist/retrieval/filter.js.map +1 -0
  359. package/dist/retrieval/fts.d.ts +44 -0
  360. package/dist/retrieval/fts.d.ts.map +1 -0
  361. package/dist/retrieval/fts.js +120 -0
  362. package/dist/retrieval/fts.js.map +1 -0
  363. package/dist/retrieval/index.d.ts +10 -0
  364. package/dist/retrieval/index.d.ts.map +1 -0
  365. package/dist/retrieval/index.js +10 -0
  366. package/dist/retrieval/index.js.map +1 -0
  367. package/dist/retrieval/orchestrator.d.ts +78 -0
  368. package/dist/retrieval/orchestrator.d.ts.map +1 -0
  369. package/dist/retrieval/orchestrator.js +156 -0
  370. package/dist/retrieval/orchestrator.js.map +1 -0
  371. package/dist/retrieval/scoring.d.ts +105 -0
  372. package/dist/retrieval/scoring.d.ts.map +1 -0
  373. package/dist/retrieval/scoring.js +165 -0
  374. package/dist/retrieval/scoring.js.map +1 -0
  375. package/dist/signals/extract.d.ts +98 -0
  376. package/dist/signals/extract.d.ts.map +1 -0
  377. package/dist/signals/extract.js +388 -0
  378. package/dist/signals/extract.js.map +1 -0
  379. package/dist/signals/file-sink.d.ts +81 -0
  380. package/dist/signals/file-sink.d.ts.map +1 -0
  381. package/dist/signals/file-sink.js +227 -0
  382. package/dist/signals/file-sink.js.map +1 -0
  383. package/dist/signals/index.d.ts +12 -0
  384. package/dist/signals/index.d.ts.map +1 -0
  385. package/dist/signals/index.js +12 -0
  386. package/dist/signals/index.js.map +1 -0
  387. package/dist/signals/rpe.d.ts +60 -0
  388. package/dist/signals/rpe.d.ts.map +1 -0
  389. package/dist/signals/rpe.js +68 -0
  390. package/dist/signals/rpe.js.map +1 -0
  391. package/dist/signals/types.d.ts +80 -0
  392. package/dist/signals/types.d.ts.map +1 -0
  393. package/dist/signals/types.js +14 -0
  394. package/dist/signals/types.js.map +1 -0
  395. package/dist/storage/engram-index.d.ts +73 -0
  396. package/dist/storage/engram-index.d.ts.map +1 -0
  397. package/dist/storage/engram-index.js +215 -0
  398. package/dist/storage/engram-index.js.map +1 -0
  399. package/dist/storage/engram-store.d.ts +147 -0
  400. package/dist/storage/engram-store.d.ts.map +1 -0
  401. package/dist/storage/engram-store.js +221 -0
  402. package/dist/storage/engram-store.js.map +1 -0
  403. package/dist/storage/git-stage.d.ts +56 -0
  404. package/dist/storage/git-stage.d.ts.map +1 -0
  405. package/dist/storage/git-stage.js +147 -0
  406. package/dist/storage/git-stage.js.map +1 -0
  407. package/dist/storage/git.d.ts +57 -0
  408. package/dist/storage/git.d.ts.map +1 -0
  409. package/dist/storage/git.js +156 -0
  410. package/dist/storage/git.js.map +1 -0
  411. package/dist/storage/hash.d.ts +23 -0
  412. package/dist/storage/hash.d.ts.map +1 -0
  413. package/dist/storage/hash.js +31 -0
  414. package/dist/storage/hash.js.map +1 -0
  415. package/dist/storage/index.d.ts +14 -0
  416. package/dist/storage/index.d.ts.map +1 -0
  417. package/dist/storage/index.js +14 -0
  418. package/dist/storage/index.js.map +1 -0
  419. package/dist/storage/path.d.ts +51 -0
  420. package/dist/storage/path.d.ts.map +1 -0
  421. package/dist/storage/path.js +72 -0
  422. package/dist/storage/path.js.map +1 -0
  423. package/dist/storage/repository.d.ts +342 -0
  424. package/dist/storage/repository.d.ts.map +1 -0
  425. package/dist/storage/repository.js +1221 -0
  426. package/dist/storage/repository.js.map +1 -0
  427. package/dist/storage/synapse-store.d.ts +113 -0
  428. package/dist/storage/synapse-store.d.ts.map +1 -0
  429. package/dist/storage/synapse-store.js +279 -0
  430. package/dist/storage/synapse-store.js.map +1 -0
  431. package/dist/tools/doctor-tools.d.ts +53 -0
  432. package/dist/tools/doctor-tools.d.ts.map +1 -0
  433. package/dist/tools/doctor-tools.js +97 -0
  434. package/dist/tools/doctor-tools.js.map +1 -0
  435. package/dist/tools/engram-tools.d.ts +166 -0
  436. package/dist/tools/engram-tools.d.ts.map +1 -0
  437. package/dist/tools/engram-tools.js +834 -0
  438. package/dist/tools/engram-tools.js.map +1 -0
  439. package/dist/tools/index.d.ts +16 -0
  440. package/dist/tools/index.d.ts.map +1 -0
  441. package/dist/tools/index.js +16 -0
  442. package/dist/tools/index.js.map +1 -0
  443. package/dist/tools/llm-descriptions.d.ts +61 -0
  444. package/dist/tools/llm-descriptions.d.ts.map +1 -0
  445. package/dist/tools/llm-descriptions.js +556 -0
  446. package/dist/tools/llm-descriptions.js.map +1 -0
  447. package/dist/tools/proposal-tools.d.ts +40 -0
  448. package/dist/tools/proposal-tools.d.ts.map +1 -0
  449. package/dist/tools/proposal-tools.js +99 -0
  450. package/dist/tools/proposal-tools.js.map +1 -0
  451. package/dist/tools/registry.d.ts +30 -0
  452. package/dist/tools/registry.d.ts.map +1 -0
  453. package/dist/tools/registry.js +43 -0
  454. package/dist/tools/registry.js.map +1 -0
  455. package/dist/tools/schemas.d.ts +715 -0
  456. package/dist/tools/schemas.d.ts.map +1 -0
  457. package/dist/tools/schemas.js +383 -0
  458. package/dist/tools/schemas.js.map +1 -0
  459. package/dist/tools/skill-tools.d.ts +26 -0
  460. package/dist/tools/skill-tools.d.ts.map +1 -0
  461. package/dist/tools/skill-tools.js +84 -0
  462. package/dist/tools/skill-tools.js.map +1 -0
  463. package/dist/tools/synapse-tools.d.ts +28 -0
  464. package/dist/tools/synapse-tools.d.ts.map +1 -0
  465. package/dist/tools/synapse-tools.js +177 -0
  466. package/dist/tools/synapse-tools.js.map +1 -0
  467. package/dist/tools/tool.d.ts +84 -0
  468. package/dist/tools/tool.d.ts.map +1 -0
  469. package/dist/tools/tool.js +22 -0
  470. package/dist/tools/tool.js.map +1 -0
  471. package/dist/tools/wrapped.d.ts +28 -0
  472. package/dist/tools/wrapped.d.ts.map +1 -0
  473. package/dist/tools/wrapped.js +172 -0
  474. package/dist/tools/wrapped.js.map +1 -0
  475. package/dist/types/disclosure.d.ts +70 -0
  476. package/dist/types/disclosure.d.ts.map +1 -0
  477. package/dist/types/disclosure.js +9 -0
  478. package/dist/types/disclosure.js.map +1 -0
  479. package/dist/types/engram.d.ts +203 -0
  480. package/dist/types/engram.d.ts.map +1 -0
  481. package/dist/types/engram.js +7 -0
  482. package/dist/types/engram.js.map +1 -0
  483. package/dist/types/index.d.ts +15 -0
  484. package/dist/types/index.d.ts.map +1 -0
  485. package/dist/types/index.js +15 -0
  486. package/dist/types/index.js.map +1 -0
  487. package/dist/types/intention.d.ts +91 -0
  488. package/dist/types/intention.d.ts.map +1 -0
  489. package/dist/types/intention.js +9 -0
  490. package/dist/types/intention.js.map +1 -0
  491. package/dist/types/repository-types.d.ts +113 -0
  492. package/dist/types/repository-types.d.ts.map +1 -0
  493. package/dist/types/repository-types.js +18 -0
  494. package/dist/types/repository-types.js.map +1 -0
  495. package/dist/types/scene.d.ts +52 -0
  496. package/dist/types/scene.d.ts.map +1 -0
  497. package/dist/types/scene.js +12 -0
  498. package/dist/types/scene.js.map +1 -0
  499. package/dist/types/skill.d.ts +107 -0
  500. package/dist/types/skill.d.ts.map +1 -0
  501. package/dist/types/skill.js +10 -0
  502. package/dist/types/skill.js.map +1 -0
  503. package/dist/types/slugify.d.ts +34 -0
  504. package/dist/types/slugify.d.ts.map +1 -0
  505. package/dist/types/slugify.js +55 -0
  506. package/dist/types/slugify.js.map +1 -0
  507. package/dist/types/synapse-id.d.ts +22 -0
  508. package/dist/types/synapse-id.d.ts.map +1 -0
  509. package/dist/types/synapse-id.js +34 -0
  510. package/dist/types/synapse-id.js.map +1 -0
  511. package/dist/types/synapse.d.ts +99 -0
  512. package/dist/types/synapse.d.ts.map +1 -0
  513. package/dist/types/synapse.js +9 -0
  514. package/dist/types/synapse.js.map +1 -0
  515. package/dist/verification/index.d.ts +11 -0
  516. package/dist/verification/index.d.ts.map +1 -0
  517. package/dist/verification/index.js +11 -0
  518. package/dist/verification/index.js.map +1 -0
  519. package/dist/verification/metacognition.d.ts +102 -0
  520. package/dist/verification/metacognition.d.ts.map +1 -0
  521. package/dist/verification/metacognition.js +284 -0
  522. package/dist/verification/metacognition.js.map +1 -0
  523. package/dist/verification/state-machine.d.ts +77 -0
  524. package/dist/verification/state-machine.d.ts.map +1 -0
  525. package/dist/verification/state-machine.js +126 -0
  526. package/dist/verification/state-machine.js.map +1 -0
  527. package/dist/verification/upgrade.d.ts +133 -0
  528. package/dist/verification/upgrade.d.ts.map +1 -0
  529. package/dist/verification/upgrade.js +364 -0
  530. package/dist/verification/upgrade.js.map +1 -0
  531. package/package.json +70 -0
@@ -0,0 +1,1221 @@
1
+ /**
2
+ * EngramRepository — per-edge synapse + 人类友好存储
3
+ *
4
+ * 设计要点:
5
+ * - EngramId 是 ULID(stable,与路径解耦)
6
+ * - Engram 单文件存储(frontmatter + content)
7
+ * - Synapse per-edge 存储(synapses/{kind}/syn-{hash}.yaml)
8
+ * - readSynapses 返回双向 { outgoing, incoming }
9
+ * - deleteEngram 自动级联删除触及的 synapse
10
+ * - 内置 doctor 自愈扫描
11
+ * - 内置 path-tree(渐进式披露)
12
+ *
13
+ * @module @co-engram/core/storage
14
+ */
15
+ import { existsSync, statSync, rmSync, watch, } from "node:fs";
16
+ import { readFileSync, readdirSync } from "node:fs";
17
+ import { parse as parseYaml } from "yaml";
18
+ import { join, relative, sep } from "node:path";
19
+ import { ulid } from "ulid";
20
+ import { isStableEngramId } from "../types/repository-types.js";
21
+ import { slugify, inferDomainTagsFromPath } from "../types/slugify.js";
22
+ import { computeContentHash, computeContentSize } from "./hash.js";
23
+ import { DEFAULT_LANGUAGE } from "../i18n/index.js";
24
+ import { readEngramFile, writeEngramFile, deleteEngramFile, renameEngramFile, parseEngramFile, detectEngramFileLanguage, } from "./engram-store.js";
25
+ import { SYNAPSES_DIR, collectAllSynapses, upsertSynapse, readSynapseByEndpoints, readSynapseById, listSynapsesForEngram, deleteSynapsesTouching, synapseRelativePath, deleteSynapseFile, writeSynapseFile, parseSynapseFile, } from "./synapse-store.js";
26
+ import { buildIndexEntryFromFrontmatter, engramIndexPath, readEngramIndex, rebuildEngramIndex, removeEngramIndexEntry, upsertEngramIndexEntry, writeEngramIndex, } from "./engram-index.js";
27
+ const DEFAULT_IMPORTANCE = 0.5;
28
+ const DEFAULT_CONFIDENCE_BY_SOURCE = {
29
+ firsthand: 0.85,
30
+ secondhand: 0.65,
31
+ inferred: 0.5,
32
+ };
33
+ const DEFAULT_DECAY_HALF_LIFE_DAYS = 90;
34
+ function now() {
35
+ return new Date().toISOString();
36
+ }
37
+ function clamp01(x) {
38
+ if (Number.isNaN(x))
39
+ return 0;
40
+ if (x < 0)
41
+ return 0;
42
+ if (x > 1)
43
+ return 1;
44
+ return x;
45
+ }
46
+ /**
47
+ * 用户未显式提供 summary 时,从 content 派生一个简短摘要。
48
+ *
49
+ * 历史问题:之前默认 `summary = title`,导致 FTS 把 title 索引两次,完全无法
50
+ * 命中 content 里的关键词。用户搜 content 里明明存在的词都搜不到。
51
+ *
52
+ * 派生策略:
53
+ * - 无 content 或纯空白 → 回退 title(避免空 summary)
54
+ * - content 走单行化 + trim,前 200 字符;超出加省略号
55
+ * - 不改变用户显式提供的 summary(那一路在调用方用 ?? 短路)
56
+ */
57
+ function deriveAutoSummary(content, title) {
58
+ if (!content)
59
+ return title;
60
+ const cleaned = content.trim().replace(/\s+/g, " ");
61
+ if (cleaned.length === 0)
62
+ return title;
63
+ if (cleaned.length <= 200)
64
+ return cleaned;
65
+ return cleaned.slice(0, 197) + "...";
66
+ }
67
+ /**
68
+ * EngramRepository — per-edge synapse + ULID stable id + 单文件 engram
69
+ *
70
+ * 所有读取方法接受 stable id (ULID)。从 path 读 engram 用 readEngramByPath。
71
+ */
72
+ export class EngramRepository {
73
+ config;
74
+ indexCache;
75
+ /**
76
+ * 当前 indexCache 对应的 engram-index.json 磁盘 mtime。
77
+ * 用于跨进程缓存一致性校验:其他进程写入会使磁盘 mtime 改变,
78
+ * 本进程 getIndex 检测到不一致时失效缓存并重读。
79
+ */
80
+ indexCacheMtime;
81
+ /**
82
+ * Invalidate listeners — 在 indexCache 失效(watcher 触发或 mtime 不一致)时被调用。
83
+ *
84
+ * 主要用途:让 SearchOrchestrator 等依赖 index 派生数据的组件同步失效并重建。
85
+ * 跨进程场景:plugin 进程写 index → mcp 进程的 fs.watch 触发 → listener 重建 ftsIndex。
86
+ */
87
+ invalidateListeners = [];
88
+ /**
89
+ * fs.watch 句柄(可选)。启动后,外部进程修改 engram-index.json
90
+ * 会立即触发缓存失效,无需等下次 getIndex 的 mtime 兜底检查。
91
+ */
92
+ indexWatcher;
93
+ language;
94
+ constructor(config) {
95
+ this.config = config;
96
+ this.language = config.language ?? DEFAULT_LANGUAGE;
97
+ }
98
+ /** 当前写入语言(读取时自动兼容任意语言格式) */
99
+ get currentLanguage() {
100
+ return this.language;
101
+ }
102
+ get rootPath() {
103
+ return this.config.rootPath;
104
+ }
105
+ // ─── Index 管理 ────────────────────────────────────────────────────────
106
+ /**
107
+ * 获取 index(惰性加载 + 跨进程一致性校验)
108
+ *
109
+ * 双保险策略:
110
+ * 1. mtime 校验(主路径):每次调用 stat engram-index.json,
111
+ * 若磁盘 mtime 与 indexCacheMtime 不一致 → 缓存失效,重读。
112
+ * 2. fs.watch(可选实时):若已 startWatching,外部进程写入会
113
+ * 主动触发 invalidate,无需等下次调用。
114
+ *
115
+ * mtime 校验是兜底,即使 watcher 漏事件(NFS / Docker / WSL)也能保证最终一致。
116
+ */
117
+ getIndex() {
118
+ const indexPath = engramIndexPath(this.config.rootPath);
119
+ let diskMtime;
120
+ try {
121
+ diskMtime = statSync(indexPath).mtimeMs;
122
+ }
123
+ catch {
124
+ // engram-index.json 不存在或不可读 — 下方处理
125
+ }
126
+ // 缓存新鲜(mtime 一致)
127
+ if (this.indexCache !== undefined &&
128
+ diskMtime !== undefined &&
129
+ this.indexCacheMtime === diskMtime) {
130
+ return this.indexCache;
131
+ }
132
+ if (diskMtime !== undefined) {
133
+ // 磁盘有 index.json → 读取(快,无需扫全树)
134
+ const fresh = readEngramIndex(this.config.rootPath);
135
+ this.indexCache = fresh;
136
+ this.indexCacheMtime = diskMtime;
137
+ return fresh;
138
+ }
139
+ // 磁盘无 index.json → 全量 rebuild + 写盘(rebuildIndex 会同步更新 mtime)
140
+ return this.rebuildIndex();
141
+ }
142
+ /** 全量重建 index(并写盘) */
143
+ rebuildIndex() {
144
+ const index = rebuildEngramIndex(this.config.rootPath);
145
+ this.persistIndex(index);
146
+ return index;
147
+ }
148
+ /**
149
+ * 写入 index 到磁盘并同步更新 indexCacheMtime。
150
+ *
151
+ * 集中所有"写盘 + 更新 mtime"逻辑,确保自进程写入不会被下次 getIndex
152
+ * 误判为外部修改而重读(浪费但不出错)。
153
+ *
154
+ * 若 startWatching 之前因 engram-index.json 不存在而降级,本次写盘后
155
+ * 文件已存在,这里 lazy 重试启动 watcher(覆盖首次空 dataRoot 场景)。
156
+ */
157
+ persistIndex(index) {
158
+ writeEngramIndex(this.config.rootPath, index);
159
+ this.indexCache = index;
160
+ try {
161
+ this.indexCacheMtime = statSync(engramIndexPath(this.config.rootPath)).mtimeMs;
162
+ }
163
+ catch {
164
+ this.indexCacheMtime = Date.now();
165
+ }
166
+ // 文件刚被创建/重写 — 若 watcher 因之前文件不存在而降级,现在 lazy 重试
167
+ if (!this.indexWatcher) {
168
+ this.startWatching();
169
+ }
170
+ }
171
+ /** 增量更新单条 entry 并写盘 */
172
+ updateIndexEntry(entry) {
173
+ const index = this.getIndex();
174
+ upsertEngramIndexEntry(index, entry);
175
+ this.persistIndex(index);
176
+ }
177
+ /** 从 index 删除一条并写盘 */
178
+ deleteIndexEntry(id) {
179
+ const index = this.getIndex();
180
+ removeEngramIndexEntry(index, id);
181
+ this.persistIndex(index);
182
+ }
183
+ // ─── Cross-process watcher ─────────────────────────────────────────────
184
+ /**
185
+ * 启动对 engram-index.json 的 fs.watch 监听。
186
+ *
187
+ * 启动后,外部进程修改 index(创建/更新/删除 engram)会触发 watcher,
188
+ * 主动失效 indexCache。下次 getIndex 调用时从磁盘重读。
189
+ *
190
+ * 幂等:多次调用安全,只创建一个 watcher。
191
+ *
192
+ * 适用场景:多个 host adapter(MCP server / OpenClaw plugin)共享同一
193
+ * dataRoot 时,确保各进程的缓存相互一致。
194
+ *
195
+ * 不需要显式停止 — 进程退出时 OS 自动回收 fd。stopWatching() 仅用于
196
+ * 测试 / 显式资源管理场景。
197
+ */
198
+ startWatching() {
199
+ if (this.indexWatcher)
200
+ return;
201
+ const indexPath = engramIndexPath(this.config.rootPath);
202
+ try {
203
+ // fs.watch 的 eventType 跨平台语义不稳定(Linux inotify / macOS FSEvents /
204
+ // Windows ReadDirectoryChangesW 触发类型不同,且可能多次回调)。策略是
205
+ // 收到任意事件即 invalidate,由 getIndex 的 mtime 兜底过滤假阳性。
206
+ this.indexWatcher = watch(indexPath, { persistent: false }, () => {
207
+ this.invalidateIndexCache();
208
+ });
209
+ // watcher 出错(NFS / 文件被删 / 平台不支持)→ 静默降级到 mtime 兜底
210
+ this.indexWatcher.on("error", () => {
211
+ this.indexWatcher = undefined;
212
+ });
213
+ }
214
+ catch {
215
+ // 平台不支持 fs.watch 或文件尚不存在 → 降级到 mtime 兜底(getIndex 中已实现)。
216
+ // persistIndex 写盘后会 lazy 重试 startWatching,覆盖首次空 dataRoot 场景。
217
+ this.indexWatcher = undefined;
218
+ }
219
+ }
220
+ /** 停止 watcher(主要用于测试隔离) */
221
+ stopWatching() {
222
+ if (this.indexWatcher) {
223
+ try {
224
+ this.indexWatcher.close();
225
+ }
226
+ catch {
227
+ // ignore
228
+ }
229
+ this.indexWatcher = undefined;
230
+ }
231
+ }
232
+ /** 失效缓存 — 下次 getIndex 从磁盘重读。同时通知 invalidate listeners。 */
233
+ invalidateIndexCache() {
234
+ this.indexCache = undefined;
235
+ this.indexCacheMtime = undefined;
236
+ // 通知外部依赖(SearchOrchestrator 等)派生数据需要重建
237
+ for (const cb of this.invalidateListeners) {
238
+ try {
239
+ cb();
240
+ }
241
+ catch {
242
+ // listener 内部异常不影响 repository 主流程
243
+ }
244
+ }
245
+ }
246
+ /**
247
+ * 注册 invalidate listener — 在 indexCache 失效时被调用。
248
+ *
249
+ * 主要用途:让 SearchOrchestrator 等派生数据同步失效并重建。
250
+ * 跨进程:plugin 进程写 index → mcp 进程 fs.watch 触发 → listener 重建 ftsIndex。
251
+ *
252
+ * @returns 取消注册函数(用于测试隔离或资源释放)
253
+ */
254
+ addInvalidateListener(cb) {
255
+ this.invalidateListeners.push(cb);
256
+ return () => {
257
+ const idx = this.invalidateListeners.indexOf(cb);
258
+ if (idx >= 0)
259
+ this.invalidateListeners.splice(idx, 1);
260
+ };
261
+ }
262
+ /** 解析 stableId → 相对路径 */
263
+ resolvePath(stableId) {
264
+ if (!isStableEngramId(stableId)) {
265
+ // 兼容:可能是相对路径,直接当 path 用
266
+ if (this.existsAtPath(stableId))
267
+ return stableId;
268
+ return undefined;
269
+ }
270
+ const entry = this.getIndex().entries.get(stableId);
271
+ return entry?.path;
272
+ }
273
+ /** 检查相对路径是否存在 engram 文件 */
274
+ existsAtPath(relativePath) {
275
+ return existsSync(join(this.config.rootPath, relativePath));
276
+ }
277
+ // ─── Engram CRUD ───────────────────────────────────────────────────────
278
+ /**
279
+ * 创建 Engram(单文件 + ULID)
280
+ *
281
+ * 文件位置:
282
+ * - 若 input 提供 `pathHint`:用之(允许人类组织目录)
283
+ * - 否则:从 domainTags + title slug 推导默认路径
284
+ *
285
+ * @throws 文件已存在 / title 为空
286
+ */
287
+ createEngram(input) {
288
+ const stableId = ulid();
289
+ const timestamp = now();
290
+ const sourceType = input.sourceType ?? "firsthand";
291
+ const contentHash = computeContentHash(input.content);
292
+ const contentSize = computeContentSize(input.content);
293
+ const relativePath = input.pathHint ?? this.deriveDefaultPath(input);
294
+ const absolutePath = join(this.config.rootPath, relativePath);
295
+ if (existsSync(absolutePath)) {
296
+ throw new Error(`Engram already exists at ${relativePath}`);
297
+ }
298
+ const hasExplicitDomainTags = input.domainTags.length > 0;
299
+ const frontmatter = {
300
+ id: stableId,
301
+ title: input.title,
302
+ kind: input.kind,
303
+ kinds: input.kinds ?? [input.kind],
304
+ tags: input.contextTags,
305
+ domainTags: hasExplicitDomainTags ? input.domainTags : undefined,
306
+ summary: input.summary ?? deriveAutoSummary(input.content, input.title),
307
+ contentHash,
308
+ contentSize,
309
+ createdBy: input.createdBy,
310
+ createdAt: timestamp,
311
+ updatedBy: input.createdBy,
312
+ updatedAt: timestamp,
313
+ version: 1,
314
+ importance: input.importance ?? DEFAULT_IMPORTANCE,
315
+ confidence: input.confidence ?? DEFAULT_CONFIDENCE_BY_SOURCE[sourceType],
316
+ emotionalValence: input.emotionalValence ?? "neutral",
317
+ sourceType,
318
+ evidenceCount: 0,
319
+ retrievalCount: 0,
320
+ effectiveRetrievals: 0,
321
+ failedUses: 0,
322
+ reinforcementScore: 0,
323
+ lastRetrievalScore: 0.5,
324
+ decayHalfLifeDays: input.decayHalfLifeDays === undefined
325
+ ? DEFAULT_DECAY_HALF_LIFE_DAYS
326
+ : input.decayHalfLifeDays,
327
+ status: "active",
328
+ visibility: input.visibility ?? "public",
329
+ verificationStatus: "unverified",
330
+ encodingContext: input.encodingContext,
331
+ perspective: input.perspective,
332
+ contextTags: input.contextTags,
333
+ };
334
+ const file = {
335
+ frontmatter,
336
+ content: input.content,
337
+ };
338
+ writeEngramFile(absolutePath, file, this.language);
339
+ const stat = statSync(absolutePath);
340
+ const entry = buildIndexEntryFromFrontmatter({
341
+ relativePath,
342
+ frontmatter,
343
+ mtime: stat.mtimeMs,
344
+ contentHash,
345
+ });
346
+ this.updateIndexEntry(entry);
347
+ return this.readEngram(stableId);
348
+ }
349
+ /** 默认路径:{domainTags.join('/')/}{slug}.md */
350
+ deriveDefaultPath(input) {
351
+ const slug = slugify(input.title);
352
+ const domains = input.domainTags.length > 0 ? input.domainTags : [];
353
+ const parts = [...domains, `${slug}.md`];
354
+ return parts.join("/");
355
+ }
356
+ /**
357
+ * 读取完整 Engram(单文件 + 统计)
358
+ */
359
+ readEngram(stableId) {
360
+ const relativePath = this.resolvePath(stableId);
361
+ if (!relativePath) {
362
+ throw new Error(`Engram not found: ${stableId}`);
363
+ }
364
+ const absolutePath = join(this.config.rootPath, relativePath);
365
+ const file = readEngramFile(absolutePath);
366
+ return this.assembleEngram(file, relativePath);
367
+ }
368
+ /**
369
+ * 检查 Engram 是否存在(按 stableId 或 path)
370
+ */
371
+ exists(stableId) {
372
+ const relativePath = this.resolvePath(stableId);
373
+ return (relativePath !== undefined &&
374
+ existsSync(join(this.config.rootPath, relativePath)));
375
+ }
376
+ /**
377
+ * 更新 Engram(content/frontmatter 双写)
378
+ *
379
+ * - 自动 version++
380
+ * - title 变更时若 slug 未锁定,重新 slugify + rename 文件
381
+ * - domainTags 显式锁定时不重新推断
382
+ */
383
+ updateEngram(stableId, input) {
384
+ const relativePath = this.resolvePath(stableId);
385
+ if (!relativePath) {
386
+ throw new Error(`Engram not found: ${stableId}`);
387
+ }
388
+ const absolutePath = join(this.config.rootPath, relativePath);
389
+ const oldFile = readEngramFile(absolutePath);
390
+ const oldFrontmatter = oldFile.frontmatter;
391
+ const timestamp = now();
392
+ const newTitle = input.title ?? oldFrontmatter.title;
393
+ const newContent = input.content ?? oldFile.content;
394
+ const newSummary = input.summary ??
395
+ oldFrontmatter.summary ??
396
+ deriveAutoSummary(newContent, newTitle);
397
+ const newContentHash = input.content
398
+ ? computeContentHash(newContent)
399
+ : (oldFrontmatter.contentHash ?? computeContentHash(newContent));
400
+ const newContentSize = input.content
401
+ ? computeContentSize(newContent)
402
+ : (oldFrontmatter.contentSize ?? computeContentSize(newContent));
403
+ const newKinds = input.kinds ??
404
+ oldFrontmatter.kinds ?? [oldFrontmatter.kind];
405
+ const newKind = newKinds[0] ?? oldFrontmatter.kind;
406
+ // domainTags:显式输入优先,否则保留 frontmatter(锁定 / undefined)
407
+ const hasExplicitInputDomainTags = input.domainTags !== undefined;
408
+ const newDomainTags = hasExplicitInputDomainTags
409
+ ? input.domainTags
410
+ : oldFrontmatter.domainTags;
411
+ const newContextTags = input.contextTags ?? oldFrontmatter.contextTags;
412
+ const newEncodingContext = input.encodingContext ?? oldFrontmatter.encodingContext;
413
+ const newEmotionalValence = input.emotionalValence ?? oldFrontmatter.emotionalValence ?? "neutral";
414
+ const newImportance = input.importance ?? oldFrontmatter.importance ?? DEFAULT_IMPORTANCE;
415
+ const newConfidence = input.confidence ??
416
+ oldFrontmatter.confidence ??
417
+ DEFAULT_CONFIDENCE_BY_SOURCE.firsthand;
418
+ const newDecayHalfLife = input.decayHalfLifeDays === undefined
419
+ ? (oldFrontmatter.decayHalfLifeDays ?? DEFAULT_DECAY_HALF_LIFE_DAYS)
420
+ : input.decayHalfLifeDays;
421
+ const newVisibility = input.visibility ?? oldFrontmatter.visibility ?? "public";
422
+ const newImportanceVector = input.importanceVector === undefined
423
+ ? oldFrontmatter
424
+ .importanceVector
425
+ : input.importanceVector;
426
+ const newPerspective = input.perspective === undefined
427
+ ? oldFrontmatter.perspective
428
+ : input.perspective;
429
+ const newFrontmatter = {
430
+ ...oldFrontmatter,
431
+ title: newTitle,
432
+ kind: newKind,
433
+ kinds: newKinds,
434
+ tags: oldFrontmatter.tags,
435
+ domainTags: newDomainTags,
436
+ summary: newSummary,
437
+ contentHash: newContentHash,
438
+ contentSize: newContentSize,
439
+ updatedBy: input.updatedBy,
440
+ updatedAt: timestamp,
441
+ version: (oldFrontmatter.version ?? 1) + 1,
442
+ importance: newImportance,
443
+ confidence: newConfidence,
444
+ emotionalValence: newEmotionalValence,
445
+ decayHalfLifeDays: newDecayHalfLife,
446
+ visibility: newVisibility,
447
+ encodingContext: newEncodingContext,
448
+ perspective: newPerspective,
449
+ contextTags: newContextTags,
450
+ importanceVector: newImportanceVector,
451
+ };
452
+ // 处理 slug 变化:title 变 + slug 未锁定 → 重新 slugify + rename
453
+ const oldSlug = oldFrontmatter.slug ?? slugify(oldFrontmatter.title);
454
+ const newSlugUnlocked = oldFrontmatter.slug === undefined
455
+ ? slugify(newTitle)
456
+ : oldFrontmatter.slug;
457
+ const newPath = newSlugUnlocked !== oldSlug
458
+ ? this.rebuildPath(relativePath, newSlugUnlocked)
459
+ : relativePath;
460
+ const newFile = {
461
+ frontmatter: newFrontmatter,
462
+ content: newContent,
463
+ };
464
+ if (newPath !== relativePath) {
465
+ const newAbsolutePath = join(this.config.rootPath, newPath);
466
+ if (existsSync(newAbsolutePath)) {
467
+ throw new Error(`Slug rename conflict: ${newPath} already exists`);
468
+ }
469
+ writeEngramFile(newAbsolutePath, newFile, this.language);
470
+ rmSync(absolutePath);
471
+ }
472
+ else {
473
+ writeEngramFile(absolutePath, newFile, this.language);
474
+ }
475
+ const stat = statSync(join(this.config.rootPath, newPath));
476
+ const entry = buildIndexEntryFromFrontmatter({
477
+ relativePath: newPath,
478
+ frontmatter: newFrontmatter,
479
+ mtime: stat.mtimeMs,
480
+ contentHash: newContentHash ?? "",
481
+ });
482
+ this.updateIndexEntry(entry);
483
+ return this.readEngram(stableId);
484
+ }
485
+ /** 重新组装路径:替换 basename 为 newSlug */
486
+ rebuildPath(oldRelativePath, newSlug) {
487
+ const parts = oldRelativePath.split("/");
488
+ parts[parts.length - 1] = `${newSlug}.md`;
489
+ return parts.join("/");
490
+ }
491
+ /**
492
+ * 删除 Engram + 级联删除触及的 synapses + 清理 index
493
+ */
494
+ deleteEngram(stableId) {
495
+ const relativePath = this.resolvePath(stableId);
496
+ if (!relativePath)
497
+ return;
498
+ const absolutePath = join(this.config.rootPath, relativePath);
499
+ deleteEngramFile(absolutePath);
500
+ deleteSynapsesTouching(this.config.rootPath, stableId);
501
+ if (isStableEngramId(stableId)) {
502
+ this.deleteIndexEntry(stableId);
503
+ }
504
+ }
505
+ /**
506
+ * 按 path 读 engram(用于 doctor 自愈 / 人类浏览)
507
+ */
508
+ readEngramByPath(relativePath) {
509
+ const absolutePath = join(this.config.rootPath, relativePath);
510
+ if (!existsSync(absolutePath))
511
+ return undefined;
512
+ try {
513
+ const file = readEngramFile(absolutePath);
514
+ return this.assembleEngram(file, relativePath);
515
+ }
516
+ catch {
517
+ return undefined;
518
+ }
519
+ }
520
+ // ─── Engram Catalog / Digest ───────────────────────────────────────────
521
+ /** 列出所有 engram(catalog 元数据) */
522
+ listEngrams() {
523
+ const result = [];
524
+ for (const entry of this.getIndex().entries.values()) {
525
+ result.push({
526
+ id: entry.id,
527
+ title: entry.title,
528
+ kind: entry.kind,
529
+ domainTags: entry.domainTags,
530
+ });
531
+ }
532
+ return result;
533
+ }
534
+ listByVerificationStatus(input) {
535
+ const list = Array.isArray(input) ? input : [input];
536
+ if (list.length === 0)
537
+ return [];
538
+ const statusSet = new Set(list);
539
+ const out = [];
540
+ for (const entry of this.getIndex().entries.values()) {
541
+ const current = entry.verificationStatus ?? "unverified";
542
+ if (!statusSet.has(current))
543
+ continue;
544
+ out.push(this.readEngram(entry.id));
545
+ }
546
+ return out;
547
+ }
548
+ /**
549
+ * 列出所有 engram 的完整 index 条目(含 slug / domainTags / mtime / contentHash)
550
+ *
551
+ * 用于 viewer / 外部工具渲染人类友好的标识(slug 而非完整 title 或 path)。
552
+ */
553
+ listEngramIndex() {
554
+ return Array.from(this.getIndex().entries.values());
555
+ }
556
+ /** 单条 catalog entry */
557
+ readCatalogEntry(stableId) {
558
+ if (!isStableEngramId(stableId))
559
+ return null;
560
+ const entry = this.getIndex().entries.get(stableId);
561
+ if (!entry)
562
+ return null;
563
+ return {
564
+ id: entry.id,
565
+ title: entry.title,
566
+ kind: entry.kind,
567
+ domainTags: entry.domainTags,
568
+ };
569
+ }
570
+ /** 单条 digest(从单文件 + synapse count 构建) */
571
+ readDigest(stableId) {
572
+ if (!this.exists(stableId))
573
+ return null;
574
+ const engram = this.readEngram(stableId);
575
+ return {
576
+ id: engram.id,
577
+ title: engram.title,
578
+ kind: engram.kind,
579
+ domainTags: engram.domainTags,
580
+ summary: engram.summary,
581
+ importance: engram.importance,
582
+ emotionalValence: engram.emotionalValence,
583
+ freshness: engram.freshness,
584
+ updatedAt: engram.updatedAt,
585
+ contentSize: engram.contentSize,
586
+ };
587
+ }
588
+ // ─── Synapse 操作 ──────────────────────────────────────────────────────
589
+ /**
590
+ * 读 engram 相关的所有 synapse(双向)。
591
+ *
592
+ * 返回 { outgoing, incoming }。
593
+ * 调用方按需用 `.outgoing` 或 `.incoming`。
594
+ */
595
+ readSynapses(stableId) {
596
+ return listSynapsesForEngram(this.config.rootPath, stableId);
597
+ }
598
+ /**
599
+ * 列出所有 synapse(per-edge 扫描)。
600
+ *
601
+ * 返回 `{ fromId, synapse }` 形状以兼容历史调用方;
602
+ * synapse 自身已携带 `from` 字段,fromId 就是 synapse.from。
603
+ */
604
+ collectAllSynapses() {
605
+ const all = collectAllSynapses(this.config.rootPath);
606
+ return all.map((synapse) => ({ fromId: synapse.from, synapse }));
607
+ }
608
+ /** 按 endpoints 查单条 synapse */
609
+ readSynapseByEndpoints(from, to, kind) {
610
+ return readSynapseByEndpoints(this.config.rootPath, from, to, kind);
611
+ }
612
+ /** 按 id 查 synapse */
613
+ readSynapseById(synapseId) {
614
+ return readSynapseById(this.config.rootPath, synapseId);
615
+ }
616
+ /** 创建 synapse(idempotent) */
617
+ createSynapse(input) {
618
+ return upsertSynapse(this.config.rootPath, {
619
+ from: input.from,
620
+ to: input.to,
621
+ kind: input.kind,
622
+ direction: input.direction ?? "directional",
623
+ weight: input.weight,
624
+ evidence: input.evidence,
625
+ createdBy: input.createdBy,
626
+ sourceSemantic: input.sourceSemantic,
627
+ targetSemantic: input.targetSemantic,
628
+ language: this.language,
629
+ });
630
+ }
631
+ /**
632
+ * 更新 synapse(走 upsert,合并 evidence)。
633
+ *
634
+ * 若 input.kind 与原 kind 不同:删除旧文件,以新 kind 重建
635
+ * (synapse id 由 from+to+kind+direction 派生,kind 变更必导致 id 变更)。
636
+ */
637
+ updateSynapse(fromId, synapseId, input) {
638
+ const existing = this.readSynapses(fromId);
639
+ const target = [...existing.outgoing, ...existing.incoming].find((s) => s.id === synapseId);
640
+ if (!target) {
641
+ throw new Error(`Synapse not found: ${synapseId} (from ${fromId})`);
642
+ }
643
+ const nextKind = input.kind ?? target.kind;
644
+ const nextDirection = input.direction ?? target.direction;
645
+ const nextWeight = input.weight ?? target.weight;
646
+ // kind 变化 → id 重算 → 必须先删旧文件再 upsert,否则会留下孤儿
647
+ if (input.kind !== undefined && input.kind !== target.kind) {
648
+ const oldPath = join(this.config.rootPath, synapseRelativePath(target.id, target.kind));
649
+ deleteSynapseFile(oldPath);
650
+ }
651
+ return upsertSynapse(this.config.rootPath, {
652
+ from: target.from,
653
+ to: target.to,
654
+ kind: nextKind,
655
+ direction: nextDirection,
656
+ weight: nextWeight,
657
+ evidence: input.evidence,
658
+ createdBy: target.createdBy,
659
+ sourceSemantic: target.sourceSemantic,
660
+ targetSemantic: target.targetSemantic,
661
+ resolutionState: target.resolutionState,
662
+ language: this.language,
663
+ });
664
+ }
665
+ /** 删除某条 synapse */
666
+ deleteSynapse(synapseId) {
667
+ const syn = this.readSynapseById(synapseId);
668
+ if (!syn)
669
+ return;
670
+ const path = join(this.config.rootPath, synapseRelativePath(syn.id, syn.kind));
671
+ deleteSynapseFile(path);
672
+ }
673
+ /** 级联删除触及 engram 的所有 synapse */
674
+ deleteSynapsesTouching(engramId) {
675
+ return deleteSynapsesTouching(this.config.rootPath, engramId);
676
+ }
677
+ /**
678
+ * 添加 outgoing synapse。
679
+ *
680
+ * 内部走 upsertSynapse,synapse.id 由 (from, to, kind, direction) 确定性计算,
681
+ * 调用方传入的 id 仅用作幂等性提示(实际以计算值为准)。
682
+ *
683
+ * @returns 实际落盘的 synapse(其 id 是计算值)
684
+ */
685
+ addOutgoingSynapse(fromId, synapse) {
686
+ return upsertSynapse(this.config.rootPath, {
687
+ from: fromId,
688
+ to: synapse.to,
689
+ kind: synapse.kind,
690
+ direction: synapse.direction,
691
+ weight: synapse.weight,
692
+ evidence: synapse.evidence.map((e) => ({
693
+ description: e.description,
694
+ source: e.source,
695
+ confidence: e.confidence,
696
+ addedBy: e.addedBy,
697
+ })),
698
+ createdBy: synapse.createdBy,
699
+ sourceSemantic: synapse.sourceSemantic,
700
+ targetSemantic: synapse.targetSemantic,
701
+ resolutionState: synapse.resolutionState,
702
+ language: this.language,
703
+ });
704
+ }
705
+ /**
706
+ * 删除 outgoing synapse。
707
+ *
708
+ * fromId 可从 synapse 自身解析,这里仅保留参数以维持调用接口稳定。
709
+ */
710
+ removeOutgoingSynapse(fromId, synapseId) {
711
+ const existing = this.readSynapses(fromId);
712
+ const target = [...existing.outgoing, ...existing.incoming].find((s) => s.id === synapseId);
713
+ if (!target)
714
+ return;
715
+ const path = join(this.config.rootPath, synapseRelativePath(target.id, target.kind));
716
+ deleteSynapseFile(path);
717
+ }
718
+ /**
719
+ * 更新 synapse 的 resolutionState(contradicts 专用)。
720
+ *
721
+ * per-edge 文件原地覆盖写。activeContradictionCount 是读取时派生,无需手动 bump 缓存。
722
+ */
723
+ updateSynapseResolution(fromId, synapseId, next) {
724
+ const existing = this.readSynapses(fromId);
725
+ const target = [...existing.outgoing, ...existing.incoming].find((s) => s.id === synapseId);
726
+ if (!target) {
727
+ throw new Error(`Synapse not found: ${synapseId} (from ${fromId})`);
728
+ }
729
+ const updated = {
730
+ ...target,
731
+ resolutionState: next,
732
+ updatedAt: now(),
733
+ };
734
+ const path = join(this.config.rootPath, synapseRelativePath(target.id, target.kind));
735
+ writeSynapseFile(path, updated, this.language);
736
+ }
737
+ /**
738
+ * 替换 synapse 的 evidence 数组。
739
+ */
740
+ replaceSynapseEvidence(fromId, synapseId, evidence) {
741
+ const existing = this.readSynapses(fromId);
742
+ const target = [...existing.outgoing, ...existing.incoming].find((s) => s.id === synapseId);
743
+ if (!target) {
744
+ throw new Error(`Synapse not found: ${synapseId} (from ${fromId})`);
745
+ }
746
+ const updated = {
747
+ ...target,
748
+ evidence: [...evidence],
749
+ updatedAt: now(),
750
+ };
751
+ const path = join(this.config.rootPath, synapseRelativePath(target.id, target.kind));
752
+ writeSynapseFile(path, updated, this.language);
753
+ }
754
+ /**
755
+ * 替换整条 synapse(用于触发式进化 weight boost)。
756
+ *
757
+ * 注意:next.id / next.from / next.to / next.kind 若与原有不一致,
758
+ * 同 (from, to, kind, direction) 确定性 id 会让 evidence/weight 变化落在同一条 edge 上,
759
+ * 调用方需保证 next 的 endpoints/kind 与原有一致(只改 weight / updatedAt 等)。
760
+ */
761
+ replaceSynapse(fromId, synapseId, next) {
762
+ const existing = this.readSynapses(fromId);
763
+ const target = [...existing.outgoing, ...existing.incoming].find((s) => s.id === synapseId);
764
+ if (!target) {
765
+ throw new Error(`Synapse not found: ${synapseId} (from ${fromId})`);
766
+ }
767
+ const path = join(this.config.rootPath, synapseRelativePath(target.id, target.kind));
768
+ writeSynapseFile(path, next, this.language);
769
+ }
770
+ // ─── Engram Stats / Lifecycle(不触发 version++)────────────────────────
771
+ /**
772
+ * 批量增减检索/强化统计(retrievalCount / effectiveRetrievals / failedUses /
773
+ * reinforcementScore / importance / 各时间戳)。
774
+ *
775
+ * 不触发 version++。
776
+ */
777
+ bumpRetrievalStats(id, delta) {
778
+ this.mutateFrontmatter(id, (fm) => ({
779
+ ...fm,
780
+ retrievalCount: Math.max(0, (fm.retrievalCount ?? 0) + (delta.retrievedDelta ?? 0)),
781
+ effectiveRetrievals: Math.max(0, (fm.effectiveRetrievals ?? 0) + (delta.effectiveDelta ?? 0)),
782
+ failedUses: Math.max(0, (fm.failedUses ?? 0) + (delta.failedDelta ?? 0)),
783
+ reinforcementScore: (fm.reinforcementScore ?? 0) + (delta.reinforcementDelta ?? 0),
784
+ importance: clamp01((fm.importance ?? DEFAULT_IMPORTANCE) + (delta.importanceDelta ?? 0)),
785
+ lastRetrievedAt: delta.lastRetrievedAt ?? fm.lastRetrievedAt,
786
+ lastEffectiveAt: delta.lastEffectiveAt ?? fm.lastEffectiveAt,
787
+ ...(delta.lastRetrievalScore !== undefined
788
+ ? { lastRetrievalScore: clamp01(delta.lastRetrievalScore) }
789
+ : {}),
790
+ }));
791
+ }
792
+ /**
793
+ * 更新 status / freshness(不触发 version++)。
794
+ *
795
+ * 默认按 lastEffectiveAt + decayHalfLifeDays 派生 freshness,
796
+ * 但允许通过 forcedFreshness 显式覆盖(用于 lifecycle 工具强制切换)。
797
+ * 一旦再触发 effective 检索(lastEffectiveAt 更新),forcedFreshness 会被清除。
798
+ */
799
+ updateLifecycle(id, status, freshness) {
800
+ if (status === undefined && freshness === undefined)
801
+ return;
802
+ this.mutateFrontmatter(id, (fm) => ({
803
+ ...fm,
804
+ ...(status !== undefined ? { status } : {}),
805
+ ...(freshness !== undefined ? { forcedFreshness: freshness } : {}),
806
+ }));
807
+ }
808
+ /**
809
+ * 更新多维重要性向量(不触发 version++)。
810
+ *
811
+ * 同时把 fm.importance 设为 vector.composite,保证检索公式读到最新综合分。
812
+ */
813
+ updateImportanceVector(id, input) {
814
+ this.mutateFrontmatter(id, (fm) => ({
815
+ ...fm,
816
+ importance: input.vector.composite,
817
+ importanceVector: input.vector,
818
+ }));
819
+ }
820
+ /**
821
+ * 更新 verificationStatus(不触发 version++)。
822
+ */
823
+ updateVerificationStatus(id, status) {
824
+ this.mutateFrontmatter(id, (fm) => ({ ...fm, verificationStatus: status }));
825
+ }
826
+ /**
827
+ * 低层 frontmatter 修改:不触发 version++,不改 slug,不改 path。
828
+ *
829
+ * 供 stats / lifecycle / verification 等频次高的写入使用,避免频繁 version++。
830
+ */
831
+ mutateFrontmatter(stableId, mutator) {
832
+ const relativePath = this.resolvePath(stableId);
833
+ if (!relativePath)
834
+ return;
835
+ const absolutePath = join(this.config.rootPath, relativePath);
836
+ if (!existsSync(absolutePath))
837
+ return;
838
+ const oldFile = readEngramFile(absolutePath);
839
+ const newFrontmatter = mutator(oldFile.frontmatter);
840
+ const newFile = {
841
+ frontmatter: newFrontmatter,
842
+ content: oldFile.content,
843
+ };
844
+ writeEngramFile(absolutePath, newFile, this.language);
845
+ // 更新 index 的 mtime(其他字段未变,无需重建整条 entry)
846
+ const stat = statSync(absolutePath);
847
+ const contentHash = newFrontmatter.contentHash ?? computeContentHash(oldFile.content);
848
+ this.updateIndexEntry(buildIndexEntryFromFrontmatter({
849
+ relativePath,
850
+ frontmatter: newFrontmatter,
851
+ mtime: stat.mtimeMs,
852
+ contentHash,
853
+ }));
854
+ }
855
+ // ─── Doctor 自愈扫描 ───────────────────────────────────────────────────
856
+ /**
857
+ * Doctor 全量扫描 + 自愈修复。
858
+ *
859
+ * 修复:
860
+ * - moved_file:index 里 id 存在但 path 不匹配 → 更新 path
861
+ * - title_changed:重新 slugify + rename(若 slug 未锁定且无冲突)
862
+ * - missing_file:从 index 移除 + 标记相关 synapse 为 dangling
863
+ * - duplicate_id:警告(人工裁决)
864
+ * - orphan_markdown:提示注册为 engram
865
+ * - dangling_synapse:报告(人工清理)
866
+ */
867
+ runDoctor(options = {}) {
868
+ const startedAt = now();
869
+ const issues = [];
870
+ const fixes = [];
871
+ const pendingManualReview = [];
872
+ // 1. 全量重建 fresh index
873
+ const freshIndex = rebuildEngramIndex(this.config.rootPath, (orphanPath) => {
874
+ issues.push({
875
+ kind: "orphan_markdown",
876
+ path: orphanPath,
877
+ message: `Markdown file without frontmatter: ${orphanPath} (either delete it or add frontmatter with a stable id)`,
878
+ autoFixed: false,
879
+ });
880
+ pendingManualReview.push(issues[issues.length - 1]);
881
+ });
882
+ // 2. 比对旧 index 检测 moved_file / title_changed / missing_file
883
+ const oldIndex = readEngramIndex(this.config.rootPath);
884
+ const freshIds = new Set(freshIndex.entries.keys());
885
+ for (const [oldId, oldEntry] of oldIndex.entries) {
886
+ if (!freshIds.has(oldId)) {
887
+ // id 在旧 index 但不在 fresh → 文件被删除或被移动到不同 path(且 frontmatter id 保留)
888
+ // 进一步检查是否在磁盘上能找到这个 id
889
+ const foundInFresh = Array.from(freshIndex.entries.values()).find((e) => e.id === oldId);
890
+ if (foundInFresh) {
891
+ // moved:id 还在,但 path 变了
892
+ if (foundInFresh.path !== oldEntry.path) {
893
+ const fix = {
894
+ kind: "moved_file",
895
+ stableId: oldId,
896
+ path: foundInFresh.path,
897
+ message: `File moved: ${oldEntry.path} → ${foundInFresh.path} (index updated to new path)`,
898
+ autoFixed: true,
899
+ };
900
+ fixes.push(fix);
901
+ }
902
+ }
903
+ else {
904
+ // missing:id 完全找不到
905
+ const issue = {
906
+ kind: "missing_file",
907
+ stableId: oldId,
908
+ path: oldEntry.path,
909
+ message: `Index references ${oldId} at ${oldEntry.path} but the file is gone from disk (index entry cleared)`,
910
+ autoFixed: true,
911
+ };
912
+ fixes.push(issue);
913
+ // 标记相关 synapse dangling
914
+ const touching = listSynapsesForEngram(this.config.rootPath, oldId);
915
+ if (touching.outgoing.length + touching.incoming.length > 0) {
916
+ pendingManualReview.push({
917
+ kind: "dangling_synapse",
918
+ stableId: oldId,
919
+ message: `Engram ${oldId} was deleted but ${touching.outgoing.length + touching.incoming.length} synapse(s) still reference it (clean up manually or restore the engram)`,
920
+ autoFixed: false,
921
+ });
922
+ }
923
+ }
924
+ }
925
+ else {
926
+ // id 在 fresh,检查 path / title / slug 变化
927
+ const freshEntry = freshIndex.entries.get(oldId);
928
+ // 1) path 变化(通过 doctor 之外的途径,如人类 mv + 手动改 index)
929
+ if (freshEntry.path !== oldEntry.path) {
930
+ fixes.push({
931
+ kind: "moved_file",
932
+ stableId: oldId,
933
+ path: freshEntry.path,
934
+ message: `Path updated: ${oldEntry.path} → ${freshEntry.path}`,
935
+ autoFixed: true,
936
+ });
937
+ }
938
+ // 2) title 变 → 重新 slugify + rename(slug 未锁定时)
939
+ if (freshEntry.title !== oldEntry.title && !freshEntry.slugLocked) {
940
+ const newSlug = slugify(freshEntry.title);
941
+ if (newSlug !== freshEntry.slug) {
942
+ const newPath = this.rebuildPath(freshEntry.path, newSlug);
943
+ const newAbs = join(this.config.rootPath, newPath);
944
+ if (!existsSync(newAbs)) {
945
+ renameEngramFile(join(this.config.rootPath, freshEntry.path), newAbs);
946
+ fixes.push({
947
+ kind: "title_changed",
948
+ stableId: oldId,
949
+ path: newPath,
950
+ message: `Title changed, file renamed to match new slug: ${freshEntry.path} → ${newPath}`,
951
+ autoFixed: true,
952
+ });
953
+ }
954
+ else {
955
+ const issue = {
956
+ kind: "slug_conflict",
957
+ stableId: oldId,
958
+ path: newPath,
959
+ message: `New slug "${newSlug}" collides with an existing file; kept the old slug. Rename one of the files manually or change a title.`,
960
+ autoFixed: false,
961
+ };
962
+ issues.push(issue);
963
+ pendingManualReview.push(issue);
964
+ }
965
+ }
966
+ }
967
+ }
968
+ }
969
+ // 3. 写回 fresh index(同步更新 mtime,避免下次 getIndex 误判)
970
+ this.persistIndex(freshIndex);
971
+ // 4. 检测 dangling synapse(from/to 不在 index)
972
+ const allSynapses = collectAllSynapses(this.config.rootPath);
973
+ for (const syn of allSynapses) {
974
+ if (!freshIndex.entries.has(syn.from)) {
975
+ pendingManualReview.push({
976
+ kind: "dangling_synapse",
977
+ stableId: syn.from,
978
+ message: `Synapse ${syn.id} references .from="${syn.from}" which no longer exists`,
979
+ autoFixed: false,
980
+ });
981
+ }
982
+ if (!freshIndex.entries.has(syn.to)) {
983
+ pendingManualReview.push({
984
+ kind: "dangling_synapse",
985
+ stableId: syn.to,
986
+ message: `Synapse ${syn.id} references .to="${syn.to}" which no longer exists`,
987
+ autoFixed: false,
988
+ });
989
+ }
990
+ }
991
+ return {
992
+ startedAt,
993
+ finishedAt: now(),
994
+ totalEngrams: freshIndex.entries.size,
995
+ totalSynapses: allSynapses.length,
996
+ issues,
997
+ fixes,
998
+ pendingManualReview,
999
+ };
1000
+ }
1001
+ // ─── Path Tree(渐进式披露) ───────────────────────────────────────────
1002
+ /**
1003
+ * 构建目录树(用于 viewer 和 engram_list_paths 工具)。
1004
+ *
1005
+ * 每节点:{ path, engramCount, children }
1006
+ * engramCount = 该目录及其所有子目录的 engram 总数(累积)
1007
+ */
1008
+ listPathTree() {
1009
+ const root = { path: "/", engramCount: 0, children: [] };
1010
+ const nodeMap = new Map();
1011
+ nodeMap.set("", root);
1012
+ for (const entry of this.getIndex().entries.values()) {
1013
+ // entry.path 形如 "<domain1>/<domain2>/.../<slug>.md"
1014
+ // 最后一段是文件名,跳过;之前每段都是目录,需逐级累加(包含 root)
1015
+ const segments = entry.path.split("/");
1016
+ const dirSegments = segments.slice(0, -1);
1017
+ root.engramCount++;
1018
+ let currentPath = "";
1019
+ let parentNode = root;
1020
+ for (const seg of dirSegments) {
1021
+ currentPath = currentPath.length === 0 ? seg : `${currentPath}/${seg}`;
1022
+ let node = nodeMap.get(currentPath);
1023
+ if (!node) {
1024
+ node = {
1025
+ path: currentPath,
1026
+ engramCount: 0,
1027
+ children: [],
1028
+ };
1029
+ nodeMap.set(currentPath, node);
1030
+ parentNode.children.push(node);
1031
+ }
1032
+ node.engramCount++;
1033
+ parentNode = node;
1034
+ }
1035
+ }
1036
+ return root;
1037
+ }
1038
+ // ─── Internal helpers ──────────────────────────────────────────────────
1039
+ /**
1040
+ * 把单文件 + synapse 统计组装成 Engram 对象
1041
+ */
1042
+ assembleEngram(file, relativePath) {
1043
+ const fm = file.frontmatter;
1044
+ const synapses = listSynapsesForEngram(this.config.rootPath, fm.id);
1045
+ const seenSynapseIds = new Set();
1046
+ const activeContradictionCount = [
1047
+ ...synapses.outgoing,
1048
+ ...synapses.incoming,
1049
+ ].filter((s) => {
1050
+ if (s.kind !== "contradicts")
1051
+ return false;
1052
+ // bidirectional 会被同时计入 outgoing/incoming,按 id 去重
1053
+ if (seenSynapseIds.has(s.id))
1054
+ return false;
1055
+ seenSynapseIds.add(s.id);
1056
+ const status = s.resolutionState?.status;
1057
+ // 无 resolutionState / pending / escalated 都视为活跃矛盾
1058
+ return (status === undefined || status === "pending" || status === "escalated");
1059
+ }).length;
1060
+ const createdAt = fm.createdAt ?? now();
1061
+ const lastEffectiveAtForFreshness = fm.lastEffectiveAt ?? createdAt;
1062
+ const decayHalfLifeDays = fm.decayHalfLifeDays === undefined
1063
+ ? DEFAULT_DECAY_HALF_LIFE_DAYS
1064
+ : fm.decayHalfLifeDays;
1065
+ const status = fm.status ?? "active";
1066
+ const freshness = status === "forgotten"
1067
+ ? "forgotten"
1068
+ : (fm.forcedFreshness ??
1069
+ this.computeFreshness(lastEffectiveAtForFreshness, decayHalfLifeDays));
1070
+ // domainTags:frontmatter 锁定则用之,否则从 path 推断
1071
+ const hasExplicitDomainTags = Array.isArray(fm.domainTags) && fm.domainTags.length > 0;
1072
+ const domainTags = hasExplicitDomainTags
1073
+ ? [...fm.domainTags]
1074
+ : inferDomainTagsFromPath(relativePath);
1075
+ return {
1076
+ id: fm.id,
1077
+ title: fm.title,
1078
+ contentHash: fm.contentHash ?? computeContentHash(file.content),
1079
+ kind: fm.kind,
1080
+ kinds: fm.kinds ?? [fm.kind],
1081
+ domainTags,
1082
+ content: file.content,
1083
+ summary: fm.summary ?? fm.title,
1084
+ contentSize: fm.contentSize ?? computeContentSize(file.content),
1085
+ createdBy: fm.createdBy,
1086
+ createdAt,
1087
+ updatedBy: fm.updatedBy,
1088
+ updatedAt: fm.updatedAt ?? now(),
1089
+ encodingContext: fm.encodingContext,
1090
+ version: fm.version ?? 1,
1091
+ importance: fm.importance ?? DEFAULT_IMPORTANCE,
1092
+ confidence: fm.confidence ?? DEFAULT_CONFIDENCE_BY_SOURCE.firsthand,
1093
+ emotionalValence: fm.emotionalValence ?? "neutral",
1094
+ sourceType: fm.sourceType ?? "firsthand",
1095
+ evidenceCount: fm.evidenceCount ?? 0,
1096
+ importanceVector: fm
1097
+ .importanceVector,
1098
+ retrievalCount: fm.retrievalCount ?? 0,
1099
+ effectiveRetrievals: fm.effectiveRetrievals ?? 0,
1100
+ failedUses: fm.failedUses ?? 0,
1101
+ lastRetrievedAt: fm.lastRetrievedAt,
1102
+ lastEffectiveAt: fm.lastEffectiveAt,
1103
+ reinforcementScore: fm.reinforcementScore ?? 0,
1104
+ decayHalfLifeDays,
1105
+ lastRetrievalScore: fm.lastRetrievalScore ?? 0.5,
1106
+ outgoingSynapseCount: synapses.outgoing.length,
1107
+ incomingSynapseCount: synapses.incoming.length,
1108
+ activeContradictionCount,
1109
+ freshness,
1110
+ status,
1111
+ contextTags: fm.contextTags ?? [],
1112
+ visibility: fm.visibility ?? "public",
1113
+ verificationStatus: fm.verificationStatus ?? "unverified",
1114
+ perspective: fm.perspective,
1115
+ };
1116
+ }
1117
+ computeFreshness(lastEffectiveAt, decayHalfLifeDays) {
1118
+ if (decayHalfLifeDays === null)
1119
+ return "fresh";
1120
+ const ageDays = (Date.now() - new Date(lastEffectiveAt).getTime()) / 86400000;
1121
+ if (ageDays < decayHalfLifeDays)
1122
+ return "fresh";
1123
+ if (ageDays < decayHalfLifeDays * 2)
1124
+ return "aging";
1125
+ if (ageDays < decayHalfLifeDays * 4)
1126
+ return "stale";
1127
+ return "forgotten";
1128
+ }
1129
+ /** 把绝对路径转回相对路径(用于 doctor 报告) */
1130
+ relativePath(absolutePath) {
1131
+ return relative(this.config.rootPath, absolutePath).replaceAll(sep, "/");
1132
+ }
1133
+ /**
1134
+ * 把所有 engram + synapse 文件迁移到目标语言格式。
1135
+ *
1136
+ * 行为:
1137
+ * - 遍历 dataRoot 下所有 engram .md 文件 + synapses 子目录的 .yaml
1138
+ * - 每个文件:parse 归一化,按目标 language 重新 serialize,写回
1139
+ * - 文件已是目标格式则跳过
1140
+ * - 损坏文件(parse 失败)计入 errors,不阻塞其他文件
1141
+ *
1142
+ * 返回:迁移统计 { migrated, skipped, errors }
1143
+ */
1144
+ migrateFormat(targetLanguage) {
1145
+ const result = { migrated: 0, skipped: 0, errors: [] };
1146
+ // 1. 迁移 engram 文件(所有非 .co-engram/synapses 目录下的 .md)
1147
+ const index = this.getIndex();
1148
+ for (const entry of index.entries.values()) {
1149
+ const absolutePath = join(this.config.rootPath, entry.path);
1150
+ if (!existsSync(absolutePath)) {
1151
+ result.errors.push(`missing file: ${entry.path}`);
1152
+ continue;
1153
+ }
1154
+ try {
1155
+ const raw = readFileSync(absolutePath, "utf8");
1156
+ const diskLang = detectEngramFileLanguage(raw);
1157
+ if (diskLang === targetLanguage) {
1158
+ result.skipped++;
1159
+ continue;
1160
+ }
1161
+ const file = parseEngramFile(raw);
1162
+ writeEngramFile(absolutePath, file, targetLanguage);
1163
+ result.migrated++;
1164
+ }
1165
+ catch (err) {
1166
+ result.errors.push(`engram ${entry.path}: ${err instanceof Error ? err.message : String(err)}`);
1167
+ }
1168
+ }
1169
+ // 2. 迁移 synapse 文件
1170
+ const synapseRoot = join(this.config.rootPath, SYNAPSES_DIR);
1171
+ if (existsSync(synapseRoot)) {
1172
+ const visitSynapseDir = (kindDir) => {
1173
+ for (const entry of readdirSync(kindDir, { withFileTypes: true })) {
1174
+ if (entry.isDirectory()) {
1175
+ visitSynapseDir(join(kindDir, entry.name));
1176
+ continue;
1177
+ }
1178
+ if (!entry.isFile())
1179
+ continue;
1180
+ if (!entry.name.endsWith(".yaml") && !entry.name.endsWith(".yml"))
1181
+ continue;
1182
+ const absolutePath = join(kindDir, entry.name);
1183
+ try {
1184
+ const raw = readFileSync(absolutePath, "utf8");
1185
+ const parsed = parseYaml(raw);
1186
+ if (!parsed || typeof parsed !== "object") {
1187
+ result.errors.push(`synapse ${entry.name}: not an object`);
1188
+ continue;
1189
+ }
1190
+ // 已是目标语言则跳过(检测标记字段或语言标记)
1191
+ const hasLangMarker = "__语言" in parsed
1192
+ ? parsed["__语言"] === "zh"
1193
+ : "__lang" in parsed
1194
+ ? parsed["__lang"] === "en"
1195
+ : false;
1196
+ if (hasLangMarker) {
1197
+ const currentLang = "__语言" in parsed ? "zh" : "en";
1198
+ if (currentLang === targetLanguage) {
1199
+ result.skipped++;
1200
+ continue;
1201
+ }
1202
+ }
1203
+ const file = parseSynapseFile(raw);
1204
+ writeSynapseFile(absolutePath, file, targetLanguage);
1205
+ result.migrated++;
1206
+ }
1207
+ catch (err) {
1208
+ result.errors.push(`synapse ${entry.name}: ${err instanceof Error ? err.message : String(err)}`);
1209
+ }
1210
+ }
1211
+ };
1212
+ visitSynapseDir(synapseRoot);
1213
+ }
1214
+ // 3. 重建索引(路径未变,但 mtime 变了)
1215
+ if (result.migrated > 0) {
1216
+ this.persistIndex(rebuildEngramIndex(this.config.rootPath));
1217
+ }
1218
+ return result;
1219
+ }
1220
+ }
1221
+ //# sourceMappingURL=repository.js.map