kibi-cli 0.18.1 → 1.0.1

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 (539) hide show
  1. package/dist/cli-json-command.d.ts +1 -2
  2. package/dist/cli-json-command.d.ts.map +1 -1
  3. package/dist/cli-json-command.js +2 -34
  4. package/dist/cli-operation-loader.d.ts +3 -0
  5. package/dist/cli-operation-loader.d.ts.map +1 -0
  6. package/dist/cli-operation-loader.js +91 -0
  7. package/dist/cli-operation-metadata.d.ts +94 -0
  8. package/dist/cli-operation-metadata.d.ts.map +1 -0
  9. package/dist/cli-operation-metadata.js +121 -0
  10. package/dist/cli-protocol.d.ts +1 -1
  11. package/dist/cli-protocol.d.ts.map +1 -1
  12. package/dist/cli-protocol.js +42 -11
  13. package/dist/cli-register-foundation.d.ts.map +1 -1
  14. package/dist/cli-register-foundation.js +28 -19
  15. package/dist/cli-register-json.d.ts +3 -0
  16. package/dist/cli-register-json.d.ts.map +1 -0
  17. package/dist/cli-register-json.js +35 -0
  18. package/dist/cli-register-maintenance.d.ts.map +1 -1
  19. package/dist/cli-register-maintenance.js +60 -13
  20. package/dist/cli-register-reporting.d.ts.map +1 -1
  21. package/dist/cli-register-reporting.js +23 -14
  22. package/dist/cli-register-skills.d.ts.map +1 -1
  23. package/dist/cli-register-skills.js +8 -6
  24. package/dist/cli-register-verification.d.ts +3 -0
  25. package/dist/cli-register-verification.d.ts.map +1 -0
  26. package/dist/cli-register-verification.js +16 -0
  27. package/dist/cli.d.ts.map +1 -1
  28. package/dist/cli.js +3 -1
  29. package/dist/commands/aggregated-checks.d.ts +1 -2
  30. package/dist/commands/aggregated-checks.d.ts.map +1 -1
  31. package/dist/commands/aggregated-checks.js +14 -9
  32. package/dist/commands/branch.d.ts +36 -0
  33. package/dist/commands/branch.d.ts.map +1 -1
  34. package/dist/commands/branch.js +371 -40
  35. package/dist/commands/check.d.ts.map +1 -1
  36. package/dist/commands/check.js +48 -77
  37. package/dist/commands/coverage.js +1 -1
  38. package/dist/commands/discovery-shared.d.ts +2 -1
  39. package/dist/commands/discovery-shared.d.ts.map +1 -1
  40. package/dist/commands/discovery-shared.js +38 -25
  41. package/dist/commands/doctor.d.ts +4 -1
  42. package/dist/commands/doctor.d.ts.map +1 -1
  43. package/dist/commands/doctor.js +259 -24
  44. package/dist/commands/engine.d.ts +8 -0
  45. package/dist/commands/engine.d.ts.map +1 -0
  46. package/dist/commands/engine.js +103 -0
  47. package/dist/commands/gaps.js +1 -1
  48. package/dist/commands/gc.d.ts +5 -2
  49. package/dist/commands/gc.d.ts.map +1 -1
  50. package/dist/commands/gc.js +140 -71
  51. package/dist/commands/github-init.d.ts +56 -0
  52. package/dist/commands/github-init.d.ts.map +1 -0
  53. package/dist/commands/github-init.js +310 -0
  54. package/dist/commands/graph.js +1 -1
  55. package/dist/commands/init-helpers.d.ts +1 -1
  56. package/dist/commands/init-helpers.d.ts.map +1 -1
  57. package/dist/commands/init-helpers.js +73 -25
  58. package/dist/commands/init.d.ts +2 -0
  59. package/dist/commands/init.d.ts.map +1 -1
  60. package/dist/commands/init.js +79 -24
  61. package/dist/commands/legacy-storage-migration.d.ts +54 -0
  62. package/dist/commands/legacy-storage-migration.d.ts.map +1 -0
  63. package/dist/commands/legacy-storage-migration.js +277 -0
  64. package/dist/commands/migrate.d.ts +7 -0
  65. package/dist/commands/migrate.d.ts.map +1 -1
  66. package/dist/commands/migrate.js +244 -91
  67. package/dist/commands/query.d.ts.map +1 -1
  68. package/dist/commands/query.js +3 -1
  69. package/dist/commands/report.d.ts +24 -0
  70. package/dist/commands/report.d.ts.map +1 -0
  71. package/dist/commands/report.js +191 -0
  72. package/dist/commands/search.d.ts.map +1 -1
  73. package/dist/commands/search.js +3 -1
  74. package/dist/commands/status.d.ts.map +1 -1
  75. package/dist/commands/status.js +2 -1
  76. package/dist/commands/sync/cache.d.ts +26 -3
  77. package/dist/commands/sync/cache.d.ts.map +1 -1
  78. package/dist/commands/sync/cache.js +141 -5
  79. package/dist/commands/sync/discovery.d.ts +23 -2
  80. package/dist/commands/sync/discovery.d.ts.map +1 -1
  81. package/dist/commands/sync/discovery.js +196 -15
  82. package/dist/commands/sync/extraction.d.ts +8 -1
  83. package/dist/commands/sync/extraction.d.ts.map +1 -1
  84. package/dist/commands/sync/extraction.js +38 -4
  85. package/dist/commands/sync/manifest.d.ts +8 -5
  86. package/dist/commands/sync/manifest.d.ts.map +1 -1
  87. package/dist/commands/sync/manifest.js +142 -39
  88. package/dist/commands/sync/persistence.d.ts +10 -1
  89. package/dist/commands/sync/persistence.d.ts.map +1 -1
  90. package/dist/commands/sync/persistence.js +223 -44
  91. package/dist/commands/sync/staging.d.ts +10 -1
  92. package/dist/commands/sync/staging.d.ts.map +1 -1
  93. package/dist/commands/sync/staging.js +84 -1
  94. package/dist/commands/sync.d.ts +6 -0
  95. package/dist/commands/sync.d.ts.map +1 -1
  96. package/dist/commands/sync.js +710 -48
  97. package/dist/commands/verify.d.ts +10 -0
  98. package/dist/commands/verify.d.ts.map +1 -0
  99. package/dist/commands/verify.js +126 -0
  100. package/dist/diagnostics.d.ts +1 -1
  101. package/dist/diagnostics.d.ts.map +1 -1
  102. package/dist/engine-daemon.d.ts +3 -0
  103. package/dist/engine-daemon.d.ts.map +1 -0
  104. package/dist/engine-daemon.js +34 -0
  105. package/dist/engine.d.ts +156 -0
  106. package/dist/engine.d.ts.map +1 -0
  107. package/dist/engine.js +1625 -0
  108. package/dist/env.d.ts.map +1 -1
  109. package/dist/env.js +5 -6
  110. package/dist/extractors/manifest.d.ts +8 -1
  111. package/dist/extractors/manifest.d.ts.map +1 -1
  112. package/dist/extractors/manifest.js +51 -9
  113. package/dist/extractors/markdown.d.ts +2 -1
  114. package/dist/extractors/markdown.d.ts.map +1 -1
  115. package/dist/extractors/markdown.js +21 -3
  116. package/dist/extractors/playwright-cases.d.ts +24 -0
  117. package/dist/extractors/playwright-cases.d.ts.map +1 -0
  118. package/dist/extractors/playwright-cases.js +126 -0
  119. package/dist/extractors/relationships.d.ts +2 -0
  120. package/dist/extractors/relationships.d.ts.map +1 -1
  121. package/dist/extractors/relationships.js +16 -17
  122. package/dist/extractors/symbol-coordinates.d.ts +81 -3
  123. package/dist/extractors/symbol-coordinates.d.ts.map +1 -1
  124. package/dist/extractors/symbol-coordinates.js +389 -51
  125. package/dist/extractors/symbols-coordinator.js +11 -1
  126. package/dist/intent-search.d.ts +60 -0
  127. package/dist/intent-search.d.ts.map +1 -0
  128. package/dist/intent-search.js +433 -0
  129. package/dist/operations/bootstrap/activation.d.ts.map +1 -0
  130. package/dist/operations/{autopilot → bootstrap}/activation.js +48 -31
  131. package/dist/operations/{autopilot → bootstrap}/candidate-helpers.d.ts.map +1 -1
  132. package/dist/operations/bootstrap/candidates.d.ts +4 -0
  133. package/dist/operations/{autopilot → bootstrap}/candidates.d.ts.map +1 -1
  134. package/dist/operations/{autopilot → bootstrap}/candidates.js +22 -8
  135. package/dist/operations/{autopilot → bootstrap}/discovery-evidence.d.ts +2 -2
  136. package/dist/operations/{autopilot → bootstrap}/discovery-evidence.d.ts.map +1 -1
  137. package/dist/operations/{autopilot → bootstrap}/discovery-evidence.js +46 -6
  138. package/dist/operations/{autopilot → bootstrap}/discovery.d.ts +1 -1
  139. package/dist/operations/bootstrap/discovery.d.ts.map +1 -0
  140. package/dist/operations/{autopilot → bootstrap}/discovery.js +21 -14
  141. package/dist/operations/bootstrap/generate.d.ts +8 -0
  142. package/dist/operations/bootstrap/generate.d.ts.map +1 -0
  143. package/dist/operations/bootstrap/generate.js +204 -0
  144. package/dist/operations/{autopilot → bootstrap}/guidance-actions.d.ts +2 -2
  145. package/dist/operations/{autopilot → bootstrap}/guidance-actions.d.ts.map +1 -1
  146. package/dist/operations/{autopilot → bootstrap}/guidance-actions.js +4 -4
  147. package/dist/operations/{autopilot → bootstrap}/guidance.d.ts +2 -2
  148. package/dist/operations/{autopilot → bootstrap}/guidance.d.ts.map +1 -1
  149. package/dist/operations/{autopilot → bootstrap}/guidance.js +2 -0
  150. package/dist/operations/{autopilot → bootstrap}/markdown-candidates.d.ts +2 -2
  151. package/dist/operations/{autopilot → bootstrap}/markdown-candidates.d.ts.map +1 -1
  152. package/dist/operations/{autopilot → bootstrap}/markdown-candidates.js +4 -3
  153. package/dist/operations/bootstrap/presentation.d.ts +15 -0
  154. package/dist/operations/bootstrap/presentation.d.ts.map +1 -0
  155. package/dist/operations/bootstrap/presentation.js +177 -0
  156. package/dist/operations/{autopilot → bootstrap}/requirement-claims.d.ts.map +1 -1
  157. package/dist/operations/{autopilot → bootstrap}/types.d.ts +63 -21
  158. package/dist/operations/bootstrap/types.d.ts.map +1 -0
  159. package/dist/operations/bootstrap/types.js +25 -0
  160. package/dist/operations/modeling/model-requirement.d.ts.map +1 -1
  161. package/dist/operations/modeling/model-requirement.js +4 -17
  162. package/dist/operations/modeling/predicate-applicability.d.ts +16 -0
  163. package/dist/operations/modeling/predicate-applicability.d.ts.map +1 -0
  164. package/dist/operations/modeling/predicate-applicability.js +276 -0
  165. package/dist/operations/modeling/predicate-applyplan.d.ts +13 -2
  166. package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
  167. package/dist/operations/modeling/predicate-applyplan.js +100 -3
  168. package/dist/operations/modeling/predicate-bindings.d.ts +6 -0
  169. package/dist/operations/modeling/predicate-bindings.d.ts.map +1 -0
  170. package/dist/operations/modeling/predicate-bindings.js +122 -0
  171. package/dist/operations/modeling/predicate-catalog-4.d.ts.map +1 -1
  172. package/dist/operations/modeling/predicate-catalog-4.js +155 -0
  173. package/dist/operations/modeling/predicate-inference-1.d.ts.map +1 -1
  174. package/dist/operations/modeling/predicate-inference-1.js +8 -0
  175. package/dist/operations/modeling/predicate-inference-launcher.d.ts +10 -0
  176. package/dist/operations/modeling/predicate-inference-launcher.d.ts.map +1 -0
  177. package/dist/operations/modeling/predicate-inference-launcher.js +13 -0
  178. package/dist/operations/modeling/predicate-inference.d.ts.map +1 -1
  179. package/dist/operations/modeling/predicate-inference.js +4 -0
  180. package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
  181. package/dist/operations/modeling/predicate-loader.js +7 -0
  182. package/dist/operations/modeling/predicate-ranker.d.ts +8 -1
  183. package/dist/operations/modeling/predicate-ranker.d.ts.map +1 -1
  184. package/dist/operations/modeling/predicate-ranker.js +156 -24
  185. package/dist/operations/modeling/predicate-score-3.d.ts.map +1 -1
  186. package/dist/operations/modeling/predicate-score-3.js +6 -0
  187. package/dist/operations/modeling/predicate-types.d.ts +34 -0
  188. package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
  189. package/dist/operations/modeling/predicate-usage-hints-3.d.ts.map +1 -1
  190. package/dist/operations/modeling/predicate-usage-hints-3.js +40 -0
  191. package/dist/operations/modeling/predicate-utils.d.ts.map +1 -1
  192. package/dist/operations/modeling/predicate-utils.js +2 -0
  193. package/dist/operations/modeling/suggest-predicates.d.ts +1 -1
  194. package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
  195. package/dist/operations/modeling/suggest-predicates.js +170 -32
  196. package/dist/operations/mutation/delete.d.ts.map +1 -1
  197. package/dist/operations/mutation/delete.js +415 -19
  198. package/dist/operations/mutation/entity-projection.d.ts +11 -0
  199. package/dist/operations/mutation/entity-projection.d.ts.map +1 -0
  200. package/dist/operations/mutation/entity-projection.js +14 -0
  201. package/dist/operations/mutation/relationships.d.ts +10 -0
  202. package/dist/operations/mutation/relationships.d.ts.map +1 -1
  203. package/dist/operations/mutation/relationships.js +71 -1
  204. package/dist/operations/mutation/serialization.d.ts.map +1 -1
  205. package/dist/operations/mutation/serialization.js +1 -0
  206. package/dist/operations/mutation/source-authoring.d.ts +61 -0
  207. package/dist/operations/mutation/source-authoring.d.ts.map +1 -0
  208. package/dist/operations/mutation/source-authoring.js +543 -0
  209. package/dist/operations/mutation/symbol-compiler-lock.d.ts +51 -0
  210. package/dist/operations/mutation/symbol-compiler-lock.d.ts.map +1 -0
  211. package/dist/operations/mutation/symbol-compiler-lock.js +274 -0
  212. package/dist/operations/mutation/symbol-granularity.d.ts.map +1 -1
  213. package/dist/operations/mutation/symbol-granularity.js +34 -3
  214. package/dist/operations/mutation/symbol-refresh.d.ts +18 -1
  215. package/dist/operations/mutation/symbol-refresh.d.ts.map +1 -1
  216. package/dist/operations/mutation/symbol-refresh.js +225 -63
  217. package/dist/operations/mutation/types.d.ts +57 -1
  218. package/dist/operations/mutation/types.d.ts.map +1 -1
  219. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  220. package/dist/operations/mutation/upsert.js +363 -24
  221. package/dist/operations/mutation/validation.d.ts.map +1 -1
  222. package/dist/operations/mutation/validation.js +1 -0
  223. package/dist/operations/planning/apply-plan.d.ts +117 -0
  224. package/dist/operations/planning/apply-plan.d.ts.map +1 -0
  225. package/dist/operations/planning/apply-plan.js +1437 -0
  226. package/dist/operations/planning/compile-intent.d.ts +106 -0
  227. package/dist/operations/planning/compile-intent.d.ts.map +1 -0
  228. package/dist/operations/planning/compile-intent.js +562 -0
  229. package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
  230. package/dist/operations/semantic-advisor/analyze-prose.js +6 -2
  231. package/dist/operations/semantic-advisor/clauses.d.ts.map +1 -1
  232. package/dist/operations/semantic-advisor/clauses.js +15 -2
  233. package/dist/operations/semantic-advisor/ingestion-boundary.d.ts.map +1 -1
  234. package/dist/operations/semantic-advisor/ingestion-boundary.js +7 -3
  235. package/dist/operations/semantic-advisor/predicate-rules-launcher.d.ts +51 -0
  236. package/dist/operations/semantic-advisor/predicate-rules-launcher.d.ts.map +1 -0
  237. package/dist/operations/semantic-advisor/predicate-rules-launcher.js +96 -0
  238. package/dist/operations/semantic-advisor/types.d.ts +13 -5
  239. package/dist/operations/semantic-advisor/types.d.ts.map +1 -1
  240. package/dist/operations/verification/ingest-verification.d.ts +29 -0
  241. package/dist/operations/verification/ingest-verification.d.ts.map +1 -0
  242. package/dist/operations/verification/ingest-verification.js +192 -0
  243. package/dist/prolog/codec.d.ts.map +1 -1
  244. package/dist/prolog/codec.js +2 -1
  245. package/dist/prolog.d.ts +8 -0
  246. package/dist/prolog.d.ts.map +1 -1
  247. package/dist/prolog.js +66 -16
  248. package/dist/public/branch-resolver.d.ts +1 -0
  249. package/dist/public/branch-resolver.d.ts.map +1 -1
  250. package/dist/public/branch-resolver.js +1 -0
  251. package/dist/public/check-types.d.ts +2 -2
  252. package/dist/public/check-types.d.ts.map +1 -1
  253. package/dist/public/check-types.js +16 -16
  254. package/dist/public/diagnostic-usage.d.ts.map +1 -1
  255. package/dist/public/diagnostic-usage.js +69 -1
  256. package/dist/public/distribution-parity.d.ts +1 -1
  257. package/dist/public/distribution-parity.d.ts.map +1 -1
  258. package/dist/public/distribution-parity.js +1 -0
  259. package/dist/public/ignore-policy.d.ts.map +1 -1
  260. package/dist/public/ignore-policy.js +16 -4
  261. package/dist/public/impact/coverage-depth-quality.d.ts +6 -1
  262. package/dist/public/impact/coverage-depth-quality.d.ts.map +1 -1
  263. package/dist/public/impact/coverage-depth-quality.js +21 -3
  264. package/dist/public/impact/full-kb-quality.d.ts +4 -2
  265. package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
  266. package/dist/public/impact/full-kb-quality.js +61 -1
  267. package/dist/public/impact/manifest.d.ts.map +1 -1
  268. package/dist/public/impact/manifest.js +2 -5
  269. package/dist/public/operational-artifacts.d.ts.map +1 -1
  270. package/dist/public/operational-artifacts.js +7 -1
  271. package/dist/public/operations/catalog.d.ts +682 -67
  272. package/dist/public/operations/catalog.d.ts.map +1 -1
  273. package/dist/public/operations/catalog.js +126 -4
  274. package/dist/public/operations/check-executor.d.ts +2 -2
  275. package/dist/public/operations/check-executor.d.ts.map +1 -1
  276. package/dist/public/operations/check-executor.js +72 -13
  277. package/dist/public/operations/check-format-shared.d.ts +3 -0
  278. package/dist/public/operations/check-format-shared.d.ts.map +1 -1
  279. package/dist/public/operations/check-format-shared.js +3 -0
  280. package/dist/public/operations/check-helpers.d.ts +27 -2
  281. package/dist/public/operations/check-helpers.d.ts.map +1 -1
  282. package/dist/public/operations/check-helpers.js +62 -26
  283. package/dist/public/operations/contracts.d.ts +11 -0
  284. package/dist/public/operations/contracts.d.ts.map +1 -0
  285. package/dist/public/operations/contracts.js +325 -0
  286. package/dist/public/operations/discovery-entities.d.ts.map +1 -1
  287. package/dist/public/operations/discovery-entities.js +3 -2
  288. package/dist/public/operations/discovery-executors.d.ts +37 -1
  289. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  290. package/dist/public/operations/discovery-executors.js +246 -9
  291. package/dist/public/operations/index.d.ts +9 -4
  292. package/dist/public/operations/index.d.ts.map +1 -1
  293. package/dist/public/operations/index.js +8 -3
  294. package/dist/public/operations/migration-plan.d.ts +101 -0
  295. package/dist/public/operations/migration-plan.d.ts.map +1 -0
  296. package/dist/public/operations/migration-plan.js +537 -0
  297. package/dist/public/operations/node-ports.d.ts.map +1 -1
  298. package/dist/public/operations/node-ports.js +68 -8
  299. package/dist/public/operations/prolog-json.d.ts +14 -0
  300. package/dist/public/operations/prolog-json.d.ts.map +1 -1
  301. package/dist/public/operations/prolog-json.js +55 -6
  302. package/dist/public/operations/repair-plan.d.ts.map +1 -1
  303. package/dist/public/operations/repair-plan.js +1 -0
  304. package/dist/public/operations/result-envelope.d.ts +13 -0
  305. package/dist/public/operations/result-envelope.d.ts.map +1 -0
  306. package/dist/public/operations/result-envelope.js +104 -0
  307. package/dist/public/operations/runtime-types.d.ts +126 -5
  308. package/dist/public/operations/runtime-types.d.ts.map +1 -1
  309. package/dist/public/operations/source-relationship-parity.d.ts +13 -0
  310. package/dist/public/operations/source-relationship-parity.d.ts.map +1 -0
  311. package/dist/public/operations/source-relationship-parity.js +162 -0
  312. package/dist/public/operations/specs/{autopilot.d.ts → bootstrap.d.ts} +9 -9
  313. package/dist/public/operations/specs/bootstrap.d.ts.map +1 -0
  314. package/dist/public/operations/specs/{autopilot.js → bootstrap.js} +8 -7
  315. package/dist/public/operations/specs/check.d.ts +4 -4
  316. package/dist/public/operations/specs/check.d.ts.map +1 -1
  317. package/dist/public/operations/specs/check.js +4 -3
  318. package/dist/public/operations/specs/discovery.d.ts +87 -3
  319. package/dist/public/operations/specs/discovery.d.ts.map +1 -1
  320. package/dist/public/operations/specs/discovery.js +53 -3
  321. package/dist/public/operations/specs/modeling.d.ts +3 -3
  322. package/dist/public/operations/specs/modeling.js +3 -3
  323. package/dist/public/operations/specs/mutation-schemas.d.ts +190 -47
  324. package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
  325. package/dist/public/operations/specs/mutation-schemas.js +3 -1
  326. package/dist/public/operations/specs/mutation.d.ts +42 -2
  327. package/dist/public/operations/specs/mutation.d.ts.map +1 -1
  328. package/dist/public/operations/specs/mutation.js +26 -2
  329. package/dist/public/operations/specs/planning.d.ts +270 -0
  330. package/dist/public/operations/specs/planning.d.ts.map +1 -0
  331. package/dist/public/operations/specs/planning.js +191 -0
  332. package/dist/public/operations/specs/reporting.d.ts +4 -1
  333. package/dist/public/operations/specs/reporting.d.ts.map +1 -1
  334. package/dist/public/operations/specs/reporting.js +46 -1
  335. package/dist/public/operations/specs/upsert.d.ts +204 -47
  336. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  337. package/dist/public/operations/specs/upsert.js +9 -0
  338. package/dist/public/operations/specs/verification.d.ts +78 -0
  339. package/dist/public/operations/specs/verification.d.ts.map +1 -0
  340. package/dist/public/operations/specs/verification.js +62 -0
  341. package/dist/public/operations/symbol-repair-plan.d.ts +11 -0
  342. package/dist/public/operations/symbol-repair-plan.d.ts.map +1 -0
  343. package/dist/public/operations/symbol-repair-plan.js +123 -0
  344. package/dist/public/operations/types.d.ts +52 -1
  345. package/dist/public/operations/types.d.ts.map +1 -1
  346. package/dist/public/playwright-case-id.d.ts +8 -0
  347. package/dist/public/playwright-case-id.d.ts.map +1 -0
  348. package/dist/public/playwright-case-id.js +25 -0
  349. package/dist/public/schemas/entity.d.ts.map +1 -1
  350. package/dist/public/schemas/entity.js +4 -2
  351. package/dist/public/skills/kibi-bootstrap/SKILL.md +58 -0
  352. package/dist/public/skills/kibi-bootstrap/resources/bootstrap.md +11 -0
  353. package/dist/public/skills/kibi-bootstrap/resources/branch-lifecycle.md +7 -0
  354. package/dist/public/skills/kibi-bootstrap/resources/operation-access.md +5 -0
  355. package/dist/public/skills/kibi-bootstrap/resources/source-authoring.md +8 -0
  356. package/dist/public/skills/kibi-freshness/SKILL.md +40 -10
  357. package/dist/public/skills/kibi-freshness/resources/branch-lifecycle.md +7 -0
  358. package/dist/public/skills/kibi-freshness/resources/operation-access.md +6 -0
  359. package/dist/public/skills/kibi-freshness/resources/source-authoring.md +7 -0
  360. package/dist/public/skills/kibi-traceability/SKILL.md +47 -47
  361. package/dist/public/skills/kibi-traceability/resources/branch-lifecycle.md +7 -0
  362. package/dist/public/skills/kibi-traceability/resources/operation-access.md +5 -0
  363. package/dist/public/skills/kibi-traceability/resources/source-authoring.md +6 -0
  364. package/dist/public/skills/kibi-traceability/resources/traceability.md +8 -0
  365. package/dist/public/skills/kibi-usage/SKILL.md +115 -239
  366. package/dist/public/skills/kibi-usage/resources/branch-lifecycle.md +31 -0
  367. package/dist/public/skills/kibi-usage/resources/kb-improvement.md +38 -0
  368. package/dist/public/skills/kibi-usage/resources/operation-access.md +36 -38
  369. package/dist/public/skills/kibi-usage/resources/relationship-directions.md +3 -3
  370. package/dist/public/skills/kibi-usage/resources/source-authoring.md +31 -0
  371. package/dist/public/skills/kibi-usage/resources/workflows.md +56 -3
  372. package/dist/public/symbol-granularity.d.ts +1 -1
  373. package/dist/public/symbol-granularity.d.ts.map +1 -1
  374. package/dist/public/symbol-granularity.js +1 -0
  375. package/dist/public/telemetry-acceptance.d.ts +7 -0
  376. package/dist/public/telemetry-acceptance.d.ts.map +1 -1
  377. package/dist/public/telemetry-acceptance.js +8 -1
  378. package/dist/public/telemetry-remediation.js +1 -1
  379. package/dist/public/verification-receipt.d.ts +206 -39
  380. package/dist/public/verification-receipt.d.ts.map +1 -1
  381. package/dist/public/verification-receipt.js +171 -4
  382. package/dist/relationships/shards.d.ts +16 -0
  383. package/dist/relationships/shards.d.ts.map +1 -1
  384. package/dist/relationships/shards.js +87 -2
  385. package/dist/report/brand.d.ts +19 -0
  386. package/dist/report/brand.d.ts.map +1 -0
  387. package/dist/report/brand.js +112 -0
  388. package/dist/report/html-report.d.ts +19 -0
  389. package/dist/report/html-report.d.ts.map +1 -0
  390. package/dist/report/html-report.js +963 -0
  391. package/dist/report/relative-time.d.ts +4 -0
  392. package/dist/report/relative-time.d.ts.map +1 -0
  393. package/dist/report/relative-time.js +46 -0
  394. package/dist/report/report-view.d.ts +17 -0
  395. package/dist/report/report-view.d.ts.map +1 -0
  396. package/dist/report/report-view.js +33 -0
  397. package/dist/report/repository.d.ts +35 -0
  398. package/dist/report/repository.d.ts.map +1 -0
  399. package/dist/report/repository.js +151 -0
  400. package/dist/runtime/cli-runtime.d.ts.map +1 -1
  401. package/dist/runtime/cli-runtime.js +98 -42
  402. package/dist/schemas/entity.schema.json +1148 -273
  403. package/dist/traceability/evidence-model.d.ts +2 -2
  404. package/dist/traceability/evidence-model.d.ts.map +1 -1
  405. package/dist/traceability/evidence-model.js +2 -2
  406. package/dist/traceability/git-staged.d.ts.map +1 -1
  407. package/dist/traceability/git-staged.js +15 -40
  408. package/dist/traceability/markdown-validate.d.ts.map +1 -1
  409. package/dist/traceability/markdown-validate.js +12 -7
  410. package/dist/traceability/staged-impact-contract.d.ts +3 -3
  411. package/dist/traceability/staged-impact-contract.d.ts.map +1 -1
  412. package/dist/traceability/staged-impact-contract.js +10 -38
  413. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  414. package/dist/traceability/staged-symbols-manifest.js +8 -3
  415. package/dist/traceability/temp-kb.d.ts.map +1 -1
  416. package/dist/traceability/temp-kb.js +3 -0
  417. package/dist/types/entities.d.ts +3 -0
  418. package/dist/types/entities.d.ts.map +1 -1
  419. package/dist/utils/branch-resolver.d.ts +24 -10
  420. package/dist/utils/branch-resolver.d.ts.map +1 -1
  421. package/dist/utils/branch-resolver.js +121 -38
  422. package/dist/utils/branch-store-locator.d.ts +23 -0
  423. package/dist/utils/branch-store-locator.d.ts.map +1 -0
  424. package/dist/utils/branch-store-locator.js +90 -0
  425. package/dist/utils/branch-store.d.ts +15 -0
  426. package/dist/utils/branch-store.d.ts.map +1 -0
  427. package/dist/utils/branch-store.js +130 -0
  428. package/dist/utils/config.d.ts +39 -37
  429. package/dist/utils/config.d.ts.map +1 -1
  430. package/dist/utils/config.js +107 -119
  431. package/dist/utils/kb-manifest.d.ts +35 -0
  432. package/dist/utils/kb-manifest.d.ts.map +1 -0
  433. package/dist/utils/kb-manifest.js +154 -0
  434. package/dist/utils/kb-paths.d.ts +54 -0
  435. package/dist/utils/kb-paths.d.ts.map +1 -0
  436. package/dist/utils/kb-paths.js +125 -0
  437. package/dist/utils/manifest-paths.d.ts +4 -4
  438. package/dist/utils/manifest-paths.d.ts.map +1 -1
  439. package/dist/utils/manifest-paths.js +8 -56
  440. package/dist/utils/rule-registry.d.ts +48 -20
  441. package/dist/utils/rule-registry.d.ts.map +1 -1
  442. package/dist/utils/rule-registry.js +82 -77
  443. package/dist/utils/schema-version.d.ts +5 -5
  444. package/dist/utils/schema-version.d.ts.map +1 -1
  445. package/dist/utils/schema-version.js +26 -6
  446. package/dist/utils/strict-modeling.d.ts +0 -4
  447. package/dist/utils/strict-modeling.d.ts.map +1 -1
  448. package/dist/utils/strict-modeling.js +7 -8
  449. package/dist/verification/playwright-reporter.d.ts +21 -0
  450. package/dist/verification/playwright-reporter.d.ts.map +1 -0
  451. package/dist/verification/playwright-reporter.js +114 -0
  452. package/package.json +51 -8
  453. package/schema/config.json +2 -2
  454. package/schema/manifest.json +26 -0
  455. package/src/public/branch-resolver.ts +1 -0
  456. package/src/public/check-types.ts +22 -19
  457. package/src/public/diagnostic-usage.ts +79 -1
  458. package/src/public/distribution-parity.ts +1 -0
  459. package/src/public/ignore-policy.ts +16 -10
  460. package/src/public/impact/coverage-depth-quality.ts +36 -0
  461. package/src/public/impact/full-kb-quality.ts +118 -5
  462. package/src/public/impact/manifest.ts +2 -4
  463. package/src/public/operational-artifacts.ts +9 -1
  464. package/src/public/operations/catalog.ts +146 -9
  465. package/src/public/operations/check-executor.ts +102 -15
  466. package/src/public/operations/check-format-shared.ts +6 -0
  467. package/src/public/operations/check-helpers.ts +74 -35
  468. package/src/public/operations/contracts.ts +366 -0
  469. package/src/public/operations/discovery-entities.ts +3 -2
  470. package/src/public/operations/discovery-executors.ts +343 -15
  471. package/src/public/operations/index.ts +9 -4
  472. package/src/public/operations/migration-plan.ts +740 -0
  473. package/src/public/operations/node-ports.ts +84 -10
  474. package/src/public/operations/prolog-json.ts +73 -6
  475. package/src/public/operations/repair-plan.ts +1 -0
  476. package/src/public/operations/result-envelope.ts +136 -0
  477. package/src/public/operations/runtime-types.ts +130 -5
  478. package/src/public/operations/source-relationship-parity.ts +226 -0
  479. package/src/public/operations/specs/{autopilot.ts → bootstrap.ts} +17 -16
  480. package/src/public/operations/specs/check.ts +4 -3
  481. package/src/public/operations/specs/discovery.ts +57 -3
  482. package/src/public/operations/specs/modeling.ts +3 -3
  483. package/src/public/operations/specs/mutation-schemas.ts +6 -1
  484. package/src/public/operations/specs/mutation.ts +27 -2
  485. package/src/public/operations/specs/planning.ts +234 -0
  486. package/src/public/operations/specs/reporting.ts +55 -1
  487. package/src/public/operations/specs/upsert.ts +10 -0
  488. package/src/public/operations/specs/verification.ts +78 -0
  489. package/src/public/operations/symbol-repair-plan.ts +164 -0
  490. package/src/public/operations/types.ts +62 -1
  491. package/src/public/playwright-case-id.ts +35 -0
  492. package/src/public/schemas/entity.ts +7 -2
  493. package/src/public/skills/kibi-bootstrap/SKILL.md +58 -0
  494. package/src/public/skills/kibi-bootstrap/resources/bootstrap.md +11 -0
  495. package/src/public/skills/kibi-bootstrap/resources/branch-lifecycle.md +7 -0
  496. package/src/public/skills/kibi-bootstrap/resources/operation-access.md +5 -0
  497. package/src/public/skills/kibi-bootstrap/resources/source-authoring.md +8 -0
  498. package/src/public/skills/kibi-freshness/SKILL.md +40 -10
  499. package/src/public/skills/kibi-freshness/resources/branch-lifecycle.md +7 -0
  500. package/src/public/skills/kibi-freshness/resources/operation-access.md +6 -0
  501. package/src/public/skills/kibi-freshness/resources/source-authoring.md +7 -0
  502. package/src/public/skills/kibi-traceability/SKILL.md +47 -47
  503. package/src/public/skills/kibi-traceability/resources/branch-lifecycle.md +7 -0
  504. package/src/public/skills/kibi-traceability/resources/operation-access.md +5 -0
  505. package/src/public/skills/kibi-traceability/resources/source-authoring.md +6 -0
  506. package/src/public/skills/kibi-traceability/resources/traceability.md +8 -0
  507. package/src/public/skills/kibi-usage/SKILL.md +115 -239
  508. package/src/public/skills/kibi-usage/resources/branch-lifecycle.md +31 -0
  509. package/src/public/skills/kibi-usage/resources/kb-improvement.md +38 -0
  510. package/src/public/skills/kibi-usage/resources/operation-access.md +36 -38
  511. package/src/public/skills/kibi-usage/resources/relationship-directions.md +3 -3
  512. package/src/public/skills/kibi-usage/resources/source-authoring.md +31 -0
  513. package/src/public/skills/kibi-usage/resources/workflows.md +56 -3
  514. package/src/public/symbol-granularity.ts +1 -0
  515. package/src/public/telemetry-acceptance.ts +16 -1
  516. package/src/public/telemetry-remediation.ts +1 -1
  517. package/src/public/verification-receipt.ts +248 -6
  518. package/src/schemas/entity.schema.json +1148 -273
  519. package/templates/github/kibi-badge.yml +86 -0
  520. package/templates/github/kibi-report.yml +84 -0
  521. package/dist/operations/autopilot/activation.d.ts.map +0 -1
  522. package/dist/operations/autopilot/candidates.d.ts +0 -4
  523. package/dist/operations/autopilot/discovery.d.ts.map +0 -1
  524. package/dist/operations/autopilot/generate.d.ts +0 -8
  525. package/dist/operations/autopilot/generate.d.ts.map +0 -1
  526. package/dist/operations/autopilot/generate.js +0 -112
  527. package/dist/operations/autopilot/presentation.d.ts +0 -13
  528. package/dist/operations/autopilot/presentation.d.ts.map +0 -1
  529. package/dist/operations/autopilot/presentation.js +0 -91
  530. package/dist/operations/autopilot/types.d.ts.map +0 -1
  531. package/dist/operations/autopilot/types.js +0 -8
  532. package/dist/public/operations/specs/autopilot.d.ts.map +0 -1
  533. package/dist/public/skills/init-kibi/SKILL.md +0 -62
  534. package/src/public/skills/init-kibi/SKILL.md +0 -62
  535. /package/dist/operations/{autopilot → bootstrap}/activation.d.ts +0 -0
  536. /package/dist/operations/{autopilot → bootstrap}/candidate-helpers.d.ts +0 -0
  537. /package/dist/operations/{autopilot → bootstrap}/candidate-helpers.js +0 -0
  538. /package/dist/operations/{autopilot → bootstrap}/requirement-claims.d.ts +0 -0
  539. /package/dist/operations/{autopilot → bootstrap}/requirement-claims.js +0 -0
package/dist/engine.js ADDED
@@ -0,0 +1,1625 @@
1
+ /*
2
+ * Kibi — single-writer workspace engine and framed RPC client.
3
+ *
4
+ * The engine deliberately owns one interactive SWI-Prolog process for one
5
+ * workspace/branch. CLI and MCP callers only exchange typed envelopes over
6
+ * the local socket; Prolog itself never crosses the process boundary.
7
+ */
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";
11
+ import * as net from "node:net";
12
+ import * as os from "node:os";
13
+ import * as path from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ import { PrologProcess, resolveKbPlPath } from "./prolog.js";
16
+ import { parseEntityFromList, parseListOfLists } from "./prolog/codec.js";
17
+ import { isValidBranchName } from "./utils/branch-resolver.js";
18
+ import { ensureBranchStoreManifest } from "./utils/branch-store-locator.js";
19
+ export const ENGINE_PROTOCOL_VERSION = 1;
20
+ export const ENGINE_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
21
+ export const ENGINE_PACKAGE_VERSIONS = process.env.KIBI_PACKAGE_VERSIONS ?? "unknown";
22
+ const ENGINE_QUERY_CACHE_MAX_ENTRIES = 128;
23
+ const ENGINE_QUERY_CACHE_MAX_RESULT_BYTES = 8 * 1024 * 1024;
24
+ const ENGINE_FRESHNESS_CACHE_MS = 100;
25
+ const ENGINE_PUBLICATION_LOCK_STALE_MS = 5_000;
26
+ function engineIdleTimeoutMs() {
27
+ const configured = Number.parseInt(process.env.KIBI_ENGINE_IDLE_TIMEOUT_MS ?? "", 10);
28
+ return Number.isFinite(configured) && configured >= 100
29
+ ? configured
30
+ : ENGINE_IDLE_TIMEOUT_MS;
31
+ }
32
+ function quoteProlog(value) {
33
+ return value.replaceAll("'", "''");
34
+ }
35
+ function cacheableEngineGoal(goal) {
36
+ const normalized = goal.trim();
37
+ if (!normalized ||
38
+ normalized.startsWith("(") ||
39
+ normalized.includes("kb_storage_status")) {
40
+ return false;
41
+ }
42
+ // The daemon cache is deliberately limited to deterministic discovery and
43
+ // graph reads. Status/freshness reads external files and writes invalidate
44
+ // the entire cache before they can be observed by another client.
45
+ return (normalized.startsWith("findall(") ||
46
+ normalized.startsWith("kb_entity(") ||
47
+ normalized.startsWith("kb_entities_by_source(") ||
48
+ normalized.startsWith("kb_relationship("));
49
+ }
50
+ function mutatingEngineGoal(goal) {
51
+ return /(?:kb_(?:assert|retract|commit|save|delete)|rdf_transaction|rdf_assert|rdf_retract)/.test(goal);
52
+ }
53
+ function maskPrologData(goal) {
54
+ const chars = [...goal];
55
+ let quote = null;
56
+ let lineComment = false;
57
+ let blockComment = false;
58
+ for (let index = 0; index < chars.length; index += 1) {
59
+ const current = chars[index];
60
+ const next = chars[index + 1];
61
+ if (lineComment) {
62
+ if (current === "\n")
63
+ lineComment = false;
64
+ else
65
+ chars[index] = " ";
66
+ continue;
67
+ }
68
+ if (blockComment) {
69
+ if (current === "*" && next === "/") {
70
+ chars[index] = " ";
71
+ chars[index + 1] = " ";
72
+ index += 1;
73
+ blockComment = false;
74
+ }
75
+ else if (current !== "\n") {
76
+ chars[index] = " ";
77
+ }
78
+ continue;
79
+ }
80
+ if (quote !== null) {
81
+ if (current === "\\") {
82
+ chars[index] = " ";
83
+ if (index + 1 < chars.length) {
84
+ chars[index + 1] = " ";
85
+ index += 1;
86
+ }
87
+ }
88
+ else if (current === quote) {
89
+ if (next === quote) {
90
+ chars[index] = " ";
91
+ chars[index + 1] = " ";
92
+ index += 1;
93
+ }
94
+ else {
95
+ chars[index] = " ";
96
+ quote = null;
97
+ }
98
+ }
99
+ else if (current !== "\n") {
100
+ chars[index] = " ";
101
+ }
102
+ continue;
103
+ }
104
+ if (current === "%") {
105
+ chars[index] = " ";
106
+ lineComment = true;
107
+ }
108
+ else if (current === "/" && next === "*") {
109
+ chars[index] = " ";
110
+ chars[index + 1] = " ";
111
+ index += 1;
112
+ blockComment = true;
113
+ }
114
+ else if (current === "'" || current === '"' || current === "`") {
115
+ chars[index] = " ";
116
+ quote = current;
117
+ }
118
+ }
119
+ return chars.join("");
120
+ }
121
+ function safeEngineGoal(goal) {
122
+ // The socket is a local capability boundary, not a general-purpose SWI
123
+ // console. Public clients may compose the typed Kibi predicates needed by
124
+ // the typed operation contracts, but process/filesystem/network escape hatches
125
+ // are rejected before they reach Prolog.
126
+ const executable = maskPrologData(goal);
127
+ if (/\b(?:halt|abort|shell|system|process_create|consult|load_files|open|close|delete_file|rename_file|make_directory|thread_create|rdf_attach_db|rdf_load|rdf_save|assertz|asserta|retractall|set_prolog_flag)\s*\(/.test(executable)) {
128
+ return false;
129
+ }
130
+ return /(?:^|[(:,\s])(?:kb_|checks:|status:|discovery:|requirement_proof:|findall\(|aggregate_all\(|rdf_transaction\(|use_module\(|atom_json_dict\(|member\(|once\(|true\b|fail\b)/.test(goal.trim());
131
+ }
132
+ function frame(value) {
133
+ const payload = Buffer.from(JSON.stringify(value), "utf8");
134
+ const header = Buffer.allocUnsafe(4);
135
+ header.writeUInt32BE(payload.byteLength, 0);
136
+ return Buffer.concat([header, payload]);
137
+ }
138
+ function writeSocketFrame(socket, value) {
139
+ if (socket.destroyed || !socket.writable)
140
+ return;
141
+ try {
142
+ socket.write(frame(value));
143
+ }
144
+ catch {
145
+ // A client may disconnect while a queued request is finishing. The
146
+ // journal transaction remains authoritative; there is no response to
147
+ // deliver to a closed peer.
148
+ }
149
+ }
150
+ function parseFrames(buffer, onFrame) {
151
+ let cursor = 0;
152
+ while (buffer.byteLength - cursor >= 4) {
153
+ const length = buffer.readUInt32BE(cursor);
154
+ if (length > 64 * 1024 * 1024) {
155
+ throw new Error("Kibi engine frame exceeds 64 MiB limit");
156
+ }
157
+ if (buffer.byteLength - cursor - 4 < length)
158
+ break;
159
+ const payload = buffer.subarray(cursor + 4, cursor + 4 + length);
160
+ onFrame(JSON.parse(payload.toString("utf8")));
161
+ cursor += 4 + length;
162
+ }
163
+ return buffer.subarray(cursor);
164
+ }
165
+ function runtimeDirectory() {
166
+ const configured = process.env.KIBI_RUNTIME_DIR ??
167
+ process.env.XDG_RUNTIME_DIR ??
168
+ path.join(os.tmpdir(), "kibi-runtime");
169
+ const candidates = [configured, path.join(os.tmpdir(), "kibi-runtime")];
170
+ for (const candidate of candidates) {
171
+ try {
172
+ mkdirSync(candidate, { recursive: true, mode: 0o700 });
173
+ // XDG_RUNTIME_DIR can exist but be mounted read-only (for example in a
174
+ // constrained container). Probe the directory before using it for the
175
+ // socket, start lock, and pid files so auto-start fails over cleanly.
176
+ const probe = path.join(candidate, `.kibi-write-${process.pid}`);
177
+ writeFileSync(probe, "", { mode: 0o600 });
178
+ unlinkSync(probe);
179
+ return candidate;
180
+ }
181
+ catch {
182
+ // Try the private system-temp fallback below.
183
+ }
184
+ }
185
+ throw new Error("Unable to create a writable Kibi engine runtime directory");
186
+ }
187
+ function fsyncPath(filePath) {
188
+ if (!existsSync(filePath))
189
+ return;
190
+ let descriptor = null;
191
+ try {
192
+ descriptor = openSync(filePath, "r");
193
+ fsyncSync(descriptor);
194
+ }
195
+ finally {
196
+ if (descriptor !== null)
197
+ closeSync(descriptor);
198
+ }
199
+ }
200
+ /**
201
+ * Cross the kernel durability boundary before a journaled mutation is
202
+ * acknowledged. SWI flushes completed RDF transaction records to its journal
203
+ * stream; fsyncing the journal and atomic CURRENT pointer makes that flush
204
+ * survive a host crash as well as a process crash.
205
+ */
206
+ // implements REQ-core-journaled-engine-persistence
207
+ export function fsyncJournaledBranchStore(branchPath) {
208
+ const rdfRoot = path.join(branchPath, "rdf");
209
+ const directories = [];
210
+ const pending = existsSync(rdfRoot) ? [rdfRoot] : [];
211
+ while (pending.length > 0) {
212
+ const directory = pending.pop();
213
+ if (directory === undefined)
214
+ break;
215
+ directories.push(directory);
216
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
217
+ const entryPath = path.join(directory, entry.name);
218
+ if (entry.isDirectory())
219
+ pending.push(entryPath);
220
+ else if (entry.isFile() && entry.name !== "lock")
221
+ fsyncPath(entryPath);
222
+ }
223
+ }
224
+ for (const metadata of ["CURRENT", "storage.json", "kb.rdf"]) {
225
+ fsyncPath(path.join(branchPath, metadata));
226
+ }
227
+ // Publish directory entries from the leaves upward, ending at the branch
228
+ // directory that owns CURRENT and the storage marker.
229
+ if (process.platform !== "win32") {
230
+ for (const directory of directories.reverse())
231
+ fsyncPath(directory);
232
+ fsyncPath(branchPath);
233
+ }
234
+ }
235
+ // implements REQ-core-journaled-engine-persistence
236
+ export function engineSocketPath(workspaceRoot, branch) {
237
+ const canonicalRoot = (() => {
238
+ try {
239
+ return realpathSync(workspaceRoot);
240
+ }
241
+ catch {
242
+ return path.resolve(workspaceRoot);
243
+ }
244
+ })();
245
+ const key = createHash("sha256")
246
+ .update(`${canonicalRoot}\0${branch}`)
247
+ // 128 bits keeps collision risk negligible while staying below the
248
+ // roughly 108-byte sockaddr_un limit even in a nested XDG runtime path.
249
+ .digest("hex")
250
+ .slice(0, 32);
251
+ if (process.platform === "win32") {
252
+ return `\\\\.\\pipe\\kibi-${key}`;
253
+ }
254
+ return path.join(runtimeDirectory(), `kibi-${key}.sock`);
255
+ }
256
+ export function enginePidPath(workspaceRoot, branch) {
257
+ const socket = engineSocketPath(workspaceRoot, branch);
258
+ if (process.platform === "win32") {
259
+ return path.join(runtimeDirectory(), `kibi-${createHash("sha256")
260
+ .update(`${path.resolve(workspaceRoot)}\0${branch}`)
261
+ .digest("hex")}.pid`);
262
+ }
263
+ return `${socket}.pid`;
264
+ }
265
+ export function engineStartLockPath(workspaceRoot, branch) {
266
+ if (process.platform === "win32") {
267
+ return path.join(runtimeDirectory(), `kibi-${createHash("sha256")
268
+ .update(`${path.resolve(workspaceRoot)}\0${branch}`)
269
+ .digest("hex")}.start.lock`);
270
+ }
271
+ return `${engineSocketPath(workspaceRoot, branch)}.start.lock`;
272
+ }
273
+ /**
274
+ * A generation publisher holds this lease from the existing daemon shutdown
275
+ * through the atomic branch-store cutover. Engine clients honor the lease so
276
+ * another process cannot auto-start a writer in the stop-to-publish gap.
277
+ */
278
+ export function enginePublicationLockPath(workspaceRoot, branch) {
279
+ return `${engineSocketPath(workspaceRoot, branch)}.publish.lock`;
280
+ }
281
+ function publicationLockOwner(lockPath) {
282
+ try {
283
+ const [pidText, createdText] = readFileSync(lockPath, "utf8")
284
+ .trim()
285
+ .split(":");
286
+ const pid = Number.parseInt(pidText ?? "", 10);
287
+ const createdAt = Number.parseInt(createdText ?? "", 10);
288
+ if (!Number.isInteger(pid) || pid <= 1 || !Number.isFinite(createdAt)) {
289
+ return null;
290
+ }
291
+ return { pid, createdAt };
292
+ }
293
+ catch {
294
+ return null;
295
+ }
296
+ }
297
+ function processIsAlive(pid) {
298
+ try {
299
+ process.kill(pid, 0);
300
+ return true;
301
+ }
302
+ catch {
303
+ return false;
304
+ }
305
+ }
306
+ function clearStalePublicationLock(lockPath) {
307
+ if (!existsSync(lockPath))
308
+ return true;
309
+ const owner = publicationLockOwner(lockPath);
310
+ let age = 0;
311
+ try {
312
+ age = Date.now() - statSync(lockPath).mtimeMs;
313
+ }
314
+ catch {
315
+ return false;
316
+ }
317
+ if ((owner === null || !processIsAlive(owner.pid)) &&
318
+ age > ENGINE_PUBLICATION_LOCK_STALE_MS) {
319
+ try {
320
+ unlinkSync(lockPath);
321
+ return true;
322
+ }
323
+ catch {
324
+ return false;
325
+ }
326
+ }
327
+ return false;
328
+ }
329
+ function publicationLockIsActive(lockPath) {
330
+ if (!existsSync(lockPath))
331
+ return false;
332
+ if (clearStalePublicationLock(lockPath))
333
+ return false;
334
+ return existsSync(lockPath);
335
+ }
336
+ export function acquireEnginePublicationLease(workspaceRoot, branch) {
337
+ const lockPath = enginePublicationLockPath(workspaceRoot, branch);
338
+ mkdirSync(path.dirname(lockPath), { recursive: true, mode: 0o700 });
339
+ let descriptor = null;
340
+ try {
341
+ descriptor = openSync(lockPath, "wx", 0o600);
342
+ }
343
+ catch {
344
+ if (clearStalePublicationLock(lockPath)) {
345
+ try {
346
+ descriptor = openSync(lockPath, "wx", 0o600);
347
+ }
348
+ catch {
349
+ // Fall through to the stable fail-closed error below.
350
+ }
351
+ }
352
+ }
353
+ if (descriptor === null) {
354
+ throw new Error(`Kibi engine publication is already in progress: ${lockPath}`);
355
+ }
356
+ try {
357
+ writeFileSync(lockPath, `${process.pid}:${Date.now()}\n`, { flag: "w" });
358
+ }
359
+ catch (error) {
360
+ closeSync(descriptor);
361
+ unlinkSync(lockPath);
362
+ throw error;
363
+ }
364
+ const startLockPath = engineStartLockPath(workspaceRoot, branch);
365
+ const startDescriptor = tryAcquireStartLock(startLockPath);
366
+ if (startDescriptor === null) {
367
+ closeSync(descriptor);
368
+ unlinkSync(lockPath);
369
+ throw new Error(`Kibi engine publication cannot acquire the engine start lease: ${startLockPath}`);
370
+ }
371
+ let released = false;
372
+ return {
373
+ path: lockPath,
374
+ release: () => {
375
+ if (released)
376
+ return;
377
+ released = true;
378
+ try {
379
+ closeSync(descriptor);
380
+ }
381
+ catch {
382
+ // The descriptor may already have been closed during process cleanup.
383
+ }
384
+ releaseStartLock(startLockPath, startDescriptor);
385
+ try {
386
+ unlinkSync(lockPath);
387
+ }
388
+ catch {
389
+ // A subsequent publisher may have replaced a stale lock after a crash.
390
+ }
391
+ },
392
+ };
393
+ }
394
+ function tryAcquireStartLock(lockPath) {
395
+ try {
396
+ const descriptor = openSync(lockPath, "wx", 0o600);
397
+ writeFileSync(lockPath, `${process.pid}:${Date.now()}\n`, { flag: "w" });
398
+ return descriptor;
399
+ }
400
+ catch {
401
+ // Remove a lock left by a crashed starter, but never steal one from a
402
+ // live process. A short age grace period avoids racing a slow Node start.
403
+ try {
404
+ const stamp = readFileSync(lockPath, "utf8").trim().split(":");
405
+ const pid = Number.parseInt(stamp[0] ?? "", 10);
406
+ const created = Number.parseInt(stamp[1] ?? "", 10);
407
+ let age = 0;
408
+ try {
409
+ age = Date.now() - statSync(lockPath).mtimeMs;
410
+ }
411
+ catch {
412
+ age = 0;
413
+ }
414
+ if (!Number.isFinite(created) && age <= 5_000)
415
+ return null;
416
+ let alive = false;
417
+ if (Number.isFinite(pid) && pid > 0) {
418
+ try {
419
+ process.kill(pid, 0);
420
+ alive = true;
421
+ }
422
+ catch {
423
+ alive = false;
424
+ }
425
+ }
426
+ if (!alive &&
427
+ (Number.isFinite(created) ? Date.now() - created > 5_000 : age > 5_000)) {
428
+ unlinkSync(lockPath);
429
+ }
430
+ }
431
+ catch {
432
+ // The other starter may be publishing/removing the lock right now.
433
+ }
434
+ return null;
435
+ }
436
+ }
437
+ function releaseStartLock(lockPath, descriptor) {
438
+ if (descriptor !== null) {
439
+ try {
440
+ closeSync(descriptor);
441
+ }
442
+ catch {
443
+ // best effort
444
+ }
445
+ }
446
+ try {
447
+ unlinkSync(lockPath);
448
+ }
449
+ catch {
450
+ // Another starter may already have cleaned the lock.
451
+ }
452
+ }
453
+ function recoverStaleJournalLock(branchPath) {
454
+ const lockPath = path.join(branchPath, "rdf", "lock");
455
+ if (!existsSync(lockPath))
456
+ return;
457
+ let contents = "";
458
+ try {
459
+ contents = readFileSync(lockPath, "utf8");
460
+ }
461
+ catch {
462
+ return;
463
+ }
464
+ const match = /pid\((\d+)\)/.exec(contents);
465
+ if (!match)
466
+ return;
467
+ const pid = Number.parseInt(match[1] ?? "", 10);
468
+ if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid)
469
+ return;
470
+ try {
471
+ process.kill(pid, 0);
472
+ return;
473
+ }
474
+ catch {
475
+ try {
476
+ rmSync(lockPath, { force: true });
477
+ }
478
+ catch {
479
+ // Let rdf_attach_db report the lock error if another process wins the
480
+ // race between the liveness probe and removal.
481
+ }
482
+ }
483
+ }
484
+ function daemonEntry() {
485
+ const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
486
+ // Packed clients keep the daemon beside engine.js. Source-tree repository
487
+ // tests resolve engine.ts from src/, while the daemon is hosted by the
488
+ // already-built Node artifact in dist/.
489
+ const candidates = [
490
+ path.join(moduleDirectory, "engine-daemon.js"),
491
+ path.join(moduleDirectory, "..", "dist", "engine-daemon.js"),
492
+ // Sandboxed/evaluation hosts may bundle the MCP server into their own
493
+ // dist/ directory while preserving kibi-cli as a staged dependency. Keep
494
+ // the Node daemon external to that bundle so it can resolve its Prolog and
495
+ // codec modules exactly as the published CLI package does.
496
+ path.join(moduleDirectory, "..", "node_modules", "kibi-cli", "dist", "engine-daemon.js"),
497
+ ];
498
+ for (const candidate of candidates) {
499
+ if (existsSync(candidate))
500
+ return candidate;
501
+ }
502
+ throw new Error("The Kibi engine is not built. Run `npm run build:cli` before using the CLI engine.");
503
+ }
504
+ function connectSocket(socketPath, timeoutMs) {
505
+ return new Promise((resolve, reject) => {
506
+ const socket = net.createConnection(socketPath);
507
+ let settled = false;
508
+ const timer = setTimeout(() => {
509
+ if (settled)
510
+ return;
511
+ settled = true;
512
+ socket.destroy();
513
+ reject(new Error(`Timed out connecting to Kibi engine at ${socketPath}`));
514
+ }, timeoutMs);
515
+ socket.once("connect", () => {
516
+ if (settled)
517
+ return;
518
+ settled = true;
519
+ clearTimeout(timer);
520
+ resolve(socket);
521
+ });
522
+ // Keep a permanent error listener. Some Node versions emit the
523
+ // connection-refused error on the next tick after a timeout destroys the
524
+ // socket; a once-listener can otherwise become an uncaught event during
525
+ // concurrent auto-start attempts.
526
+ socket.on("error", (error) => {
527
+ if (settled)
528
+ return;
529
+ settled = true;
530
+ clearTimeout(timer);
531
+ reject(error);
532
+ });
533
+ });
534
+ }
535
+ async function connectWithRetry(socketPath, timeoutMs) {
536
+ const deadline = Date.now() + timeoutMs;
537
+ let lastError;
538
+ while (Date.now() < deadline) {
539
+ const errorPath = `${socketPath}.error`;
540
+ if (existsSync(errorPath)) {
541
+ try {
542
+ const diagnostic = readFileSync(errorPath, "utf8").trim();
543
+ if (diagnostic)
544
+ throw new Error(diagnostic);
545
+ }
546
+ catch (error) {
547
+ throw new Error(`Kibi engine failed to start: ${error instanceof Error ? error.message : String(error)}`);
548
+ }
549
+ }
550
+ // Avoid creating a refused/ENOENT socket handle on every poll while the
551
+ // daemon is still starting and avoid noisy late socket errors.
552
+ if (process.platform !== "win32" && !existsSync(socketPath)) {
553
+ await new Promise((resolve) => setTimeout(resolve, 25));
554
+ continue;
555
+ }
556
+ try {
557
+ return await connectSocket(socketPath, Math.min(250, deadline - Date.now()));
558
+ }
559
+ catch (error) {
560
+ lastError = error;
561
+ await new Promise((resolve) => setTimeout(resolve, 25));
562
+ }
563
+ }
564
+ throw new Error(`Unable to connect to Kibi engine at ${socketPath}: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
565
+ }
566
+ /** PrologPort-compatible client used by CLI and MCP operation runtimes. */
567
+ // implements REQ-core-journaled-engine-persistence
568
+ export class EngineClient {
569
+ workspaceRoot;
570
+ branch;
571
+ timeout;
572
+ allowPublicationLock;
573
+ socket = null;
574
+ daemon = null;
575
+ inputBuffer = Buffer.alloc(0);
576
+ requestId = 0;
577
+ pending = new Map();
578
+ requestTail = Promise.resolve();
579
+ lastResult = null;
580
+ constructor(options) {
581
+ if (!isValidBranchName(options.branch)) {
582
+ throw new Error(`Invalid Kibi engine branch name: ${options.branch}`);
583
+ }
584
+ const resolvedWorkspaceRoot = path.resolve(options.workspaceRoot);
585
+ try {
586
+ this.workspaceRoot = realpathSync(resolvedWorkspaceRoot);
587
+ }
588
+ catch {
589
+ this.workspaceRoot = resolvedWorkspaceRoot;
590
+ }
591
+ this.branch = options.branch;
592
+ this.timeout = options.timeout ?? 120_000;
593
+ this.allowPublicationLock = options.allowPublicationLock ?? false;
594
+ }
595
+ async start(allowSpawn = true) {
596
+ if (this.socket !== null && !this.socket.destroyed)
597
+ return;
598
+ const socketPath = engineSocketPath(this.workspaceRoot, this.branch);
599
+ const publicationLockPath = enginePublicationLockPath(this.workspaceRoot, this.branch);
600
+ if (!this.allowPublicationLock &&
601
+ publicationLockIsActive(publicationLockPath)) {
602
+ throw new Error(`Kibi engine publication is in progress: ${publicationLockPath}`);
603
+ }
604
+ try {
605
+ if (existsSync(socketPath)) {
606
+ this.socket = await connectWithRetry(socketPath, 150);
607
+ }
608
+ else {
609
+ throw new Error("engine socket is absent");
610
+ }
611
+ }
612
+ catch {
613
+ if (!allowSpawn)
614
+ return;
615
+ const nodePath = process.env.KIBI_NODE_PATH ?? "node";
616
+ const entry = daemonEntry();
617
+ const lockPath = engineStartLockPath(this.workspaceRoot, this.branch);
618
+ const startDeadline = Date.now() + this.timeout;
619
+ let lock = null;
620
+ while (lock === null && Date.now() < startDeadline) {
621
+ if (existsSync(socketPath)) {
622
+ try {
623
+ this.socket = await connectWithRetry(socketPath, 100);
624
+ break;
625
+ }
626
+ catch {
627
+ // The daemon may be replacing a stale socket; retry below.
628
+ }
629
+ }
630
+ if (this.socket === null) {
631
+ lock = tryAcquireStartLock(lockPath);
632
+ if (lock === null) {
633
+ await new Promise((resolve) => setTimeout(resolve, 50));
634
+ }
635
+ }
636
+ }
637
+ if (this.socket === null && lock === null) {
638
+ throw new Error(`Unable to connect to Kibi engine at ${socketPath} while another starter owns ${lockPath}`);
639
+ }
640
+ if (lock !== null) {
641
+ try {
642
+ rmSync(`${socketPath}.error`, { force: true });
643
+ try {
644
+ const daemon = spawn(nodePath, [
645
+ entry,
646
+ "--workspace",
647
+ this.workspaceRoot,
648
+ "--branch",
649
+ this.branch,
650
+ "--socket",
651
+ socketPath,
652
+ ], {
653
+ detached: true,
654
+ stdio: "ignore",
655
+ env: {
656
+ ...process.env,
657
+ KIBI_ENGINE_PROTOCOL: String(ENGINE_PROTOCOL_VERSION),
658
+ },
659
+ });
660
+ await new Promise((resolve, reject) => {
661
+ daemon.once("spawn", resolve);
662
+ daemon.once("error", reject);
663
+ });
664
+ this.daemon = daemon;
665
+ }
666
+ catch (error) {
667
+ throw new Error(`Kibi engine requires Node.js >=18 to host the daemon; failed to start '${nodePath}': ${error instanceof Error ? error.message : String(error)}`);
668
+ }
669
+ this.daemon.unref();
670
+ this.socket = await connectWithRetry(socketPath, this.timeout);
671
+ }
672
+ finally {
673
+ releaseStartLock(lockPath, lock);
674
+ }
675
+ }
676
+ }
677
+ if (this.socket === null)
678
+ return;
679
+ this.attachSocket(this.socket);
680
+ }
681
+ attachSocket(socket) {
682
+ socket.on("data", (chunk) => {
683
+ try {
684
+ const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
685
+ this.inputBuffer = parseFrames(Buffer.concat([this.inputBuffer, bytes]), (value) => this.handleResponse(value));
686
+ }
687
+ catch (error) {
688
+ socket.destroy(error instanceof Error ? error : new Error(String(error)));
689
+ }
690
+ });
691
+ socket.on("close", () => {
692
+ this.socket = null;
693
+ const error = new Error("Kibi engine connection closed");
694
+ for (const pending of this.pending.values())
695
+ pending.reject(error);
696
+ this.pending.clear();
697
+ });
698
+ socket.on("error", (error) => {
699
+ for (const pending of this.pending.values())
700
+ pending.reject(error);
701
+ this.pending.clear();
702
+ });
703
+ }
704
+ handleResponse(value) {
705
+ if (value === null || typeof value !== "object")
706
+ return;
707
+ const response = value;
708
+ if (typeof response.id !== "number")
709
+ return;
710
+ const pending = this.pending.get(response.id);
711
+ if (!pending)
712
+ return;
713
+ this.pending.delete(response.id);
714
+ if (response.ok)
715
+ pending.resolve(response.result);
716
+ else
717
+ pending.reject(new Error(response.error ?? "Kibi engine request failed"));
718
+ }
719
+ async request(request, signal) {
720
+ await this.start();
721
+ const socket = this.socket;
722
+ if (socket === null || socket.destroyed)
723
+ throw new Error("Kibi engine is not connected");
724
+ const id = ++this.requestId;
725
+ const result = new Promise((resolve, reject) => {
726
+ this.pending.set(id, {
727
+ resolve: resolve,
728
+ reject,
729
+ });
730
+ try {
731
+ socket.write(frame({
732
+ ...request,
733
+ id,
734
+ protocolVersion: ENGINE_PROTOCOL_VERSION,
735
+ packageVersions: ENGINE_PACKAGE_VERSIONS,
736
+ workspaceRoot: this.workspaceRoot,
737
+ branch: this.branch,
738
+ }));
739
+ }
740
+ catch (error) {
741
+ this.pending.delete(id);
742
+ reject(error instanceof Error ? error : new Error(String(error)));
743
+ return;
744
+ }
745
+ if (signal !== undefined) {
746
+ const abort = () => {
747
+ if (!this.pending.has(id))
748
+ return;
749
+ this.pending.delete(id);
750
+ this.cancel(id);
751
+ reject(new Error("Kibi engine request cancelled"));
752
+ };
753
+ if (signal.aborted)
754
+ abort();
755
+ else
756
+ signal.addEventListener("abort", abort, { once: true });
757
+ }
758
+ });
759
+ return result;
760
+ }
761
+ async query(goal, signal) {
762
+ let result;
763
+ const previous = this.requestTail;
764
+ let release;
765
+ this.requestTail = new Promise((resolve) => {
766
+ release = resolve;
767
+ });
768
+ await previous;
769
+ try {
770
+ result = await this.request({ method: "query", goal }, signal);
771
+ this.lastResult = result;
772
+ return result;
773
+ }
774
+ finally {
775
+ release();
776
+ }
777
+ }
778
+ /** Execute a versioned, goal-free engine command. Raw query remains private
779
+ * compatibility traffic for the in-process Prolog adapter only. */
780
+ async command(command, signal) {
781
+ return this.request({ method: "command", command }, signal);
782
+ }
783
+ async queryBatch(goals) {
784
+ const normalized = goals.map((goal) => goal.trim().replace(/\.+\s*$/, ""));
785
+ return this.query(`rdf_transaction((${normalized.join(", ")}))`);
786
+ }
787
+ async queryEntities(input, signal) {
788
+ const result = await this.command({
789
+ version: 1,
790
+ kind: "entities",
791
+ ...(input.type !== undefined ? { type: input.type } : {}),
792
+ ...(input.id !== undefined ? { id: input.id } : {}),
793
+ ...(input.tags !== undefined ? { tags: input.tags } : {}),
794
+ ...(input.sourceFile !== undefined
795
+ ? { sourceFile: input.sourceFile }
796
+ : {}),
797
+ limit: input.limit,
798
+ offset: input.offset,
799
+ }, signal);
800
+ if (!result.success) {
801
+ throw new Error(result.error ?? "Indexed entity query failed");
802
+ }
803
+ const rows = result.bindings.Rows
804
+ ? parseListOfLists(result.bindings.Rows).map(parseEntityFromList)
805
+ : [];
806
+ const count = Number.parseInt(result.bindings.Count ?? "0", 10);
807
+ return {
808
+ entities: rows,
809
+ count: Number.isFinite(count) ? count : rows.length,
810
+ };
811
+ }
812
+ async searchEntities(input, signal) {
813
+ const result = await this.command({
814
+ version: 1,
815
+ kind: "search",
816
+ query: input.query,
817
+ ...(input.type !== undefined ? { type: input.type } : {}),
818
+ limit: input.limit,
819
+ offset: input.offset,
820
+ }, signal);
821
+ if (!result.success) {
822
+ throw new Error(result.error ?? "Indexed search candidate query failed");
823
+ }
824
+ const rows = result.bindings.Rows
825
+ ? parseListOfLists(result.bindings.Rows).map(parseEntityFromList)
826
+ : [];
827
+ const count = Number.parseInt(result.bindings.Count ?? "0", 10);
828
+ return {
829
+ entities: rows,
830
+ count: Number.isFinite(count) ? count : rows.length,
831
+ };
832
+ }
833
+ /** Best-effort cancellation for a queued request. */
834
+ cancel(requestId) {
835
+ const socket = this.socket;
836
+ if (socket === null || socket.destroyed)
837
+ return;
838
+ const id = ++this.requestId;
839
+ socket.write(frame({
840
+ id,
841
+ method: "cancel",
842
+ cancelOf: requestId,
843
+ protocolVersion: ENGINE_PROTOCOL_VERSION,
844
+ packageVersions: ENGINE_PACKAGE_VERSIONS,
845
+ workspaceRoot: this.workspaceRoot,
846
+ branch: this.branch,
847
+ }));
848
+ }
849
+ async nextSolution() {
850
+ const result = this.lastResult;
851
+ this.lastResult = null;
852
+ return result;
853
+ }
854
+ async save(signal) {
855
+ return this.query("kb_save", signal);
856
+ }
857
+ async storageStatus() {
858
+ return this.request({ method: "status" });
859
+ }
860
+ async checkpoint() {
861
+ return this.command({ version: 1, kind: "checkpoint" });
862
+ }
863
+ async queryStatusJson() {
864
+ return this.command({ version: 1, kind: "status" });
865
+ }
866
+ async compact() {
867
+ return this.command({ version: 1, kind: "compact" });
868
+ }
869
+ async exportStorage(targetDirectory) {
870
+ return this.request({
871
+ method: "export",
872
+ targetDirectory,
873
+ });
874
+ }
875
+ invalidateCache() {
876
+ this.lastResult = null;
877
+ }
878
+ isRunning() {
879
+ return this.socket !== null && !this.socket.destroyed;
880
+ }
881
+ getPid() {
882
+ const pidPath = enginePidPath(this.workspaceRoot, this.branch);
883
+ try {
884
+ return Number.parseInt(readFileSync(pidPath, "utf8").trim(), 10) || 0;
885
+ }
886
+ catch {
887
+ return 0;
888
+ }
889
+ }
890
+ async stop(startIfMissing = true) {
891
+ const socketPath = engineSocketPath(this.workspaceRoot, this.branch);
892
+ if (!this.isRunning()) {
893
+ try {
894
+ await this.start(startIfMissing);
895
+ }
896
+ catch {
897
+ if (!startIfMissing)
898
+ return;
899
+ throw new Error("Unable to start Kibi engine for stop request");
900
+ }
901
+ }
902
+ if (!this.isRunning()) {
903
+ if (existsSync(socketPath)) {
904
+ throw new Error(`Kibi engine socket remains present but is not reachable: ${socketPath}`);
905
+ }
906
+ return;
907
+ }
908
+ let requestError;
909
+ try {
910
+ await this.request({ method: "stop" });
911
+ }
912
+ catch (error) {
913
+ requestError = error;
914
+ }
915
+ this.socket?.destroy();
916
+ this.socket = null;
917
+ // The daemon acknowledges stop before its SWI child has flushed and the
918
+ // listening socket has closed. Do not let a following auto-start attach to
919
+ // that half-shutdown process.
920
+ const deadline = Date.now() + 5_000;
921
+ while (existsSync(socketPath) && Date.now() < deadline) {
922
+ await new Promise((resolve) => setTimeout(resolve, 25));
923
+ }
924
+ if (existsSync(socketPath)) {
925
+ const detail = requestError instanceof Error ? `: ${requestError.message}` : "";
926
+ throw new Error(`Kibi engine did not stop within 5 seconds${detail}`);
927
+ }
928
+ }
929
+ async terminate() {
930
+ // Closing a client connection releases the engine; the daemon remains
931
+ // available for other operations until its idle timeout expires.
932
+ this.socket?.end();
933
+ this.socket?.destroy();
934
+ this.socket = null;
935
+ this.daemon = null;
936
+ this.lastResult = null;
937
+ }
938
+ }
939
+ // implements REQ-core-journaled-engine-persistence
940
+ export async function ensureJournaledBranchStoreAsync(branchPath) {
941
+ mkdirSync(branchPath, { recursive: true });
942
+ const markerPath = path.join(branchPath, "storage.json");
943
+ const marker = existsSync(markerPath) ? readFileSync(markerPath, "utf8") : "";
944
+ if (marker.includes("kibi.rdf-journal.v1")) {
945
+ const rdfDirectory = path.join(branchPath, "rdf");
946
+ const currentPath = path.join(branchPath, "CURRENT");
947
+ const sentinelPath = path.join(branchPath, "kb.rdf");
948
+ if (!existsSync(rdfDirectory) || !existsSync(currentPath)) {
949
+ recoverInterruptedGeneration(branchPath);
950
+ if (existsSync(rdfDirectory) && existsSync(currentPath)) {
951
+ recoverStaleJournalLock(branchPath);
952
+ archiveLegacyFiles(branchPath);
953
+ if (!existsSync(sentinelPath)) {
954
+ writeFileSync(sentinelPath, "KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1\n", { mode: 0o600 });
955
+ }
956
+ return;
957
+ }
958
+ // Publication moves the validated generation before archiving the
959
+ // legacy files. If the process dies between those moves, preserve the
960
+ // still-live legacy store and discard only the incomplete publication.
961
+ const legacyRdf = path.join(branchPath, "kb.rdf");
962
+ const legacyAudit = path.join(branchPath, "audit.log");
963
+ const legacyContents = existsSync(legacyRdf)
964
+ ? readFileSync(legacyRdf, "utf8")
965
+ : "";
966
+ if (existsSync(legacyAudit) ||
967
+ (existsSync(legacyRdf) &&
968
+ !legacyContents.includes("kibi.rdf-journal.v1"))) {
969
+ rmSync(rdfDirectory, { recursive: true, force: true });
970
+ rmSync(currentPath, { force: true });
971
+ rmSync(markerPath, { force: true });
972
+ }
973
+ else {
974
+ throw new Error(`Journaled branch store is incomplete at ${branchPath}; restore the immutable legacy/ backup or remove the interrupted generation before retrying`);
975
+ }
976
+ }
977
+ else {
978
+ recoverStaleJournalLock(branchPath);
979
+ // The sentinel is deliberately written last during migration. If it is
980
+ // missing after a crash, archive any remaining legacy files and fence
981
+ // old clients before allowing a new engine to attach.
982
+ archiveLegacyFiles(branchPath);
983
+ if (!existsSync(sentinelPath)) {
984
+ writeFileSync(sentinelPath, "KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1\n", { mode: 0o600 });
985
+ }
986
+ return;
987
+ }
988
+ }
989
+ const legacyRdf = path.join(branchPath, "kb.rdf");
990
+ const legacyAudit = path.join(branchPath, "audit.log");
991
+ if (!existsSync(legacyRdf) && !existsSync(legacyAudit)) {
992
+ mkdirSync(path.join(branchPath, "rdf"), { recursive: true });
993
+ writeFileSync(markerPath, '{"format":"kibi.rdf-journal.v1","schemaVersion":1}\n', { mode: 0o600 });
994
+ writeFileSync(path.join(branchPath, "CURRENT"), "generation-1:0\n", {
995
+ mode: 0o600,
996
+ });
997
+ writeFileSync(path.join(branchPath, "kb.rdf"), "KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1\n", { mode: 0o600 });
998
+ return;
999
+ }
1000
+ if (existsSync(legacyRdf)) {
1001
+ const legacyContents = readFileSync(legacyRdf, "utf8");
1002
+ if (!/<rdf:RDF(?:\s|>)/.test(legacyContents)) {
1003
+ throw new Error(`Legacy Kibi store is corrupt at ${legacyRdf}: expected RDF/XML root`);
1004
+ }
1005
+ }
1006
+ const parent = path.dirname(branchPath);
1007
+ const migrationKey = createHash("sha256")
1008
+ .update(path.resolve(branchPath))
1009
+ .digest("hex")
1010
+ .slice(0, 16);
1011
+ const staging = path.join(parent, `.${migrationKey}.migration.${process.pid}.${Date.now()}`);
1012
+ mkdirSync(staging, { recursive: true, mode: 0o700 });
1013
+ // Production migration runs in the Node daemon. The one-shot branch exists
1014
+ // solely for repository tests that import this helper under Bun.
1015
+ const testOneShot = process.env.NODE_ENV === "test" &&
1016
+ typeof globalThis.Bun !== "undefined";
1017
+ const migrator = new PrologProcess({
1018
+ timeout: 120_000,
1019
+ oneShot: testOneShot,
1020
+ });
1021
+ try {
1022
+ if (!testOneShot)
1023
+ await migrator.start();
1024
+ const attachGoal = `kb_attach('${quoteProlog(branchPath)}')`;
1025
+ const migrateGoal = `kb_migrate_legacy('${quoteProlog(branchPath)}','${quoteProlog(staging)}')`;
1026
+ const migrated = testOneShot
1027
+ ? await migrator.query(`(${attachGoal}, ${migrateGoal})`)
1028
+ : await (async () => {
1029
+ const attach = await migrator.query(attachGoal);
1030
+ if (!attach.success)
1031
+ throw new Error(attach.error ?? "Failed to attach legacy KB");
1032
+ return migrator.query(migrateGoal);
1033
+ })();
1034
+ if (!migrated.success)
1035
+ throw new Error(migrated.error ?? "Legacy KB migration failed");
1036
+ fsyncJournaledBranchStore(staging);
1037
+ }
1038
+ catch (error) {
1039
+ rmSync(staging, { recursive: true, force: true });
1040
+ throw error;
1041
+ }
1042
+ finally {
1043
+ await migrator.terminate();
1044
+ }
1045
+ // Publish the validated generation while the originals are still present.
1046
+ // A crash before the marker is moved leaves the legacy branch readable; a
1047
+ // crash after it is moved is repaired by the marker branch above.
1048
+ for (const file of ["rdf", "storage.json", "CURRENT"]) {
1049
+ const source = path.join(staging, file);
1050
+ if (!existsSync(source))
1051
+ continue;
1052
+ const target = path.join(branchPath, file);
1053
+ if (existsSync(target))
1054
+ rmSync(target, { recursive: true, force: true });
1055
+ renameSync(source, target);
1056
+ }
1057
+ archiveLegacyFiles(branchPath);
1058
+ // Older clients must fail closed instead of seeing an empty branch.
1059
+ writeFileSync(path.join(branchPath, "kb.rdf"), "KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1\n", { mode: 0o600 });
1060
+ fsyncJournaledBranchStore(branchPath);
1061
+ rmSync(staging, { recursive: true, force: true });
1062
+ }
1063
+ function archiveLegacyFiles(branchPath) {
1064
+ const legacyDir = path.join(branchPath, "legacy");
1065
+ mkdirSync(legacyDir, { recursive: true, mode: 0o700 });
1066
+ for (const file of ["kb.rdf", "audit.log"]) {
1067
+ const source = path.join(branchPath, file);
1068
+ if (!existsSync(source))
1069
+ continue;
1070
+ if (file === "kb.rdf") {
1071
+ const contents = readFileSync(source, "utf8");
1072
+ if (contents.includes("kibi.rdf-journal.v1"))
1073
+ continue;
1074
+ }
1075
+ const destination = path.join(legacyDir, file);
1076
+ if (existsSync(destination)) {
1077
+ // Never overwrite an immutable backup from an earlier interrupted
1078
+ // attempt. Preserve the second copy with an explicit recovery suffix.
1079
+ renameSync(source, path.join(legacyDir, `${file}.recovery.${Date.now()}`));
1080
+ }
1081
+ else {
1082
+ renameSync(source, destination);
1083
+ }
1084
+ }
1085
+ }
1086
+ function recoverInterruptedGeneration(branchPath) {
1087
+ let entries = [];
1088
+ try {
1089
+ entries = readdirSync(branchPath);
1090
+ }
1091
+ catch {
1092
+ return;
1093
+ }
1094
+ const newest = (prefix) => {
1095
+ const candidates = entries
1096
+ .filter((entry) => entry.startsWith(prefix))
1097
+ .sort()
1098
+ .reverse();
1099
+ const candidate = candidates[0];
1100
+ return candidate === undefined ? null : path.join(branchPath, candidate);
1101
+ };
1102
+ const oldRdf = newest("rdf.old.");
1103
+ const oldCurrent = newest("CURRENT.old.");
1104
+ const rdfPath = path.join(branchPath, "rdf");
1105
+ const currentPath = path.join(branchPath, "CURRENT");
1106
+ // If either half of the pointer/database pair is missing, prefer restoring
1107
+ // the matching old pair. This prevents a new RDF generation from being
1108
+ // opened with the old commit sequence after a crash between renames.
1109
+ if ((!existsSync(rdfPath) || !existsSync(currentPath)) &&
1110
+ oldRdf &&
1111
+ oldCurrent) {
1112
+ try {
1113
+ if (existsSync(rdfPath))
1114
+ rmSync(rdfPath, { recursive: true, force: true });
1115
+ if (existsSync(currentPath))
1116
+ rmSync(currentPath, { force: true });
1117
+ renameSync(oldRdf, rdfPath);
1118
+ renameSync(oldCurrent, currentPath);
1119
+ return;
1120
+ }
1121
+ catch {
1122
+ // Fall through to the individual best-effort restores below.
1123
+ }
1124
+ }
1125
+ if (!existsSync(rdfPath) && oldRdf) {
1126
+ try {
1127
+ renameSync(oldRdf, rdfPath);
1128
+ }
1129
+ catch {
1130
+ // Leave the actionable incomplete-store error for the caller.
1131
+ }
1132
+ }
1133
+ if (!existsSync(currentPath) && oldCurrent) {
1134
+ try {
1135
+ renameSync(oldCurrent, currentPath);
1136
+ }
1137
+ catch {
1138
+ // Leave the actionable incomplete-store error for the caller.
1139
+ }
1140
+ }
1141
+ }
1142
+ // implements REQ-core-journaled-engine-lifecycle
1143
+ export async function runEngineDaemon(options) {
1144
+ if (!isValidBranchName(options.branch)) {
1145
+ throw new Error(`Invalid Kibi engine branch name: ${options.branch}`);
1146
+ }
1147
+ const branchPath = ensureBranchStoreManifest(options.workspaceRoot, options.branch);
1148
+ await ensureJournaledBranchStoreAsync(branchPath);
1149
+ const prolog = new PrologProcess({ timeout: 120_000, oneShot: false });
1150
+ await prolog.start();
1151
+ const attached = await prolog.query(`kb_attach('${quoteProlog(branchPath)}')`);
1152
+ if (!attached.success)
1153
+ throw new Error(attached.error ?? "Failed to attach branch KB");
1154
+ const statusModulePath = path.join(path.dirname(resolveKbPlPath()), "status.pl");
1155
+ const statusLoaded = await prolog.query(`use_module('${quoteProlog(statusModulePath.replaceAll("\\", "/"))}')`);
1156
+ if (!statusLoaded.success) {
1157
+ throw new Error(statusLoaded.error ?? "Failed to load Kibi status module");
1158
+ }
1159
+ mkdirSync(path.dirname(options.socketPath), { recursive: true, mode: 0o700 });
1160
+ if (existsSync(options.socketPath)) {
1161
+ let live = false;
1162
+ try {
1163
+ const existing = await connectSocket(options.socketPath, 100);
1164
+ existing.destroy();
1165
+ live = true;
1166
+ }
1167
+ catch {
1168
+ // A refused connection means this is a stale filesystem socket.
1169
+ }
1170
+ if (live) {
1171
+ throw new Error(`A Kibi engine is already listening at ${options.socketPath}`);
1172
+ }
1173
+ try {
1174
+ rmSync(options.socketPath, { force: true });
1175
+ }
1176
+ catch {
1177
+ // The listen call below reports permission/address-in-use failures.
1178
+ }
1179
+ }
1180
+ writeFileSync(enginePidPath(options.workspaceRoot, options.branch), `${process.pid}\n`, { mode: 0o600 });
1181
+ const server = net.createServer();
1182
+ let activeClients = 0;
1183
+ let idleTimer = null;
1184
+ let queue = Promise.resolve();
1185
+ let idleCompactionQueued = false;
1186
+ const queryCache = new Map();
1187
+ let freshnessCache = null;
1188
+ const cancelledRequests = new Set();
1189
+ const clients = new Set();
1190
+ let shuttingDown = false;
1191
+ const exactBranchStatus = (result) => {
1192
+ if (!result.success || typeof result.bindings.JsonString !== "string") {
1193
+ return result;
1194
+ }
1195
+ try {
1196
+ const decoded = JSON.parse(JSON.parse(result.bindings.JsonString));
1197
+ decoded.branch = options.branch;
1198
+ return {
1199
+ ...result,
1200
+ bindings: {
1201
+ ...result.bindings,
1202
+ JsonString: JSON.stringify(JSON.stringify(decoded)),
1203
+ },
1204
+ };
1205
+ }
1206
+ catch {
1207
+ return result;
1208
+ }
1209
+ };
1210
+ const scheduleIdleExit = () => {
1211
+ if (activeClients > 0)
1212
+ return;
1213
+ if (!idleCompactionQueued) {
1214
+ idleCompactionQueued = true;
1215
+ queue = queue
1216
+ .then(async () => {
1217
+ if (activeClients === 0) {
1218
+ await prolog.query("kb_storage_compact_if_needed");
1219
+ }
1220
+ })
1221
+ .catch(() => undefined)
1222
+ .finally(() => {
1223
+ idleCompactionQueued = false;
1224
+ });
1225
+ }
1226
+ if (idleTimer)
1227
+ clearTimeout(idleTimer);
1228
+ idleTimer = setTimeout(() => {
1229
+ if (activeClients === 0)
1230
+ void shutdown();
1231
+ }, engineIdleTimeoutMs());
1232
+ };
1233
+ const handle = async (request) => {
1234
+ if (request.protocolVersion !== ENGINE_PROTOCOL_VERSION) {
1235
+ throw new Error(`Kibi engine protocol mismatch: client=${request.protocolVersion ?? "missing"}, server=${ENGINE_PROTOCOL_VERSION}`);
1236
+ }
1237
+ const expectedPackages = process.env.KIBI_PACKAGE_VERSIONS;
1238
+ if (expectedPackages !== undefined &&
1239
+ request.packageVersions !== expectedPackages) {
1240
+ throw new Error(`Kibi engine package-version mismatch: client=${request.packageVersions ?? "missing"}, server=${expectedPackages}`);
1241
+ }
1242
+ if (path.resolve(request.workspaceRoot ?? "") !==
1243
+ path.resolve(options.workspaceRoot) ||
1244
+ request.branch !== options.branch) {
1245
+ throw new Error("Kibi engine workspace identity mismatch");
1246
+ }
1247
+ switch (request.method) {
1248
+ case "entities": {
1249
+ const limit = request.limit ?? 100;
1250
+ const offset = request.offset ?? 0;
1251
+ if (!Number.isInteger(limit) ||
1252
+ !Number.isInteger(offset) ||
1253
+ limit < 0 ||
1254
+ offset < 0 ||
1255
+ limit > 100_000) {
1256
+ throw new Error("entities.limit/offset must be bounded integers");
1257
+ }
1258
+ const type = request.type === undefined
1259
+ ? "none"
1260
+ : `'${quoteProlog(request.type)}'`;
1261
+ const id = request.entityId === undefined
1262
+ ? "none"
1263
+ : `'${quoteProlog(request.entityId)}'`;
1264
+ const tags = `[${(request.tags ?? [])
1265
+ .map((tag) => `'${quoteProlog(tag)}'`)
1266
+ .join(",")}]`;
1267
+ const source = request.sourceFile === undefined
1268
+ ? "none"
1269
+ : `'${quoteProlog(request.sourceFile)}'`;
1270
+ const result = await prolog.query(`kb_query_entities(${type}, ${id}, ${tags}, ${source}, ${limit}, ${offset}, Rows, Count)`);
1271
+ if (!result.success) {
1272
+ throw new Error(result.error ?? "Indexed entity query failed");
1273
+ }
1274
+ return result;
1275
+ }
1276
+ case "search": {
1277
+ const limit = request.limit ?? 10_000;
1278
+ const offset = request.offset ?? 0;
1279
+ if (typeof request.searchQuery !== "string" ||
1280
+ request.searchQuery.trim().length === 0) {
1281
+ throw new Error("search.searchQuery must be a non-empty string");
1282
+ }
1283
+ if (!Number.isInteger(limit) ||
1284
+ !Number.isInteger(offset) ||
1285
+ limit < 0 ||
1286
+ offset < 0 ||
1287
+ limit > 100_000) {
1288
+ throw new Error("search.limit/offset must be bounded integers");
1289
+ }
1290
+ const type = request.type === undefined
1291
+ ? "none"
1292
+ : `'${quoteProlog(request.type)}'`;
1293
+ const result = await prolog.query(`kb_search_entities(${type}, '${quoteProlog(request.searchQuery)}', ${limit}, ${offset}, Rows, Count)`);
1294
+ if (!result.success) {
1295
+ throw new Error(result.error ?? "Indexed search candidate query failed");
1296
+ }
1297
+ return result;
1298
+ }
1299
+ case "command": {
1300
+ const command = request.command;
1301
+ if (command === undefined || command.version !== 1) {
1302
+ throw new Error("engine.command must use version 1");
1303
+ }
1304
+ switch (command.kind) {
1305
+ case "status":
1306
+ return exactBranchStatus(await prolog.query("status:kb_status_json(JsonString)"));
1307
+ case "checkpoint": {
1308
+ queryCache.clear();
1309
+ freshnessCache = null;
1310
+ const result = await prolog.query("kb_sync_checkpoint");
1311
+ if (result.success)
1312
+ fsyncJournaledBranchStore(branchPath);
1313
+ return result;
1314
+ }
1315
+ case "compact": {
1316
+ const result = await prolog.query("kb_storage_compact");
1317
+ if (result.success)
1318
+ fsyncJournaledBranchStore(branchPath);
1319
+ return result;
1320
+ }
1321
+ case "save": {
1322
+ const result = await prolog.query("kb_save");
1323
+ if (result.success)
1324
+ fsyncJournaledBranchStore(branchPath);
1325
+ return result;
1326
+ }
1327
+ case "check": {
1328
+ if (command.rule !== undefined &&
1329
+ !/^[a-z][a-z0-9_]*$/.test(command.rule)) {
1330
+ throw new Error("engine.check.rule must be a lowercase rule name");
1331
+ }
1332
+ // Rule selection is intentionally resolved inside the engine. A
1333
+ // caller never supplies a Prolog goal over the command boundary.
1334
+ return prolog.query("checks:check_all_json(JsonString)");
1335
+ }
1336
+ case "relationship": {
1337
+ if (!command.type || !command.from || !command.to) {
1338
+ throw new Error("engine.relationship requires type, from, and to");
1339
+ }
1340
+ const type = quoteProlog(command.type);
1341
+ const from = quoteProlog(command.from);
1342
+ const to = quoteProlog(command.to);
1343
+ const goal = command.action === "assert"
1344
+ ? `kb_assert_relationship('${type}', '${from}', '${to}', [])`
1345
+ : `kb_retract_relationship('${type}', '${from}', '${to}')`;
1346
+ const result = await prolog.query(goal);
1347
+ if (result.success) {
1348
+ queryCache.clear();
1349
+ freshnessCache = null;
1350
+ fsyncJournaledBranchStore(branchPath);
1351
+ }
1352
+ return result;
1353
+ }
1354
+ case "persistence": {
1355
+ const goal = command.action === "checkpoint"
1356
+ ? "kb_sync_checkpoint"
1357
+ : command.action === "compact"
1358
+ ? "kb_storage_compact"
1359
+ : command.action === "save"
1360
+ ? "kb_save"
1361
+ : command.targetDirectory
1362
+ ? `kb_storage_export('${quoteProlog(command.targetDirectory)}')`
1363
+ : null;
1364
+ if (goal === null) {
1365
+ throw new Error("engine.persistence.export requires targetDirectory");
1366
+ }
1367
+ const result = await prolog.query(goal);
1368
+ if (result.success)
1369
+ fsyncJournaledBranchStore(branchPath);
1370
+ return result;
1371
+ }
1372
+ case "lifecycle": {
1373
+ if (command.action === "stop") {
1374
+ setImmediate(() => void shutdown());
1375
+ return { stopped: true };
1376
+ }
1377
+ if (!Number.isInteger(command.requestId)) {
1378
+ throw new Error("engine.lifecycle.cancel requires requestId");
1379
+ }
1380
+ const requestId = command.requestId;
1381
+ cancelledRequests.add(requestId);
1382
+ return { cancelled: requestId };
1383
+ }
1384
+ case "stop":
1385
+ setImmediate(() => void shutdown());
1386
+ return { stopped: true };
1387
+ case "cancel":
1388
+ cancelledRequests.add(command.requestId);
1389
+ return { cancelled: command.requestId };
1390
+ case "entities": {
1391
+ if (!Number.isInteger(command.limit) ||
1392
+ !Number.isInteger(command.offset) ||
1393
+ command.limit < 0 ||
1394
+ command.offset < 0 ||
1395
+ command.limit > 100_000) {
1396
+ throw new Error("engine.entities.limit/offset must be bounded integers");
1397
+ }
1398
+ const type = command.type === undefined
1399
+ ? "none"
1400
+ : `'${quoteProlog(command.type)}'`;
1401
+ const id = command.id === undefined
1402
+ ? "none"
1403
+ : `'${quoteProlog(command.id)}'`;
1404
+ const tags = `[${(command.tags ?? []).map((tag) => `'${quoteProlog(tag)}'`).join(",")}]`;
1405
+ const source = command.sourceFile === undefined
1406
+ ? "none"
1407
+ : `'${quoteProlog(command.sourceFile)}'`;
1408
+ return prolog.query(`kb_query_entities(${type}, ${id}, ${tags}, ${source}, ${command.limit}, ${command.offset}, Rows, Count)`);
1409
+ }
1410
+ case "search": {
1411
+ if (!command.query.trim())
1412
+ throw new Error("engine.search.query must be non-empty");
1413
+ if (!Number.isInteger(command.limit) ||
1414
+ !Number.isInteger(command.offset) ||
1415
+ command.limit < 0 ||
1416
+ command.offset < 0 ||
1417
+ command.limit > 100_000) {
1418
+ throw new Error("engine.search.limit/offset must be bounded integers");
1419
+ }
1420
+ const type = command.type === undefined
1421
+ ? "none"
1422
+ : `'${quoteProlog(command.type)}'`;
1423
+ return prolog.query(`kb_search_entities(${type}, '${quoteProlog(command.query)}', ${command.limit}, ${command.offset}, Rows, Count)`);
1424
+ }
1425
+ default:
1426
+ throw new Error("engine.command kind is unsupported");
1427
+ }
1428
+ }
1429
+ case "kbStatus": {
1430
+ if (freshnessCache !== null &&
1431
+ Date.now() - freshnessCache.capturedAt <= ENGINE_FRESHNESS_CACHE_MS) {
1432
+ return freshnessCache.result;
1433
+ }
1434
+ const result = exactBranchStatus(await prolog.query("status:kb_status_json(JsonString)"));
1435
+ if (result.success) {
1436
+ freshnessCache = { capturedAt: Date.now(), result };
1437
+ }
1438
+ return result;
1439
+ }
1440
+ case "query": {
1441
+ if (typeof request.goal !== "string")
1442
+ throw new Error("query.goal must be a string");
1443
+ if (!safeEngineGoal(request.goal)) {
1444
+ throw new Error("Kibi engine accepts only typed Kibi predicates; arbitrary Prolog goals are not exposed");
1445
+ }
1446
+ if (mutatingEngineGoal(request.goal)) {
1447
+ queryCache.clear();
1448
+ freshnessCache = null;
1449
+ }
1450
+ if (cacheableEngineGoal(request.goal)) {
1451
+ const cached = queryCache.get(request.goal);
1452
+ if (cached !== undefined)
1453
+ return cached;
1454
+ }
1455
+ const result = await prolog.query(request.goal);
1456
+ if (result.success && mutatingEngineGoal(request.goal)) {
1457
+ fsyncJournaledBranchStore(branchPath);
1458
+ }
1459
+ if (result.success &&
1460
+ cacheableEngineGoal(request.goal) &&
1461
+ Buffer.byteLength(JSON.stringify(result), "utf8") <=
1462
+ ENGINE_QUERY_CACHE_MAX_RESULT_BYTES) {
1463
+ if (queryCache.size >= ENGINE_QUERY_CACHE_MAX_ENTRIES) {
1464
+ const oldest = queryCache.keys().next().value;
1465
+ if (typeof oldest === "string")
1466
+ queryCache.delete(oldest);
1467
+ }
1468
+ queryCache.set(request.goal, result);
1469
+ }
1470
+ return result;
1471
+ }
1472
+ case "status":
1473
+ return prolog.query("kb_storage_status(Status), atom_json_dict(Json, Status, [])");
1474
+ case "checkpoint": {
1475
+ queryCache.clear();
1476
+ freshnessCache = null;
1477
+ const result = await prolog.query("kb_sync_checkpoint");
1478
+ if (result.success)
1479
+ fsyncJournaledBranchStore(branchPath);
1480
+ return result;
1481
+ }
1482
+ case "compact": {
1483
+ const result = await prolog.query("kb_storage_compact");
1484
+ if (result.success)
1485
+ fsyncJournaledBranchStore(branchPath);
1486
+ return result;
1487
+ }
1488
+ case "export":
1489
+ if (typeof request.targetDirectory !== "string")
1490
+ throw new Error("export.targetDirectory is required");
1491
+ return prolog.query(`kb_storage_export('${quoteProlog(request.targetDirectory)}')`);
1492
+ case "stop":
1493
+ setImmediate(() => void shutdown());
1494
+ return { stopped: true };
1495
+ case "cancel":
1496
+ if (typeof request.cancelOf === "number") {
1497
+ cancelledRequests.add(request.cancelOf);
1498
+ }
1499
+ return { cancelled: request.cancelOf ?? null };
1500
+ }
1501
+ };
1502
+ const shutdown = async () => {
1503
+ if (shuttingDown)
1504
+ return;
1505
+ shuttingDown = true;
1506
+ process.off("SIGTERM", requestSignalShutdown);
1507
+ process.off("SIGINT", requestSignalShutdown);
1508
+ if (idleTimer)
1509
+ clearTimeout(idleTimer);
1510
+ const saved = await prolog.query("kb_save").catch(() => null);
1511
+ if (saved?.success) {
1512
+ try {
1513
+ fsyncJournaledBranchStore(branchPath);
1514
+ }
1515
+ catch {
1516
+ // Shutdown remains best effort. Request-time writes fail closed if
1517
+ // their durability barrier cannot complete.
1518
+ }
1519
+ }
1520
+ await prolog.terminate().catch(() => undefined);
1521
+ for (const client of clients)
1522
+ client.destroy();
1523
+ server.close();
1524
+ // A close callback can remain pending on older Node releases when a
1525
+ // client disconnects during `server.close()`. The daemon has already
1526
+ // removed its socket/pid and has no useful work left, so do not keep the
1527
+ // hosting process alive on that bookkeeping handle.
1528
+ server.unref();
1529
+ try {
1530
+ if (existsSync(options.socketPath))
1531
+ rmSync(options.socketPath, { force: true });
1532
+ if (existsSync(enginePidPath(options.workspaceRoot, options.branch)))
1533
+ rmSync(enginePidPath(options.workspaceRoot, options.branch), {
1534
+ force: true,
1535
+ });
1536
+ }
1537
+ catch {
1538
+ // best effort cleanup
1539
+ }
1540
+ // The daemon is a detached host process. Once the SWI child and socket are
1541
+ // gone, force the final event-loop exit rather than retaining a stale
1542
+ // detached Node process on platform-specific server bookkeeping.
1543
+ setTimeout(() => process.exit(0), 50).unref();
1544
+ };
1545
+ // Detached engines must cross the same durability boundary when a service
1546
+ // manager or a test harness terminates them as they do for an RPC stop.
1547
+ // implements REQ-test-journaled-engine-harness
1548
+ const requestSignalShutdown = () => {
1549
+ void shutdown();
1550
+ };
1551
+ process.once("SIGTERM", requestSignalShutdown);
1552
+ process.once("SIGINT", requestSignalShutdown);
1553
+ server.on("connection", (socket) => {
1554
+ clients.add(socket);
1555
+ activeClients += 1;
1556
+ let buffer = Buffer.alloc(0);
1557
+ socket.on("data", (chunk) => {
1558
+ try {
1559
+ const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
1560
+ buffer = parseFrames(Buffer.concat([buffer, bytes]), (value) => {
1561
+ const request = value;
1562
+ if (request.method === "cancel" &&
1563
+ typeof request.cancelOf === "number") {
1564
+ cancelledRequests.add(request.cancelOf);
1565
+ }
1566
+ queue = queue
1567
+ .then(async () => {
1568
+ if (cancelledRequests.delete(request.id)) {
1569
+ writeSocketFrame(socket, {
1570
+ id: request.id,
1571
+ ok: false,
1572
+ error: "Kibi engine request cancelled",
1573
+ serverPackageVersions: ENGINE_PACKAGE_VERSIONS,
1574
+ });
1575
+ return;
1576
+ }
1577
+ try {
1578
+ const result = await handle(request);
1579
+ writeSocketFrame(socket, {
1580
+ id: request.id,
1581
+ ok: true,
1582
+ result,
1583
+ serverPackageVersions: ENGINE_PACKAGE_VERSIONS,
1584
+ });
1585
+ }
1586
+ catch (error) {
1587
+ writeSocketFrame(socket, {
1588
+ id: request.id,
1589
+ ok: false,
1590
+ error: error instanceof Error ? error.message : String(error),
1591
+ serverPackageVersions: ENGINE_PACKAGE_VERSIONS,
1592
+ });
1593
+ }
1594
+ })
1595
+ .catch(() => undefined);
1596
+ });
1597
+ }
1598
+ catch (error) {
1599
+ socket.destroy(error instanceof Error ? error : new Error(String(error)));
1600
+ }
1601
+ });
1602
+ socket.on("close", () => {
1603
+ clients.delete(socket);
1604
+ activeClients -= 1;
1605
+ scheduleIdleExit();
1606
+ });
1607
+ });
1608
+ await new Promise((resolve, reject) => {
1609
+ server.once("error", reject);
1610
+ server.listen(options.socketPath, () => resolve());
1611
+ });
1612
+ if (process.platform !== "win32") {
1613
+ try {
1614
+ chmodSync(options.socketPath, 0o600);
1615
+ }
1616
+ catch {
1617
+ // The runtime directory is private; retain a usable socket if a
1618
+ // platform does not expose chmod for its local socket implementation.
1619
+ }
1620
+ }
1621
+ scheduleIdleExit();
1622
+ await new Promise((resolve) => {
1623
+ server.once("close", resolve);
1624
+ });
1625
+ }