kibi-cli 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (398) hide show
  1. package/dist/cli-operation-loader.d.ts.map +1 -1
  2. package/dist/cli-operation-loader.js +3 -0
  3. package/dist/cli-operation-metadata.d.ts +4 -0
  4. package/dist/cli-operation-metadata.d.ts.map +1 -1
  5. package/dist/cli-operation-metadata.js +5 -0
  6. package/dist/cli-protocol.d.ts.map +1 -1
  7. package/dist/cli-protocol.js +3 -2
  8. package/dist/cli-register-reporting.d.ts.map +1 -1
  9. package/dist/cli-register-reporting.js +4 -0
  10. package/dist/commands/aggregated-checks.d.ts +7 -0
  11. package/dist/commands/aggregated-checks.d.ts.map +1 -1
  12. package/dist/commands/aggregated-checks.js +25 -1
  13. package/dist/commands/check-diff.d.ts +5 -0
  14. package/dist/commands/check-diff.d.ts.map +1 -0
  15. package/dist/commands/check-diff.js +62 -0
  16. package/dist/commands/check-generated.d.ts.map +1 -1
  17. package/dist/commands/check-generated.js +41 -18
  18. package/dist/commands/check.d.ts.map +1 -1
  19. package/dist/commands/check.js +231 -86
  20. package/dist/commands/doctor.d.ts.map +1 -1
  21. package/dist/commands/doctor.js +168 -53
  22. package/dist/commands/github-init.d.ts +1 -1
  23. package/dist/commands/github-init.d.ts.map +1 -1
  24. package/dist/commands/github-init.js +10 -3
  25. package/dist/commands/init-helpers.d.ts +17 -2
  26. package/dist/commands/init-helpers.d.ts.map +1 -1
  27. package/dist/commands/init-helpers.js +123 -23
  28. package/dist/commands/init.d.ts.map +1 -1
  29. package/dist/commands/init.js +110 -15
  30. package/dist/commands/prove.d.ts.map +1 -1
  31. package/dist/commands/prove.js +10 -18
  32. package/dist/commands/sync/cache.d.ts +20 -1
  33. package/dist/commands/sync/cache.d.ts.map +1 -1
  34. package/dist/commands/sync/cache.js +51 -2
  35. package/dist/commands/sync/discovery.d.ts.map +1 -1
  36. package/dist/commands/sync/discovery.js +1 -1
  37. package/dist/commands/sync/manifest.d.ts.map +1 -1
  38. package/dist/commands/sync/manifest.js +3 -1
  39. package/dist/commands/sync/persistence.d.ts.map +1 -1
  40. package/dist/commands/sync/persistence.js +8 -0
  41. package/dist/commands/sync.d.ts.map +1 -1
  42. package/dist/commands/sync.js +11 -3
  43. package/dist/engine.d.ts +41 -1
  44. package/dist/engine.d.ts.map +1 -1
  45. package/dist/engine.js +377 -80
  46. package/dist/extractors/manifest.d.ts.map +1 -1
  47. package/dist/extractors/manifest.js +66 -28
  48. package/dist/extractors/markdown.d.ts +2 -0
  49. package/dist/extractors/markdown.d.ts.map +1 -1
  50. package/dist/extractors/markdown.js +14 -3
  51. package/dist/extractors/relationships.d.ts.map +1 -1
  52. package/dist/extractors/relationships.js +1 -0
  53. package/dist/extractors/symbols-coordinator.d.ts +7 -0
  54. package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
  55. package/dist/extractors/symbols-coordinator.js +75 -39
  56. package/dist/intent-search.d.ts.map +1 -1
  57. package/dist/intent-search.js +68 -29
  58. package/dist/operations/bootstrap/generate.d.ts.map +1 -1
  59. package/dist/operations/bootstrap/generate.js +4 -3
  60. package/dist/operations/modeling/model-requirement.d.ts +1 -1
  61. package/dist/operations/modeling/model-requirement.d.ts.map +1 -1
  62. package/dist/operations/modeling/model-requirement.js +176 -10
  63. package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
  64. package/dist/operations/modeling/predicate-applyplan.js +14 -2
  65. package/dist/operations/modeling/predicate-inference-1.js +1 -1
  66. package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
  67. package/dist/operations/modeling/predicate-loader.js +24 -0
  68. package/dist/operations/modeling/predicate-types.d.ts +4 -0
  69. package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
  70. package/dist/operations/modeling/predicate-vocabulary.d.ts +54 -0
  71. package/dist/operations/modeling/predicate-vocabulary.d.ts.map +1 -0
  72. package/dist/operations/modeling/predicate-vocabulary.js +169 -0
  73. package/dist/operations/modeling/requirement-types.d.ts +5 -0
  74. package/dist/operations/modeling/requirement-types.d.ts.map +1 -1
  75. package/dist/operations/modeling/vocabulary-alignment.d.ts +60 -0
  76. package/dist/operations/modeling/vocabulary-alignment.d.ts.map +1 -0
  77. package/dist/operations/modeling/vocabulary-alignment.js +178 -0
  78. package/dist/operations/mutation/contradictions.d.ts +3 -0
  79. package/dist/operations/mutation/contradictions.d.ts.map +1 -1
  80. package/dist/operations/mutation/contradictions.js +29 -0
  81. package/dist/operations/mutation/delete.d.ts.map +1 -1
  82. package/dist/operations/mutation/delete.js +33 -1
  83. package/dist/operations/mutation/predicate-vocabulary-guard.d.ts +8 -0
  84. package/dist/operations/mutation/predicate-vocabulary-guard.d.ts.map +1 -0
  85. package/dist/operations/mutation/predicate-vocabulary-guard.js +64 -0
  86. package/dist/operations/mutation/relationships.d.ts +1 -1
  87. package/dist/operations/mutation/relationships.d.ts.map +1 -1
  88. package/dist/operations/mutation/relationships.js +1 -0
  89. package/dist/operations/mutation/saga.d.ts +5 -0
  90. package/dist/operations/mutation/saga.d.ts.map +1 -1
  91. package/dist/operations/mutation/saga.js +10 -0
  92. package/dist/operations/mutation/serialization.d.ts.map +1 -1
  93. package/dist/operations/mutation/serialization.js +2 -0
  94. package/dist/operations/mutation/source-authoring.d.ts +6 -0
  95. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  96. package/dist/operations/mutation/source-authoring.js +213 -52
  97. package/dist/operations/mutation/strict-fact.d.ts.map +1 -1
  98. package/dist/operations/mutation/strict-fact.js +5 -0
  99. package/dist/operations/mutation/types.d.ts +17 -0
  100. package/dist/operations/mutation/types.d.ts.map +1 -1
  101. package/dist/operations/mutation/upsert.d.ts +6 -0
  102. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  103. package/dist/operations/mutation/upsert.js +77 -11
  104. package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
  105. package/dist/operations/mutation/validate-upsert.js +24 -1
  106. package/dist/operations/mutation/validation.d.ts.map +1 -1
  107. package/dist/operations/mutation/validation.js +16 -3
  108. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  109. package/dist/operations/planning/apply-plan.js +66 -26
  110. package/dist/operations/planning/compile-intent.d.ts.map +1 -1
  111. package/dist/operations/planning/compile-intent.js +3 -1
  112. package/dist/operations/proof/code-scope.d.ts +17 -0
  113. package/dist/operations/proof/code-scope.d.ts.map +1 -0
  114. package/dist/operations/proof/code-scope.js +60 -0
  115. package/dist/operations/proof/ingest-proof.d.ts +4 -2
  116. package/dist/operations/proof/ingest-proof.d.ts.map +1 -1
  117. package/dist/operations/proof/ingest-proof.js +208 -74
  118. package/dist/operations/proof/migrate-legacy-receipts.d.ts.map +1 -1
  119. package/dist/operations/proof/migrate-legacy-receipts.js +7 -5
  120. package/dist/operations/proof/prune-receipts.d.ts.map +1 -1
  121. package/dist/operations/proof/prune-receipts.js +7 -5
  122. package/dist/operations/semantic-advisor/analysis-receipt.d.ts.map +1 -1
  123. package/dist/operations/semantic-advisor/analysis-receipt.js +7 -1
  124. package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
  125. package/dist/operations/semantic-advisor/analyze-prose.js +26 -4
  126. package/dist/operations/semantic-advisor/clauses.d.ts +1 -0
  127. package/dist/operations/semantic-advisor/clauses.d.ts.map +1 -1
  128. package/dist/operations/semantic-advisor/clauses.js +7 -1
  129. package/dist/performance-trace.d.ts +17 -0
  130. package/dist/performance-trace.d.ts.map +1 -0
  131. package/dist/performance-trace.js +134 -0
  132. package/dist/plugins/approved-source-analyzers.d.ts +13 -0
  133. package/dist/plugins/approved-source-analyzers.d.ts.map +1 -0
  134. package/dist/plugins/approved-source-analyzers.generated.d.ts +71 -0
  135. package/dist/plugins/approved-source-analyzers.generated.d.ts.map +1 -0
  136. package/dist/plugins/approved-source-analyzers.generated.js +77 -0
  137. package/dist/plugins/approved-source-analyzers.js +3 -0
  138. package/dist/plugins/compose-semantic-classifier.d.ts +6 -1
  139. package/dist/plugins/compose-semantic-classifier.d.ts.map +1 -1
  140. package/dist/plugins/compose-semantic-classifier.js +8 -3
  141. package/dist/plugins/compose-vocabulary-alignment.d.ts +23 -0
  142. package/dist/plugins/compose-vocabulary-alignment.d.ts.map +1 -0
  143. package/dist/plugins/compose-vocabulary-alignment.js +141 -0
  144. package/dist/plugins/external-allowlist.d.ts +7 -0
  145. package/dist/plugins/external-allowlist.d.ts.map +1 -1
  146. package/dist/plugins/external-allowlist.js +13 -0
  147. package/dist/plugins/index.d.ts +2 -0
  148. package/dist/plugins/index.d.ts.map +1 -1
  149. package/dist/plugins/index.js +4 -0
  150. package/dist/plugins/maintenance-source-analysis.d.ts +14 -0
  151. package/dist/plugins/maintenance-source-analysis.d.ts.map +1 -0
  152. package/dist/plugins/maintenance-source-analysis.js +191 -0
  153. package/dist/plugins/registry.d.ts +3 -1
  154. package/dist/plugins/registry.d.ts.map +1 -1
  155. package/dist/plugins/registry.js +27 -1
  156. package/dist/plugins/source-analysis-service.d.ts +36 -1
  157. package/dist/plugins/source-analysis-service.d.ts.map +1 -1
  158. package/dist/plugins/source-analysis-service.js +184 -27
  159. package/dist/plugins/source-change-analysis.d.ts +15 -0
  160. package/dist/plugins/source-change-analysis.d.ts.map +1 -0
  161. package/dist/plugins/source-change-analysis.js +54 -0
  162. package/dist/plugins/source-classification.d.ts +41 -0
  163. package/dist/plugins/source-classification.d.ts.map +1 -0
  164. package/dist/plugins/source-classification.js +490 -0
  165. package/dist/prolog/codec.d.ts.map +1 -1
  166. package/dist/prolog/codec.js +2 -0
  167. package/dist/prolog/swipl-resolver.d.ts +88 -0
  168. package/dist/prolog/swipl-resolver.d.ts.map +1 -0
  169. package/dist/prolog/swipl-resolver.js +446 -0
  170. package/dist/prolog.d.ts +51 -1
  171. package/dist/prolog.d.ts.map +1 -1
  172. package/dist/prolog.js +145 -15
  173. package/dist/public/diagnostic-usage.d.ts.map +1 -1
  174. package/dist/public/diagnostic-usage.js +7 -25
  175. package/dist/public/impact/analyzer.d.ts.map +1 -1
  176. package/dist/public/impact/analyzer.js +56 -5
  177. package/dist/public/impact/diagnostics.d.ts.map +1 -1
  178. package/dist/public/impact/diagnostics.js +3 -1
  179. package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
  180. package/dist/public/impact/full-kb-quality.js +4 -12
  181. package/dist/public/impact/manifest.d.ts.map +1 -1
  182. package/dist/public/impact/manifest.js +15 -1
  183. package/dist/public/impact/source-changes.d.ts.map +1 -1
  184. package/dist/public/impact/source-changes.js +35 -27
  185. package/dist/public/impact/types.d.ts +2 -0
  186. package/dist/public/impact/types.d.ts.map +1 -1
  187. package/dist/public/operations/catalog.d.ts +80 -7
  188. package/dist/public/operations/catalog.d.ts.map +1 -1
  189. package/dist/public/operations/catalog.js +2 -0
  190. package/dist/public/operations/check-executor.d.ts.map +1 -1
  191. package/dist/public/operations/check-executor.js +10 -2
  192. package/dist/public/operations/check-helpers.d.ts +6 -0
  193. package/dist/public/operations/check-helpers.d.ts.map +1 -1
  194. package/dist/public/operations/check-helpers.js +21 -1
  195. package/dist/public/operations/contracts.d.ts.map +1 -1
  196. package/dist/public/operations/contracts.js +325 -8
  197. package/dist/public/operations/discovery-entities.d.ts +42 -2
  198. package/dist/public/operations/discovery-entities.d.ts.map +1 -1
  199. package/dist/public/operations/discovery-entities.js +195 -22
  200. package/dist/public/operations/discovery-executors.d.ts +1 -0
  201. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  202. package/dist/public/operations/discovery-executors.js +75 -46
  203. package/dist/public/operations/impact-review-preparation.d.ts +14 -0
  204. package/dist/public/operations/impact-review-preparation.d.ts.map +1 -0
  205. package/dist/public/operations/impact-review-preparation.js +379 -0
  206. package/dist/public/operations/index.d.ts +2 -0
  207. package/dist/public/operations/index.d.ts.map +1 -1
  208. package/dist/public/operations/index.js +2 -0
  209. package/dist/public/operations/migration-plan.d.ts +2 -0
  210. package/dist/public/operations/migration-plan.d.ts.map +1 -1
  211. package/dist/public/operations/migration-plan.js +50 -0
  212. package/dist/public/operations/node-ports.d.ts.map +1 -1
  213. package/dist/public/operations/node-ports.js +1 -35
  214. package/dist/public/operations/predicate-schema-conformance.d.ts +41 -0
  215. package/dist/public/operations/predicate-schema-conformance.d.ts.map +1 -0
  216. package/dist/public/operations/predicate-schema-conformance.js +242 -0
  217. package/dist/public/operations/prolog-json.d.ts.map +1 -1
  218. package/dist/public/operations/prolog-json.js +9 -3
  219. package/dist/public/operations/proof-receipt-projection.d.ts +5 -0
  220. package/dist/public/operations/proof-receipt-projection.d.ts.map +1 -0
  221. package/dist/public/operations/proof-receipt-projection.js +36 -0
  222. package/dist/public/operations/result-envelope.d.ts +34 -1
  223. package/dist/public/operations/result-envelope.d.ts.map +1 -1
  224. package/dist/public/operations/result-envelope.js +78 -2
  225. package/dist/public/operations/runtime-types.d.ts +1 -1
  226. package/dist/public/operations/runtime-types.d.ts.map +1 -1
  227. package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
  228. package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
  229. package/dist/public/operations/specs/check-rules.generated.js +8 -1
  230. package/dist/public/operations/specs/check.d.ts +1 -1
  231. package/dist/public/operations/specs/discovery.d.ts +7 -1
  232. package/dist/public/operations/specs/discovery.d.ts.map +1 -1
  233. package/dist/public/operations/specs/discovery.js +7 -1
  234. package/dist/public/operations/specs/impact-review-preparation.d.ts +47 -0
  235. package/dist/public/operations/specs/impact-review-preparation.d.ts.map +1 -0
  236. package/dist/public/operations/specs/impact-review-preparation.js +43 -0
  237. package/dist/public/operations/specs/mutation-schemas.d.ts +27 -4
  238. package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
  239. package/dist/public/operations/specs/mutation-schemas.js +21 -3
  240. package/dist/public/operations/specs/reporting.d.ts +8 -0
  241. package/dist/public/operations/specs/reporting.d.ts.map +1 -1
  242. package/dist/public/operations/specs/reporting.js +46 -15
  243. package/dist/public/operations/specs/upsert.d.ts +28 -5
  244. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  245. package/dist/public/operations/specs/upsert.js +1 -1
  246. package/dist/public/operations/symbol-repair-plan.d.ts.map +1 -1
  247. package/dist/public/operations/symbol-repair-plan.js +8 -3
  248. package/dist/public/operations/types.d.ts +1 -1
  249. package/dist/public/operations/types.d.ts.map +1 -1
  250. package/dist/public/schemas/entity.d.ts.map +1 -1
  251. package/dist/public/schemas/entity.js +22 -0
  252. package/dist/public/schemas/relationship.d.ts.map +1 -1
  253. package/dist/public/schemas/relationship.js +1 -0
  254. package/dist/public/skills/kibi-usage/SKILL.md +7 -3
  255. package/dist/public/skills/kibi-usage/resources/fact-lanes.md +4 -0
  256. package/dist/public/skills/kibi-usage/resources/operation-access.md +1 -0
  257. package/dist/public/skills/kibi-usage/resources/relationship-directions.md +35 -17
  258. package/dist/public/skills/kibi-usage/resources/source-authoring.md +36 -0
  259. package/dist/public/skills/kibi-usage/resources/workflows.md +52 -1
  260. package/dist/public/telemetry-acceptance.d.ts.map +1 -1
  261. package/dist/public/telemetry-acceptance.js +21 -5
  262. package/dist/relationships/shards.d.ts +6 -0
  263. package/dist/relationships/shards.d.ts.map +1 -1
  264. package/dist/relationships/shards.js +26 -19
  265. package/dist/schemas/entity.schema.json +32 -0
  266. package/dist/traceability/analysis-gate.d.ts +39 -0
  267. package/dist/traceability/analysis-gate.d.ts.map +1 -0
  268. package/dist/traceability/analysis-gate.js +51 -0
  269. package/dist/traceability/git-change-snapshot.d.ts +27 -0
  270. package/dist/traceability/git-change-snapshot.d.ts.map +1 -0
  271. package/dist/traceability/git-change-snapshot.js +411 -0
  272. package/dist/traceability/git-staged.d.ts +5 -1
  273. package/dist/traceability/git-staged.d.ts.map +1 -1
  274. package/dist/traceability/git-staged.js +16 -11
  275. package/dist/traceability/impact-evaluator.d.ts +54 -0
  276. package/dist/traceability/impact-evaluator.d.ts.map +1 -0
  277. package/dist/traceability/impact-evaluator.js +270 -0
  278. package/dist/traceability/impact-git-tree.d.ts +12 -0
  279. package/dist/traceability/impact-git-tree.d.ts.map +1 -0
  280. package/dist/traceability/impact-git-tree.js +46 -0
  281. package/dist/traceability/impact-knowledge.d.ts +16 -0
  282. package/dist/traceability/impact-knowledge.d.ts.map +1 -0
  283. package/dist/traceability/impact-knowledge.js +165 -0
  284. package/dist/traceability/impact-policy.d.ts +15 -0
  285. package/dist/traceability/impact-policy.d.ts.map +1 -0
  286. package/dist/traceability/impact-policy.js +48 -0
  287. package/dist/traceability/impact-policy.v1.schema.json +48 -0
  288. package/dist/traceability/impact-pr-diff.d.ts +5 -0
  289. package/dist/traceability/impact-pr-diff.d.ts.map +1 -0
  290. package/dist/traceability/impact-pr-diff.js +83 -0
  291. package/dist/traceability/impact-record-validation.d.ts +9 -0
  292. package/dist/traceability/impact-record-validation.d.ts.map +1 -0
  293. package/dist/traceability/impact-record-validation.js +113 -0
  294. package/dist/traceability/impact-review.d.ts +148 -0
  295. package/dist/traceability/impact-review.d.ts.map +1 -0
  296. package/dist/traceability/impact-review.js +494 -0
  297. package/dist/traceability/impact-review.schema.json +365 -0
  298. package/dist/traceability/impact-side-evidence.d.ts +13 -0
  299. package/dist/traceability/impact-side-evidence.d.ts.map +1 -0
  300. package/dist/traceability/impact-side-evidence.js +240 -0
  301. package/dist/traceability/reviewed-coordinate-refresh.d.ts +10 -0
  302. package/dist/traceability/reviewed-coordinate-refresh.d.ts.map +1 -0
  303. package/dist/traceability/reviewed-coordinate-refresh.js +49 -0
  304. package/dist/traceability/snapshot-knowledge.d.ts +4 -0
  305. package/dist/traceability/snapshot-knowledge.d.ts.map +1 -0
  306. package/dist/traceability/snapshot-knowledge.js +98 -0
  307. package/dist/traceability/staged-diagnostics.d.ts +1 -1
  308. package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
  309. package/dist/traceability/staged-entity-id-style.d.ts +12 -0
  310. package/dist/traceability/staged-entity-id-style.d.ts.map +1 -0
  311. package/dist/traceability/staged-entity-id-style.js +68 -0
  312. package/dist/traceability/staged-file-coverage.d.ts +23 -0
  313. package/dist/traceability/staged-file-coverage.d.ts.map +1 -1
  314. package/dist/traceability/staged-impact-contract.d.ts.map +1 -1
  315. package/dist/traceability/staged-impact-contract.js +2 -16
  316. package/dist/traceability/staged-symbols-manifest.d.ts +4 -0
  317. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  318. package/dist/traceability/staged-symbols-manifest.js +17 -6
  319. package/dist/traceability/symbol-extract.d.ts +3 -5
  320. package/dist/traceability/symbol-extract.d.ts.map +1 -1
  321. package/dist/traceability/symbol-extract.js +49 -34
  322. package/dist/traceability/temp-kb.d.ts +1 -1
  323. package/dist/traceability/temp-kb.d.ts.map +1 -1
  324. package/dist/traceability/temp-kb.js +57 -13
  325. package/dist/traceability/trusted-pr-event.d.ts +13 -0
  326. package/dist/traceability/trusted-pr-event.d.ts.map +1 -0
  327. package/dist/traceability/trusted-pr-event.js +105 -0
  328. package/dist/traceability/validate.js +1 -1
  329. package/dist/types/entities.d.ts +2 -0
  330. package/dist/types/entities.d.ts.map +1 -1
  331. package/dist/types/relationships.d.ts +1 -1
  332. package/dist/types/relationships.d.ts.map +1 -1
  333. package/dist/utils/canonical-path.d.ts +3 -0
  334. package/dist/utils/canonical-path.d.ts.map +1 -0
  335. package/dist/utils/canonical-path.js +33 -0
  336. package/dist/utils/entity-id-style.d.ts +27 -0
  337. package/dist/utils/entity-id-style.d.ts.map +1 -0
  338. package/dist/utils/entity-id-style.js +81 -0
  339. package/dist/utils/git-repository-context.d.ts +50 -0
  340. package/dist/utils/git-repository-context.d.ts.map +1 -0
  341. package/dist/utils/git-repository-context.js +259 -0
  342. package/dist/utils/rule-registry.d.ts +5 -0
  343. package/dist/utils/rule-registry.d.ts.map +1 -1
  344. package/dist/utils/rule-registry.generated.d.ts +37 -0
  345. package/dist/utils/rule-registry.generated.d.ts.map +1 -1
  346. package/dist/utils/rule-registry.generated.js +44 -0
  347. package/dist/utils/strict-modeling.d.ts +7 -0
  348. package/dist/utils/strict-modeling.d.ts.map +1 -1
  349. package/dist/utils/strict-modeling.js +11 -0
  350. package/package.json +14 -5
  351. package/schema/entities.pl +60 -1
  352. package/schema/relationships.pl +17 -4
  353. package/schema/validation.pl +118 -20
  354. package/src/public/diagnostic-usage.ts +16 -24
  355. package/src/public/impact/analyzer.ts +88 -5
  356. package/src/public/impact/diagnostics.ts +7 -5
  357. package/src/public/impact/full-kb-quality.ts +4 -16
  358. package/src/public/impact/manifest.ts +14 -1
  359. package/src/public/impact/source-changes.ts +39 -34
  360. package/src/public/impact/types.ts +5 -0
  361. package/src/public/operations/catalog.ts +2 -0
  362. package/src/public/operations/check-executor.ts +12 -1
  363. package/src/public/operations/check-helpers.ts +27 -1
  364. package/src/public/operations/contracts.ts +404 -9
  365. package/src/public/operations/discovery-entities.ts +292 -23
  366. package/src/public/operations/discovery-executors.ts +91 -55
  367. package/src/public/operations/impact-review-preparation.ts +548 -0
  368. package/src/public/operations/index.ts +2 -0
  369. package/src/public/operations/migration-plan.ts +57 -0
  370. package/src/public/operations/node-ports.ts +1 -39
  371. package/src/public/operations/predicate-schema-conformance.ts +362 -0
  372. package/src/public/operations/prolog-json.ts +9 -3
  373. package/src/public/operations/proof-receipt-projection.ts +39 -0
  374. package/src/public/operations/result-envelope.ts +114 -2
  375. package/src/public/operations/runtime-types.ts +1 -1
  376. package/src/public/operations/specs/check-rules.generated.ts +8 -1
  377. package/src/public/operations/specs/discovery.ts +8 -1
  378. package/src/public/operations/specs/impact-review-preparation.ts +46 -0
  379. package/src/public/operations/specs/mutation-schemas.ts +23 -3
  380. package/src/public/operations/specs/reporting.ts +69 -22
  381. package/src/public/operations/specs/upsert.ts +1 -1
  382. package/src/public/operations/symbol-repair-plan.ts +13 -3
  383. package/src/public/operations/types.ts +1 -0
  384. package/src/public/schemas/entity.ts +22 -0
  385. package/src/public/schemas/relationship.ts +1 -0
  386. package/src/public/skills/kibi-usage/SKILL.md +7 -3
  387. package/src/public/skills/kibi-usage/resources/fact-lanes.md +4 -0
  388. package/src/public/skills/kibi-usage/resources/operation-access.md +1 -0
  389. package/src/public/skills/kibi-usage/resources/relationship-directions.md +35 -17
  390. package/src/public/skills/kibi-usage/resources/source-authoring.md +36 -0
  391. package/src/public/skills/kibi-usage/resources/workflows.md +52 -1
  392. package/src/public/telemetry-acceptance.ts +37 -6
  393. package/src/schemas/entity.schema.json +32 -0
  394. package/src/schemas/relationship.schema.json +1 -0
  395. package/src/traceability/impact-policy.v1.schema.json +48 -0
  396. package/src/traceability/impact-review.schema.json +365 -0
  397. package/templates/github/kibi-badge.yml +0 -4
  398. package/templates/github/kibi-report.yml +0 -4
package/dist/engine.js CHANGED
@@ -6,20 +6,34 @@
6
6
  * the local socket; Prolog itself never crosses the process boundary.
7
7
  */
8
8
  import { spawn } from "node:child_process";
9
- import { createHash } from "node:crypto";
10
- import { chmodSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
9
+ import { createHash, randomUUID } from "node:crypto";
10
+ import { chmodSync, closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
11
11
  import * as net from "node:net";
12
12
  import * as os from "node:os";
13
13
  import * as path from "node:path";
14
+ import { performance } from "node:perf_hooks";
14
15
  import { fileURLToPath } from "node:url";
16
+ import { writePerformanceTraceEvent } from "./performance-trace.js";
15
17
  import { PrologProcess, resolveKbPlPath } from "./prolog.js";
16
18
  import { parseEntityFromList, parseListOfLists } from "./prolog/codec.js";
17
19
  import { retryAttachAfterBreakingStaleLock } from "./prolog/store-lock.js";
20
+ import { resolveSwipl, swiplIdentity } from "./prolog/swipl-resolver.js";
21
+ import { queryEntityChunks } from "./public/operations/discovery-entities.js";
18
22
  import { isValidBranchName } from "./utils/branch-resolver.js";
19
23
  import { branchStorePath, ensureBranchStoreManifest, } from "./utils/branch-store-locator.js";
24
+ import { canonicalFilesystemPath } from "./utils/canonical-path.js";
20
25
  export const ENGINE_PROTOCOL_VERSION = 1;
21
26
  export const ENGINE_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
22
27
  export const ENGINE_PACKAGE_VERSIONS = process.env.KIBI_PACKAGE_VERSIONS ?? "unknown";
28
+ /**
29
+ * Identity of the SWI-Prolog this process would launch (`<bin>@<version>`).
30
+ * The daemon and every client compare it; a daemon running a different
31
+ * resolved Prolog is replaced, never reused.
32
+ */
33
+ // implements REQ-prolog-daemon-runtime-identity
34
+ export function engineSwiplIdentity() {
35
+ return swiplIdentity(resolveSwipl());
36
+ }
23
37
  const ENGINE_QUERY_CACHE_MAX_ENTRIES = 128;
24
38
  const ENGINE_QUERY_CACHE_MAX_RESULT_BYTES = 8 * 1024 * 1024;
25
39
  const ENGINE_FRESHNESS_CACHE_MS = 100;
@@ -219,11 +233,19 @@ function frame(value) {
219
233
  header.writeUInt32BE(payload.byteLength, 0);
220
234
  return Buffer.concat([header, payload]);
221
235
  }
236
+ /**
237
+ * Swallow a write failure for a peer that is already gone.
238
+ *
239
+ * A socket write reports EPIPE/ECONNRESET asynchronously, so a surrounding
240
+ * try/catch cannot see it and Node raises it as an unhandled error. Passing a
241
+ * callback keeps the failure local to this write.
242
+ */
243
+ function ignoreWriteFailure() { }
222
244
  function writeSocketFrame(socket, value) {
223
245
  if (socket.destroyed || !socket.writable)
224
246
  return;
225
247
  try {
226
- socket.write(frame(value));
248
+ socket.write(frame(value), ignoreWriteFailure);
227
249
  }
228
250
  catch {
229
251
  // A client may disconnect while a queued request is finishing. The
@@ -250,17 +272,75 @@ export function runtimeDirectory() {
250
272
  const configured = process.env.KIBI_RUNTIME_DIR ??
251
273
  process.env.XDG_RUNTIME_DIR ??
252
274
  path.join(os.tmpdir(), "kibi-runtime");
253
- const candidates = [configured, path.join(os.tmpdir(), "kibi-runtime")];
275
+ // Windows named pipes have no Unix socket pathname budget. Preserve its
276
+ // filesystem location policy for the associated pid files.
277
+ if (process.platform === "win32") {
278
+ for (const candidate of [
279
+ configured,
280
+ path.join(os.tmpdir(), "kibi-runtime"),
281
+ ]) {
282
+ try {
283
+ mkdirSync(candidate, { recursive: true, mode: 0o700 });
284
+ const probe = path.join(candidate, `.kibi-write-${process.pid}`);
285
+ writeFileSync(probe, "", { mode: 0o600 });
286
+ unlinkSync(probe);
287
+ return candidate;
288
+ }
289
+ catch {
290
+ // Try the existing system-temp fallback.
291
+ }
292
+ }
293
+ throw new Error("Unable to create a writable Kibi engine runtime directory");
294
+ }
295
+ const euid = process.geteuid?.();
296
+ if (euid === undefined)
297
+ throw new Error("Kibi engine runtime euid unavailable");
298
+ let configuredIdentity;
299
+ try {
300
+ configuredIdentity = canonicalFilesystemPath(configured);
301
+ }
302
+ catch {
303
+ // An inaccessible or dangling configured path remains a distinct identity;
304
+ // it is never accepted as the socket directory below.
305
+ configuredIdentity = path.resolve(configured);
306
+ }
307
+ const configuredKey = createHash("sha256")
308
+ .update(configuredIdentity)
309
+ .digest("hex")
310
+ .slice(0, 32);
311
+ const candidates = [
312
+ configured,
313
+ path.join("/tmp", `kb-${euid}-${configuredKey}`),
314
+ ];
315
+ const maximumBytes = process.platform === "linux" ? 107 : 103;
316
+ const socketName = `kibi-${"0".repeat(32)}.sock`;
254
317
  for (const candidate of candidates) {
255
318
  try {
319
+ try {
320
+ if (lstatSync(candidate).isSymbolicLink())
321
+ continue;
322
+ }
323
+ catch (error) {
324
+ if (error.code !== "ENOENT")
325
+ throw error;
326
+ }
256
327
  mkdirSync(candidate, { recursive: true, mode: 0o700 });
328
+ const directory = lstatSync(candidate);
329
+ if (!directory.isDirectory() ||
330
+ directory.uid !== euid ||
331
+ (directory.mode & 0o077) !== 0)
332
+ continue;
333
+ const canonical = canonicalFilesystemPath(candidate);
334
+ if (Buffer.byteLength(path.join(canonical, socketName), "utf8") >
335
+ maximumBytes)
336
+ continue;
257
337
  // XDG_RUNTIME_DIR can exist but be mounted read-only (for example in a
258
338
  // constrained container). Probe the directory before using it for the
259
339
  // socket, start lock, and pid files so auto-start fails over cleanly.
260
- const probe = path.join(candidate, `.kibi-write-${process.pid}`);
261
- writeFileSync(probe, "", { mode: 0o600 });
340
+ const probe = path.join(canonical, `.kibi-write-${randomUUID()}`);
341
+ writeFileSync(probe, "", { mode: 0o600, flag: "wx" });
262
342
  unlinkSync(probe);
263
- return candidate;
343
+ return canonical;
264
344
  }
265
345
  catch {
266
346
  // Try the private system-temp fallback below.
@@ -318,18 +398,11 @@ export function fsyncJournaledBranchStore(branchPath) {
318
398
  }
319
399
  // implements REQ-core-journaled-engine-persistence
320
400
  export function engineSocketPath(workspaceRoot, branch) {
321
- const canonicalRoot = (() => {
322
- try {
323
- return realpathSync(workspaceRoot);
324
- }
325
- catch {
326
- return path.resolve(workspaceRoot);
327
- }
328
- })();
401
+ const canonicalRoot = canonicalFilesystemPath(workspaceRoot);
329
402
  const key = createHash("sha256")
330
403
  .update(`${canonicalRoot}\0${branch}`)
331
- // 128 bits keeps collision risk negligible while staying below the
332
- // roughly 108-byte sockaddr_un limit even in a nested XDG runtime path.
404
+ // Retain 128 bits of workspace/branch identity; runtimeDirectory budgets
405
+ // the complete pathname rather than shortening this identity.
333
406
  .digest("hex")
334
407
  .slice(0, 32);
335
408
  if (process.platform === "win32") {
@@ -666,22 +739,36 @@ export class EngineClient {
666
739
  if (!isValidBranchName(options.branch)) {
667
740
  throw new Error(`Invalid Kibi engine branch name: ${options.branch}`);
668
741
  }
669
- const resolvedWorkspaceRoot = path.resolve(options.workspaceRoot);
670
- try {
671
- this.workspaceRoot = realpathSync(resolvedWorkspaceRoot);
672
- }
673
- catch {
674
- this.workspaceRoot = resolvedWorkspaceRoot;
675
- }
742
+ this.workspaceRoot = canonicalFilesystemPath(options.workspaceRoot);
676
743
  this.branch = options.branch;
677
744
  this.timeout = options.timeout ?? 120_000;
678
745
  this.allowPublicationLock = options.allowPublicationLock ?? false;
679
746
  }
680
747
  async start(allowSpawn = true) {
681
748
  const wasConnected = this.socket !== null && !this.socket.destroyed;
682
- await this.connect(allowSpawn);
683
- if (!wasConnected && this.socket !== null) {
684
- await this.reconcileAttachment();
749
+ for (let attempt = 0;; attempt += 1) {
750
+ await this.connect(allowSpawn);
751
+ if (wasConnected || this.socket === null)
752
+ return;
753
+ try {
754
+ await this.reconcileRuntime();
755
+ await this.reconcileAttachment();
756
+ return;
757
+ }
758
+ catch (error) {
759
+ // A listener can begin draining after the kernel accepts a connection
760
+ // but before the read-only runtime and attachment handshakes finish.
761
+ // Retry those handshakes once; request() never retries an application
762
+ // RPC.
763
+ if (attempt > 0 ||
764
+ !allowSpawn ||
765
+ !(error instanceof Error) ||
766
+ error.message !== "Kibi engine connection closed" ||
767
+ (this.socket !== null && !this.socket.destroyed))
768
+ throw error;
769
+ this.socket?.destroy();
770
+ this.socket = null;
771
+ }
685
772
  }
686
773
  }
687
774
  async connect(allowSpawn = true) {
@@ -720,6 +807,22 @@ export class EngineClient {
720
807
  }
721
808
  }
722
809
  if (this.socket === null) {
810
+ // A draining daemon removes its listener before saving, but retains
811
+ // its PID until the branch lock is released. Do not start a second
812
+ // writer while that process is still completing shutdown.
813
+ const existingPid = this.getPid();
814
+ if (existingPid > 0) {
815
+ let alive = false;
816
+ try {
817
+ process.kill(existingPid, 0);
818
+ alive = true;
819
+ }
820
+ catch { }
821
+ if (alive) {
822
+ await new Promise((resolve) => setTimeout(resolve, 50));
823
+ continue;
824
+ }
825
+ }
723
826
  lock = tryAcquireStartLock(lockPath);
724
827
  if (lock === null) {
725
828
  await new Promise((resolve) => setTimeout(resolve, 50));
@@ -771,7 +874,10 @@ export class EngineClient {
771
874
  this.attachSocket(this.socket);
772
875
  }
773
876
  attachSocket(socket) {
877
+ this.inputBuffer = Buffer.alloc(0);
774
878
  socket.on("data", (chunk) => {
879
+ if (this.socket !== socket)
880
+ return;
775
881
  try {
776
882
  const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
777
883
  this.inputBuffer = parseFrames(Buffer.concat([this.inputBuffer, bytes]), (value) => this.handleResponse(value));
@@ -781,6 +887,8 @@ export class EngineClient {
781
887
  }
782
888
  });
783
889
  socket.on("close", () => {
890
+ if (this.socket !== socket)
891
+ return;
784
892
  this.socket = null;
785
893
  const error = new Error("Kibi engine connection closed");
786
894
  for (const pending of this.pending.values()) {
@@ -789,6 +897,8 @@ export class EngineClient {
789
897
  this.pending.clear();
790
898
  });
791
899
  socket.on("error", (error) => {
900
+ if (this.socket !== socket)
901
+ return;
792
902
  for (const pending of this.pending.values()) {
793
903
  this.settlePending(pending, () => pending.reject(error));
794
904
  }
@@ -850,9 +960,10 @@ export class EngineClient {
850
960
  id,
851
961
  protocolVersion: ENGINE_PROTOCOL_VERSION,
852
962
  packageVersions: ENGINE_PACKAGE_VERSIONS,
963
+ prologIdentity: engineSwiplIdentity(),
853
964
  workspaceRoot: this.workspaceRoot,
854
965
  branch: this.branch,
855
- }));
966
+ }), ignoreWriteFailure);
856
967
  }
857
968
  catch (error) {
858
969
  const pending = this.pending.get(id);
@@ -897,6 +1008,11 @@ export class EngineClient {
897
1008
  return this.query(`rdf_transaction((${normalized.join(", ")}))`);
898
1009
  }
899
1010
  async queryEntities(input, signal) {
1011
+ // Full entities carry receipt histories; a caller-sized page (up to 100k
1012
+ // rows) is split into bounded engine requests.
1013
+ return queryEntityChunks((chunk) => this.queryEntityChunk(chunk, signal), input);
1014
+ }
1015
+ async queryEntityChunk(input, signal) {
900
1016
  const result = await this.command({
901
1017
  version: 1,
902
1018
  kind: "entities",
@@ -954,9 +1070,10 @@ export class EngineClient {
954
1070
  cancelOf: requestId,
955
1071
  protocolVersion: ENGINE_PROTOCOL_VERSION,
956
1072
  packageVersions: ENGINE_PACKAGE_VERSIONS,
1073
+ prologIdentity: engineSwiplIdentity(),
957
1074
  workspaceRoot: this.workspaceRoot,
958
1075
  branch: this.branch,
959
- }));
1076
+ }), ignoreWriteFailure);
960
1077
  }
961
1078
  async nextSolution() {
962
1079
  const result = this.lastResult;
@@ -996,6 +1113,36 @@ export class EngineClient {
996
1113
  return null;
997
1114
  return parseEngineAttachmentIdentity(status.bindings.JsonString);
998
1115
  }
1116
+ async daemonRuntimeMatches() {
1117
+ const reply = await this.request({
1118
+ method: "handshake",
1119
+ });
1120
+ return reply?.prologIdentity === engineSwiplIdentity();
1121
+ }
1122
+ /**
1123
+ * A live daemon keeps the SWI-Prolog it was started with. When this client
1124
+ * resolves a different executable or version (KIBI_SWIPL changed, the
1125
+ * bundled package was upgraded, a pre-handshake daemon), the daemon is
1126
+ * stopped and replaced; it is never reused.
1127
+ */
1128
+ // implements REQ-prolog-daemon-runtime-identity
1129
+ async reconcileRuntime() {
1130
+ if (this.replacingStaleDaemon || this.socket === null)
1131
+ return;
1132
+ if (await this.daemonRuntimeMatches())
1133
+ return;
1134
+ this.replacingStaleDaemon = true;
1135
+ try {
1136
+ await this.shutdownConnectedDaemon();
1137
+ await this.connect(true);
1138
+ if (!(await this.daemonRuntimeMatches())) {
1139
+ throw new Error(`Kibi engine is running a different SWI-Prolog than this client resolved (${engineSwiplIdentity()}); run 'kibi engine stop' and retry`);
1140
+ }
1141
+ }
1142
+ finally {
1143
+ this.replacingStaleDaemon = false;
1144
+ }
1145
+ }
999
1146
  async reconcileAttachment() {
1000
1147
  if (this.replacingStaleDaemon || this.socket === null)
1001
1148
  return;
@@ -1020,26 +1167,40 @@ export class EngineClient {
1020
1167
  }
1021
1168
  async shutdownConnectedDaemon() {
1022
1169
  const socketPath = engineSocketPath(this.workspaceRoot, this.branch);
1170
+ let requestError;
1023
1171
  if (!this.isRunning()) {
1024
1172
  if (existsSync(socketPath)) {
1025
1173
  throw new Error(`Kibi engine socket remains present but is not reachable: ${socketPath}`);
1026
1174
  }
1027
- return;
1028
- }
1029
- let requestError;
1030
- try {
1031
- await this.request({ method: "stop" });
1175
+ const existingPid = this.getPid();
1176
+ if (existingPid <= 0)
1177
+ return;
1178
+ try {
1179
+ process.kill(existingPid, 0);
1180
+ }
1181
+ catch {
1182
+ return;
1183
+ }
1184
+ // An already-draining daemon can have no listener while its durability
1185
+ // save still owns the store. A stop caller must wait for that handoff.
1032
1186
  }
1033
- catch (error) {
1034
- requestError = error;
1187
+ else {
1188
+ try {
1189
+ await this.request({ method: "stop" });
1190
+ }
1191
+ catch (error) {
1192
+ requestError = error;
1193
+ }
1194
+ this.socket?.destroy();
1195
+ this.socket = null;
1035
1196
  }
1036
- this.socket?.destroy();
1037
- this.socket = null;
1197
+ const pidPath = enginePidPath(this.workspaceRoot, this.branch);
1038
1198
  const deadline = Date.now() + 5_000;
1039
- while (existsSync(socketPath) && Date.now() < deadline) {
1199
+ while ((existsSync(socketPath) || existsSync(pidPath)) &&
1200
+ Date.now() < deadline) {
1040
1201
  await new Promise((resolve) => setTimeout(resolve, 25));
1041
1202
  }
1042
- if (existsSync(socketPath)) {
1203
+ if (existsSync(socketPath) || existsSync(pidPath)) {
1043
1204
  const detail = requestError instanceof Error ? `: ${requestError.message}` : "";
1044
1205
  throw new Error(`Kibi engine did not stop within 5 seconds${detail}`);
1045
1206
  }
@@ -1065,11 +1226,8 @@ export class EngineClient {
1065
1226
  throw new Error("Unable to start Kibi engine for stop request");
1066
1227
  }
1067
1228
  }
1068
- if (!this.isRunning()) {
1069
- if (existsSync(socketPath)) {
1070
- throw new Error(`Kibi engine socket remains present but is not reachable: ${socketPath}`);
1071
- }
1072
- return;
1229
+ if (!this.isRunning() && existsSync(socketPath)) {
1230
+ throw new Error(`Kibi engine socket remains present but is not reachable: ${socketPath}`);
1073
1231
  }
1074
1232
  await this.shutdownConnectedDaemon();
1075
1233
  }
@@ -1286,37 +1444,116 @@ function recoverInterruptedGeneration(branchPath) {
1286
1444
  }
1287
1445
  }
1288
1446
  }
1289
- // implements REQ-core-journaled-engine-lifecycle
1290
- export async function runEngineDaemon(options) {
1291
- if (!isValidBranchName(options.branch)) {
1292
- throw new Error(`Invalid Kibi engine branch name: ${options.branch}`);
1447
+ function isEngineLifecycleRequest(request) {
1448
+ if (request.method === "stop" ||
1449
+ request.method === "cancel" ||
1450
+ request.method === "handshake")
1451
+ return true;
1452
+ const kind = request.command?.kind;
1453
+ return (request.method === "command" &&
1454
+ (kind === "stop" || kind === "cancel" || kind === "lifecycle"));
1455
+ }
1456
+ const ENGINE_PRELOADED_MODULES = [
1457
+ ["status.pl", "status"],
1458
+ ["discovery.pl", "discovery"],
1459
+ ];
1460
+ const USE_MODULE_GOAL = /^use_module\('(?:[^']|'')*'\)$/u;
1461
+ /**
1462
+ * Owns the daemon's interactive SWI session: the attached branch store plus
1463
+ * every module loaded into it. An overflow or timeout terminates the SWI
1464
+ * child; the next request recycles the session (restart, reattach, reload
1465
+ * modules) instead of letting the port degrade into isolated one-shot
1466
+ * processes that have lost the attachment and module state.
1467
+ */
1468
+ // implements REQ-core-journaled-engine-persistence
1469
+ export class EngineSession {
1470
+ prolog;
1471
+ branchPath;
1472
+ modules = [];
1473
+ constructor(prolog, branchPath) {
1474
+ this.prolog = prolog;
1475
+ this.branchPath = branchPath;
1476
+ const coreModuleDir = path.dirname(resolveKbPlPath());
1477
+ for (const [fileName, label] of ENGINE_PRELOADED_MODULES) {
1478
+ const modulePath = path.join(coreModuleDir, fileName);
1479
+ this.modules.push({
1480
+ goal: `use_module('${quoteProlog(modulePath.replaceAll("\\", "/"))}')`,
1481
+ label,
1482
+ });
1483
+ }
1293
1484
  }
1294
- const branchPath = ensureBranchStoreManifest(options.workspaceRoot, options.branch);
1295
- await ensureJournaledBranchStoreAsync(branchPath);
1296
- const prolog = new PrologProcess({ timeout: 120_000, oneShot: false });
1297
- await prolog.start();
1298
- const attached = await prolog.query(`kb_attach('${quoteProlog(branchPath)}')`);
1299
- if (!attached.success) {
1300
- // The daemon hosts the branch store for every later request, so a
1301
- // store-locked startup with a provably dead holder is auto-healed here
1302
- // exactly like the CLI runtime path; live holders abort the startup
1303
- // with the holder identity surfaced.
1304
- const recovered = await retryAttachAfterBreakingStaleLock(prolog, branchPath, attached);
1305
- if (!recovered.success)
1306
- throw new Error(recovered.error ?? "Failed to attach branch KB");
1307
- }
1308
- const attachedIdentity = readEngineAttachmentIdentity(branchPath);
1309
- const coreModuleDir = path.dirname(resolveKbPlPath());
1310
- for (const [fileName, label] of [
1311
- ["status.pl", "status"],
1312
- ["discovery.pl", "discovery"],
1313
- ]) {
1314
- const modulePath = path.join(coreModuleDir, fileName);
1315
- const loaded = await prolog.query(`use_module('${quoteProlog(modulePath.replaceAll("\\", "/"))}')`);
1316
- if (!loaded.success) {
1317
- throw new Error(loaded.error ?? `Failed to load Kibi ${label} module`);
1485
+ async boot() {
1486
+ await this.prolog.start();
1487
+ const attached = await this.prolog.query(`kb_attach('${quoteProlog(this.branchPath)}')`);
1488
+ if (!attached.success) {
1489
+ // The daemon hosts the branch store for every later request, so a
1490
+ // store-locked startup with a provably dead holder is auto-healed here
1491
+ // exactly like the CLI runtime path; live holders abort the startup
1492
+ // with the holder identity surfaced.
1493
+ const recovered = await retryAttachAfterBreakingStaleLock(this.prolog, this.branchPath, attached);
1494
+ if (!recovered.success)
1495
+ throw new Error(recovered.error ?? "Failed to attach branch KB");
1496
+ }
1497
+ for (const { goal, label } of this.modules) {
1498
+ const loaded = await this.prolog.query(goal);
1499
+ if (!loaded.success) {
1500
+ throw new Error(loaded.error ?? `Failed to load Kibi ${label} module`);
1501
+ }
1318
1502
  }
1319
1503
  }
1504
+ /** Remember a successful client module load so a recycle restores it. */
1505
+ recordModuleLoad(goal, result) {
1506
+ const normalized = goal.trim().replace(/\.+\s*$/u, "");
1507
+ if (result.success &&
1508
+ USE_MODULE_GOAL.test(normalized) &&
1509
+ !this.modules.some((module) => module.goal === normalized)) {
1510
+ this.modules.push({ goal: normalized, label: "client-loaded" });
1511
+ }
1512
+ }
1513
+ needsRecycle() {
1514
+ return this.prolog.needsRestart();
1515
+ }
1516
+ /**
1517
+ * Restart a lost interactive session. Returns true when a recycle ran. A
1518
+ * failed recycle leaves the process stopped so the next request retries.
1519
+ */
1520
+ async ensureLive() {
1521
+ if (!this.prolog.needsRestart())
1522
+ return false;
1523
+ console.error("[KIBI] engine Prolog session was lost (output overflow, timeout, or crash); restarting and reattaching the branch store");
1524
+ try {
1525
+ await this.boot();
1526
+ }
1527
+ catch (error) {
1528
+ await this.prolog.terminate().catch(() => undefined);
1529
+ const message = error instanceof Error ? error.message : String(error);
1530
+ throw new Error(`Kibi engine failed to recycle its Prolog session: ${message}`);
1531
+ }
1532
+ return true;
1533
+ }
1534
+ }
1535
+ // implements REQ-core-journaled-engine-persistence
1536
+ export async function runEngineDaemon(requestedOptions) {
1537
+ if (!isValidBranchName(requestedOptions.branch)) {
1538
+ throw new Error(`Invalid Kibi engine branch name: ${requestedOptions.branch}`);
1539
+ }
1540
+ const options = {
1541
+ ...requestedOptions,
1542
+ workspaceRoot: canonicalFilesystemPath(requestedOptions.workspaceRoot),
1543
+ };
1544
+ const branchPath = ensureBranchStoreManifest(options.workspaceRoot, options.branch);
1545
+ await ensureJournaledBranchStoreAsync(branchPath);
1546
+ const prolog = new PrologProcess({
1547
+ timeout: 120_000,
1548
+ oneShot: false,
1549
+ ...(options.maxOutputBytes === undefined
1550
+ ? {}
1551
+ : { maxOutputBytes: options.maxOutputBytes }),
1552
+ });
1553
+ const session = new EngineSession(prolog, branchPath);
1554
+ await session.boot();
1555
+ let attachedIdentity = readEngineAttachmentIdentity(branchPath);
1556
+ const serverPrologIdentity = engineSwiplIdentity();
1320
1557
  mkdirSync(path.dirname(options.socketPath), { recursive: true, mode: 0o700 });
1321
1558
  if (existsSync(options.socketPath)) {
1322
1559
  const isLivePeerSignal = (error) => {
@@ -1387,6 +1624,10 @@ export async function runEngineDaemon(options) {
1387
1624
  let freshnessCache = null;
1388
1625
  const clients = new Set();
1389
1626
  let shuttingDown = false;
1627
+ let finishShutdown;
1628
+ const shutdownFinished = new Promise((resolve) => {
1629
+ finishShutdown = resolve;
1630
+ });
1390
1631
  const exactBranchStatus = (result) => {
1391
1632
  if (!result.success || typeof result.bindings.JsonString !== "string") {
1392
1633
  return result;
@@ -1434,13 +1675,14 @@ export async function runEngineDaemon(options) {
1434
1675
  }, engineWorkspaceWatchdogMs());
1435
1676
  workspaceWatchdog.unref();
1436
1677
  const scheduleIdleExit = () => {
1437
- if (activeClients > 0)
1678
+ if (shuttingDown || activeClients > 0)
1438
1679
  return;
1439
1680
  if (!idleCompactionQueued) {
1440
1681
  idleCompactionQueued = true;
1441
1682
  queue = queue
1442
1683
  .then(async () => {
1443
- if (activeClients === 0) {
1684
+ // A lost session is recycled by the next request, not by idle work.
1685
+ if (activeClients === 0 && !session.needsRecycle()) {
1444
1686
  await prolog.query("kb_storage_compact_if_needed");
1445
1687
  }
1446
1688
  })
@@ -1465,11 +1707,26 @@ export async function runEngineDaemon(options) {
1465
1707
  request.packageVersions !== expectedPackages) {
1466
1708
  throw new Error(`Kibi engine package-version mismatch: client=${request.packageVersions ?? "missing"}, server=${expectedPackages}`);
1467
1709
  }
1468
- if (path.resolve(request.workspaceRoot ?? "") !==
1469
- path.resolve(options.workspaceRoot) ||
1710
+ if (canonicalFilesystemPath(request.workspaceRoot ?? "") !==
1711
+ options.workspaceRoot ||
1470
1712
  request.branch !== options.branch) {
1471
1713
  throw new Error("Kibi engine workspace identity mismatch");
1472
1714
  }
1715
+ // A client that resolved a different Prolog must not be served by this
1716
+ // daemon. Handshake reports the identity and stop must still work so the
1717
+ // client can replace the daemon.
1718
+ if (request.method !== "handshake" &&
1719
+ request.method !== "stop" &&
1720
+ request.prologIdentity !== serverPrologIdentity) {
1721
+ throw new Error(`Kibi engine SWI-Prolog mismatch: client=${request.prologIdentity ?? "missing"}, server=${serverPrologIdentity}`);
1722
+ }
1723
+ if (!isEngineLifecycleRequest(request) && (await session.ensureLive())) {
1724
+ // A recycled session re-read the store; nothing cached from the lost
1725
+ // session may be served, and the attachment identity may have moved.
1726
+ queryCache.clear();
1727
+ freshnessCache = null;
1728
+ attachedIdentity = readEngineAttachmentIdentity(branchPath);
1729
+ }
1473
1730
  switch (request.method) {
1474
1731
  case "entities": {
1475
1732
  const limit = request.limit ?? 100;
@@ -1653,8 +1910,15 @@ export async function runEngineDaemon(options) {
1653
1910
  }
1654
1911
  }
1655
1912
  case "kbStatus": {
1913
+ const freshnessCacheLookupStartedAt = process.env.KIBI_PERF_TIMINGS === "1" ? performance.now() : null;
1656
1914
  if (freshnessCache !== null &&
1657
1915
  Date.now() - freshnessCache.capturedAt <= ENGINE_FRESHNESS_CACHE_MS) {
1916
+ if (freshnessCacheLookupStartedAt !== null) {
1917
+ writePerformanceTraceEvent({
1918
+ kind: "engine-freshness-cache-hit",
1919
+ durationMs: performance.now() - freshnessCacheLookupStartedAt,
1920
+ });
1921
+ }
1658
1922
  return freshnessCache.result;
1659
1923
  }
1660
1924
  const result = exactBranchStatus(await prolog.query("status:kb_status_json(JsonString)"));
@@ -1674,11 +1938,20 @@ export async function runEngineDaemon(options) {
1674
1938
  freshnessCache = null;
1675
1939
  }
1676
1940
  if (cacheableEngineGoal(request.goal)) {
1941
+ const queryCacheLookupStartedAt = process.env.KIBI_PERF_TIMINGS === "1" ? performance.now() : null;
1677
1942
  const cached = queryCache.get(request.goal);
1678
- if (cached !== undefined)
1943
+ if (cached !== undefined) {
1944
+ if (queryCacheLookupStartedAt !== null) {
1945
+ writePerformanceTraceEvent({
1946
+ kind: "engine-query-cache-hit",
1947
+ durationMs: performance.now() - queryCacheLookupStartedAt,
1948
+ });
1949
+ }
1679
1950
  return cached;
1951
+ }
1680
1952
  }
1681
1953
  const result = await prolog.query(request.goal);
1954
+ session.recordModuleLoad(request.goal, result);
1682
1955
  if (result.success && mutatingEngineGoal(request.goal)) {
1683
1956
  fsyncJournaledBranchStore(branchPath);
1684
1957
  }
@@ -1715,6 +1988,8 @@ export async function runEngineDaemon(options) {
1715
1988
  if (typeof request.targetDirectory !== "string")
1716
1989
  throw new Error("export.targetDirectory is required");
1717
1990
  return prolog.query(`kb_storage_export('${quoteProlog(request.targetDirectory)}')`);
1991
+ case "handshake":
1992
+ return { prologIdentity: serverPrologIdentity };
1718
1993
  case "stop":
1719
1994
  setImmediate(() => void shutdown());
1720
1995
  return { stopped: true };
@@ -1741,7 +2016,13 @@ export async function runEngineDaemon(options) {
1741
2016
  if (idleTimer)
1742
2017
  clearTimeout(idleTimer);
1743
2018
  clearInterval(workspaceWatchdog);
1744
- const saved = await prolog.query("kb_save").catch(() => null);
2019
+ // Stop accepting clients before the first asynchronous durability step.
2020
+ // Keep the PID published until Prolog releases the branch-store lock.
2021
+ server.close();
2022
+ await queue.catch(() => undefined);
2023
+ const saved = session.needsRecycle()
2024
+ ? null
2025
+ : await prolog.query("kb_save").catch(() => null);
1745
2026
  if (saved?.success) {
1746
2027
  try {
1747
2028
  fsyncJournaledBranchStore(branchPath);
@@ -1754,7 +2035,6 @@ export async function runEngineDaemon(options) {
1754
2035
  await prolog.terminate().catch(() => undefined);
1755
2036
  for (const client of clients)
1756
2037
  client.destroy();
1757
- server.close();
1758
2038
  // A close callback can remain pending on older Node releases when a
1759
2039
  // client disconnects during `server.close()`. The daemon has already
1760
2040
  // removed its socket/pid and has no useful work left, so do not keep the
@@ -1775,6 +2055,7 @@ export async function runEngineDaemon(options) {
1775
2055
  // gone, force the final event-loop exit rather than retaining a stale
1776
2056
  // detached Node process on platform-specific server bookkeeping.
1777
2057
  setTimeout(() => process.exit(0), 50).unref();
2058
+ finishShutdown();
1778
2059
  };
1779
2060
  // Detached engines must cross the same durability boundary when a service
1780
2061
  // manager or a test harness terminates them as they do for an RPC stop.
@@ -1783,6 +2064,12 @@ export async function runEngineDaemon(options) {
1783
2064
  process.once("SIGTERM", requestSignalShutdown);
1784
2065
  process.once("SIGINT", requestSignalShutdown);
1785
2066
  server.on("connection", (socket) => {
2067
+ // A peer may disappear while the daemon refuses or drains its socket.
2068
+ socket.on("error", () => undefined);
2069
+ if (shuttingDown) {
2070
+ socket.destroy();
2071
+ return;
2072
+ }
1786
2073
  clients.add(socket);
1787
2074
  activeClients += 1;
1788
2075
  // Request ids are per-connection. Cancel marks must not be daemon-global
@@ -1794,6 +2081,15 @@ export async function runEngineDaemon(options) {
1794
2081
  const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
1795
2082
  buffer = parseFrames(Buffer.concat([buffer, bytes]), (value) => {
1796
2083
  const request = value;
2084
+ if (shuttingDown) {
2085
+ writeSocketFrame(socket, {
2086
+ id: request.id,
2087
+ ok: false,
2088
+ error: "Kibi engine is shutting down; request was not executed",
2089
+ serverPackageVersions: ENGINE_PACKAGE_VERSIONS,
2090
+ });
2091
+ return;
2092
+ }
1797
2093
  if (request.method === "cancel" &&
1798
2094
  typeof request.cancelOf === "number") {
1799
2095
  cancelledRequests.add(request.cancelOf);
@@ -1862,4 +2158,5 @@ export async function runEngineDaemon(options) {
1862
2158
  await new Promise((resolve) => {
1863
2159
  server.once("close", resolve);
1864
2160
  });
2161
+ await shutdownFinished;
1865
2162
  }