dsh-data-domain-knowledge 0.1.0-alpha.9 → 0.1.1-alpha.2

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 (438) hide show
  1. package/README.md +118 -567
  2. package/cordis.patch.yml +5 -418
  3. package/eval/README.md +19 -8
  4. package/eval/benchmark-extraction-format.mjs +20 -0
  5. package/eval/evaluate-retrieval.mjs +17 -8
  6. package/eval/queries.example.jsonl +1 -0
  7. package/lib/client.js +4181 -3239
  8. package/lib/contract-envelope.d.ts +91 -0
  9. package/lib/contract-envelope.js +106 -0
  10. package/lib/contract-layout.d.ts +130 -0
  11. package/lib/contract-layout.js +138 -0
  12. package/lib/contract-refs.d.ts +39 -0
  13. package/lib/contract-refs.js +9 -0
  14. package/lib/contract-version.d.ts +311 -0
  15. package/lib/contract-version.js +270 -0
  16. package/lib/contract-wire.d.ts +147 -0
  17. package/lib/contract-wire.js +99 -0
  18. package/lib/contracts.d.ts +12 -0
  19. package/lib/contracts.js +12 -0
  20. package/lib/doc-page.js +2 -2
  21. package/lib/pages.js +3329 -1296
  22. package/lib/types/agent-behavior-rules.d.ts +10 -0
  23. package/lib/types/agent-behavior-rules.d.ts.map +1 -0
  24. package/lib/types/agent-behavior-rules.js +28 -0
  25. package/lib/types/agent-behavior-rules.js.map +1 -0
  26. package/lib/types/axis-registry.d.ts +37 -5
  27. package/lib/types/axis-registry.d.ts.map +1 -1
  28. package/lib/types/axis-registry.js +234 -26
  29. package/lib/types/axis-registry.js.map +1 -1
  30. package/lib/types/card-organize.d.ts +27 -12
  31. package/lib/types/card-organize.d.ts.map +1 -1
  32. package/lib/types/card-organize.js +58 -73
  33. package/lib/types/card-organize.js.map +1 -1
  34. package/lib/types/compiler/embedding-identity.d.ts +3 -0
  35. package/lib/types/compiler/embedding-identity.d.ts.map +1 -0
  36. package/lib/types/compiler/embedding-identity.js +24 -0
  37. package/lib/types/compiler/embedding-identity.js.map +1 -0
  38. package/lib/types/compiler/snapshot.d.ts +16 -0
  39. package/lib/types/compiler/snapshot.d.ts.map +1 -0
  40. package/lib/types/compiler/snapshot.js +2 -0
  41. package/lib/types/compiler/snapshot.js.map +1 -0
  42. package/lib/types/config-schema.d.ts +6 -3
  43. package/lib/types/config-schema.d.ts.map +1 -1
  44. package/lib/types/config-schema.js +6 -2
  45. package/lib/types/config-schema.js.map +1 -1
  46. package/lib/types/convert/host.d.ts +6 -5
  47. package/lib/types/convert/host.d.ts.map +1 -1
  48. package/lib/types/convert/host.js +58 -18
  49. package/lib/types/convert/host.js.map +1 -1
  50. package/lib/types/convert/ingest.d.ts +10 -1
  51. package/lib/types/convert/ingest.d.ts.map +1 -1
  52. package/lib/types/convert/ingest.js +68 -9
  53. package/lib/types/convert/ingest.js.map +1 -1
  54. package/lib/types/derive-contract.d.ts +3 -0
  55. package/lib/types/derive-contract.d.ts.map +1 -0
  56. package/lib/types/derive-contract.js +15 -0
  57. package/lib/types/derive-contract.js.map +1 -0
  58. package/lib/types/diagnosis/cards.d.ts +7 -3
  59. package/lib/types/diagnosis/cards.d.ts.map +1 -1
  60. package/lib/types/diagnosis/cards.js +13 -8
  61. package/lib/types/diagnosis/cards.js.map +1 -1
  62. package/lib/types/diagnosis/run.d.ts +4 -13
  63. package/lib/types/diagnosis/run.d.ts.map +1 -1
  64. package/lib/types/diagnosis/run.js +26 -16
  65. package/lib/types/diagnosis/run.js.map +1 -1
  66. package/lib/types/diagnosis/schema.js +2 -2
  67. package/lib/types/diagnosis/schema.js.map +1 -1
  68. package/lib/types/diagnosis/seed-source.d.ts +1 -1
  69. package/lib/types/diagnosis/seed-source.d.ts.map +1 -1
  70. package/lib/types/diagnosis/seed-source.js +5 -4
  71. package/lib/types/diagnosis/seed-source.js.map +1 -1
  72. package/lib/types/diagnosis/seed.d.ts +4 -4
  73. package/lib/types/diagnosis/seed.d.ts.map +1 -1
  74. package/lib/types/diagnosis/seed.js +6 -8
  75. package/lib/types/diagnosis/seed.js.map +1 -1
  76. package/lib/types/diagnosis/store.d.ts +24 -18
  77. package/lib/types/diagnosis/store.d.ts.map +1 -1
  78. package/lib/types/diagnosis/store.js +130 -65
  79. package/lib/types/diagnosis/store.js.map +1 -1
  80. package/lib/types/diagnosis/types.d.ts +47 -15
  81. package/lib/types/diagnosis/types.d.ts.map +1 -1
  82. package/lib/types/diagnosis/types.js.map +1 -1
  83. package/lib/types/environment/dsh-python.d.ts +77 -0
  84. package/lib/types/environment/dsh-python.d.ts.map +1 -0
  85. package/lib/types/environment/dsh-python.js +139 -0
  86. package/lib/types/environment/dsh-python.js.map +1 -0
  87. package/lib/types/environment/install.d.ts +15 -0
  88. package/lib/types/environment/install.d.ts.map +1 -0
  89. package/lib/types/environment/install.js +297 -0
  90. package/lib/types/environment/install.js.map +1 -0
  91. package/lib/types/environment/manifest.d.ts +208 -0
  92. package/lib/types/environment/manifest.d.ts.map +1 -0
  93. package/lib/types/environment/manifest.js +26 -0
  94. package/lib/types/environment/manifest.js.map +1 -0
  95. package/lib/types/environment/model-catalog.d.ts +223 -0
  96. package/lib/types/environment/model-catalog.d.ts.map +1 -0
  97. package/lib/types/environment/model-catalog.js +129 -0
  98. package/lib/types/environment/model-catalog.js.map +1 -0
  99. package/lib/types/environment/process.d.ts +10 -0
  100. package/lib/types/environment/process.d.ts.map +1 -0
  101. package/lib/types/environment/process.js +54 -0
  102. package/lib/types/environment/process.js.map +1 -0
  103. package/lib/types/environment/selection.d.ts +9 -0
  104. package/lib/types/environment/selection.d.ts.map +1 -0
  105. package/lib/types/environment/selection.js +29 -0
  106. package/lib/types/environment/selection.js.map +1 -0
  107. package/lib/types/environment/service.d.ts +64 -0
  108. package/lib/types/environment/service.d.ts.map +1 -0
  109. package/lib/types/environment/service.js +424 -0
  110. package/lib/types/environment/service.js.map +1 -0
  111. package/lib/types/environment/types.d.ts +56 -0
  112. package/lib/types/environment/types.d.ts.map +1 -0
  113. package/lib/types/environment/types.js +2 -0
  114. package/lib/types/environment/types.js.map +1 -0
  115. package/lib/types/events.d.ts +76 -0
  116. package/lib/types/events.d.ts.map +1 -0
  117. package/lib/types/events.js +52 -0
  118. package/lib/types/events.js.map +1 -0
  119. package/lib/types/extract-contract.d.ts +3 -0
  120. package/lib/types/extract-contract.d.ts.map +1 -0
  121. package/lib/types/extract-contract.js +41 -0
  122. package/lib/types/extract-contract.js.map +1 -0
  123. package/lib/types/extract-model.d.ts +5 -0
  124. package/lib/types/extract-model.d.ts.map +1 -1
  125. package/lib/types/extract-model.js.map +1 -1
  126. package/lib/types/extract-table-contract.d.ts +3 -0
  127. package/lib/types/extract-table-contract.d.ts.map +1 -0
  128. package/lib/types/extract-table-contract.js +27 -0
  129. package/lib/types/extract-table-contract.js.map +1 -0
  130. package/lib/types/extract-table.d.ts +8 -0
  131. package/lib/types/extract-table.d.ts.map +1 -0
  132. package/lib/types/extract-table.js +158 -0
  133. package/lib/types/extract-table.js.map +1 -0
  134. package/lib/types/extraction-runs.d.ts +30 -6
  135. package/lib/types/extraction-runs.d.ts.map +1 -1
  136. package/lib/types/extraction-runs.js +69 -8
  137. package/lib/types/extraction-runs.js.map +1 -1
  138. package/lib/types/fs-atomic.d.ts +0 -7
  139. package/lib/types/fs-atomic.d.ts.map +1 -1
  140. package/lib/types/fs-atomic.js +17 -2
  141. package/lib/types/fs-atomic.js.map +1 -1
  142. package/lib/types/images.d.ts +26 -0
  143. package/lib/types/images.d.ts.map +1 -0
  144. package/lib/types/images.js +196 -0
  145. package/lib/types/images.js.map +1 -0
  146. package/lib/types/index.d.ts +2 -2
  147. package/lib/types/index.d.ts.map +1 -1
  148. package/lib/types/index.js +598 -209
  149. package/lib/types/index.js.map +1 -1
  150. package/lib/types/intent/engine.d.ts +12 -4
  151. package/lib/types/intent/engine.d.ts.map +1 -1
  152. package/lib/types/intent/engine.js +43 -5
  153. package/lib/types/intent/engine.js.map +1 -1
  154. package/lib/types/intent/host.d.ts +5 -8
  155. package/lib/types/intent/host.d.ts.map +1 -1
  156. package/lib/types/intent/host.js +56 -11
  157. package/lib/types/intent/host.js.map +1 -1
  158. package/lib/types/intent/scope.d.ts +25 -0
  159. package/lib/types/intent/scope.d.ts.map +1 -0
  160. package/lib/types/intent/scope.js +116 -0
  161. package/lib/types/intent/scope.js.map +1 -0
  162. package/lib/types/intent/tools.d.ts +24 -20
  163. package/lib/types/intent/tools.d.ts.map +1 -1
  164. package/lib/types/intent/tools.js +65 -23
  165. package/lib/types/intent/tools.js.map +1 -1
  166. package/lib/types/intent/types.d.ts +11 -4
  167. package/lib/types/intent/types.d.ts.map +1 -1
  168. package/lib/types/knowledge-bases.d.ts +11 -5
  169. package/lib/types/knowledge-bases.d.ts.map +1 -1
  170. package/lib/types/knowledge-bases.js +52 -20
  171. package/lib/types/knowledge-bases.js.map +1 -1
  172. package/lib/types/knowledge-contract.d.ts +32 -0
  173. package/lib/types/knowledge-contract.d.ts.map +1 -0
  174. package/lib/types/knowledge-contract.js +116 -0
  175. package/lib/types/knowledge-contract.js.map +1 -0
  176. package/lib/types/knowledge.d.ts +31 -9
  177. package/lib/types/knowledge.d.ts.map +1 -1
  178. package/lib/types/knowledge.js +137 -60
  179. package/lib/types/knowledge.js.map +1 -1
  180. package/lib/types/layout.d.ts +10 -29
  181. package/lib/types/layout.d.ts.map +1 -1
  182. package/lib/types/layout.js +17 -36
  183. package/lib/types/layout.js.map +1 -1
  184. package/lib/types/materialize-snapshot.d.ts +49 -0
  185. package/lib/types/materialize-snapshot.d.ts.map +1 -0
  186. package/lib/types/materialize-snapshot.js +2 -0
  187. package/lib/types/materialize-snapshot.js.map +1 -0
  188. package/lib/types/migrate.d.ts +11 -0
  189. package/lib/types/migrate.d.ts.map +1 -1
  190. package/lib/types/migrate.js +83 -2
  191. package/lib/types/migrate.js.map +1 -1
  192. package/lib/types/model-text.d.ts +58 -1
  193. package/lib/types/model-text.d.ts.map +1 -1
  194. package/lib/types/model-text.js +85 -30
  195. package/lib/types/model-text.js.map +1 -1
  196. package/lib/types/oplog.d.ts.map +1 -1
  197. package/lib/types/oplog.js +7 -0
  198. package/lib/types/oplog.js.map +1 -1
  199. package/lib/types/organize-contract.d.ts +18 -0
  200. package/lib/types/organize-contract.d.ts.map +1 -0
  201. package/lib/types/organize-contract.js +82 -0
  202. package/lib/types/organize-contract.js.map +1 -0
  203. package/lib/types/pipeline/engine.d.ts +66 -0
  204. package/lib/types/pipeline/engine.d.ts.map +1 -0
  205. package/lib/types/pipeline/engine.js +352 -0
  206. package/lib/types/pipeline/engine.js.map +1 -0
  207. package/lib/types/pipeline/store.d.ts +73 -0
  208. package/lib/types/pipeline/store.d.ts.map +1 -0
  209. package/lib/types/pipeline/store.js +143 -0
  210. package/lib/types/pipeline/store.js.map +1 -0
  211. package/lib/types/pipeline/types.d.ts +102 -0
  212. package/lib/types/pipeline/types.d.ts.map +1 -0
  213. package/lib/types/pipeline/types.js +5 -0
  214. package/lib/types/pipeline/types.js.map +1 -0
  215. package/lib/types/present.d.ts +2 -0
  216. package/lib/types/present.d.ts.map +1 -1
  217. package/lib/types/present.js +9 -4
  218. package/lib/types/present.js.map +1 -1
  219. package/lib/types/preset-ids.d.ts +4 -0
  220. package/lib/types/preset-ids.d.ts.map +1 -0
  221. package/lib/types/preset-ids.js +6 -0
  222. package/lib/types/preset-ids.js.map +1 -0
  223. package/lib/types/query-analysis.d.ts +13 -1
  224. package/lib/types/query-analysis.d.ts.map +1 -1
  225. package/lib/types/query-analysis.js +62 -6
  226. package/lib/types/query-analysis.js.map +1 -1
  227. package/lib/types/recall-rank.d.ts +3 -3
  228. package/lib/types/recall-rank.d.ts.map +1 -1
  229. package/lib/types/recall-rank.js +4 -4
  230. package/lib/types/recall-rank.js.map +1 -1
  231. package/lib/types/retention.d.ts +39 -0
  232. package/lib/types/retention.d.ts.map +1 -0
  233. package/lib/types/retention.js +42 -0
  234. package/lib/types/retention.js.map +1 -0
  235. package/lib/types/retrieval/vector-index.d.ts +32 -0
  236. package/lib/types/retrieval/vector-index.d.ts.map +1 -0
  237. package/lib/types/retrieval/vector-index.js +15 -0
  238. package/lib/types/retrieval/vector-index.js.map +1 -0
  239. package/lib/types/retrieval/visibility.d.ts +6 -0
  240. package/lib/types/retrieval/visibility.d.ts.map +1 -0
  241. package/lib/types/retrieval/visibility.js +11 -0
  242. package/lib/types/retrieval/visibility.js.map +1 -0
  243. package/lib/types/runtime.d.ts +272 -27
  244. package/lib/types/runtime.d.ts.map +1 -1
  245. package/lib/types/runtime.js +2751 -537
  246. package/lib/types/runtime.js.map +1 -1
  247. package/lib/types/settings-schema.d.ts +17 -15
  248. package/lib/types/settings-schema.d.ts.map +1 -1
  249. package/lib/types/settings-schema.js +50 -47
  250. package/lib/types/settings-schema.js.map +1 -1
  251. package/lib/types/settings-volatile.d.ts +4 -4
  252. package/lib/types/settings-volatile.js +4 -4
  253. package/lib/types/source-evidence.d.ts +14 -0
  254. package/lib/types/source-evidence.d.ts.map +1 -0
  255. package/lib/types/source-evidence.js +36 -0
  256. package/lib/types/source-evidence.js.map +1 -0
  257. package/lib/types/store/axis-references.d.ts +5 -0
  258. package/lib/types/store/axis-references.d.ts.map +1 -0
  259. package/lib/types/store/axis-references.js +106 -0
  260. package/lib/types/store/axis-references.js.map +1 -0
  261. package/lib/types/store/derived.d.ts +12 -1
  262. package/lib/types/store/derived.d.ts.map +1 -1
  263. package/lib/types/store/derived.js +71 -12
  264. package/lib/types/store/derived.js.map +1 -1
  265. package/lib/types/store/knowledge-db.d.ts +31 -1
  266. package/lib/types/store/knowledge-db.d.ts.map +1 -1
  267. package/lib/types/store/knowledge-db.js +252 -18
  268. package/lib/types/store/knowledge-db.js.map +1 -1
  269. package/lib/types/store/migrate.d.ts.map +1 -1
  270. package/lib/types/store/migrate.js +17 -6
  271. package/lib/types/store/migrate.js.map +1 -1
  272. package/lib/types/trajectory-types.d.ts +172 -0
  273. package/lib/types/trajectory-types.d.ts.map +1 -0
  274. package/lib/types/trajectory-types.js +36 -0
  275. package/lib/types/trajectory-types.js.map +1 -0
  276. package/lib/types/types.d.ts +59 -27
  277. package/lib/types/types.d.ts.map +1 -1
  278. package/lib/types/types.js +6 -15
  279. package/lib/types/types.js.map +1 -1
  280. package/lib/types/wiki-finalize.d.ts +16 -0
  281. package/lib/types/wiki-finalize.d.ts.map +1 -1
  282. package/lib/types/wiki-finalize.js +26 -0
  283. package/lib/types/wiki-finalize.js.map +1 -1
  284. package/lib/types/wiki-materialize.d.ts +4 -0
  285. package/lib/types/wiki-materialize.d.ts.map +1 -1
  286. package/lib/types/wiki-materialize.js +21 -7
  287. package/lib/types/wiki-materialize.js.map +1 -1
  288. package/lib/types/wiki-trial-types.d.ts +87 -0
  289. package/lib/types/wiki-trial-types.d.ts.map +1 -1
  290. package/lib/types/wiki-trial-types.js +0 -16
  291. package/lib/types/wiki-trial-types.js.map +1 -1
  292. package/lib/types/wiki-trial.d.ts +40 -16
  293. package/lib/types/wiki-trial.d.ts.map +1 -1
  294. package/lib/types/wiki-trial.js +337 -224
  295. package/lib/types/wiki-trial.js.map +1 -1
  296. package/package.json +29 -20
  297. package/python/prepare-models.py +19 -0
  298. package/python/requirements.txt +8 -6
  299. package/scripts/doctor.mjs +13 -19
  300. package/scripts/environment.mjs +29 -0
  301. package/scripts/setup-embedding.sh +15 -5
  302. package/src/agent-behavior-rules.ts +37 -0
  303. package/src/axis-registry.ts +234 -27
  304. package/src/card-organize.ts +69 -75
  305. package/src/client/AgentBehaviorRulesCard.tsx +65 -0
  306. package/src/client/AttributionEventGuide.tsx +41 -39
  307. package/src/client/AxisRegistrySection.tsx +204 -164
  308. package/src/client/CompileDialogs.tsx +44 -29
  309. package/src/client/DeleteDocumentDialog.tsx +36 -5
  310. package/src/client/DirectoryPicker.tsx +0 -10
  311. package/src/client/EnvironmentSetup.tsx +243 -0
  312. package/src/client/ExpansionGraph.tsx +13 -7
  313. package/src/client/ExtractionTasksPage.tsx +578 -0
  314. package/src/client/GuideArchitectureCanvas.tsx +470 -0
  315. package/src/client/ImportFilesDialog.tsx +2 -13
  316. package/src/client/IntentLogic.tsx +2 -2
  317. package/src/client/IntentTab.tsx +118 -47
  318. package/src/client/KnowledgeBaseTab.tsx +277 -197
  319. package/src/client/KnowledgeLayerSection.tsx +24 -6
  320. package/src/client/KnowledgePanel.tsx +58 -93
  321. package/src/client/KnowledgeSettingsDrawer.tsx +90 -0
  322. package/src/client/ListPager.tsx +15 -5
  323. package/src/client/MaterializeTrajectoryView.tsx +124 -0
  324. package/src/client/NodeSettings.tsx +210 -89
  325. package/src/client/NormalizeTrajectoryView.tsx +136 -0
  326. package/src/client/PipelineNotice.tsx +26 -0
  327. package/src/client/PreviewSections.tsx +118 -51
  328. package/src/client/RecallPanel.tsx +89 -85
  329. package/src/client/TabGuide.tsx +159 -184
  330. package/src/client/TrialReportView.tsx +391 -214
  331. package/src/client/TypeDefinitionDrawer.tsx +150 -23
  332. package/src/client/WikiCardPreview.tsx +62 -16
  333. package/src/client/WikiFlowPanel.tsx +4 -4
  334. package/src/client/arch/NodeLogicPanel.tsx +3 -4
  335. package/src/client/arch/model.ts +6 -9
  336. package/src/client/architecture.tsx +23 -33
  337. package/src/client/attribution/AttributionGraph.tsx +10 -6
  338. package/src/client/attribution/AttributionTab.tsx +136 -84
  339. package/src/client/attribution/CaseView.tsx +3 -1
  340. package/src/client/attribution/EventView.tsx +108 -0
  341. package/src/client/attribution/RunView.tsx +2 -2
  342. package/src/client/attribution/StageChrome.tsx +34 -3
  343. package/src/client/attribution/ViewSwitch.tsx +20 -5
  344. package/src/client/attribution/graph-options.ts +47 -39
  345. package/src/client/attribution/model.ts +17 -10
  346. package/src/client/config-scope.ts +3 -3
  347. package/src/client/graph/palette.ts +165 -0
  348. package/src/client/index.ts +49 -42
  349. package/src/client/pages.css +459 -135
  350. package/src/client/pages.tsx +37 -9
  351. package/src/client/problem-domains.ts +1 -1
  352. package/src/client/session-remote.ts +9 -0
  353. package/src/client/styles.css +205 -62
  354. package/src/client/wiki-trial.ts +44 -3
  355. package/src/compiler/embedding-identity.ts +21 -0
  356. package/src/compiler/snapshot.ts +17 -0
  357. package/src/config-schema.ts +8 -3
  358. package/src/convert/host.ts +56 -21
  359. package/src/convert/ingest.ts +74 -10
  360. package/src/derive-contract.ts +14 -0
  361. package/src/diagnosis/cards.ts +13 -8
  362. package/src/diagnosis/run.ts +27 -17
  363. package/src/diagnosis/schema.ts +2 -2
  364. package/src/diagnosis/seed-source.ts +5 -4
  365. package/src/diagnosis/seed.ts +6 -8
  366. package/src/diagnosis/store.ts +138 -69
  367. package/src/diagnosis/types.ts +34 -15
  368. package/src/environment/dsh-python.ts +156 -0
  369. package/src/environment/install.ts +244 -0
  370. package/src/environment/manifest.ts +28 -0
  371. package/src/environment/model-catalog.ts +147 -0
  372. package/src/environment/process.ts +54 -0
  373. package/src/environment/selection.ts +29 -0
  374. package/src/environment/service.ts +348 -0
  375. package/src/environment/types.ts +45 -0
  376. package/src/events.ts +108 -0
  377. package/src/extract-contract.ts +42 -0
  378. package/src/extract-model.ts +10 -1
  379. package/src/extract-table-contract.ts +26 -0
  380. package/src/extract-table.ts +132 -0
  381. package/src/extraction-runs.ts +70 -11
  382. package/src/fs-atomic.ts +19 -2
  383. package/src/images.ts +203 -0
  384. package/src/index.ts +553 -205
  385. package/src/intent/README.md +8 -6
  386. package/src/intent/engine.ts +44 -6
  387. package/src/intent/host.ts +59 -18
  388. package/src/intent/scope.ts +125 -0
  389. package/src/intent/tools.ts +70 -28
  390. package/src/intent/types.ts +11 -4
  391. package/src/knowledge-bases.ts +56 -20
  392. package/src/knowledge-contract.ts +114 -0
  393. package/src/knowledge.ts +138 -69
  394. package/src/layout.ts +17 -37
  395. package/src/materialize-snapshot.ts +46 -0
  396. package/src/migrate.ts +75 -2
  397. package/src/model-text.ts +109 -4
  398. package/src/oplog.ts +7 -0
  399. package/src/organize-contract.ts +83 -0
  400. package/src/pipeline/engine.ts +323 -0
  401. package/src/pipeline/store.ts +147 -0
  402. package/src/pipeline/types.ts +93 -0
  403. package/src/present.ts +10 -4
  404. package/src/preset-ids.ts +6 -0
  405. package/src/python/embedding_worker.py +66 -2
  406. package/src/query-analysis.ts +68 -8
  407. package/src/recall-rank.ts +5 -5
  408. package/src/retention.ts +76 -0
  409. package/src/retrieval/vector-index.ts +45 -0
  410. package/src/retrieval/visibility.ts +14 -0
  411. package/src/runtime.ts +2708 -567
  412. package/src/settings-schema.ts +11 -8
  413. package/src/settings-volatile.ts +4 -4
  414. package/src/source-evidence.ts +37 -0
  415. package/src/store/axis-references.ts +96 -0
  416. package/src/store/derived.ts +70 -12
  417. package/src/store/knowledge-db.ts +248 -17
  418. package/src/store/migrate.ts +16 -6
  419. package/src/trajectory-types.ts +142 -0
  420. package/src/types.ts +61 -35
  421. package/src/wiki-finalize.ts +28 -0
  422. package/src/wiki-materialize.ts +25 -6
  423. package/src/wiki-trial-types.ts +83 -1
  424. package/src/wiki-trial.ts +357 -185
  425. package/lib/types/intent/preset-tools.d.ts +0 -37
  426. package/lib/types/intent/preset-tools.d.ts.map +0 -1
  427. package/lib/types/intent/preset-tools.js +0 -45
  428. package/lib/types/intent/preset-tools.js.map +0 -1
  429. package/preset/tita-data-agent/agent.cordis.yml +0 -394
  430. package/preset/tita-data-agent/preset.yml +0 -3
  431. package/scripts/install-agent-preset.mjs +0 -92
  432. package/scripts/sync-agent-preset.mjs +0 -188
  433. package/src/client/AgentPresetCard.tsx +0 -247
  434. package/src/client/agent-preset.ts +0 -121
  435. package/src/client/convert/ConvertPanel.tsx +0 -583
  436. package/src/client/convert/styles.css +0 -138
  437. package/src/client/convert/styles.ts +0 -49
  438. package/src/intent/preset-tools.ts +0 -48
@@ -34,17 +34,31 @@
34
34
  * ## 为什么存在记忆段
35
35
  *
36
36
  * 注册表**不可重建**:它是人确认过的命名决定(改 canonical name、并两个实体、加一个别名),
37
- * 重跑一次抽取不会得到同样的结果。所以它和操作日志、抽取轨迹一样落在
38
- * `<memoryDir>/knowledge.sqlite`,不在 `03_compiled/`。
37
+ * 重跑一次抽取不会得到同样的结果。所以它与 Knowledge 共享 `knowledge.db`,
38
+ * 经过同一个属主与 schema 版本入口校验。
39
39
  */
40
40
 
41
- import { mkdir } from 'node:fs/promises'
42
- import { dirname } from 'node:path'
41
+ import { createHash } from 'node:crypto'
43
42
  import { loadSqlite, type SqliteDatabase } from './fts.js'
43
+ import { KnowledgeDb } from './store/knowledge-db.js'
44
44
 
45
45
  /** 三个轴。三者地位相同(§3.1)—— 没有哪个更特殊。 */
46
46
  export type AxisKind = 'object' | 'action' | 'scene'
47
47
 
48
+ /** 当前参与关系扩展召回的轴关系。它连接相关对象,不表示父子层级。 */
49
+ export const AXIS_RELATION_TYPES = ['related_to'] as const
50
+ export type AxisRelationType = typeof AXIS_RELATION_TYPES[number]
51
+
52
+ export interface AxisRelation {
53
+ id: string
54
+ fromId: string
55
+ toId: string
56
+ relationType: AxisRelationType
57
+ evidence: string
58
+ createdAt: string
59
+ updatedAt: string
60
+ }
61
+
48
62
  /**
49
63
  * 一个轴实体的状态。
50
64
  *
@@ -215,6 +229,18 @@ CREATE TABLE IF NOT EXISTS entity_guidance (
215
229
  created_at TEXT NOT NULL
216
230
  );
217
231
  CREATE INDEX IF NOT EXISTS idx_axis_guidance_entity ON entity_guidance(entity_id);
232
+ CREATE TABLE IF NOT EXISTS axis_relations (
233
+ id TEXT PRIMARY KEY,
234
+ from_id TEXT NOT NULL,
235
+ to_id TEXT NOT NULL,
236
+ relation_type TEXT NOT NULL,
237
+ evidence TEXT NOT NULL DEFAULT '',
238
+ created_at TEXT NOT NULL,
239
+ updated_at TEXT NOT NULL,
240
+ UNIQUE(from_id, to_id, relation_type)
241
+ );
242
+ CREATE INDEX IF NOT EXISTS idx_axis_relation_from ON axis_relations(from_id, relation_type);
243
+ CREATE INDEX IF NOT EXISTS idx_axis_relation_to ON axis_relations(to_id, relation_type);
218
244
  `
219
245
 
220
246
  /**
@@ -225,20 +251,27 @@ CREATE INDEX IF NOT EXISTS idx_axis_guidance_entity ON entity_guidance(entity_id
225
251
  */
226
252
  export class AxisRegistry {
227
253
  private db: SqliteDatabase | null = null
254
+ private owner: KnowledgeDb | null = null
228
255
 
229
- constructor(private readonly file: string) {}
256
+ constructor(private readonly file: string, private readonly shared?: SqliteDatabase) {}
230
257
 
231
258
  async open(): Promise<boolean> {
232
259
  if (this.db !== null) return true
233
260
  const sqlite = await loadSqlite()
234
261
  if (sqlite === null) return false
235
262
  try {
236
- await mkdir(dirname(this.file), { recursive: true })
237
- this.db = new sqlite.DatabaseSync(this.file)
263
+ if (this.shared) this.db = this.shared
264
+ else {
265
+ const opened = await KnowledgeDb.open(this.file, sqlite.DatabaseSync)
266
+ if (!opened.ok) return false
267
+ this.owner = opened.db
268
+ this.db = opened.db.connection()
269
+ }
238
270
  this.db.exec(SCHEMA)
271
+ this.db.exec('CREATE TABLE IF NOT EXISTS axis_input_revision (id INTEGER PRIMARY KEY CHECK(id = 1), revision INTEGER NOT NULL); INSERT OR IGNORE INTO axis_input_revision VALUES (1, 0)')
239
272
  /*
240
- * 补列。这个库**没有 `user_version` 那一套**(它是记忆段里的一张注册表,不是知识本体),
241
- * 所以判据落在列上:缺就补,补齐之后什么都不做。`CREATE TABLE IF NOT EXISTS` 对
273
+ * 主库已通过 `user_version` 校验,再幂等补齐存量注册表的扩展列。缺就补。
274
+ * `CREATE TABLE IF NOT EXISTS` 对
242
275
  * 已经存在的表一个字都不改 —— 少了这一段,0068 之前建的库会永远没有这两列,
243
276
  * 而症状是「定义存进去了,下次打开又没了」。
244
277
  */
@@ -250,11 +283,57 @@ export class AxisRegistry {
250
283
  }
251
284
  return true
252
285
  } catch {
286
+ this.owner?.close()
287
+ this.owner = null
253
288
  this.db = null
254
289
  return false
255
290
  }
256
291
  }
257
292
 
293
+ inputRevision(): number {
294
+ return Number((this.db?.prepare('SELECT revision FROM axis_input_revision WHERE id = 1').get() as { revision?: number } | undefined)?.revision ?? 0)
295
+ }
296
+
297
+ /** Resolve against an isolated copy; commit only if the live registry still matches. */
298
+ async draft(): Promise<{ registry: AxisRegistry; commit: () => void; close: () => void }> {
299
+ const sqlite = await loadSqlite()
300
+ if (!sqlite || !this.db) throw new Error('AXIS_REGISTRY_UNAVAILABLE')
301
+ const live = this.db
302
+ const tables = ['axis_entities', 'axis_aliases', 'entity_guidance', 'axis_relations', 'axis_input_revision']
303
+ const read = (db: SqliteDatabase): Array<Array<Record<string, unknown>>> => tables.map(table => db.prepare(`SELECT * FROM ${table} ORDER BY rowid`).all() as Array<Record<string, unknown>>)
304
+ const before = read(live)
305
+ const copy = new sqlite.DatabaseSync(':memory:')
306
+ const registry = new AxisRegistry(':memory:', copy)
307
+ try {
308
+ if (!await registry.open()) throw new Error('AXIS_DRAFT_UNAVAILABLE')
309
+ tables.forEach((table, index) => {
310
+ copy.exec(`DELETE FROM ${table}`)
311
+ for (const row of before[index]!) {
312
+ const columns = Object.keys(row)
313
+ copy.prepare(`INSERT INTO ${table} (${columns.join(',')}) VALUES (${columns.map(() => '?').join(',')})`).run(...Object.values(row))
314
+ }
315
+ })
316
+ return {
317
+ registry,
318
+ commit: () => {
319
+ if (JSON.stringify(read(live)) !== JSON.stringify(before)) throw new Error('归一期间实体注册表已变化,请重试归一')
320
+ // Resolution only adds entities/aliases and fills model definitions.
321
+ for (const table of ['axis_entities', 'axis_aliases']) {
322
+ for (const row of copy.prepare(`SELECT * FROM ${table}`).all() as Array<Record<string, unknown>>) {
323
+ const columns = Object.keys(row)
324
+ const keys = table === 'axis_entities' ? ['id'] : ['entity_id', 'normalized', 'scope_id']
325
+ const updates = columns.filter(column => !keys.includes(column)).map(column => `${column}=excluded.${column}`)
326
+ live.prepare(`INSERT INTO ${table} (${columns.join(',')}) VALUES (${columns.map(() => '?').join(',')}) ON CONFLICT (${keys.join(',')}) DO UPDATE SET ${updates.join(',')}`).run(...Object.values(row))
327
+ }
328
+ }
329
+ },
330
+ close: () => { registry.close(); copy.close() },
331
+ }
332
+ } catch (cause) { registry.close(); copy.close(); throw cause }
333
+ }
334
+
335
+ private changedInput(): void { this.db?.prepare('UPDATE axis_input_revision SET revision = revision + 1 WHERE id = 1').run() }
336
+
258
337
  available(): boolean {
259
338
  return this.db !== null
260
339
  }
@@ -316,10 +395,19 @@ export class AxisRegistry {
316
395
 
317
396
  // 2 · 作用域内 alias
318
397
  if (parentId) {
319
- const aliased = this.findAlias(normalized, parentId)
320
- if (aliased) {
321
- this.learn(aliased, mention, normalized, parentId, input.description)
322
- return { mention, normalized, outcome: 'matched', entity: aliased, how: 'scoped-alias' }
398
+ const aliased = this.findAliases(input.kind, normalized, parentId)
399
+ if (aliased.length === 1) {
400
+ this.learn(aliased[0]!, mention, normalized, parentId, input.description)
401
+ return { mention, normalized, outcome: 'matched', entity: aliased[0]!, how: 'scoped-alias' }
402
+ }
403
+ /**
404
+ * 同一作用域里两个实体可以合法地共有同一条别名(`axis_aliases` 的主键是
405
+ * `(entity_id, normalized, scope_id)`,不是 `(normalized, scope_id)`)。这时候
406
+ * **不能取第一个** —— 本文件的规矩是「不唯一就不猜」,猜错的代价是把一条知识挂到
407
+ * 错误的对象上并一路传到卡名。全局那条路(下面)一直是这么做的,只有这里漏了。
408
+ */
409
+ if (aliased.length > 1) {
410
+ return { mention, normalized, outcome: 'ambiguous', candidates: aliased.slice().sort((left, right) => left.name.localeCompare(right.name)) }
323
411
  }
324
412
  }
325
413
 
@@ -447,6 +535,61 @@ export class AxisRegistry {
447
535
  })
448
536
  }
449
537
 
538
+ /** 列出对象关系;传实体 id 时只返回它的邻接关系。 */
539
+ async listRelations(entityId?: string): Promise<AxisRelation[]> {
540
+ if (this.db === null) throw new Error('AXIS_REGISTRY_UNAVAILABLE')
541
+ const params: Record<string, unknown> = {}
542
+ const filter = entityId === undefined ? '' : 'AND (r.from_id = :entityId OR r.to_id = :entityId)'
543
+ if (entityId !== undefined) params.entityId = entityId
544
+ const rows = this.db.prepare(`
545
+ SELECT r.* FROM axis_relations r
546
+ JOIN axis_entities a ON a.id = r.from_id
547
+ JOIN axis_entities b ON b.id = r.to_id
548
+ WHERE r.relation_type = 'related_to'
549
+ AND a.kind = 'object' AND b.kind = 'object'
550
+ AND a.status <> 'merged' AND b.status <> 'merged'
551
+ ${filter}
552
+ ORDER BY r.from_id, r.to_id
553
+ `).all(params) as Array<Record<string, unknown>>
554
+ return rows.map(rowToRelation)
555
+ }
556
+
557
+ /** 建立无方向的 related_to 边;同一个对象不能关联自己。 */
558
+ async addRelation(fromId: string, toId: string, evidence = ''): Promise<AxisRelation | null> {
559
+ if (this.db === null) throw new Error('AXIS_REGISTRY_UNAVAILABLE')
560
+ if (!fromId || !toId || fromId === toId) return null
561
+ const [from, to] = await Promise.all([this.get(fromId), this.get(toId)])
562
+ if (from === null || to === null || from.kind !== 'object' || to.kind !== 'object' || from.status === 'merged' || to.status === 'merged') return null
563
+ const [left, right] = [fromId, toId].sort((a, b) => a.localeCompare(b))
564
+ const now = new Date().toISOString()
565
+ const relation: AxisRelation = {
566
+ id: axisRelationId(left, right, 'related_to'),
567
+ fromId: left,
568
+ toId: right,
569
+ relationType: 'related_to',
570
+ evidence: evidence.trim(),
571
+ createdAt: now,
572
+ updatedAt: now,
573
+ }
574
+ this.db.prepare(`
575
+ INSERT INTO axis_relations (id, from_id, to_id, relation_type, evidence, created_at, updated_at)
576
+ VALUES (?, ?, ?, ?, ?, ?, ?)
577
+ ON CONFLICT(from_id, to_id, relation_type) DO UPDATE SET
578
+ evidence = CASE WHEN excluded.evidence <> '' THEN excluded.evidence ELSE axis_relations.evidence END,
579
+ updated_at = excluded.updated_at
580
+ `).run(relation.id, relation.fromId, relation.toId, relation.relationType, relation.evidence, relation.createdAt, relation.updatedAt)
581
+ const row = this.db.prepare('SELECT * FROM axis_relations WHERE from_id = ? AND to_id = ? AND relation_type = ?')
582
+ .get(left, right, 'related_to') as Record<string, unknown> | undefined
583
+ return row === undefined ? null : rowToRelation(row)
584
+ }
585
+
586
+ /** 移除一条对象关系。 */
587
+ async removeRelation(id: string): Promise<boolean> {
588
+ if (this.db === null) throw new Error('AXIS_REGISTRY_UNAVAILABLE')
589
+ const result = this.db.prepare('DELETE FROM axis_relations WHERE id = ?').run(id) as { changes?: unknown }
590
+ return Number(result.changes) > 0
591
+ }
592
+
450
593
  /** 每个轴各有多少个实体、其中几个待人确认。类型定义那一屏的计数读它。 */
451
594
  async stats(): Promise<Record<AxisKind, { total: number; candidate: number }>> {
452
595
  const empty = { total: 0, candidate: 0 }
@@ -475,6 +618,7 @@ export class AxisRegistry {
475
618
  const now = new Date().toISOString()
476
619
  this.db.prepare('UPDATE axis_entities SET name = ?, updated_at = ? WHERE id = ?').run(next, now, id)
477
620
  this.insertAlias(id, current.name, normalizeAxisName(current.name), current.scopeId, now)
621
+ this.changedInput()
478
622
  return this.get(id)
479
623
  }
480
624
 
@@ -490,6 +634,7 @@ export class AxisRegistry {
490
634
  const normalized = normalizeAxisName(alias)
491
635
  if (!normalized) return false
492
636
  this.insertAlias(id, alias.trim(), normalized, scopeId.trim(), new Date().toISOString())
637
+ this.changedInput()
493
638
  return true
494
639
  }
495
640
 
@@ -501,6 +646,7 @@ export class AxisRegistry {
501
646
  async setStatus(id: string, status: AxisStatus): Promise<AxisEntity | null> {
502
647
  if (this.db === null) throw new Error('AXIS_REGISTRY_UNAVAILABLE')
503
648
  this.db.prepare('UPDATE axis_entities SET status = ?, updated_at = ? WHERE id = ?').run(status, new Date().toISOString(), id)
649
+ this.changedInput()
504
650
  return this.get(id)
505
651
  }
506
652
 
@@ -511,18 +657,58 @@ export class AxisRegistry {
511
657
  * 它转成 `merged`,`scopeId` 改存目标 id —— 复用这一列而不是新加一列,因为
512
658
  * 「它现在归谁」与「它在谁的下面」对解析来说问的是同一件事。
513
659
  */
514
- async merge(fromId: string, intoId: string): Promise<boolean> {
660
+ async merge(fromId: string, intoId: string, redirect?: (kind: AxisKind) => void): Promise<boolean> {
515
661
  if (this.db === null) throw new Error('AXIS_REGISTRY_UNAVAILABLE')
516
662
  if (fromId === intoId) return false
517
663
  const [from, into] = [await this.get(fromId), await this.get(intoId)]
518
- if (from === null || into === null || from.kind !== into.kind) return false
664
+ if (from === null || into === null || from.kind !== into.kind || from.status === 'merged' || into.status === 'merged') return false
519
665
  const now = new Date().toISOString()
520
- this.db.prepare('UPDATE axis_entities SET status = ?, scope_id = ?, updated_at = ? WHERE id = ?').run('merged', intoId, now, fromId)
521
- // 被并者的别名与名字一起搬过去:按旧名、旧别名都要搜得到。
522
- this.insertAlias(intoId, from.name, normalizeAxisName(from.name), into.scopeId, now)
523
- const aliases = this.db.prepare('SELECT alias, scope_id FROM axis_aliases WHERE entity_id = ?').all(fromId) as Array<Record<string, unknown>>
524
- for (const row of aliases) this.insertAlias(intoId, String(row.alias), normalizeAxisName(String(row.alias)), String(row.scope_id ?? ''), now)
525
- return true
666
+ this.db.exec('SAVEPOINT axis_merge')
667
+ try {
668
+ this.db.prepare('UPDATE axis_entities SET status = ?, scope_id = ?, updated_at = ? WHERE id = ?').run('merged', intoId, now, fromId)
669
+ // 被并者的别名与名字一起搬过去:按旧名、旧别名都要搜得到。
670
+ this.insertAlias(intoId, from.name, normalizeAxisName(from.name), into.scopeId, now)
671
+ const aliases = this.db.prepare('SELECT alias, scope_id FROM axis_aliases WHERE entity_id = ?').all(fromId) as Array<Record<string, unknown>>
672
+ for (const row of aliases) this.insertAlias(intoId, String(row.alias), normalizeAxisName(String(row.alias)), String(row.scope_id ?? ''), now)
673
+ this.redirectRelations(fromId, intoId, now)
674
+ redirect?.(from.kind)
675
+ // Scope references on both canonical names and aliases move together.
676
+ this.db.prepare("UPDATE axis_entities SET scope_id = ? WHERE scope_id = ? AND status <> 'merged'").run(intoId, fromId)
677
+ this.db.prepare(`INSERT OR IGNORE INTO axis_aliases (entity_id, alias, normalized, scope_id, created_at)
678
+ SELECT entity_id, alias, normalized, ?, created_at FROM axis_aliases WHERE scope_id = ?`).run(intoId, fromId)
679
+ this.db.prepare('DELETE FROM axis_aliases WHERE scope_id = ?').run(fromId)
680
+ this.db.prepare('UPDATE entity_guidance SET entity_id = ? WHERE entity_id = ?').run(intoId, fromId)
681
+ this.changedInput()
682
+ this.db.exec('RELEASE axis_merge')
683
+ return true
684
+ } catch (cause) {
685
+ this.db.exec('ROLLBACK TO axis_merge; RELEASE axis_merge')
686
+ throw cause
687
+ }
688
+ }
689
+
690
+ /** 合并轴实体时转移相关关系,并折叠重复边与自环。 */
691
+ private redirectRelations(fromId: string, intoId: string, now: string): void {
692
+ if (this.db === null) return
693
+ const rows = this.db.prepare('SELECT * FROM axis_relations WHERE from_id = ? OR to_id = ?').all(fromId, fromId) as Array<Record<string, unknown>>
694
+ if (rows.length === 0) return
695
+ this.db.prepare('DELETE FROM axis_relations WHERE from_id = ? OR to_id = ?').run(fromId, fromId)
696
+ const insert = this.db.prepare(`
697
+ INSERT INTO axis_relations (id, from_id, to_id, relation_type, evidence, created_at, updated_at)
698
+ VALUES (?, ?, ?, ?, ?, ?, ?)
699
+ ON CONFLICT(from_id, to_id, relation_type) DO UPDATE SET
700
+ evidence = CASE WHEN excluded.evidence <> '' THEN excluded.evidence ELSE axis_relations.evidence END,
701
+ updated_at = excluded.updated_at
702
+ `)
703
+ for (const row of rows) {
704
+ const oldFrom = String(row.from_id)
705
+ const oldTo = String(row.to_id)
706
+ const pair = [oldFrom === fromId ? intoId : oldFrom, oldTo === fromId ? intoId : oldTo]
707
+ if (pair[0] === pair[1]) continue
708
+ const [left, right] = pair.sort((a, b) => a.localeCompare(b))
709
+ const type = String(row.relation_type) as AxisRelationType
710
+ insert.run(axisRelationId(left, right, type), left, right, type, String(row.evidence ?? ''), String(row.created_at ?? now), now)
711
+ }
526
712
  }
527
713
 
528
714
  /* ─────────────────────────────────────────── 定义(0068) ── */
@@ -535,6 +721,7 @@ export class AxisRegistry {
535
721
  const next = text.trim()
536
722
  this.db.prepare('UPDATE axis_entities SET description = ?, description_source = ?, updated_at = ? WHERE id = ?')
537
723
  .run(next, next ? 'human' : '', new Date().toISOString(), id)
724
+ this.changedInput()
538
725
  return this.get(id)
539
726
  }
540
727
 
@@ -625,6 +812,7 @@ export class AxisRegistry {
625
812
  }
626
813
  this.db.prepare('INSERT INTO entity_guidance (id, entity_id, kind, text, author, created_at) VALUES (?, ?, ?, ?, ?, ?)')
627
814
  .run(guidance.id, guidance.entityId, guidance.kind, guidance.text, guidance.author, guidance.createdAt)
815
+ this.changedInput()
628
816
  return guidance
629
817
  }
630
818
 
@@ -634,6 +822,7 @@ export class AxisRegistry {
634
822
  const before = this.db.prepare('SELECT id FROM entity_guidance WHERE id = ?').get(id) as { id?: unknown } | undefined
635
823
  if (before === undefined) return false
636
824
  this.db.prepare('DELETE FROM entity_guidance WHERE id = ?').run(id)
825
+ this.changedInput()
637
826
  return true
638
827
  }
639
828
 
@@ -722,7 +911,8 @@ export class AxisRegistry {
722
911
  }
723
912
 
724
913
  close(): void {
725
- this.db?.close()
914
+ this.owner?.close()
915
+ this.owner = null
726
916
  this.db = null
727
917
  }
728
918
 
@@ -736,11 +926,12 @@ export class AxisRegistry {
736
926
  return hit === undefined ? undefined : rowToEntity(hit)
737
927
  }
738
928
 
739
- private findAlias(normalized: string, scopeId: string): AxisEntity | undefined {
740
- const row = this.db!.prepare(
741
- "SELECT e.* FROM axis_aliases a JOIN axis_entities e ON e.id = a.entity_id WHERE a.normalized = ? AND a.scope_id = ? AND e.status <> 'merged' LIMIT 1",
742
- ).get(normalized, scopeId) as Record<string, unknown> | undefined
743
- return row === undefined ? undefined : rowToEntity(row)
929
+ /** 作用域内的别名命中。**可能不止一条** —— 调用方必须自己判唯一性。 */
930
+ private findAliases(kind: AxisKind, normalized: string, scopeId: string): AxisEntity[] {
931
+ const rows = this.db!.prepare(
932
+ "SELECT e.* FROM axis_aliases a JOIN axis_entities e ON e.id = a.entity_id WHERE a.normalized = ? AND a.scope_id = ? AND e.kind = ? AND e.status <> 'merged' ORDER BY e.id",
933
+ ).all(normalized, scopeId, kind) as Array<Record<string, unknown>>
934
+ return rows.map(rowToEntity)
744
935
  }
745
936
 
746
937
  /** 全局 canonical 名精确命中。它可能不止一个 —— 两个实体同名时就是歧义。 */
@@ -862,6 +1053,22 @@ function rowToEntity(row: Record<string, unknown>): AxisEntity {
862
1053
  }
863
1054
  }
864
1055
 
1056
+ function axisRelationId(fromId: string, toId: string, relationType: AxisRelationType): string {
1057
+ return `axis_rel_${createHash('sha256').update(`${relationType}:${fromId}:${toId}`).digest('hex').slice(0, 14)}`
1058
+ }
1059
+
1060
+ function rowToRelation(row: Record<string, unknown>): AxisRelation {
1061
+ return {
1062
+ id: String(row.id),
1063
+ fromId: String(row.from_id),
1064
+ toId: String(row.to_id),
1065
+ relationType: String(row.relation_type) as AxisRelationType,
1066
+ evidence: String(row.evidence ?? ''),
1067
+ createdAt: String(row.created_at),
1068
+ updatedAt: String(row.updated_at),
1069
+ }
1070
+ }
1071
+
865
1072
  function rowToGuidance(row: Record<string, unknown>): AxisGuidance {
866
1073
  return {
867
1074
  id: String(row.id),
@@ -63,10 +63,10 @@ import { createHash } from 'node:crypto'
63
63
  import type { CardKnowledgeRow } from './store/knowledge-db.js'
64
64
 
65
65
  /** 组织方案的版本。它进 `layout_hash` —— 改提示词就该让已有的方案失效重排。 */
66
- export const ORGANIZE_VERSION = 2
66
+ export const ORGANIZE_VERSION = 4
67
67
 
68
- /** 小节数量的上限。不是省略号:超过这个数,卡片就不再是「一个坐标的说明书」。 */
69
- export const MAX_SECTIONS = 12
68
+ /** 章节计划的资源上限,覆盖当前单次组织的 Knowledge 上限;不是预设目录数量。 */
69
+ export const MAX_SECTIONS = 200
70
70
 
71
71
  /** 小节标题的字符上限。 */
72
72
  export const MAX_SECTION_TITLE = 60
@@ -112,6 +112,9 @@ export interface OrganizeBlock {
112
112
  }
113
113
 
114
114
  export interface OrganizeSection {
115
+ sectionId?: string
116
+ retrievalNeed?: string
117
+ knowledgeIds?: string[]
115
118
  title: string
116
119
  blocks: OrganizeBlock[]
117
120
  }
@@ -144,11 +147,12 @@ export interface OrganizeInput {
144
147
  cardTitle: string
145
148
  coordinate: { object: string; action: string; scene: string }
146
149
  /** 这个坐标下的**全部**知识(库里已有的 ∪ 本轮新增的)。 */
147
- knowledge: Array<{ id: string; text: string }>
150
+ knowledge: Array<{ id: string; text: string; sourceHeadings?: string[] }>
148
151
  /**
149
152
  * 卡片骨架允许的小节(类型定义里那套)。**是建议,不是白名单** ——
150
153
  * 一个小节该不该存在,取决于这个坐标下真的有什么知识,而那是模型这一次才看到的。
151
154
  */
155
+ existingSections?: SectionRecord[]
152
156
  suggestedSections?: string[]
153
157
  /** 骨架里的建议小节。下发进提示词,好让模型优先用它们承载对应内容。 */
154
158
  requiredSections?: string[]
@@ -178,70 +182,9 @@ export interface OrganizeCheck {
178
182
  summaryCited: string[]
179
183
  }
180
184
 
181
- // ---------------------------------------------------------------- 提示词
182
185
 
183
- /**
184
- * 组织阶段的 system 提示词。
185
- *
186
- * 四条规则是这份提示词的全部:**只组织不创造**、**每条知识都要落地**、
187
- * **每句都要挂引用**。其余全是输出形状。
188
- */
189
- export function buildOrganizeSystem(): string {
190
- return [
191
- '你在把一批已经抽好的知识组织成一张 Wiki 卡片。',
192
- '',
193
- '这批知识属于同一个坐标(对象 × 动作 × 场景)。它们是**事实真源**,你不许新增、',
194
- '删除或改动它们说的事实。你能做的是:分小节、定顺序、把它们改写成读得下去的',
195
- '叙述、给卡片起一个自然的标题、写一句概述。',
196
- '',
197
- '## 三条规则',
198
- '',
199
- '1. **只组织,不创造。** 你写的每一句话,说的都必须是这批知识里已经有的事。',
200
- ' 你觉得「应该还有一步」而知识里没有 —— 那就不要写。',
201
- '2. **每条知识都要落地。** 输入给了几条,正文里就必须用上几条,一条都不能丢。',
202
- ' 可以把两条合成一行表格、合成一个步骤 —— 但它们的 id 都要挂上。',
203
- '3. **每句都要挂引用。** 陈述事实的句子末尾挂 `[^知识id]`,可以挂多个。',
204
- ' 只有很短的导语(「这一节讲取数流程」)可以不挂。长句子不挂引用 = 没有来源的',
205
- ' 断言,整份方案会被打回。',
206
- '4. **标题只做归纳,不要机械拼接。** 标题要能让人一眼知道卡片讲什么;不要照抄候选卡名,',
207
- ' 不要写成「对象的动作」「动作的动作」这类重复结构,不要加入知识里没有的新事实。',
208
- '',
209
- '## 可以做的改写',
210
- '',
211
- '- 缩写、重排、把陈述句改成祈使句(「楼层 ID 会变」→「不要写死楼层 ID」)。',
212
- '- 把两条同类的知识合成一张表或一个步骤。',
213
- '- 把字段名、SQL、URL 放进反引号。',
214
- '- 加很短的小节导语,把一节的内容串起来。',
215
- '',
216
- '## 输出形状',
217
- '',
218
- '只回一个 JSON 对象,不要散文,不要代码围栏。',
219
- '',
220
- '```json',
221
- '{',
222
- ' "title": "简洁、自然、可检索的卡片标题,不要机械拼接对象和动作",',
223
- ' "summary": "一句话说清这张卡讲什么。挂引用,如 [^k_abc]。",',
224
- ' "sections": [',
225
- ' {',
226
- ' "title": "一、这一节讲什么",',
227
- ' "blocks": [',
228
- ' { "kind": "prose", "text": "一段话。[^k_abc]" },',
229
- ' { "kind": "steps", "items": ["第一步。[^k_def]", "第二步。[^k_ghi]"] },',
230
- ' { "kind": "bullets", "items": ["一个要点。[^k_jkl]"] },',
231
- ' { "kind": "table", "header": ["", "甲", "乙"], "rows": [["来源", "A[^k_mno]", "B[^k_pqr]"]] },',
232
- ' { "kind": "note", "text": "一句提醒。[^k_stu]" }',
233
- ' ]',
234
- ' }',
235
- ' ]',
236
- '}',
237
- '```',
238
- '',
239
- '`kind` 只能是这五个之一:`prose`(一段话)、`steps`(有序步骤)、`bullets`(无序要点)、',
240
- '`table`(对比表)、`note`(提醒)。表格的每一行列数必须与表头一致。',
241
- '',
242
- '小节标题自己起,要具体(「二、取数流程」而不是「二、步骤」)。小节数量 1 到 12。',
243
- ].join('\n')
244
- }
186
+ /** 组织阶段的 system 段搬到了 `organize-contract.ts`(说明页要显示它,而这里带着 node:crypto)。 */
187
+ export { buildOrganizeSystem } from './organize-contract.js'
245
188
 
246
189
  /** 组织阶段的 user 提示词。知识清单在这里下发。 */
247
190
  export function buildOrganizePrompt(input: OrganizeInput): string {
@@ -258,23 +201,25 @@ export function buildOrganizePrompt(input: OrganizeInput): string {
258
201
  lines.push('')
259
202
 
260
203
  if (input.suggestedSections !== undefined && input.suggestedSections.length > 0) {
261
- lines.push('# 本库卡片骨架里的小节(建议)')
204
+ lines.push('# 历史章节参考(不是固定目录)')
262
205
  lines.push('')
263
206
  const required = new Set(input.requiredSections ?? [])
264
207
  for (const section of input.suggestedSections) {
265
208
  lines.push(`- ${section}${required.has(section) ? '(建议)' : ''}`)
266
209
  }
267
210
  lines.push('')
268
- lines.push('能用它们承载内容就用;装不下这批知识时可以自己起更贴切的小节名。')
211
+ lines.push('不要为了复用旧标题而合并不同检索需求。请按本批内容决定章节。')
269
212
  lines.push('')
270
213
  }
271
214
 
215
+ if (input.existingSections?.length) lines.push('# 已有章节身份(相同检索需求保留 section_id)', JSON.stringify(input.existingSections), '')
272
216
  lines.push(`# 这批知识(${input.knowledge.length} 条)`)
273
217
  lines.push('')
274
218
  lines.push('每条形如 `id :: 原文`。正文里的引用必须用这些 id。')
275
219
  lines.push('')
276
220
  for (const item of input.knowledge) {
277
221
  lines.push(`- \`${item.id}\` :: ${item.text.replace(/\s+/g, ' ').trim()}`)
222
+ if (item.sourceHeadings?.length) lines.push(` 原文标题路径(定位线索):${[...new Set(item.sourceHeadings)].join(';')}`)
278
223
  }
279
224
  lines.push('')
280
225
 
@@ -460,7 +405,7 @@ export function parseOrganize(raw: string): { plan?: OrganizePlan; problems: Org
460
405
  }
461
406
  problems.push({ code: 'ORGANIZE_BLOCK_KIND', message: `小节「${title}」里有一个认不出的块类型「${kind}」` })
462
407
  }
463
- if (blocks.length > 0) sections.push({ title, blocks })
408
+ if (blocks.length > 0) sections.push({ title, blocks, ...(asString(section.section_id) ? { sectionId: asString(section.section_id) } : {}), ...(asString(section.retrieval_need) ? { retrievalNeed: asString(section.retrieval_need) } : {}), ...(asStringList(section.knowledge_ids) ? { knowledgeIds: asStringList(section.knowledge_ids)! } : {}) })
464
409
  }
465
410
 
466
411
  if (sections.length === 0) problems.push({ code: 'ORGANIZE_NO_USABLE_SECTION', message: '没有一个可用的小节' })
@@ -488,6 +433,8 @@ export function checkOrganize(
488
433
  plan: OrganizePlan,
489
434
  known: string[],
490
435
  options: {
436
+ /** Check literal changes; this does not prove semantic entailment. */
437
+ knowledge?: Array<{ id: string; text: string }>
491
438
  /**
492
439
  * 正文里**可以**指向的其他卡片(`buildOrganizePrompt` 下发的那一份)。
493
440
  *
@@ -499,14 +446,36 @@ export function checkOrganize(
499
446
  } = {},
500
447
  ): OrganizeCheck {
501
448
  const problems: OrganizeProblem[] = []
449
+ for (const section of plan.sections) {
450
+ if (/\n|^#/.test(section.title) || section.blocks.some(block => blockTexts(block).some(text => /^#{1,2} /m.test(text)))) problems.push({ code: 'ORGANIZE_NESTED_H2', message: '章节正文不能插入一级或二级标题' })
451
+ }
452
+ for (const section of plan.sections) {
453
+ if (section.knowledgeIds) {
454
+ const actual = [...new Set(section.blocks.flatMap(block => blockTexts(block).flatMap(citationsIn)))].sort()
455
+ if (JSON.stringify([...new Set(section.knowledgeIds)].sort()) !== JSON.stringify(actual)) problems.push({ code: 'ORGANIZE_PLACEMENT_MISMATCH', message: `章节「${section.title}」的 knowledge_ids 与正文引用不一致` })
456
+ }
457
+ }
502
458
  const knownSet = new Set(known)
503
459
  const cited: string[] = []
504
460
  const seen = new Set<string>()
461
+ const byId = new Map(options.knowledge?.map(item => [item.id, item.text]) ?? [])
462
+ const literals = (text: string): string[] => [...text.replace(/\[\^[^\]]+\]/g, '').matchAll(/(?<![A-Za-z0-9_])(?:[<>≤≥]=?\s*)?[+-]?\d+(?:\.\d+)?(?:%|%)?/g)].map(match => match[0].replace(/\s/g, '').replace('%', '%'))
463
+ if (options.knowledge) {
464
+ const supported = new Set(literals(citationsIn(plan.summary).map(id => byId.get(id) ?? '').join('\n')))
465
+ const unsupported = literals(plan.summary).filter(value => !supported.has(value))
466
+ if (unsupported.length > 0) problems.push({ code: 'ORGANIZE_UNSUPPORTED_LITERAL', message: `概述的数值或比较条件没有引用依据:${[...new Set(unsupported)].join('、')}` })
467
+ }
505
468
 
506
469
  // 1 + 2:正文里的引用必须都存在,且每条知识都要落地。
507
470
  for (const section of plan.sections) {
508
471
  for (const block of section.blocks) {
509
472
  for (const text of blockTexts(block)) {
473
+ if (options.knowledge) {
474
+ const citedText = citationsIn(text).map(id => byId.get(id) ?? '').join('\n')
475
+ const supported = new Set(literals(citedText))
476
+ const unsupported = literals(text).filter(value => !supported.has(value))
477
+ if (unsupported.length > 0) problems.push({ code: 'ORGANIZE_UNSUPPORTED_LITERAL', message: `小节「${section.title}」的数值或比较条件没有引用依据:${[...new Set(unsupported)].join('、')}` })
478
+ }
510
479
  for (const id of citationsIn(text)) {
511
480
  if (!knownSet.has(id)) {
512
481
  problems.push({ code: 'ORGANIZE_UNKNOWN_CITATION', message: `小节「${section.title}」引用了这批知识里没有的 id「${id}」` })
@@ -708,16 +677,17 @@ export function placementsOf(plan: OrganizePlan, cited: string[]): CardKnowledge
708
677
  *
709
678
  * 输入是「知识集合 + 骨架 + 提示词版本」—— 三者任一变了才重排。所以:
710
679
  *
711
- * - 同一批知识再编译一次 → 指纹相同 → **不调模型**,复用库里那份方案。
712
- * - 这个坐标新进来一条知识 → 指纹变了 → 重排这一张卡(也只重排这一张)。
680
+ * - 知识与来源标题路径都没变 → 指纹相同 → **不调模型**,复用库里那份方案。
681
+ * - 知识或来源标题路径改变 → 指纹变化 → 重排这一张卡(也只重排这一张)。
713
682
  *
714
683
  * 少了它,每次编译都会把每张卡重排一遍:模型会给出**不一样**的排版,于是「重编一次、
715
684
  * 没变的卡片不产生写入」这条增量承诺失效,而人看到的是卡片莫名其妙地换了样子。
716
685
  */
717
- export function layoutHash(input: { knowledgeIds: string[]; profileId: string; version?: number }): string {
686
+ export function layoutHash(input: { knowledgeIds: string[]; profileId: string; knowledgeContext?: string[]; version?: number }): string {
718
687
  const ids = [...input.knowledgeIds].sort()
688
+ const context = [...(input.knowledgeContext ?? [])].sort()
719
689
  return createHash('sha256')
720
- .update(`v${input.version ?? ORGANIZE_VERSION}\u0001${input.profileId}\u0001${ids.join('\u0001')}`, 'utf8')
690
+ .update(`v${input.version ?? ORGANIZE_VERSION}\u0001${input.profileId}\u0001${ids.join('\u0001')}\u0001${context.join('\u0001')}`, 'utf8')
721
691
  .digest('hex')
722
692
  .slice(0, 16)
723
693
  }
@@ -734,12 +704,36 @@ export function layoutHash(input: { knowledgeIds: string[]; profileId: string; v
734
704
  * `origin` 是 `'fallback'` 时**下次编译会重试**:模型不可用是暂时的,而一次失败不该
735
705
  * 把这张卡永久钉在确定性布局上。代价是失败会重复花钱 —— 所以只在真的调用过之后才写。
736
706
  */
707
+ export interface SectionRecord {
708
+ id: string
709
+ title: string
710
+ retrievalNeed: string
711
+ knowledgeIds: string[]
712
+ }
713
+
714
+ /** Stable identity follows the retrieval need and supporting Knowledge, never section order. */
715
+ export function sectionRecords(plan: OrganizePlan, previous: SectionRecord[] = []): SectionRecord[] {
716
+ const used = new Set<string>()
717
+ return plan.sections.map(section => {
718
+ const knowledgeIds = [...new Set(section.blocks.flatMap(block => blockTexts(block).flatMap(citationsIn)))].sort()
719
+ const candidates = previous.filter(old => !used.has(old.id)).map(old => ({ old,
720
+ overlap: old.knowledgeIds.filter(id => knowledgeIds.includes(id)).length,
721
+ sameTitle: old.title === section.title,
722
+ })).filter(item => item.sameTitle || item.overlap > 0).sort((a, b) => Number(b.sameTitle) - Number(a.sameTitle) || b.overlap - a.overlap)
723
+ const matched = candidates.find(item => item.old.id === section.sectionId) ?? candidates[0]
724
+ const id = matched?.old.id ?? `sec_${createHash('sha256').update(JSON.stringify([knowledgeIds, section.retrievalNeed ?? section.title])).digest('hex').slice(0, 16)}`
725
+ used.add(id)
726
+ return { id, title: section.title, retrievalNeed: section.retrievalNeed ?? section.title, knowledgeIds }
727
+ })
728
+ }
729
+
737
730
  export interface CardLayoutRecord {
738
731
  /** 排序时用的骨架 id。骨架换了,小节名就该重排。 */
739
732
  profile: string
740
733
  /** `layoutHash()` 的结果。相同 = 知识集合没变 = 不重排。 */
741
734
  hash: string
742
735
  origin: 'model' | 'fallback'
736
+ sections?: SectionRecord[]
743
737
  }
744
738
 
745
739
  /**
@@ -776,7 +770,7 @@ export function layoutOf(metadata: Record<string, unknown> | undefined): CardLay
776
770
  const profile = typeof record.profile === 'string' ? record.profile : ''
777
771
  const origin = record.origin === 'model' || record.origin === 'fallback' ? record.origin : undefined
778
772
  if (hash === '' || origin === undefined) return undefined
779
- return { hash, profile, origin }
773
+ return { hash, profile, origin, ...(Array.isArray(record.sections) ? { sections: record.sections as SectionRecord[] } : {}) }
780
774
  }
781
775
 
782
776
  /**