@co-engram/core 0.1.2 → 0.1.3

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 (227) hide show
  1. package/dist/bootstrap/classify.d.ts +67 -0
  2. package/dist/bootstrap/classify.d.ts.map +1 -0
  3. package/dist/bootstrap/classify.js +119 -0
  4. package/dist/bootstrap/classify.js.map +1 -0
  5. package/dist/bootstrap/index.d.ts +89 -0
  6. package/dist/bootstrap/index.d.ts.map +1 -0
  7. package/dist/bootstrap/index.js +158 -0
  8. package/dist/bootstrap/index.js.map +1 -0
  9. package/dist/concepts/dictionary.d.ts +260 -0
  10. package/dist/concepts/dictionary.d.ts.map +1 -0
  11. package/dist/concepts/dictionary.js +253 -0
  12. package/dist/concepts/dictionary.js.map +1 -0
  13. package/dist/concepts/index.d.ts +11 -0
  14. package/dist/concepts/index.d.ts.map +1 -0
  15. package/dist/concepts/index.js +10 -0
  16. package/dist/concepts/index.js.map +1 -0
  17. package/dist/concepts/types.d.ts +65 -0
  18. package/dist/concepts/types.d.ts.map +1 -0
  19. package/dist/concepts/types.js +15 -0
  20. package/dist/concepts/types.js.map +1 -0
  21. package/dist/config/defaults.d.ts +32 -3
  22. package/dist/config/defaults.d.ts.map +1 -1
  23. package/dist/config/defaults.js +52 -2
  24. package/dist/config/defaults.js.map +1 -1
  25. package/dist/config/index.d.ts +18 -1
  26. package/dist/config/index.d.ts.map +1 -1
  27. package/dist/config/index.js +75 -3
  28. package/dist/config/index.js.map +1 -1
  29. package/dist/config/loader.d.ts +10 -0
  30. package/dist/config/loader.d.ts.map +1 -0
  31. package/dist/config/loader.js +10 -0
  32. package/dist/config/loader.js.map +1 -0
  33. package/dist/config/types.d.ts +97 -1
  34. package/dist/config/types.d.ts.map +1 -1
  35. package/dist/contradiction/auto-degrade.d.ts +11 -2
  36. package/dist/contradiction/auto-degrade.d.ts.map +1 -1
  37. package/dist/contradiction/auto-degrade.js +22 -0
  38. package/dist/contradiction/auto-degrade.js.map +1 -1
  39. package/dist/contradiction/resolver.d.ts.map +1 -1
  40. package/dist/contradiction/resolver.js +7 -1
  41. package/dist/contradiction/resolver.js.map +1 -1
  42. package/dist/dreaming/index.d.ts +1 -0
  43. package/dist/dreaming/index.d.ts.map +1 -1
  44. package/dist/dreaming/index.js +1 -0
  45. package/dist/dreaming/index.js.map +1 -1
  46. package/dist/dreaming/llm-pattern-abstraction.d.ts +31 -0
  47. package/dist/dreaming/llm-pattern-abstraction.d.ts.map +1 -0
  48. package/dist/dreaming/llm-pattern-abstraction.js +70 -0
  49. package/dist/dreaming/llm-pattern-abstraction.js.map +1 -0
  50. package/dist/dreaming/rem.d.ts.map +1 -1
  51. package/dist/dreaming/rem.js +1 -0
  52. package/dist/dreaming/rem.js.map +1 -1
  53. package/dist/dreaming/scheduler.d.ts +13 -0
  54. package/dist/dreaming/scheduler.d.ts.map +1 -1
  55. package/dist/dreaming/scheduler.js +14 -2
  56. package/dist/dreaming/scheduler.js.map +1 -1
  57. package/dist/evolution/triggered.d.ts.map +1 -1
  58. package/dist/evolution/triggered.js +1 -0
  59. package/dist/evolution/triggered.js.map +1 -1
  60. package/dist/generative/hypothesis.d.ts.map +1 -1
  61. package/dist/generative/hypothesis.js +1 -0
  62. package/dist/generative/hypothesis.js.map +1 -1
  63. package/dist/i18n/en.d.ts.map +1 -1
  64. package/dist/i18n/en.js +1262 -32
  65. package/dist/i18n/en.js.map +1 -1
  66. package/dist/i18n/index.d.ts +34 -1
  67. package/dist/i18n/index.d.ts.map +1 -1
  68. package/dist/i18n/index.js +36 -7
  69. package/dist/i18n/index.js.map +1 -1
  70. package/dist/i18n/zh.d.ts +698 -32
  71. package/dist/i18n/zh.d.ts.map +1 -1
  72. package/dist/i18n/zh.js +1267 -33
  73. package/dist/i18n/zh.js.map +1 -1
  74. package/dist/index/graph-builder.js +3 -3
  75. package/dist/index/graph-builder.js.map +1 -1
  76. package/dist/index.d.ts +3 -0
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +3 -0
  79. package/dist/index.js.map +1 -1
  80. package/dist/learning/loop.d.ts +9 -0
  81. package/dist/learning/loop.d.ts.map +1 -1
  82. package/dist/learning/loop.js +42 -1
  83. package/dist/learning/loop.js.map +1 -1
  84. package/dist/maintenance/types.d.ts +11 -0
  85. package/dist/maintenance/types.d.ts.map +1 -1
  86. package/dist/maintenance/types.js.map +1 -1
  87. package/dist/merge/synapse-merger.js +6 -0
  88. package/dist/merge/synapse-merger.js.map +1 -1
  89. package/dist/merge-driver.cjs +64 -5
  90. package/dist/observability/audit-log.d.ts +7 -1
  91. package/dist/observability/audit-log.d.ts.map +1 -1
  92. package/dist/observability/audit-log.js.map +1 -1
  93. package/dist/observability/necessity-evaluator.d.ts +29 -0
  94. package/dist/observability/necessity-evaluator.d.ts.map +1 -1
  95. package/dist/observability/necessity-evaluator.js +240 -13
  96. package/dist/observability/necessity-evaluator.js.map +1 -1
  97. package/dist/observability/proposal-engine.d.ts.map +1 -1
  98. package/dist/observability/proposal-engine.js +12 -0
  99. package/dist/observability/proposal-engine.js.map +1 -1
  100. package/dist/observability/runtime-description-check.d.ts +55 -0
  101. package/dist/observability/runtime-description-check.d.ts.map +1 -0
  102. package/dist/observability/runtime-description-check.js +63 -0
  103. package/dist/observability/runtime-description-check.js.map +1 -0
  104. package/dist/prompt-signals/cache.d.ts +73 -0
  105. package/dist/prompt-signals/cache.d.ts.map +1 -1
  106. package/dist/prompt-signals/cache.js +102 -0
  107. package/dist/prompt-signals/cache.js.map +1 -1
  108. package/dist/prompt-signals/event-bus.d.ts +82 -0
  109. package/dist/prompt-signals/event-bus.d.ts.map +1 -0
  110. package/dist/prompt-signals/event-bus.js +105 -0
  111. package/dist/prompt-signals/event-bus.js.map +1 -0
  112. package/dist/prompt-signals/index.d.ts +2 -1
  113. package/dist/prompt-signals/index.d.ts.map +1 -1
  114. package/dist/prompt-signals/index.js +2 -1
  115. package/dist/prompt-signals/index.js.map +1 -1
  116. package/dist/reinforcement/ltp.d.ts +15 -1
  117. package/dist/reinforcement/ltp.d.ts.map +1 -1
  118. package/dist/reinforcement/ltp.js +24 -5
  119. package/dist/reinforcement/ltp.js.map +1 -1
  120. package/dist/reinforcement/related.d.ts +31 -2
  121. package/dist/reinforcement/related.d.ts.map +1 -1
  122. package/dist/reinforcement/related.js +39 -3
  123. package/dist/reinforcement/related.js.map +1 -1
  124. package/dist/retrieval/filter.d.ts.map +1 -1
  125. package/dist/retrieval/filter.js +7 -0
  126. package/dist/retrieval/filter.js.map +1 -1
  127. package/dist/retrieval/fts.d.ts +6 -5
  128. package/dist/retrieval/fts.d.ts.map +1 -1
  129. package/dist/retrieval/fts.js +74 -22
  130. package/dist/retrieval/fts.js.map +1 -1
  131. package/dist/status/index.d.ts +7 -0
  132. package/dist/status/index.d.ts.map +1 -0
  133. package/dist/status/index.js +7 -0
  134. package/dist/status/index.js.map +1 -0
  135. package/dist/status/status.d.ts +132 -0
  136. package/dist/status/status.d.ts.map +1 -0
  137. package/dist/status/status.js +437 -0
  138. package/dist/status/status.js.map +1 -0
  139. package/dist/storage/engram-store.d.ts.map +1 -1
  140. package/dist/storage/engram-store.js +17 -2
  141. package/dist/storage/engram-store.js.map +1 -1
  142. package/dist/storage/git.d.ts +168 -0
  143. package/dist/storage/git.d.ts.map +1 -1
  144. package/dist/storage/git.js +616 -33
  145. package/dist/storage/git.js.map +1 -1
  146. package/dist/storage/index.d.ts +1 -0
  147. package/dist/storage/index.d.ts.map +1 -1
  148. package/dist/storage/index.js +1 -0
  149. package/dist/storage/index.js.map +1 -1
  150. package/dist/storage/infra-doctor.d.ts +42 -0
  151. package/dist/storage/infra-doctor.d.ts.map +1 -0
  152. package/dist/storage/infra-doctor.js +92 -0
  153. package/dist/storage/infra-doctor.js.map +1 -0
  154. package/dist/storage/obsidian-links.d.ts +73 -0
  155. package/dist/storage/obsidian-links.d.ts.map +1 -0
  156. package/dist/storage/obsidian-links.js +177 -0
  157. package/dist/storage/obsidian-links.js.map +1 -0
  158. package/dist/storage/path.d.ts +24 -0
  159. package/dist/storage/path.d.ts.map +1 -1
  160. package/dist/storage/path.js +53 -0
  161. package/dist/storage/path.js.map +1 -1
  162. package/dist/storage/repository.d.ts +37 -1
  163. package/dist/storage/repository.d.ts.map +1 -1
  164. package/dist/storage/repository.js +235 -17
  165. package/dist/storage/repository.js.map +1 -1
  166. package/dist/storage/synapse-store.d.ts +7 -1
  167. package/dist/storage/synapse-store.d.ts.map +1 -1
  168. package/dist/storage/synapse-store.js +8 -0
  169. package/dist/storage/synapse-store.js.map +1 -1
  170. package/dist/tools/audit-query-tool.d.ts +53 -0
  171. package/dist/tools/audit-query-tool.d.ts.map +1 -0
  172. package/dist/tools/audit-query-tool.js +123 -0
  173. package/dist/tools/audit-query-tool.js.map +1 -0
  174. package/dist/tools/doctor-tools.d.ts +5 -0
  175. package/dist/tools/doctor-tools.d.ts.map +1 -1
  176. package/dist/tools/doctor-tools.js +11 -3
  177. package/dist/tools/doctor-tools.js.map +1 -1
  178. package/dist/tools/engram-tools.d.ts +13 -0
  179. package/dist/tools/engram-tools.d.ts.map +1 -1
  180. package/dist/tools/engram-tools.js +72 -8
  181. package/dist/tools/engram-tools.js.map +1 -1
  182. package/dist/tools/index.d.ts +3 -0
  183. package/dist/tools/index.d.ts.map +1 -1
  184. package/dist/tools/index.js +3 -0
  185. package/dist/tools/index.js.map +1 -1
  186. package/dist/tools/llm-descriptions.d.ts +28 -28
  187. package/dist/tools/llm-descriptions.d.ts.map +1 -1
  188. package/dist/tools/llm-descriptions.js +56 -489
  189. package/dist/tools/llm-descriptions.js.map +1 -1
  190. package/dist/tools/normalization.d.ts +43 -0
  191. package/dist/tools/normalization.d.ts.map +1 -0
  192. package/dist/tools/normalization.js +68 -0
  193. package/dist/tools/normalization.js.map +1 -0
  194. package/dist/tools/registry.d.ts.map +1 -1
  195. package/dist/tools/registry.js +6 -0
  196. package/dist/tools/registry.js.map +1 -1
  197. package/dist/tools/schemas.d.ts +67 -11
  198. package/dist/tools/schemas.d.ts.map +1 -1
  199. package/dist/tools/schemas.js +61 -6
  200. package/dist/tools/schemas.js.map +1 -1
  201. package/dist/tools/skill-tools.js +1 -1
  202. package/dist/tools/skill-tools.js.map +1 -1
  203. package/dist/tools/synapse-tools.d.ts.map +1 -1
  204. package/dist/tools/synapse-tools.js +1 -0
  205. package/dist/tools/synapse-tools.js.map +1 -1
  206. package/dist/tools/sync-tools.d.ts +102 -0
  207. package/dist/tools/sync-tools.d.ts.map +1 -0
  208. package/dist/tools/sync-tools.js +309 -0
  209. package/dist/tools/sync-tools.js.map +1 -0
  210. package/dist/tools/synthesize-tools.d.ts +79 -0
  211. package/dist/tools/synthesize-tools.d.ts.map +1 -0
  212. package/dist/tools/synthesize-tools.js +297 -0
  213. package/dist/tools/synthesize-tools.js.map +1 -0
  214. package/dist/tools/tool-profile.d.ts +68 -0
  215. package/dist/tools/tool-profile.d.ts.map +1 -0
  216. package/dist/tools/tool-profile.js +174 -0
  217. package/dist/tools/tool-profile.js.map +1 -0
  218. package/dist/tools/tool.d.ts +17 -0
  219. package/dist/tools/tool.d.ts.map +1 -1
  220. package/dist/tools/tool.js.map +1 -1
  221. package/dist/types/disclosure.d.ts +7 -0
  222. package/dist/types/disclosure.d.ts.map +1 -1
  223. package/dist/types/repository-types.d.ts +17 -1
  224. package/dist/types/repository-types.d.ts.map +1 -1
  225. package/dist/types/synapse.d.ts +19 -1
  226. package/dist/types/synapse.d.ts.map +1 -1
  227. package/package.json +9 -9
@@ -90,6 +90,18 @@ export declare class EngramRepository {
90
90
  private updateIndexEntry;
91
91
  /** 从 index 删除一条并写盘 */
92
92
  private deleteIndexEntry;
93
+ /**
94
+ * 清理索引中所有指向 relativePath 的孤儿 entry,返回清理数量。
95
+ *
96
+ * 触发场景:外部(用户 rm / git 操作 / 进程异常)删除 engram 文件后,
97
+ * engram-index.json 仍保留旧 ULID 的 entry。下一次 createEngram 写入
98
+ * 同 path 的新 ULID 会留下永不消失的孤儿,导致 listEngrams / viewer
99
+ * 显示"重影"(同一文件被两个 ULID 引用)。
100
+ *
101
+ * 同 path 出现多条 entry 本身就是不变量破坏 —— 此处发现即清。多数调用
102
+ * 不会命中,N=0 时是 O(|index|) 扫一次,可接受。
103
+ */
104
+ private purgeStaleIndexEntriesForPath;
93
105
  /**
94
106
  * 启动对 engram-index.json 的 fs.watch 监听。
95
107
  *
@@ -134,7 +146,13 @@ export declare class EngramRepository {
134
146
  createEngram(input: EngramCreateInput & {
135
147
  pathHint?: string;
136
148
  }): Engram;
137
- /** 默认路径:{domainTags.join('/')/}{slug}.md */
149
+ /**
150
+ * 默认路径:{domainTags.join('/')/}{slug}.md
151
+ *
152
+ * private engram 自动落 `private/` 子目录(被 .gitignore 隔离出团队仓库)。
153
+ * 注意:本函数只用于「无 pathHint」场景;调用方传 pathHint 时直接尊重用户路径,
154
+ * 不会自动加 private 前缀(避免 `private/private/...` 双前缀)。
155
+ */
138
156
  private deriveDefaultPath;
139
157
  /**
140
158
  * 读取完整 Engram(单文件 + 统计)
@@ -154,8 +172,20 @@ export declare class EngramRepository {
154
172
  updateEngram(stableId: string, input: EngramUpdateInput): Engram;
155
173
  /** 重新组装路径:替换 basename 为 newSlug */
156
174
  private rebuildPath;
175
+ /**
176
+ * 调整路径的 visibility 前缀(不改动 basename):
177
+ * - `'private'` → 加 `private/` 前缀(若已含则幂等返回原值)
178
+ * - 其他(`'public'`/`'team'`/`'restricted'`) → 移除 `private/` 前缀(若不含则幂等返回)
179
+ *
180
+ * 用于 updateEngram 中 visibility 变更时的路径迁移。
181
+ * 注意:仅处理前缀,basename 由 rebuildPath 负责,两者正交可串联应用。
182
+ */
183
+ private rebuildPathForVisibility;
157
184
  /**
158
185
  * 删除 Engram + 级联删除触及的 synapses + 清理 index
186
+ *
187
+ * 走 `this.deleteSynapsesTouching` 方法版而非模块函数,以触发
188
+ * 邻居派生段( Obsidian wikilinks)的 cascade refresh。
159
189
  */
160
190
  deleteEngram(stableId: string): void;
161
191
  /**
@@ -205,6 +235,12 @@ export declare class EngramRepository {
205
235
  readSynapseByEndpoints(from: string, to: string, kind: SynapseKind): Synapse | undefined;
206
236
  /** 按 id 查 synapse */
207
237
  readSynapseById(synapseId: string): Synapse | undefined;
238
+ /**
239
+ * 触发 Obsidian 派生段重建(多条 engram 一次性刷新,去重)。
240
+ *
241
+ * 永不抛(regenerateObsidianLinks 内部已吞错)。调用方无需 try/catch。
242
+ */
243
+ private refreshObsidianLinks;
208
244
  /** 创建 synapse(idempotent) */
209
245
  createSynapse(input: SynapseCreateInput): Synapse;
210
246
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/storage/repository.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/storage/repository.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,OAAO,KAAK,EACV,MAAM,EACN,kBAAkB,EAClB,iBAAiB,EACjB,YAAY,EACZ,eAAe,EAGf,YAAY,EACZ,iBAAiB,EAGjB,gBAAgB,EAChB,kBAAkB,EAClB,OAAO,EACP,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,sBAAsB,EACtB,kBAAkB,EACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAEV,gBAAgB,EAChB,YAAY,EAEZ,YAAY,EACb,MAAM,8BAA8B,CAAC;AAWtC,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAyBnE,OAAO,EAYL,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAM3B,oBAAoB;AACpB,MAAM,WAAW,gBAAgB;IAC/B,4BAA4B;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;;;OAQG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;CAC9B;AA4DD;;;;GAIG;AACH,qBAAa,gBAAgB;IAwBf,OAAO,CAAC,QAAQ,CAAC,MAAM;IAvBnC,OAAO,CAAC,UAAU,CAA6B;IAC/C;;;;OAIG;IACH,OAAO,CAAC,eAAe,CAAqB;IAE5C;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAyB;IAE7D;;;OAGG;IACH,OAAO,CAAC,YAAY,CAAwB;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;gBAEP,MAAM,EAAE,gBAAgB;IAIrD,4BAA4B;IAC5B,IAAI,eAAe,IAAI,QAAQ,CAE9B;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAID;;;;;;;;;;OAUG;IACH,OAAO,CAAC,QAAQ;IA8BhB,sBAAsB;IACtB,YAAY,IAAI,cAAc;IAM9B;;;;;;;;OAQG;IACH,OAAO,CAAC,YAAY;IAgBpB,uBAAuB;IACvB,OAAO,CAAC,gBAAgB;IAMxB,sBAAsB;IACtB,OAAO,CAAC,gBAAgB;IAMxB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,6BAA6B;IAcrC;;;;;;;;;;;;;OAaG;IACH,aAAa,IAAI,IAAI;IAqBrB,2BAA2B;IAC3B,YAAY,IAAI,IAAI;IAWpB,0DAA0D;IAC1D,OAAO,CAAC,oBAAoB;IAa5B;;;;;;;OAOG;IACH,qBAAqB,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAQjD,yBAAyB;IACzB,OAAO,CAAC,WAAW;IAgBnB,2BAA2B;IAC3B,OAAO,CAAC,YAAY;IAOpB;;;;;;;;OAQG;IACH,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM;IAqFtE;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAUzB;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAWpC;;OAEG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IASjC;;;;;;OAMG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,MAAM;IAyIhE,mCAAmC;IACnC,OAAO,CAAC,WAAW;IAMnB;;;;;;;OAOG;IACH,OAAO,CAAC,wBAAwB;IAahC;;;;;OAKG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAWpC;;OAEG;IACH,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAa1D,+BAA+B;IAC/B,WAAW,IAAI,kBAAkB,EAAE;IAanC;;;;OAIG;IACH,wBAAwB,CACtB,QAAQ,EAAE,SAAS,kBAAkB,EAAE,GACtC,SAAS,MAAM,EAAE;IACpB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,SAAS,MAAM,EAAE;IAgBvE;;;;OAIG;IACH,eAAe,IAAI,SAAS,gBAAgB,EAAE;IAI9C,uBAAuB;IACvB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI;IAY7D,yCAAyC;IACzC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAmBjD;;;;;OAKG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG;QAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;KAAE;IAI5E;;;;;OAKG;IACH,kBAAkB,IAAI,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAKjE,8BAA8B;IAC9B,sBAAsB,CACpB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,WAAW,GAChB,OAAO,GAAG,SAAS;IAItB,qBAAqB;IACrB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAIvD;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAS5B,6BAA6B;IAC7B,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;IAwCjD;;;;;OAKG;IACH,aAAa,CACX,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,kBAAkB,GACxB,OAAO;IA0CV,mBAAmB;IACnB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAWtC,gCAAgC;IAChC,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAgBhD;;;;;;;OAOG;IACH,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;IAuB7D;;;;OAIG;IACH,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAc9D;;;;OAIG;IACH,uBAAuB,CACrB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,sBAAsB,GAC3B,IAAI;IAoBP;;OAEG;IACH,sBAAsB,CACpB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,SAAS,eAAe,EAAE,GACnC,IAAI;IAoBP;;;;;;OAMG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI;IAiBtE;;;;;OAKG;IACH,kBAAkB,CAChB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE;QACL,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;KAC7B,GACA,IAAI;IAyBP;;;;;;OAMG;IACH,eAAe,CACb,EAAE,EAAE,MAAM,EACV,MAAM,CAAC,EAAE,YAAY,EACrB,SAAS,CAAC,EAAE,eAAe,GAC1B,IAAI;IASP;;;;OAIG;IACH,sBAAsB,CACpB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE;QAAE,MAAM,EAAE,gBAAgB,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GACtD,IAAI;IAQP;;OAEG;IACH,wBAAwB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAUtE;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAgCzB;;;;;;;;;;OAUG;IACH,SAAS,CAAC,OAAO,GAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,YAAY;IA8LhE;;;;;OAKG;IACH,YAAY,IAAI,YAAY;IAyC5B;;OAEG;IACH,OAAO,CAAC,cAAc;IAqFtB,OAAO,CAAC,gBAAgB;IAaxB,gCAAgC;IAChC,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;IAI1C;;;;;;;;;;OAUG;IACH,aAAa,CAAC,cAAc,EAAE,QAAQ,GAAG;QACvC,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,EAAE,CAAC;KAClB;CAkFF"}
@@ -19,11 +19,14 @@ import { join, relative, sep } from "node:path";
19
19
  import { ulid } from "ulid";
20
20
  import { isStableEngramId } from "../types/repository-types.js";
21
21
  import { slugify, inferDomainTagsFromPath } from "../types/slugify.js";
22
+ import { safeEmit } from "../prompt-signals/event-bus.js";
23
+ import { safeJoinWithinRoot, isPathWithinRoot, } from "./path.js";
22
24
  import { computeContentHash, computeContentSize } from "./hash.js";
23
25
  import { DEFAULT_LANGUAGE } from "../i18n/index.js";
24
26
  import { readEngramFile, writeEngramFile, deleteEngramFile, renameEngramFile, parseEngramFile, detectEngramFileLanguage, } from "./engram-store.js";
25
27
  import { SYNAPSES_DIR, collectAllSynapses, upsertSynapse, readSynapseByEndpoints, readSynapseById, listSynapsesForEngram, deleteSynapsesTouching, synapseRelativePath, deleteSynapseFile, writeSynapseFile, parseSynapseFile, } from "./synapse-store.js";
26
28
  import { buildIndexEntryFromFrontmatter, engramIndexPath, readEngramIndex, rebuildEngramIndex, removeEngramIndexEntry, upsertEngramIndexEntry, writeEngramIndex, } from "./engram-index.js";
29
+ import { regenerateObsidianLinks, checkObsidianView, } from "./obsidian-links.js";
27
30
  const DEFAULT_IMPORTANCE = 0.5;
28
31
  const DEFAULT_CONFIDENCE_BY_SOURCE = {
29
32
  firsthand: 0.85,
@@ -64,6 +67,21 @@ function deriveAutoSummary(content, title) {
64
67
  return cleaned;
65
68
  return cleaned.slice(0, 197) + "...";
66
69
  }
70
+ /**
71
+ * Synapse visibility 继承规则:取两端 engram 的最严。
72
+ *
73
+ * 严格度排序:`private` > `restricted` > `team` > `public`。
74
+ * 任一端是 private,synapse 整条就按 private 处理(保守策略)。
75
+ */
76
+ const VIS_STRICTNESS = {
77
+ public: 0,
78
+ team: 1,
79
+ restricted: 2,
80
+ private: 3,
81
+ };
82
+ function maxVisibility(a, b) {
83
+ return VIS_STRICTNESS[a] >= VIS_STRICTNESS[b] ? a : b;
84
+ }
67
85
  /**
68
86
  * EngramRepository — per-edge synapse + ULID stable id + 单文件 engram
69
87
  *
@@ -180,6 +198,29 @@ export class EngramRepository {
180
198
  removeEngramIndexEntry(index, id);
181
199
  this.persistIndex(index);
182
200
  }
201
+ /**
202
+ * 清理索引中所有指向 relativePath 的孤儿 entry,返回清理数量。
203
+ *
204
+ * 触发场景:外部(用户 rm / git 操作 / 进程异常)删除 engram 文件后,
205
+ * engram-index.json 仍保留旧 ULID 的 entry。下一次 createEngram 写入
206
+ * 同 path 的新 ULID 会留下永不消失的孤儿,导致 listEngrams / viewer
207
+ * 显示"重影"(同一文件被两个 ULID 引用)。
208
+ *
209
+ * 同 path 出现多条 entry 本身就是不变量破坏 —— 此处发现即清。多数调用
210
+ * 不会命中,N=0 时是 O(|index|) 扫一次,可接受。
211
+ */
212
+ purgeStaleIndexEntriesForPath(relativePath) {
213
+ const index = this.getIndex();
214
+ const stale = [];
215
+ for (const [id, entry] of index.entries) {
216
+ if (entry.path === relativePath)
217
+ stale.push(id);
218
+ }
219
+ for (const id of stale) {
220
+ this.deleteIndexEntry(id);
221
+ }
222
+ return stale.length;
223
+ }
183
224
  // ─── Cross-process watcher ─────────────────────────────────────────────
184
225
  /**
185
226
  * 启动对 engram-index.json 的 fs.watch 监听。
@@ -262,17 +303,26 @@ export class EngramRepository {
262
303
  /** 解析 stableId → 相对路径 */
263
304
  resolvePath(stableId) {
264
305
  if (!isStableEngramId(stableId)) {
265
- // 兼容:可能是相对路径,直接当 path
306
+ // 兼容:可能是相对路径,直接当 path 用。
307
+ // 但必须先校验路径在 root 内(防 `..` 逃逸,path traversal 防御)
308
+ if (!isPathWithinRoot(this.config.rootPath, stableId))
309
+ return undefined;
266
310
  if (this.existsAtPath(stableId))
267
311
  return stableId;
268
312
  return undefined;
269
313
  }
270
314
  const entry = this.getIndex().entries.get(stableId);
315
+ // 防御:索引中的 path 也校验(理论上是 trusted,但 doctor 自愈后可能含异常)
316
+ if (entry?.path && !isPathWithinRoot(this.config.rootPath, entry.path)) {
317
+ return undefined;
318
+ }
271
319
  return entry?.path;
272
320
  }
273
321
  /** 检查相对路径是否存在 engram 文件 */
274
322
  existsAtPath(relativePath) {
275
- return existsSync(join(this.config.rootPath, relativePath));
323
+ if (!isPathWithinRoot(this.config.rootPath, relativePath))
324
+ return false;
325
+ return existsSync(safeJoinWithinRoot(this.config.rootPath, relativePath));
276
326
  }
277
327
  // ─── Engram CRUD ───────────────────────────────────────────────────────
278
328
  /**
@@ -290,11 +340,18 @@ export class EngramRepository {
290
340
  const sourceType = input.sourceType ?? "firsthand";
291
341
  const contentHash = computeContentHash(input.content);
292
342
  const contentSize = computeContentSize(input.content);
343
+ // pathHint 优先;否则用 deriveDefaultPath(slugify title + raw domainTags + .md)
344
+ // safeJoinWithinRoot 拦截 `..` 逃逸与绝对路径(path traversal 防御)
293
345
  const relativePath = input.pathHint ?? this.deriveDefaultPath(input);
294
- const absolutePath = join(this.config.rootPath, relativePath);
346
+ const absolutePath = safeJoinWithinRoot(this.config.rootPath, relativePath);
295
347
  if (existsSync(absolutePath)) {
296
348
  throw new Error(`Engram already exists at ${relativePath}`);
297
349
  }
350
+ // 自愈:外部 rm / git 操作可能让 engram-index.json 残留指向同 path
351
+ // 但磁盘已无文件的孤儿 entry。新 ULID 写入后会留下永不消失的"重影"
352
+ // (listEngrams / viewer 显示重复节点)。在写入前清理。engram_doctor
353
+ // 是手动巡检版本,此处是写入路径的自动防线。
354
+ this.purgeStaleIndexEntriesForPath(relativePath);
298
355
  const hasExplicitDomainTags = input.domainTags.length > 0;
299
356
  const frontmatter = {
300
357
  id: stableId,
@@ -344,14 +401,29 @@ export class EngramRepository {
344
401
  contentHash,
345
402
  });
346
403
  this.updateIndexEntry(entry);
404
+ // Task 3.4 Phase B:engram 创建后 emit,让 prompt-signals cache 失效并 debounced rebuild
405
+ safeEmit({
406
+ type: "engram_created",
407
+ engramId: stableId,
408
+ at: new Date().toISOString(),
409
+ });
347
410
  return this.readEngram(stableId);
348
411
  }
349
- /** 默认路径:{domainTags.join('/')/}{slug}.md */
412
+ /**
413
+ * 默认路径:{domainTags.join('/')/}{slug}.md
414
+ *
415
+ * private engram 自动落 `private/` 子目录(被 .gitignore 隔离出团队仓库)。
416
+ * 注意:本函数只用于「无 pathHint」场景;调用方传 pathHint 时直接尊重用户路径,
417
+ * 不会自动加 private 前缀(避免 `private/private/...` 双前缀)。
418
+ */
350
419
  deriveDefaultPath(input) {
351
420
  const slug = slugify(input.title);
352
421
  const domains = input.domainTags.length > 0 ? input.domainTags : [];
353
422
  const parts = [...domains, `${slug}.md`];
354
- return parts.join("/");
423
+ const basePath = parts.join("/");
424
+ return input.visibility === "private"
425
+ ? `private/${basePath}`
426
+ : basePath;
355
427
  }
356
428
  /**
357
429
  * 读取完整 Engram(单文件 + 统计)
@@ -361,7 +433,8 @@ export class EngramRepository {
361
433
  if (!relativePath) {
362
434
  throw new Error(`Engram not found: ${stableId}`);
363
435
  }
364
- const absolutePath = join(this.config.rootPath, relativePath);
436
+ // resolvePath 已校验路径在 root 内,这里再防御一次
437
+ const absolutePath = safeJoinWithinRoot(this.config.rootPath, relativePath);
365
438
  const file = readEngramFile(absolutePath);
366
439
  return this.assembleEngram(file, relativePath);
367
440
  }
@@ -371,7 +444,8 @@ export class EngramRepository {
371
444
  exists(stableId) {
372
445
  const relativePath = this.resolvePath(stableId);
373
446
  return (relativePath !== undefined &&
374
- existsSync(join(this.config.rootPath, relativePath)));
447
+ isPathWithinRoot(this.config.rootPath, relativePath) &&
448
+ existsSync(safeJoinWithinRoot(this.config.rootPath, relativePath)));
375
449
  }
376
450
  /**
377
451
  * 更新 Engram(content/frontmatter 双写)
@@ -454,9 +528,18 @@ export class EngramRepository {
454
528
  const newSlugUnlocked = oldFrontmatter.slug === undefined
455
529
  ? slugify(newTitle)
456
530
  : oldFrontmatter.slug;
457
- const newPath = newSlugUnlocked !== oldSlug
458
- ? this.rebuildPath(relativePath, newSlugUnlocked)
459
- : relativePath;
531
+ // 处理 visibility 变化:public/team/restricted private → 路径前缀调整
532
+ // (private engram 落 `private/` 子目录,变更 visibility 时同步迁移路径)
533
+ const oldVisibility = oldFrontmatter.visibility ?? "public";
534
+ const visibilityChanged = newVisibility !== oldVisibility;
535
+ // slug + visibility 都可能触发 rename,正交串联应用
536
+ let newPath = relativePath;
537
+ if (newSlugUnlocked !== oldSlug) {
538
+ newPath = this.rebuildPath(newPath, newSlugUnlocked);
539
+ }
540
+ if (visibilityChanged) {
541
+ newPath = this.rebuildPathForVisibility(newPath, newVisibility);
542
+ }
460
543
  const newFile = {
461
544
  frontmatter: newFrontmatter,
462
545
  content: newContent,
@@ -464,10 +547,15 @@ export class EngramRepository {
464
547
  if (newPath !== relativePath) {
465
548
  const newAbsolutePath = join(this.config.rootPath, newPath);
466
549
  if (existsSync(newAbsolutePath)) {
467
- throw new Error(`Slug rename conflict: ${newPath} already exists`);
550
+ // 原子性保证:目标已存在就报错,不动旧文件
551
+ throw new Error(`Rename conflict: ${newPath} already exists`);
468
552
  }
469
553
  writeEngramFile(newAbsolutePath, newFile, this.language);
470
554
  rmSync(absolutePath);
555
+ // 旧路径孤儿 index entry 清理(原 updateEngram 漏了这一步):
556
+ // rename 后旧 path 的反向 entry(path → stableId)若不清理,会留下
557
+ // 「磁盘无文件但 index 有记录」的孤儿,listEngrams / viewer 会显示重影。
558
+ this.purgeStaleIndexEntriesForPath(relativePath);
471
559
  }
472
560
  else {
473
561
  writeEngramFile(absolutePath, newFile, this.language);
@@ -488,8 +576,28 @@ export class EngramRepository {
488
576
  parts[parts.length - 1] = `${newSlug}.md`;
489
577
  return parts.join("/");
490
578
  }
579
+ /**
580
+ * 调整路径的 visibility 前缀(不改动 basename):
581
+ * - `'private'` → 加 `private/` 前缀(若已含则幂等返回原值)
582
+ * - 其他(`'public'`/`'team'`/`'restricted'`) → 移除 `private/` 前缀(若不含则幂等返回)
583
+ *
584
+ * 用于 updateEngram 中 visibility 变更时的路径迁移。
585
+ * 注意:仅处理前缀,basename 由 rebuildPath 负责,两者正交可串联应用。
586
+ */
587
+ rebuildPathForVisibility(path, visibility) {
588
+ const PRIVATE_PREFIX = "private/";
589
+ if (visibility === "private") {
590
+ return path.startsWith(PRIVATE_PREFIX) ? path : `${PRIVATE_PREFIX}${path}`;
591
+ }
592
+ return path.startsWith(PRIVATE_PREFIX)
593
+ ? path.slice(PRIVATE_PREFIX.length)
594
+ : path;
595
+ }
491
596
  /**
492
597
  * 删除 Engram + 级联删除触及的 synapses + 清理 index
598
+ *
599
+ * 走 `this.deleteSynapsesTouching` 方法版而非模块函数,以触发
600
+ * 邻居派生段( Obsidian wikilinks)的 cascade refresh。
493
601
  */
494
602
  deleteEngram(stableId) {
495
603
  const relativePath = this.resolvePath(stableId);
@@ -497,7 +605,7 @@ export class EngramRepository {
497
605
  return;
498
606
  const absolutePath = join(this.config.rootPath, relativePath);
499
607
  deleteEngramFile(absolutePath);
500
- deleteSynapsesTouching(this.config.rootPath, stableId);
608
+ this.deleteSynapsesTouching(stableId);
501
609
  if (isStableEngramId(stableId)) {
502
610
  this.deleteIndexEntry(stableId);
503
611
  }
@@ -613,9 +721,40 @@ export class EngramRepository {
613
721
  readSynapseById(synapseId) {
614
722
  return readSynapseById(this.config.rootPath, synapseId);
615
723
  }
724
+ /**
725
+ * 触发 Obsidian 派生段重建(多条 engram 一次性刷新,去重)。
726
+ *
727
+ * 永不抛(regenerateObsidianLinks 内部已吞错)。调用方无需 try/catch。
728
+ */
729
+ refreshObsidianLinks(...ids) {
730
+ const seen = new Set();
731
+ for (const id of ids) {
732
+ if (!id || seen.has(id))
733
+ continue;
734
+ seen.add(id);
735
+ regenerateObsidianLinks(this.config.rootPath, id, this.language);
736
+ }
737
+ }
616
738
  /** 创建 synapse(idempotent) */
617
739
  createSynapse(input) {
618
- return upsertSynapse(this.config.rootPath, {
740
+ // 继承最严 visibility:取 from/to 两端 engram 的 max(private > restricted > team > public)
741
+ // 端点查不到(已删/无效 id)时降级 'public',不阻塞 synapse 创建
742
+ let fromVis = "public";
743
+ let toVis = "public";
744
+ try {
745
+ fromVis = this.readEngram(input.from).visibility ?? "public";
746
+ }
747
+ catch {
748
+ // from engram 不存在或不可读,降级 public
749
+ }
750
+ try {
751
+ toVis = this.readEngram(input.to).visibility ?? "public";
752
+ }
753
+ catch {
754
+ // to engram 不存在或不可读,降级 public
755
+ }
756
+ const inheritedVisibility = maxVisibility(fromVis, toVis);
757
+ const result = upsertSynapse(this.config.rootPath, {
619
758
  from: input.from,
620
759
  to: input.to,
621
760
  kind: input.kind,
@@ -625,8 +764,17 @@ export class EngramRepository {
625
764
  createdBy: input.createdBy,
626
765
  sourceSemantic: input.sourceSemantic,
627
766
  targetSemantic: input.targetSemantic,
767
+ visibility: inheritedVisibility,
628
768
  language: this.language,
629
769
  });
770
+ this.refreshObsidianLinks(input.from, input.to);
771
+ // Task 3.4 Phase B:synapse 创建影响 graph 结构,触发 prompt-signals rebuild
772
+ safeEmit({
773
+ type: "synapse_created",
774
+ engramId: input.from,
775
+ at: new Date().toISOString(),
776
+ });
777
+ return result;
630
778
  }
631
779
  /**
632
780
  * 更新 synapse(走 upsert,合并 evidence)。
@@ -648,7 +796,7 @@ export class EngramRepository {
648
796
  const oldPath = join(this.config.rootPath, synapseRelativePath(target.id, target.kind));
649
797
  deleteSynapseFile(oldPath);
650
798
  }
651
- return upsertSynapse(this.config.rootPath, {
799
+ const result = upsertSynapse(this.config.rootPath, {
652
800
  from: target.from,
653
801
  to: target.to,
654
802
  kind: nextKind,
@@ -659,8 +807,13 @@ export class EngramRepository {
659
807
  sourceSemantic: target.sourceSemantic,
660
808
  targetSemantic: target.targetSemantic,
661
809
  resolutionState: target.resolutionState,
810
+ // 保守策略:不因端点 visibility 提升而自动调整 synapse visibility,
811
+ // 保留原值。Phase 1.5 可加 recomputeSynapseVisibility(engramId)。
812
+ visibility: target.visibility ?? "public",
662
813
  language: this.language,
663
814
  });
815
+ this.refreshObsidianLinks(target.from, target.to);
816
+ return result;
664
817
  }
665
818
  /** 删除某条 synapse */
666
819
  deleteSynapse(synapseId) {
@@ -669,10 +822,26 @@ export class EngramRepository {
669
822
  return;
670
823
  const path = join(this.config.rootPath, synapseRelativePath(syn.id, syn.kind));
671
824
  deleteSynapseFile(path);
825
+ this.refreshObsidianLinks(syn.from, syn.to);
672
826
  }
673
827
  /** 级联删除触及 engram 的所有 synapse */
674
828
  deleteSynapsesTouching(engramId) {
675
- return deleteSynapsesTouching(this.config.rootPath, engramId);
829
+ // 先抓所有邻居 endpoint — 删除后这些 synapse 就找不到了,
830
+ // 邻居 engram.md 的派生段还引用着 engramId,需要重建。
831
+ const touching = listSynapsesForEngram(this.config.rootPath, engramId);
832
+ const neighbors = new Set();
833
+ for (const s of touching.outgoing) {
834
+ if (s.to !== engramId)
835
+ neighbors.add(s.to);
836
+ }
837
+ for (const s of touching.incoming) {
838
+ if (s.from !== engramId)
839
+ neighbors.add(s.from);
840
+ }
841
+ const count = deleteSynapsesTouching(this.config.rootPath, engramId);
842
+ if (neighbors.size > 0)
843
+ this.refreshObsidianLinks(...neighbors);
844
+ return count;
676
845
  }
677
846
  /**
678
847
  * 添加 outgoing synapse。
@@ -683,7 +852,7 @@ export class EngramRepository {
683
852
  * @returns 实际落盘的 synapse(其 id 是计算值)
684
853
  */
685
854
  addOutgoingSynapse(fromId, synapse) {
686
- return upsertSynapse(this.config.rootPath, {
855
+ const result = upsertSynapse(this.config.rootPath, {
687
856
  from: fromId,
688
857
  to: synapse.to,
689
858
  kind: synapse.kind,
@@ -701,6 +870,8 @@ export class EngramRepository {
701
870
  resolutionState: synapse.resolutionState,
702
871
  language: this.language,
703
872
  });
873
+ this.refreshObsidianLinks(fromId, synapse.to);
874
+ return result;
704
875
  }
705
876
  /**
706
877
  * 删除 outgoing synapse。
@@ -714,6 +885,7 @@ export class EngramRepository {
714
885
  return;
715
886
  const path = join(this.config.rootPath, synapseRelativePath(target.id, target.kind));
716
887
  deleteSynapseFile(path);
888
+ this.refreshObsidianLinks(target.from, target.to);
717
889
  }
718
890
  /**
719
891
  * 更新 synapse 的 resolutionState(contradicts 专用)。
@@ -822,6 +994,12 @@ export class EngramRepository {
822
994
  */
823
995
  updateVerificationStatus(id, status) {
824
996
  this.mutateFrontmatter(id, (fm) => ({ ...fm, verificationStatus: status }));
997
+ // Task 3.4 Phase B:验证状态变化影响 prompt 的 lowConfidenceTopics
998
+ safeEmit({
999
+ type: "engram_verified",
1000
+ engramId: id,
1001
+ at: new Date().toISOString(),
1002
+ });
825
1003
  }
826
1004
  /**
827
1005
  * 低层 frontmatter 修改:不触发 version++,不改 slug,不改 path。
@@ -876,6 +1054,11 @@ export class EngramRepository {
876
1054
  path: orphanPath,
877
1055
  message: `Markdown file without frontmatter: ${orphanPath} (either delete it or add frontmatter with a stable id)`,
878
1056
  autoFixed: false,
1057
+ nextAction: {
1058
+ tool: "engram_create",
1059
+ argsHint: `{ title, content, kind, domainTags, createdBy } // 直接读取这个 markdown 的内容作为 engram body`,
1060
+ explanation: "Markdown 文件没有 frontmatter,所以不在 engram 索引里。如果是新记忆,用 engram_create 注册(把现有正文粘到 content);如果是废弃草稿,直接 rm 即可。",
1061
+ },
879
1062
  });
880
1063
  pendingManualReview.push(issues[issues.length - 1]);
881
1064
  });
@@ -913,11 +1096,17 @@ export class EngramRepository {
913
1096
  // 标记相关 synapse dangling
914
1097
  const touching = listSynapsesForEngram(this.config.rootPath, oldId);
915
1098
  if (touching.outgoing.length + touching.incoming.length > 0) {
1099
+ const danglingCount = touching.outgoing.length + touching.incoming.length;
916
1100
  pendingManualReview.push({
917
1101
  kind: "dangling_synapse",
918
1102
  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)`,
1103
+ message: `Engram ${oldId} was deleted but ${danglingCount} synapse(s) still reference it (clean up manually or restore the engram)`,
920
1104
  autoFixed: false,
1105
+ nextAction: {
1106
+ tool: "synapse_delete",
1107
+ argsHint: `{ id: "<synapseId>" } // synapseId 在每个 synapse 的 yaml id 字段,逐条删`,
1108
+ explanation: `被删 engram 还有 ${danglingCount} 条 synapse 指向它,这些 synapse 现在是悬空的。去 synapses/ 目录找出涉及该 engram 的 synapse,用 synapse_delete 逐条清理;或者用 engram_create 重建该 engram(让 synapse 重新有目标)。`,
1109
+ },
921
1110
  });
922
1111
  }
923
1112
  }
@@ -988,6 +1177,35 @@ export class EngramRepository {
988
1177
  });
989
1178
  }
990
1179
  }
1180
+ // 5. Obsidian 视图一致性(派生段 wikilinks)
1181
+ // 对每条 engram:checkObsidianView 检测派生段与权威源(synapse yaml)不一致,
1182
+ // 不一致 → regenerateObsidianLinks 重写派生段(wikilink target=文件名)。
1183
+ for (const [id, entry] of freshIndex.entries) {
1184
+ const absPath = join(this.config.rootPath, entry.path);
1185
+ if (!existsSync(absPath))
1186
+ continue;
1187
+ let file;
1188
+ try {
1189
+ file = readEngramFile(absPath);
1190
+ }
1191
+ catch {
1192
+ continue; // parse 错误由别处报告(或phan_markdown 路径)
1193
+ }
1194
+ const touching = listSynapsesForEngram(this.config.rootPath, id);
1195
+ const status = checkObsidianView(file, touching, freshIndex);
1196
+ if (!status.stale)
1197
+ continue;
1198
+ regenerateObsidianLinks(this.config.rootPath, id, this.language);
1199
+ fixes.push({
1200
+ kind: "obsidian_view_stale",
1201
+ stableId: id,
1202
+ path: entry.path,
1203
+ message: `Obsidian derived wikilinks regenerated (target=filename, display=title·kind)`,
1204
+ autoFixed: true,
1205
+ });
1206
+ }
1207
+ // Task 3.4 Phase B:doctor 完成后 emit(doctor 可能 sweep/forget,engram 集合变化)
1208
+ safeEmit({ type: "doctor_completed", at: new Date().toISOString() });
991
1209
  return {
992
1210
  startedAt,
993
1211
  finishedAt: now(),