@cavelang/cli 0.27.3 → 0.29.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 (321) hide show
  1. package/Authors.md +5 -0
  2. package/License.md +6 -0
  3. package/README.md +122 -24
  4. package/dist/internal/act/action.d.ts +69 -0
  5. package/dist/internal/act/action.d.ts.map +1 -0
  6. package/dist/internal/act/action.js +201 -0
  7. package/dist/internal/act/action.js.map +1 -0
  8. package/dist/internal/act/declare.d.ts +102 -0
  9. package/dist/internal/act/declare.d.ts.map +1 -0
  10. package/dist/internal/act/declare.js +270 -0
  11. package/dist/internal/act/declare.js.map +1 -0
  12. package/dist/internal/act/engine.d.ts +103 -0
  13. package/dist/internal/act/engine.d.ts.map +1 -0
  14. package/dist/internal/act/engine.js +298 -0
  15. package/dist/internal/act/engine.js.map +1 -0
  16. package/dist/internal/act/index.d.ts +27 -0
  17. package/dist/internal/act/index.d.ts.map +1 -0
  18. package/dist/internal/act/index.js +25 -0
  19. package/dist/internal/act/index.js.map +1 -0
  20. package/dist/internal/automate/automation.d.ts +78 -0
  21. package/dist/internal/automate/automation.d.ts.map +1 -0
  22. package/dist/internal/automate/automation.js +135 -0
  23. package/dist/internal/automate/automation.js.map +1 -0
  24. package/dist/internal/automate/declare.d.ts +84 -0
  25. package/dist/internal/automate/declare.d.ts.map +1 -0
  26. package/dist/internal/automate/declare.js +233 -0
  27. package/dist/internal/automate/declare.js.map +1 -0
  28. package/dist/internal/automate/engine.d.ts +125 -0
  29. package/dist/internal/automate/engine.d.ts.map +1 -0
  30. package/dist/internal/automate/engine.js +388 -0
  31. package/dist/internal/automate/engine.js.map +1 -0
  32. package/dist/internal/automate/index.d.ts +28 -0
  33. package/dist/internal/automate/index.d.ts.map +1 -0
  34. package/dist/internal/automate/index.js +26 -0
  35. package/dist/internal/automate/index.js.map +1 -0
  36. package/dist/internal/automate/main.d.ts +26 -0
  37. package/dist/internal/automate/main.d.ts.map +1 -0
  38. package/dist/internal/automate/main.js +298 -0
  39. package/dist/internal/automate/main.js.map +1 -0
  40. package/dist/internal/connect/index.d.ts +26 -0
  41. package/dist/internal/connect/index.d.ts.map +1 -0
  42. package/dist/internal/connect/index.js +24 -0
  43. package/dist/internal/connect/index.js.map +1 -0
  44. package/dist/internal/connect/main.d.ts +23 -0
  45. package/dist/internal/connect/main.d.ts.map +1 -0
  46. package/dist/internal/connect/main.js +330 -0
  47. package/dist/internal/connect/main.js.map +1 -0
  48. package/dist/internal/connect/run.d.ts +85 -0
  49. package/dist/internal/connect/run.d.ts.map +1 -0
  50. package/dist/internal/connect/run.js +242 -0
  51. package/dist/internal/connect/run.js.map +1 -0
  52. package/dist/internal/connect/source.d.ts +44 -0
  53. package/dist/internal/connect/source.d.ts.map +1 -0
  54. package/dist/internal/connect/source.js +215 -0
  55. package/dist/internal/connect/source.js.map +1 -0
  56. package/dist/internal/connect/template.d.ts +69 -0
  57. package/dist/internal/connect/template.d.ts.map +1 -0
  58. package/dist/internal/connect/template.js +221 -0
  59. package/dist/internal/connect/template.js.map +1 -0
  60. package/dist/internal/eval/index.d.ts +28 -0
  61. package/dist/internal/eval/index.d.ts.map +1 -0
  62. package/dist/internal/eval/index.js +27 -0
  63. package/dist/internal/eval/index.js.map +1 -0
  64. package/dist/internal/eval/judge.d.ts +30 -0
  65. package/dist/internal/eval/judge.d.ts.map +1 -0
  66. package/dist/internal/eval/judge.js +101 -0
  67. package/dist/internal/eval/judge.js.map +1 -0
  68. package/dist/internal/eval/loop.d.ts +64 -0
  69. package/dist/internal/eval/loop.d.ts.map +1 -0
  70. package/dist/internal/eval/loop.js +123 -0
  71. package/dist/internal/eval/loop.js.map +1 -0
  72. package/dist/internal/eval/main.d.ts +17 -0
  73. package/dist/internal/eval/main.d.ts.map +1 -0
  74. package/dist/internal/eval/main.js +236 -0
  75. package/dist/internal/eval/main.js.map +1 -0
  76. package/dist/internal/eval/queries.d.ts +74 -0
  77. package/dist/internal/eval/queries.d.ts.map +1 -0
  78. package/dist/internal/eval/queries.js +161 -0
  79. package/dist/internal/eval/queries.js.map +1 -0
  80. package/dist/internal/eval/run.d.ts +143 -0
  81. package/dist/internal/eval/run.d.ts.map +1 -0
  82. package/dist/internal/eval/run.js +376 -0
  83. package/dist/internal/eval/run.js.map +1 -0
  84. package/dist/internal/eval/score.d.ts +81 -0
  85. package/dist/internal/eval/score.d.ts.map +1 -0
  86. package/dist/internal/eval/score.js +126 -0
  87. package/dist/internal/eval/score.js.map +1 -0
  88. package/dist/internal/eval/suite.d.ts +66 -0
  89. package/dist/internal/eval/suite.d.ts.map +1 -0
  90. package/dist/internal/eval/suite.js +130 -0
  91. package/dist/internal/eval/suite.js.map +1 -0
  92. package/dist/internal/ingest/context.d.ts +20 -0
  93. package/dist/internal/ingest/context.d.ts.map +1 -0
  94. package/dist/internal/ingest/context.js +59 -0
  95. package/dist/internal/ingest/context.js.map +1 -0
  96. package/dist/internal/ingest/files.d.ts +46 -0
  97. package/dist/internal/ingest/files.d.ts.map +1 -0
  98. package/dist/internal/ingest/files.js +115 -0
  99. package/dist/internal/ingest/files.js.map +1 -0
  100. package/dist/internal/ingest/index.d.ts +31 -0
  101. package/dist/internal/ingest/index.d.ts.map +1 -0
  102. package/dist/internal/ingest/index.js +29 -0
  103. package/dist/internal/ingest/index.js.map +1 -0
  104. package/dist/internal/ingest/main.d.ts +12 -0
  105. package/dist/internal/ingest/main.d.ts.map +1 -0
  106. package/dist/internal/ingest/main.js +180 -0
  107. package/dist/internal/ingest/main.js.map +1 -0
  108. package/dist/internal/ingest/prompt.d.ts +32 -0
  109. package/dist/internal/ingest/prompt.d.ts.map +1 -0
  110. package/dist/internal/ingest/prompt.js +75 -0
  111. package/dist/internal/ingest/prompt.js.map +1 -0
  112. package/dist/internal/ingest/run.d.ts +142 -0
  113. package/dist/internal/ingest/run.d.ts.map +1 -0
  114. package/dist/internal/ingest/run.js +350 -0
  115. package/dist/internal/ingest/run.js.map +1 -0
  116. package/dist/internal/ingest/web.d.ts +48 -0
  117. package/dist/internal/ingest/web.d.ts.map +1 -0
  118. package/dist/internal/ingest/web.js +140 -0
  119. package/dist/internal/ingest/web.js.map +1 -0
  120. package/dist/internal/loop/demo.d.ts +22 -0
  121. package/dist/internal/loop/demo.d.ts.map +1 -0
  122. package/dist/internal/loop/demo.js +67 -0
  123. package/dist/internal/loop/demo.js.map +1 -0
  124. package/dist/internal/loop/index.d.ts +20 -0
  125. package/dist/internal/loop/index.d.ts.map +1 -0
  126. package/dist/internal/loop/index.js +17 -0
  127. package/dist/internal/loop/index.js.map +1 -0
  128. package/dist/internal/loop/llm.d.ts +99 -0
  129. package/dist/internal/loop/llm.d.ts.map +1 -0
  130. package/dist/internal/loop/llm.js +231 -0
  131. package/dist/internal/loop/llm.js.map +1 -0
  132. package/dist/internal/loop/reconstruct.d.ts +87 -0
  133. package/dist/internal/loop/reconstruct.d.ts.map +1 -0
  134. package/dist/internal/loop/reconstruct.js +157 -0
  135. package/dist/internal/loop/reconstruct.js.map +1 -0
  136. package/dist/internal/loop/sqlite.d.ts +13 -0
  137. package/dist/internal/loop/sqlite.d.ts.map +1 -0
  138. package/dist/internal/loop/sqlite.js +33 -0
  139. package/dist/internal/loop/sqlite.js.map +1 -0
  140. package/dist/internal/loop/store.d.ts +50 -0
  141. package/dist/internal/loop/store.d.ts.map +1 -0
  142. package/dist/internal/loop/store.js +100 -0
  143. package/dist/internal/loop/store.js.map +1 -0
  144. package/dist/internal/mcp/bin.d.ts +7 -0
  145. package/dist/internal/mcp/bin.d.ts.map +1 -0
  146. package/dist/internal/mcp/bin.js +8 -0
  147. package/dist/internal/mcp/bin.js.map +1 -0
  148. package/dist/internal/mcp/index.d.ts +25 -0
  149. package/dist/internal/mcp/index.d.ts.map +1 -0
  150. package/dist/internal/mcp/index.js +23 -0
  151. package/dist/internal/mcp/index.js.map +1 -0
  152. package/dist/internal/mcp/main.d.ts +23 -0
  153. package/dist/internal/mcp/main.d.ts.map +1 -0
  154. package/dist/internal/mcp/main.js +156 -0
  155. package/dist/internal/mcp/main.js.map +1 -0
  156. package/dist/internal/mcp/server.d.ts +92 -0
  157. package/dist/internal/mcp/server.d.ts.map +1 -0
  158. package/dist/internal/mcp/server.js +251 -0
  159. package/dist/internal/mcp/server.js.map +1 -0
  160. package/dist/internal/mcp/tools.d.ts +91 -0
  161. package/dist/internal/mcp/tools.d.ts.map +1 -0
  162. package/dist/internal/mcp/tools.js +584 -0
  163. package/dist/internal/mcp/tools.js.map +1 -0
  164. package/dist/internal/rules/declare.d.ts +58 -0
  165. package/dist/internal/rules/declare.d.ts.map +1 -0
  166. package/dist/internal/rules/declare.js +152 -0
  167. package/dist/internal/rules/declare.js.map +1 -0
  168. package/dist/internal/rules/engine.d.ts +125 -0
  169. package/dist/internal/rules/engine.d.ts.map +1 -0
  170. package/dist/internal/rules/engine.js +575 -0
  171. package/dist/internal/rules/engine.js.map +1 -0
  172. package/dist/internal/rules/index.d.ts +23 -0
  173. package/dist/internal/rules/index.d.ts.map +1 -0
  174. package/dist/internal/rules/index.js +21 -0
  175. package/dist/internal/rules/index.js.map +1 -0
  176. package/dist/internal/rules/rule.d.ts +88 -0
  177. package/dist/internal/rules/rule.d.ts.map +1 -0
  178. package/dist/internal/rules/rule.js +221 -0
  179. package/dist/internal/rules/rule.js.map +1 -0
  180. package/dist/internal/shape/check.d.ts +118 -0
  181. package/dist/internal/shape/check.d.ts.map +1 -0
  182. package/dist/internal/shape/check.js +331 -0
  183. package/dist/internal/shape/check.js.map +1 -0
  184. package/dist/internal/shape/client.d.ts +31 -0
  185. package/dist/internal/shape/client.d.ts.map +1 -0
  186. package/dist/internal/shape/client.js +170 -0
  187. package/dist/internal/shape/client.js.map +1 -0
  188. package/dist/internal/shape/gate.d.ts +33 -0
  189. package/dist/internal/shape/gate.d.ts.map +1 -0
  190. package/dist/internal/shape/gate.js +0 -0
  191. package/dist/internal/shape/gate.js.map +1 -0
  192. package/dist/internal/shape/index.d.ts +27 -0
  193. package/dist/internal/shape/index.d.ts.map +1 -0
  194. package/dist/internal/shape/index.js +23 -0
  195. package/dist/internal/shape/index.js.map +1 -0
  196. package/dist/internal/shape/suggest.d.ts +82 -0
  197. package/dist/internal/shape/suggest.d.ts.map +1 -0
  198. package/dist/internal/shape/suggest.js +374 -0
  199. package/dist/internal/shape/suggest.js.map +1 -0
  200. package/dist/internal/sync/index.d.ts +22 -0
  201. package/dist/internal/sync/index.d.ts.map +1 -0
  202. package/dist/internal/sync/index.js +21 -0
  203. package/dist/internal/sync/index.js.map +1 -0
  204. package/dist/internal/sync/sync.d.ts +95 -0
  205. package/dist/internal/sync/sync.d.ts.map +1 -0
  206. package/dist/internal/sync/sync.js +342 -0
  207. package/dist/internal/sync/sync.js.map +1 -0
  208. package/dist/internal/view/api.d.ts +196 -0
  209. package/dist/internal/view/api.d.ts.map +1 -0
  210. package/dist/internal/view/api.js +189 -0
  211. package/dist/internal/view/api.js.map +1 -0
  212. package/dist/internal/view/index.d.ts +33 -0
  213. package/dist/internal/view/index.d.ts.map +1 -0
  214. package/dist/internal/view/index.js +30 -0
  215. package/dist/internal/view/index.js.map +1 -0
  216. package/dist/internal/view/main.d.ts +11 -0
  217. package/dist/internal/view/main.d.ts.map +1 -0
  218. package/dist/internal/view/main.js +98 -0
  219. package/dist/internal/view/main.js.map +1 -0
  220. package/dist/internal/view/page.d.ts +14 -0
  221. package/dist/internal/view/page.d.ts.map +1 -0
  222. package/dist/internal/view/page.js +370 -0
  223. package/dist/internal/view/page.js.map +1 -0
  224. package/dist/internal/view/report.d.ts +52 -0
  225. package/dist/internal/view/report.d.ts.map +1 -0
  226. package/dist/internal/view/report.js +367 -0
  227. package/dist/internal/view/report.js.map +1 -0
  228. package/dist/internal/view/scope.d.ts +11 -0
  229. package/dist/internal/view/scope.d.ts.map +1 -0
  230. package/dist/internal/view/scope.js +41 -0
  231. package/dist/internal/view/scope.js.map +1 -0
  232. package/dist/internal/view/server.d.ts +35 -0
  233. package/dist/internal/view/server.d.ts.map +1 -0
  234. package/dist/internal/view/server.js +141 -0
  235. package/dist/internal/view/server.js.map +1 -0
  236. package/dist/src/act.d.ts +3 -0
  237. package/dist/src/act.d.ts.map +1 -0
  238. package/dist/src/act.js +3 -0
  239. package/dist/src/act.js.map +1 -0
  240. package/dist/src/automate.d.ts +3 -0
  241. package/dist/src/automate.d.ts.map +1 -0
  242. package/dist/src/automate.js +3 -0
  243. package/dist/src/automate.js.map +1 -0
  244. package/dist/src/cli.d.ts +24 -17
  245. package/dist/src/cli.d.ts.map +1 -1
  246. package/dist/src/cli.js +416 -109
  247. package/dist/src/cli.js.map +1 -1
  248. package/dist/src/commands.d.ts +104 -0
  249. package/dist/src/commands.d.ts.map +1 -0
  250. package/dist/src/commands.js +33 -0
  251. package/dist/src/commands.js.map +1 -0
  252. package/dist/src/connect.d.ts +3 -0
  253. package/dist/src/connect.d.ts.map +1 -0
  254. package/dist/src/connect.js +3 -0
  255. package/dist/src/connect.js.map +1 -0
  256. package/dist/src/dispatch.d.ts +14 -0
  257. package/dist/src/dispatch.d.ts.map +1 -0
  258. package/dist/src/dispatch.js +116 -0
  259. package/dist/src/dispatch.js.map +1 -0
  260. package/dist/src/doctor.d.ts +45 -0
  261. package/dist/src/doctor.d.ts.map +1 -0
  262. package/dist/src/doctor.js +284 -0
  263. package/dist/src/doctor.js.map +1 -0
  264. package/dist/src/eval.d.ts +3 -0
  265. package/dist/src/eval.d.ts.map +1 -0
  266. package/dist/src/eval.js +3 -0
  267. package/dist/src/eval.js.map +1 -0
  268. package/dist/src/index.d.ts +9 -2
  269. package/dist/src/index.d.ts.map +1 -1
  270. package/dist/src/index.js +6 -2
  271. package/dist/src/index.js.map +1 -1
  272. package/dist/src/ingest.d.ts +3 -0
  273. package/dist/src/ingest.d.ts.map +1 -0
  274. package/dist/src/ingest.js +3 -0
  275. package/dist/src/ingest.js.map +1 -0
  276. package/dist/src/loop.d.ts +3 -0
  277. package/dist/src/loop.d.ts.map +1 -0
  278. package/dist/src/loop.js +3 -0
  279. package/dist/src/loop.js.map +1 -0
  280. package/dist/src/main.d.ts +1 -1
  281. package/dist/src/main.d.ts.map +1 -1
  282. package/dist/src/main.js +3 -75
  283. package/dist/src/main.js.map +1 -1
  284. package/dist/src/mcp.d.ts +3 -0
  285. package/dist/src/mcp.d.ts.map +1 -0
  286. package/dist/src/mcp.js +3 -0
  287. package/dist/src/mcp.js.map +1 -0
  288. package/dist/src/rules.d.ts +3 -0
  289. package/dist/src/rules.d.ts.map +1 -0
  290. package/dist/src/rules.js +3 -0
  291. package/dist/src/rules.js.map +1 -0
  292. package/dist/src/shape.d.ts +3 -0
  293. package/dist/src/shape.d.ts.map +1 -0
  294. package/dist/src/shape.js +3 -0
  295. package/dist/src/shape.js.map +1 -0
  296. package/dist/src/sync.d.ts +3 -0
  297. package/dist/src/sync.d.ts.map +1 -0
  298. package/dist/src/sync.js +3 -0
  299. package/dist/src/sync.js.map +1 -0
  300. package/dist/src/view.d.ts +3 -0
  301. package/dist/src/view.d.ts.map +1 -0
  302. package/dist/src/view.js +3 -0
  303. package/dist/src/view.js.map +1 -0
  304. package/package.json +81 -21
  305. package/src/act.ts +2 -0
  306. package/src/automate.ts +2 -0
  307. package/src/cli.ts +423 -104
  308. package/src/commands.ts +48 -0
  309. package/src/connect.ts +2 -0
  310. package/src/dispatch.ts +141 -0
  311. package/src/doctor.ts +370 -0
  312. package/src/eval.ts +2 -0
  313. package/src/index.ts +9 -2
  314. package/src/ingest.ts +2 -0
  315. package/src/loop.ts +2 -0
  316. package/src/main.ts +3 -66
  317. package/src/mcp.ts +2 -0
  318. package/src/rules.ts +2 -0
  319. package/src/shape.ts +2 -0
  320. package/src/sync.ts +2 -0
  321. package/src/view.ts +2 -0
package/dist/src/cli.js CHANGED
@@ -1,27 +1,31 @@
1
1
  /**
2
2
  * `cave` command implementation — pure functions from parsed arguments to
3
- * `{ code, out, err }`, so tests can call commands directly and `main.ts`
4
- * stays a thin dispatcher.
3
+ * `{ code, out, err }`, so tests can call commands directly while
4
+ * `dispatch.ts` gives every command one awaited process lifecycle.
5
5
  *
6
6
  * Commands:
7
7
  *
8
8
  * - `cave parse [file]` — lint / dump the AST (`--json`)
9
- * - `cave highlight [file]` — ANSI syntax colors (async, routed in `main.ts`)
9
+ * - `cave highlight [file]` — ANSI syntax colors (async)
10
10
  * - `cave add [--db <path>] [file…]` — ingest into a store (`--strict`, `--check`)
11
11
  * - `cave query [--db <path>] '<pattern>'` — CAVE-Q (`--json`, `--all`, `--aliases`, `--as-of`, `--resolve`)
12
12
  * - `cave resolve [--db <path>]` — contested facts under the §26 policy (`--aliases`, `--policy`, `--json`)
13
13
  * - `cave derive [--db <path>] [rules.cave…]` — fire rules (`--dry-run`, `--full`, `--list`, `--retract`)
14
14
  * - `cave act [--db <path>] <name> [param=value…]` — execute an action (spec §25; `--declare`, `--list`, `--retract`)
15
- * - `cave automate [--db <path>]` — the event-driven loop (spec §29; async, routed in `main.ts`)
15
+ * - `cave automate [--db <path>]` — the event-driven loop (spec §29; async)
16
16
  * - `cave check [--db <path>]` — knowledge health report (`--stale`, `--json`)
17
- * - `cave suggest-alias [--db <path>]` — same-entity candidates (spec §27; async, routed in `main.ts`)
17
+ * - `cave backup [--db <path>] --out <path>` — exact verified SQLite snapshot
18
+ * - `cave restore <snapshot> --db <path>` — verify and atomically restore a snapshot
19
+ * - `cave generate [--db <path>]` — versioned TypeScript client from EXPECTS declarations
20
+ * - `cave suggest-alias [--db <path>]` — same-entity candidates (spec §27; async)
18
21
  * - `cave sync [--db <path>] <source>` — merge another store by row identity (spec §28; `--dry-run`, `--as`, `--into`)
19
22
  * - `cave export [--db <path>]` — canonical text out (`--current`, `--tx`)
20
- * - `cave serve [--db <path>]` — the human read surface in a browser (spec §30; async, routed in `main.ts`)
23
+ * - `cave serve [--db <path>]` — the human read surface in a browser (spec §30; async)
21
24
  * - `cave report [--db <path>] [template.md…]` — cited markdown from CAVE-Q template blocks (spec §31)
22
- * - `cave connect [--db <path>] <source>` — deterministic structured ingestion (async, routed in `main.ts`)
23
- * - `cave eval <suite...>` — golden-fixture extraction/query/reconstruction evals (async, routed in `main.ts`)
24
- * - `cave reconstruct [--db <path>] <seed...>` — §18 reconstruction from seed cues (async, routed in `main.ts`)
25
+ * - `cave connect [--db <path>] <source>` — deterministic structured ingestion (async)
26
+ * - `cave eval <suite...>` — golden-fixture extraction/query/reconstruction evals (async)
27
+ * - `cave reconstruct [--db <path>] <seed...>` — §18 reconstruction from seed cues (async)
28
+ * - `cave doctor [--db <path>]` — read-only runtime, installation, and store diagnostics
25
29
  * - `cave demo` — the cave-loop multi-hop recovery demo
26
30
  * - `cave version` — print the cave version
27
31
  * - `cave help [command]` — overview, or one command's help
@@ -29,21 +33,51 @@
29
33
  * `file` defaults to stdin (`-`); `--db` defaults to `$CAVE_DB`, then
30
34
  * `cave.db`. Every command answers `--help` with options and examples.
31
35
  */
32
- import { readFileSync, statSync, writeFileSync } from 'node:fs';
36
+ import { readFileSync, readSync, statSync, writeFileSync } from 'node:fs';
33
37
  import { resolve } from 'node:path';
34
38
  import { parseArgs } from 'node:util';
35
39
  import { Version } from '@cavelang/core';
36
40
  import { parseDocument } from '@cavelang/parser';
37
41
  import { Registry, standardRegistry } from '@cavelang/canonical';
38
- import { defaultDbPath, open } from '@cavelang/store';
39
- import { query as caveQuery } from '@cavelang/query';
40
- import { check as caveCheck, defaultMinScore, defaultStaleDays, gatedIngest, judgePrompt, parseJudgeReply, suggestAliases, writeSuggestions } from '@cavelang/shape';
41
- import { declareRules, defaultMaxPasses, defaultMinConf, derive, listRules, retractRule } from '@cavelang/rules';
42
- import { act, declareActions, listActions, retractAction } from '@cavelang/act';
43
- import { Demo, heuristicPolicy, llmPolicy, reconstruct, reconstructAsync, shellComplete, sqliteStore } from '@cavelang/loop';
44
- import { labelOf, sanitizeLabel, syncFile, syncText } from '@cavelang/sync';
45
- import { report as caveReport } from '@cavelang/view';
46
- import { emitClaim, txOfLine } from '@cavelang/canonical';
42
+ import { Sensitivity, backup as backupStore, defaultDbPath, open, restoreBackup, verifyBackup } from '@cavelang/store';
43
+ import { defaultLimit as defaultQueryLimit, page as caveQueryPage } from '@cavelang/query';
44
+ import { check as caveCheck, defaultMinScore, defaultStaleDays, gatedIngest, judgePrompt, parseJudgeReply, generateClient, suggestAliases, writeSuggestions } from '@cavelang/cli/shape';
45
+ import { declareRules, defaultMaxPasses, defaultMinConf, derive, listRules, retractRule } from '@cavelang/cli/rules';
46
+ import { act, declareActions, listActions, retractAction } from '@cavelang/cli/act';
47
+ import { Demo, heuristicPolicy, llmPolicy, reconstruct, reconstructAsync, shellComplete, sqliteStore } from '@cavelang/cli/loop';
48
+ import { labelOf, sanitizeLabel, syncFile, syncText } from '@cavelang/cli/sync';
49
+ import { report as caveReport } from '@cavelang/cli/view';
50
+ import { emitClaim } from '@cavelang/canonical';
51
+ import { doctorCommand } from "./doctor.js";
52
+ import { delegatedCommandNames } from "./commands.js";
53
+ const storageRow = (value) => typeof value === 'object' && value !== null &&
54
+ typeof value['id'] === 'string' &&
55
+ typeof value['tx'] === 'string' &&
56
+ typeof value['claim_key'] === 'string' &&
57
+ typeof value['raw_line'] === 'string';
58
+ /** Recursively replace storage rows in JSON reports with cave.claim/v1 records. */
59
+ const stableJsonValue = (store, value) => {
60
+ if (storageRow(value)) {
61
+ const record = store.recordOf(value);
62
+ const ranked = value;
63
+ return typeof ranked['res_rank'] === 'number' ? {
64
+ ...record,
65
+ resolution: {
66
+ group: ranked['res_group'],
67
+ class: ranked['res_class'],
68
+ effectiveConfidence: ranked['res_conf'],
69
+ rank: ranked['res_rank'],
70
+ },
71
+ } : record;
72
+ }
73
+ if (Array.isArray(value))
74
+ return value.map(entry => stableJsonValue(store, entry));
75
+ if (typeof value === 'object' && value !== null) {
76
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, stableJsonValue(store, entry)]));
77
+ }
78
+ return value;
79
+ };
80
+ const stableJson = (store, value) => JSON.stringify(stableJsonValue(store, value), undefined, 2);
47
81
  const ok = (out) => ({ code: 0, out, err: '' });
48
82
  const fail = (err, code = 1) => ({ code, out: '', err });
49
83
  export const usage = `cave — Compressed Atomic Verb Expressions
@@ -53,23 +87,27 @@ Usage:
53
87
  cave highlight [file...] print CAVE text with ANSI syntax colors
54
88
  cave add [--db <path>] [file...] ingest into a store [--strict] [--check] [--no-prelude] [--no-src]
55
89
  cave import [--db <path>] [file...] restore/merge from CAVE text (add without @src: stamping)
56
- cave query [--db <path>] <pattern> run a CAVE-Q pattern [--json] [--all] [--aliases] [--as-of <t>] [--at <t>] [--resolve] [--no-prelude]
90
+ cave query [--db <path>] <pattern> run a bounded CAVE-Q page [--limit <n>] [--cursor <token>] [--json]
57
91
  cave resolve [--db <path>] contested facts + winners (spec §26) [--aliases] [--policy] [--json]
58
92
  cave derive [--db <path>] [rules.cave..] declare + fire rules (spec §24) [--dry-run] [--full] [--list] [--retract <rule>]
59
93
  cave act [--db <path>] <name> [p=v...] execute an action (spec §25) [--dry-run] [--no-check] [--hooks <file>]
60
94
  cave act --declare [file...] declare actions from a CAVE document; --list / --retract <name> manage them
61
95
  cave automate [--db <path>] event-driven loop (spec §29): new claims fire rules/actions/hooks/agent prompts [--once]
62
96
  cave check [--db <path>] knowledge health report (spec §20) [--stale <days>] [--json]
97
+ cave backup [--db <path>] --out <file> exact verified SQLite snapshot [--force]
98
+ cave restore <snapshot> --db <path> verify and atomically restore an exact snapshot [--force]
99
+ cave generate [--db <path>] generate a typed client from EXPECTS [--out <file>] [--version <n>]
63
100
  cave suggest-alias [--db <path>] propose same-entity ALIAS candidates (spec §27) [--min <s>] [--agent] [--write]
64
101
  cave sync [--db <path>] <source> merge another store by row identity (spec §28) [--dry-run] [--as] [--into]
65
- cave export [--db <path>] [--out <file>] emit canonical CAVE text [--current] [--tx] [--no-prelude]
66
- cave serve [--db <path>] browse the store in a browser (spec §30) [--port <n>] [--host <a>]
67
- cave report [--db <path>] [template...] render cited markdown from a CAVE-Q template (spec §31) [--out <file>] [--resolve]
102
+ cave export [--db <path>] [--out <file>] emit filtered canonical CAVE text [--max-sensitivity <level>]
103
+ cave serve [--db <path>] browse a filtered store (spec §30) [--max-sensitivity <level>]
104
+ cave report [--db <path>] [template...] render filtered cited markdown (spec §31) [--max-sensitivity <level>]
68
105
  cave mcp [--db <path>] serve the engine as an MCP server on stdio [--no-prelude]
69
106
  cave ingest [--db <path>] <globs/urls..> LLM-driven ingestion of files and web pages
70
- cave eval <suite..> --agent '<command>' golden-fixture extraction/query/reconstruction evals (items 9, 10)
107
+ cave eval <suite..> --agent '<command>' golden-fixture extraction/query/reconstruction evals
71
108
  cave connect <source> --map <file> deterministic structured ingestion (CSV/JSON/SQLite/URL, spec §23)
72
109
  cave reconstruct [--db <path>] <seed..> reconstruct memory from seed cues (spec §18) [--agent] [--query] [--trace]
110
+ cave doctor [--db <path>] diagnose runtime, installation, and store health [--hooks <file>] [--json]
73
111
  cave demo run the cave-loop reconstruction demo
74
112
  cave version print the cave version
75
113
  cave help [command] this text, or one command's options and examples
@@ -80,6 +118,25 @@ the repository root (section index in README.md).`;
80
118
  const dbHelp = `--db <path> database file (default: $CAVE_DB, or cave.db)`;
81
119
  /** Per-command help, printed for \`cave <command> --help\` and \`cave help <command>\`. */
82
120
  export const commandHelp = {
121
+ doctor: `cave doctor — diagnose runtime, installation, and store health
122
+
123
+ Usage:
124
+ cave doctor [--db <path>] [--hooks <file>] [--json]
125
+
126
+ Options:
127
+ ${dbHelp}
128
+ --hooks <file> validate an optional hooks JSON file
129
+ --json emit the versioned cave.doctor/v1 report
130
+
131
+ The command is read-only: it never creates or migrates a database and never
132
+ runs hooks. Its output is safe to share — database and hooks paths, hook names
133
+ and commands, URLs, and environment values are not included. Warnings such as
134
+ a not-yet-created database exit 0; failed required checks exit 1.
135
+
136
+ Examples:
137
+ cave doctor
138
+ cave doctor --db knowledge.db
139
+ cave doctor --hooks hooks.json --json`,
83
140
  parse: `cave parse — lint CAVE text, or dump the parsed document
84
141
 
85
142
  Usage:
@@ -152,23 +209,27 @@ file read as ordinary comments; to merge under the recorded row
152
209
  identity, use \`cave sync\` (spec §28.4).
153
210
 
154
211
  Examples:
155
- cave export --db old.db --out backup.cave
212
+ cave export --db old.db --max-sensitivity restricted --out backup.cave
156
213
  cave import --db new.db backup.cave`,
157
214
  query: `cave query — run a CAVE-Q pattern against a store
158
215
 
159
216
  Usage:
160
- cave query [--db <path>] <pattern> [WHERE <filter>] [--json] [--all] [--aliases] [--as-of <t>] [--at <t>] [--resolve] [--no-prelude]
217
+ cave query [--db <path>] <pattern> [WHERE <filter>] [--limit <n>] [--cursor <token>] [--json] [--all] [--aliases] [--as-of <t>] [--at <t>] [--resolve] [--no-prelude]
161
218
 
162
219
  Options:
163
220
  ${dbHelp}
164
- --json emit matches as JSON
221
+ --json emit a versioned cave.query-page/v1 JSON object
222
+ --limit <n> matches per page (default ${defaultQueryLimit}, maximum 1000)
223
+ --cursor <t> continue the same frozen query snapshot
165
224
  --all match all beliefs, not just current ones
166
225
  --aliases resolve entities through current ALIAS claims (spec §13.6)
167
226
  --as-of <t> resolve beliefs as of a past moment (spec §12.3): a date
168
227
  (whole day included), a timestamp (whole second), or a
169
- transaction id — rows recorded later are invisible
228
+ transaction id — rows recorded later are invisible;
229
+ timestamps without an offset mean UTC
170
230
  --at <t> anchor in valid time (spec §32.4): a date-like period
171
- (read as its start instant) or a timestamp. Claims whose
231
+ (read as its start instant) or a timestamp (offset-less
232
+ means UTC). Claims whose
172
233
  time contexts — @2026-Q1 points, @2025..2028 ranges — do
173
234
  not cover the instant are invisible; timeless claims
174
235
  always match; trajectory values (20B -> 40B USD/yr)
@@ -324,7 +385,8 @@ Options:
324
385
  --no-prelude open the store without the standard verb registry
325
386
 
326
387
  Reads the store against its own in-band EXPECTS declarations (spec §20.1)
327
- and reports shape violations, stale current beliefs, review candidates
388
+ and reports actionable presence, #cardinality:one, and #unit:<unit>
389
+ violations, stale current beliefs, review candidates
328
390
  (conf 0.3–0.7), alias disagreements (spec §13.6) and coverage stats.
329
391
  Exits 1 when violations exist; everything else is advisory.
330
392
 
@@ -332,6 +394,66 @@ Examples:
332
394
  cave check --db k.db
333
395
  cave check --db k.db --stale 30
334
396
  cave check --db k.db --json | jq '.violations'`,
397
+ backup: `cave backup — exact, verified SQLite snapshot (spec §13.2.2)
398
+
399
+ Usage:
400
+ cave backup [--db <path>] --out <file> [--force]
401
+ cave backup --verify <file> [--sha256 <hex>]
402
+
403
+ Options:
404
+ ${dbHelp}
405
+ --out <file> destination snapshot (required when creating)
406
+ --force atomically replace an existing snapshot
407
+ --verify <file> verify integrity, foreign keys, schema and SHA-256 only
408
+ --sha256 <hex> expected digest for --verify
409
+
410
+ The source remains online. SQLite VACUUM INTO takes one consistent committed
411
+ snapshot, including WAL-visible rows; CAVE writes to a temporary file,
412
+ validates it, fsyncs it, then publishes it atomically. The summary SHA-256 is
413
+ the restore verification token.
414
+
415
+ Examples:
416
+ cave backup --db k.db --out backups/k.db
417
+ cave backup --verify backups/k.db --sha256 <hex>`,
418
+ restore: `cave restore — verify and atomically restore an exact SQLite snapshot (spec §13.2.2)
419
+
420
+ Usage:
421
+ cave restore <snapshot> --db <path> [--force] [--sha256 <hex>]
422
+
423
+ Options:
424
+ --db <path> destination database (required)
425
+ --force atomically replace an existing destination
426
+ --sha256 <hex> require the recorded backup digest
427
+
428
+ Stop every process using the destination first. Restore refuses WAL/SHM
429
+ sidecars, verifies the input and copied temporary database, then publishes one
430
+ atomic file. WAL, SHM, or rollback-journal sidecars cause a refusal. On
431
+ failure the prior destination remains untouched.
432
+
433
+ Example:
434
+ cave restore backups/k.db --db restored.db --sha256 <hex>`,
435
+ generate: `cave generate — deterministic TypeScript client from EXPECTS declarations (spec §20.4)
436
+
437
+ Usage:
438
+ cave generate [--db <path>] [--out <file>] [--version <n>] [--no-prelude]
439
+
440
+ Options:
441
+ ${dbHelp}
442
+ --out <file> write the generated module instead of stdout
443
+ --version <n> generated-client format version (default and currently
444
+ supported: 1)
445
+ --no-prelude open the store without the standard verb registry
446
+
447
+ Current positive EXPECTS claims become TypeScript interfaces plus store-backed
448
+ reader functions. #cardinality:one emits a scalar checked at runtime;
449
+ attributes preserve text, number and unit; relation readers honor declared
450
+ inverse verbs. The file embeds its format version and deterministic schema
451
+ SHA-256. Conflicting declarations, invalid cardinality/unit tags, unsupported
452
+ versions and generated type-name collisions fail without writing output.
453
+
454
+ Examples:
455
+ cave generate --db k.db > cave-client.ts
456
+ cave generate --db k.db --out src/generated/cave-client.ts`,
335
457
  'suggest-alias': `cave suggest-alias — propose same-entity ALIAS candidates for review (spec §27)
336
458
 
337
459
  Usage:
@@ -414,38 +536,49 @@ a merge driver on *.cave files.
414
536
  Examples:
415
537
  cave sync --db main.db laptop.db
416
538
  cave sync --db main.db laptop.db --dry-run --json
417
- cave export --db laptop.db --tx | cave sync --db main.db -
539
+ cave export --db laptop.db --tx --max-sensitivity restricted | cave sync --db main.db -
418
540
  cave sync --db a.db b.db && cave sync --db b.db a.db # converge both
419
541
  cave sync --db work.db knowledge.cave --no-record # checkout (§28.6)
420
542
  cave sync --db main.db knowledge.cave --as reorg-auth # land the branch`,
421
543
  export: `cave export — emit canonical CAVE text from a store
422
544
 
423
545
  Usage:
424
- cave export [--db <path>] [--out <file>] [--current] [--tx] [--no-prelude]
546
+ cave export [--db <path>] [--out <file>] [--current] [--tx]
547
+ [--max-sensitivity <level>] [--no-prelude]
425
548
 
426
549
  Options:
427
550
  ${dbHelp}
428
551
  --out <file> write to a file instead of stdout — refused when it
429
552
  names the store's own database file
430
- --current current beliefs only (skip superseded rows)
553
+ --current current beliefs only (skip superseded rows); compacting
554
+ view, not sanitization or selective erasure (spec §9.6)
555
+ --max-sensitivity <level>
556
+ public, internal, confidential, or restricted (default
557
+ internal; restricted includes complete portable history)
431
558
  --tx precede every claim line with its ;@ transaction
432
559
  annotation (spec §28.4) — the text carries row identity,
433
560
  so cave sync replays it idempotently; other readers see
434
561
  ordinary comments
435
562
  --no-prelude open the store without the standard verb registry
436
563
 
564
+ Retention: claim history is permanent. Retraction and --current do not
565
+ guarantee erasure from the store, exports, peers, or backups (spec §9.6).
566
+
437
567
  Examples:
438
568
  cave export --db k.db
439
- cave export --db k.db --out backup.cave
569
+ cave export --db k.db --out internal-export.cave
570
+ cave export --db k.db --max-sensitivity restricted --out complete-history.cave
440
571
  cave export --db k.db --current --out snapshot.cave
441
- cave export --db k.db --tx | cave sync --db other.db -
442
- cave export --db work.db --tx --out knowledge.cave # the committed,
572
+ cave export --db k.db --tx --max-sensitivity restricted | cave sync --db other.db -
573
+ cave export --db work.db --tx --max-sensitivity restricted --out knowledge.cave
574
+ # the committed,
443
575
  # reviewable store text (spec §28.6)`,
444
576
  report: `cave report — render cited markdown from a CAVE-Q template (spec §31)
445
577
 
446
578
  Usage:
447
579
  cave report [--db <path>] [template.md...] [--out <file>]
448
- [--aliases] [--resolve] [--as-of <t>] [--at <t>] [--no-prelude]
580
+ [--aliases] [--resolve] [--as-of <t>] [--at <t>]
581
+ [--max-sensitivity <level>] [--no-prelude]
449
582
 
450
583
  Options:
451
584
  ${dbHelp}
@@ -458,6 +591,9 @@ Options:
458
591
  --at <t> anchor every query in valid time (spec §32.4): claims
459
592
  whose time contexts don't cover the instant drop out,
460
593
  trajectory values render interpolated
594
+ --max-sensitivity <level>
595
+ public, internal, confidential, or restricted (default
596
+ internal; malformed/unknown claim labels fail closed)
461
597
  --no-prelude open the store without the standard verb registry
462
598
 
463
599
  Reads stdin when no file (or \`-\`) is given. A template is ordinary
@@ -529,8 +665,28 @@ topic → fix over a small in-memory store.`,
529
665
  Usage:
530
666
  cave version`
531
667
  };
668
+ const readStdin = () => {
669
+ const chunks = [];
670
+ const buffer = Buffer.allocUnsafe(64 * 1024);
671
+ const pause = new Int32Array(new SharedArrayBuffer(4));
672
+ for (;;) {
673
+ try {
674
+ const size = readSync(0, buffer, 0, buffer.length, null);
675
+ if (size === 0)
676
+ return Buffer.concat(chunks).toString('utf8');
677
+ chunks.push(Buffer.from(buffer.subarray(0, size)));
678
+ }
679
+ catch (error) {
680
+ if (error.code !== 'EAGAIN')
681
+ throw error;
682
+ // A pipe may be non-blocking on macOS. The producer is another process,
683
+ // so a short synchronous retry does not prevent it from making progress.
684
+ Atomics.wait(pause, 0, 0, 5);
685
+ }
686
+ }
687
+ };
532
688
  const readInput = (files) => files.length === 0 || (files.length === 1 && files[0] === '-') ?
533
- readFileSync(0, 'utf8') :
689
+ readStdin() :
534
690
  files.map(file => readFileSync(file, 'utf8')).join('\n');
535
691
  export const parseCommand = (argv) => {
536
692
  const { values, positionals } = parseArgs({
@@ -560,8 +716,7 @@ export const parseCommand = (argv) => {
560
716
  };
561
717
  /**
562
718
  * `cave highlight` — ANSI syntax colors from the tree-sitter grammar's own
563
- * highlight query. Async (the grammar WASM loads on first use), so `main.ts`
564
- * routes it separately, like `mcp` and `ingest`.
719
+ * highlight query. Async because the grammar WASM loads on first use.
565
720
  */
566
721
  export const highlightCommand = async (argv) => {
567
722
  const { positionals } = parseArgs({ args: [...argv], options: {}, allowPositionals: true });
@@ -577,8 +732,23 @@ export const highlightCommand = async (argv) => {
577
732
  * replays interchange text, which must preserve claim keys as exported,
578
733
  * so it never stamps.
579
734
  */
580
- const formatViolation = (violation) => `${violation.entity} missing ${violation.expectation.kind} ${violation.expectation.name} ` +
581
- `(${violation.entity} IS ${violation.via}; ${violation.expectation.type} EXPECTS ${violation.expectation.name})`;
735
+ const formatViolationProblem = (violation) => {
736
+ const { entity, expectation, actualCount, actualUnits } = violation;
737
+ if (actualCount === 0) {
738
+ return `${entity} missing ${expectation.kind} ${expectation.name}`;
739
+ }
740
+ const problems = [];
741
+ if (expectation.cardinality === 'one' && actualCount !== 1) {
742
+ problems.push(`${entity} has ${actualCount} ${expectation.kind}s ${expectation.name}; expected exactly one`);
743
+ }
744
+ if (expectation.unit !== undefined && actualUnits.some(unit => unit !== expectation.unit)) {
745
+ const units = actualUnits.map(unit => unit ?? '(none)').join(', ');
746
+ problems.push(`${entity} attribute ${expectation.name} has unit${actualUnits.length === 1 ? '' : 's'} ${units}; expected ${expectation.unit}`);
747
+ }
748
+ return problems.join('; ');
749
+ };
750
+ const formatViolation = (violation) => `${formatViolationProblem(violation)} ` +
751
+ `(${violation.entity} IS ${violation.via}; ${violation.expectation.row.raw_line})`;
582
752
  const ingestCommand = (name) => (argv) => {
583
753
  const { values, positionals } = parseArgs({
584
754
  args: [...argv],
@@ -647,6 +817,8 @@ export const queryCommand = (argv) => {
647
817
  'as-of': { type: 'string' },
648
818
  at: { type: 'string' },
649
819
  resolve: { type: 'boolean' },
820
+ limit: { type: 'string' },
821
+ cursor: { type: 'string' },
650
822
  'no-prelude': { type: 'boolean' }
651
823
  },
652
824
  allowPositionals: true
@@ -657,18 +829,22 @@ export const queryCommand = (argv) => {
657
829
  const pattern = positionals.join('\n');
658
830
  const store = open(values.db ?? defaultDbPath(), values['no-prelude'] === true ? { registry: Registry.empty } : {});
659
831
  try {
660
- const matches = caveQuery(store, pattern, {
832
+ const limit = values.limit === undefined ? defaultQueryLimit : Number(values.limit);
833
+ const result = caveQueryPage(store, pattern, {
661
834
  all: values.all === true,
662
835
  aliases: values.aliases === true,
663
836
  resolve: values.resolve === true,
837
+ limit,
838
+ ...values.cursor === undefined ? {} : { cursor: values.cursor },
664
839
  ...values['as-of'] === undefined ? {} : { asOf: values['as-of'] },
665
840
  ...values.at === undefined ? {} : { at: values.at }
666
841
  });
667
842
  if (values.json === true) {
668
- return ok(`${JSON.stringify(matches, undefined, 2)}\n`);
843
+ return ok(`${JSON.stringify(result, undefined, 2)}\n`);
669
844
  }
845
+ const matches = result.matches;
670
846
  if (matches.length === 0) {
671
- return ok('no matches\n');
847
+ return ok(`no matches\n${result.next === undefined ? '' : `next: ${result.next}\n`}`);
672
848
  }
673
849
  const lines = matches.map(match => {
674
850
  const bindings = Object.entries(match.bindings)
@@ -676,14 +852,14 @@ export const queryCommand = (argv) => {
676
852
  .join(' ');
677
853
  // A fully bound pattern has no bindings to print; transitive matches
678
854
  // additionally carry no row — confirm with the pattern itself.
679
- const line = bindings !== '' ? bindings : match.row?.raw_line ?? pattern.split('\n')[0];
855
+ const line = bindings !== '' ? bindings : match.claim?.claim.raw ?? pattern.split('\n')[0];
680
856
  // An interpolated trajectory shows its value at the anchor
681
857
  // (spec §32.4) — bindings already carry it in the value slot.
682
858
  return match.at !== undefined && bindings === '' ?
683
859
  `${line} ; at ${values.at}: ${match.at.text}` :
684
860
  line;
685
861
  });
686
- return ok(`${lines.join('\n')}\n`);
862
+ return ok(`${lines.join('\n')}\n${result.next === undefined ? '' : `next: ${result.next}\n`}`);
687
863
  }
688
864
  catch (error) {
689
865
  return fail(`${error instanceof Error ? error.message : String(error)}\n`);
@@ -730,7 +906,7 @@ export const resolveCommand = (argv) => {
730
906
  }
731
907
  const contested = store.contested({ aliases: values.aliases === true });
732
908
  if (values.json === true) {
733
- return ok(`${JSON.stringify(contested, undefined, 2)}\n`);
909
+ return ok(`${stableJson(store, contested)}\n`);
734
910
  }
735
911
  if (contested.length === 0) {
736
912
  return ok('no contested facts\n');
@@ -777,7 +953,7 @@ export const deriveCommand = (argv) => {
777
953
  if (values.list === true) {
778
954
  const rules = listRules(store);
779
955
  if (values.json === true) {
780
- return ok(`${JSON.stringify(rules, undefined, 2)}\n`);
956
+ return ok(`${stableJson(store, rules)}\n`);
781
957
  }
782
958
  if (rules.length === 0) {
783
959
  return ok('no rules\n');
@@ -839,7 +1015,7 @@ export const deriveCommand = (argv) => {
839
1015
  (declaration.prelude > 0 ? `, +${declaration.prelude} prelude claim(s)` : ''));
840
1016
  }
841
1017
  if (values.json === true) {
842
- return { code: report.problems.length > 0 ? 1 : 0, out: `${JSON.stringify(report, undefined, 2)}\n`, err: err.join('\n') + (err.length > 0 ? '\n' : '') };
1018
+ return { code: report.problems.length > 0 || !report.complete ? 1 : 0, out: `${stableJson(store, report)}\n`, err: err.join('\n') + (err.length > 0 ? '\n' : '') };
843
1019
  }
844
1020
  for (const problem of report.problems) {
845
1021
  err.push(`${problem.subject}: ${problem.problems.join('; ')}`);
@@ -855,7 +1031,7 @@ export const deriveCommand = (argv) => {
855
1031
  out.push(`derived${values['dry-run'] === true ? ' (dry run)' : ''}: ` +
856
1032
  `+${report.appended} appended, ${report.updated} updated, ${report.retracted} retracted, ` +
857
1033
  `${report.unchanged} unchanged (${report.passes} pass(es))`);
858
- const hasProblems = report.problems.length > 0 || report.rules.some(rule => rule.problems.length > 0);
1034
+ const hasProblems = !report.complete || report.problems.length > 0 || report.rules.some(rule => rule.problems.length > 0);
859
1035
  return { code: hasProblems ? 1 : 0, out: `${out.join('\n')}\n`, err: err.length === 0 ? '' : `${err.join('\n')}\n` };
860
1036
  }
861
1037
  catch (error) {
@@ -943,7 +1119,7 @@ export const actCommand = (argv) => {
943
1119
  if (values.list === true) {
944
1120
  const actions = listActions(store);
945
1121
  if (values.json === true) {
946
- return ok(`${JSON.stringify(actions, undefined, 2)}\n`);
1122
+ return ok(`${stableJson(store, actions)}\n`);
947
1123
  }
948
1124
  if (actions.length === 0) {
949
1125
  return ok('no actions\n');
@@ -983,7 +1159,7 @@ export const actCommand = (argv) => {
983
1159
  ...hooksPath === undefined ? {} : { hooks: readHooks(hooksPath) }
984
1160
  });
985
1161
  if (values.json === true) {
986
- return { code: report.ok && report.hook?.error === undefined ? 0 : 1, out: `${JSON.stringify(report, undefined, 2)}\n`, err: '' };
1162
+ return { code: report.ok && report.hook?.error === undefined ? 0 : 1, out: `${stableJson(store, report)}\n`, err: '' };
987
1163
  }
988
1164
  const { lines, code } = renderActReport(report);
989
1165
  return report.ok ?
@@ -1048,7 +1224,7 @@ export const checkCommand = (argv) => {
1048
1224
  try {
1049
1225
  const report = caveCheck(store, { staleDays });
1050
1226
  const out = values.json === true ?
1051
- `${JSON.stringify(report, undefined, 2)}\n` :
1227
+ `${stableJson(store, report)}\n` :
1052
1228
  renderReport(report, staleDays);
1053
1229
  // Violations fail the check (spec §20.2); the other sections are advisory.
1054
1230
  return { code: report.violations.length > 0 ? 1 : 0, out, err: '' };
@@ -1057,11 +1233,83 @@ export const checkCommand = (argv) => {
1057
1233
  store.close();
1058
1234
  }
1059
1235
  };
1236
+ const snapshotLine = (action, snapshot) => `${action} exact backup (${snapshot.rows} row(s), schema v${snapshot.schemaVersion}, ` +
1237
+ `${snapshot.bytes} bytes, sha256:${snapshot.sha256}) at ${snapshot.path}\n`;
1238
+ /** `cave backup` — create or verify an exact SQLite snapshot (§13.2.2). */
1239
+ export const backupCommand = (argv) => {
1240
+ const { values } = parseArgs({
1241
+ args: [...argv],
1242
+ options: {
1243
+ db: { type: 'string' },
1244
+ out: { type: 'string' },
1245
+ force: { type: 'boolean' },
1246
+ verify: { type: 'string' },
1247
+ sha256: { type: 'string' }
1248
+ },
1249
+ allowPositionals: false
1250
+ });
1251
+ if (values.sha256 !== undefined && !/^[0-9a-f]{64}$/i.test(values.sha256)) {
1252
+ return fail('cave backup: --sha256 must be 64 hexadecimal characters\n');
1253
+ }
1254
+ try {
1255
+ if (values.verify !== undefined) {
1256
+ if (values.out !== undefined || values.force === true || values.db !== undefined) {
1257
+ return fail('cave backup: --verify excludes --db, --out, and --force\n');
1258
+ }
1259
+ return ok(snapshotLine('verified', verifyBackup(values.verify, values.sha256)));
1260
+ }
1261
+ if (values.sha256 !== undefined) {
1262
+ return fail('cave backup: --sha256 requires --verify\n');
1263
+ }
1264
+ if (values.out === undefined) {
1265
+ return fail('cave backup: --out <file> is required\n');
1266
+ }
1267
+ const store = open(values.db ?? defaultDbPath());
1268
+ try {
1269
+ return ok(snapshotLine('created', backupStore(store, values.out, { force: values.force === true })));
1270
+ }
1271
+ finally {
1272
+ store.close();
1273
+ }
1274
+ }
1275
+ catch (error) {
1276
+ return fail(`${error instanceof Error ? error.message : String(error)}\n`);
1277
+ }
1278
+ };
1279
+ /** `cave restore` — verify and atomically restore an exact snapshot (§13.2.2). */
1280
+ export const restoreCommand = (argv) => {
1281
+ const { values, positionals } = parseArgs({
1282
+ args: [...argv],
1283
+ options: {
1284
+ db: { type: 'string' },
1285
+ force: { type: 'boolean' },
1286
+ sha256: { type: 'string' }
1287
+ },
1288
+ allowPositionals: true
1289
+ });
1290
+ if (positionals.length !== 1) {
1291
+ return fail('cave restore: exactly one snapshot file is required\n');
1292
+ }
1293
+ if (values.db === undefined) {
1294
+ return fail('cave restore: --db <path> destination is required\n');
1295
+ }
1296
+ if (values.sha256 !== undefined && !/^[0-9a-f]{64}$/i.test(values.sha256)) {
1297
+ return fail('cave restore: --sha256 must be 64 hexadecimal characters\n');
1298
+ }
1299
+ try {
1300
+ return ok(snapshotLine('restored', restoreBackup(positionals[0], values.db, {
1301
+ force: values.force === true,
1302
+ ...values.sha256 === undefined ? {} : { expectedSha256: values.sha256 }
1303
+ })));
1304
+ }
1305
+ catch (error) {
1306
+ return fail(`${error instanceof Error ? error.message : String(error)}\n`);
1307
+ }
1308
+ };
1060
1309
  /**
1061
1310
  * `cave suggest-alias` — alias discovery (spec §27): same-entity
1062
- * candidates as suggested `ALIAS` claims for review. Async (the optional
1063
- * judge runs a shell agent), so `main.ts` routes it separately, like
1064
- * `reconstruct`.
1311
+ * candidates as suggested `ALIAS` claims for review. Async when the optional
1312
+ * judge runs a shell agent.
1065
1313
  */
1066
1314
  export const suggestAliasCommand = async (argv) => {
1067
1315
  try {
@@ -1159,7 +1407,7 @@ export const syncCommand = (argv) => {
1159
1407
  ...values.as === undefined ? {} : { from: sanitizeLabel(values.as) }
1160
1408
  };
1161
1409
  const report = source === '-' ?
1162
- syncText(store, readFileSync(0, 'utf8'), { from: 'stdin', ...options }) :
1410
+ syncText(store, readStdin(), { from: 'stdin', ...options }) :
1163
1411
  syncFile(store, source, options);
1164
1412
  if (values.json === true) {
1165
1413
  return { code: report.problems.length > 0 ? 1 : 0, out: `${JSON.stringify(report, undefined, 2)}\n`, err: '' };
@@ -1202,16 +1450,22 @@ export const reportCommand = (argv) => {
1202
1450
  resolve: { type: 'boolean' },
1203
1451
  'as-of': { type: 'string' },
1204
1452
  at: { type: 'string' },
1453
+ 'max-sensitivity': { type: 'string' },
1205
1454
  'no-prelude': { type: 'boolean' }
1206
1455
  },
1207
1456
  allowPositionals: true
1208
1457
  });
1209
1458
  const template = readInput(positionals);
1459
+ const maximum = Sensitivity.parse(values['max-sensitivity'] ?? Sensitivity.defaultMaximum);
1460
+ if (maximum === undefined) {
1461
+ return fail(`cave report: --max-sensitivity expects ${Sensitivity.levels.join(', ')}, got ${JSON.stringify(values['max-sensitivity'])}\n`);
1462
+ }
1210
1463
  const store = open(values.db ?? defaultDbPath(), values['no-prelude'] === true ? { registry: Registry.empty } : {});
1211
1464
  try {
1212
1465
  const rendered = caveReport(store, template, {
1213
1466
  aliases: values.aliases === true,
1214
1467
  resolve: values.resolve === true,
1468
+ maxSensitivity: maximum,
1215
1469
  ...values['as-of'] === undefined ? {} : { asOf: values['as-of'] },
1216
1470
  ...values.at === undefined ? {} : { at: values.at }
1217
1471
  });
@@ -1262,24 +1516,75 @@ export const exportCommand = (argv) => {
1262
1516
  current: { type: 'boolean' },
1263
1517
  tx: { type: 'boolean' },
1264
1518
  out: { type: 'string' },
1519
+ 'max-sensitivity': { type: 'string' },
1265
1520
  'no-prelude': { type: 'boolean' }
1266
1521
  },
1267
1522
  allowPositionals: false
1268
1523
  });
1269
1524
  const db = values.db ?? defaultDbPath();
1525
+ const maximum = Sensitivity.parse(values['max-sensitivity'] ?? Sensitivity.defaultMaximum);
1526
+ if (maximum === undefined) {
1527
+ return fail(`cave export: --max-sensitivity expects ${Sensitivity.levels.join(', ')}, got ${JSON.stringify(values['max-sensitivity'])}\n`);
1528
+ }
1270
1529
  if (values.out !== undefined && sameFile(db, values.out)) {
1271
1530
  return fail(`cave export: --out '${values.out}' is the source database — refusing to overwrite it\n`);
1272
1531
  }
1273
1532
  const store = open(db, values['no-prelude'] === true ? { registry: Registry.empty } : {});
1274
1533
  try {
1275
- const text = store.exportText({ current: values.current === true, tx: values.tx === true });
1534
+ const text = store.exportText({ current: values.current === true, tx: values.tx === true, maxSensitivity: maximum });
1276
1535
  if (values.out === undefined) {
1277
1536
  return ok(text);
1278
1537
  }
1279
1538
  writeFileSync(values.out, text);
1280
- const claims = text === '' ? 0 : text.trimEnd().split('\n').filter(line => txOfLine(line) === undefined).length;
1539
+ // Root claims only: indented qualifier/grouping lines are part of their
1540
+ // parent claim, and §28.4 annotations parse as comments.
1541
+ const claims = parseDocument(text).lines
1542
+ .filter(line => line.kind === 'claim' && line.depth === 0).length;
1281
1543
  return ok(`exported ${claims} claim(s) to ${values.out}\n`);
1282
1544
  }
1545
+ catch (error) {
1546
+ return fail(`${error instanceof Error ? error.message : String(error)}\n`);
1547
+ }
1548
+ finally {
1549
+ store.close();
1550
+ }
1551
+ };
1552
+ /** `cave generate` — spec §20.4's versioned TypeScript client artifact. */
1553
+ export const generateCommand = (argv) => {
1554
+ const { values } = parseArgs({
1555
+ args: [...argv],
1556
+ options: {
1557
+ db: { type: 'string' },
1558
+ out: { type: 'string' },
1559
+ version: { type: 'string' },
1560
+ 'no-prelude': { type: 'boolean' }
1561
+ },
1562
+ allowPositionals: false
1563
+ });
1564
+ const version = values.version === undefined ? undefined : Number(values.version);
1565
+ if (version !== undefined && (!Number.isInteger(version) || version < 1)) {
1566
+ return fail(`cave generate: --version expects a positive integer, got ${JSON.stringify(values.version)}\n`);
1567
+ }
1568
+ const db = values.db ?? defaultDbPath();
1569
+ if (values.out !== undefined && sameFile(db, values.out)) {
1570
+ return fail(`cave generate: --out '${values.out}' is the source database — refusing to overwrite it\n`);
1571
+ }
1572
+ const store = open(db, values['no-prelude'] === true ? { registry: Registry.empty } : {});
1573
+ try {
1574
+ const generated = generateClient(store, version === undefined ? {} : { version });
1575
+ if (!generated.ok) {
1576
+ return fail(`cave generate: schema cannot be generated:\n${generated.problems.map(problem => ` ${problem}`).join('\n')}\n`);
1577
+ }
1578
+ if (values.out === undefined) {
1579
+ return ok(generated.code);
1580
+ }
1581
+ writeFileSync(values.out, generated.code);
1582
+ return ok(`generated typed client v${generated.version} (${generated.fields.length} field(s), ` +
1583
+ `sha256:${generated.digest}) to ${values.out}\n`);
1584
+ }
1585
+ catch (error) {
1586
+ return fail(`${error instanceof Error ? error.message : String(error)}\n`);
1587
+ }
1283
1588
  finally {
1284
1589
  store.close();
1285
1590
  }
@@ -1287,8 +1592,7 @@ export const exportCommand = (argv) => {
1287
1592
  /**
1288
1593
  * `cave reconstruct` — the §18 loop over the store: heuristic policy by
1289
1594
  * default (the eval baseline), the LLM policy over a shell-agent template
1290
- * with `--agent`. Async (the agent runs once per step), so `main.ts`
1291
- * routes it separately, like `connect`.
1595
+ * with `--agent`. Async because the agent runs once per step.
1292
1596
  */
1293
1597
  export const reconstructCommand = async (argv) => {
1294
1598
  try {
@@ -1362,8 +1666,8 @@ export const helpCommand = (argv) => {
1362
1666
  if (text !== undefined) {
1363
1667
  return ok(`${text}\n`);
1364
1668
  }
1365
- // mcp, ingest, connect, eval, automate and serve own their help (main.ts forwards `help X` to `X --help`).
1366
- if (topic === 'mcp' || topic === 'ingest' || topic === 'connect' || topic === 'eval' || topic === 'automate' || topic === 'serve') {
1669
+ // Delegated commands own their help; dispatch.ts forwards `help X` to `X --help`.
1670
+ if (delegatedCommandNames.includes(topic)) {
1367
1671
  return ok(`see: cave ${topic} --help\n`);
1368
1672
  }
1369
1673
  return fail(`cave help: unknown command ${JSON.stringify(topic)}\n\n${usage}\n`, 2);
@@ -1372,52 +1676,55 @@ export const helpCommand = (argv) => {
1372
1676
  export const cave = (argv) => {
1373
1677
  const [command, ...rest] = argv;
1374
1678
  const canonical = command === 'q' ? 'query' : command;
1375
- try {
1376
- if (canonical !== undefined && canonical in commandHelp &&
1377
- (rest.includes('--help') || rest.includes('-h'))) {
1378
- return ok(`${commandHelp[canonical]}\n`);
1379
- }
1380
- switch (canonical) {
1381
- case 'parse':
1382
- return parseCommand(rest);
1383
- case 'add':
1384
- return addCommand(rest);
1385
- case 'import':
1386
- return importCommand(rest);
1387
- case 'query':
1388
- return queryCommand(rest);
1389
- case 'resolve':
1390
- return resolveCommand(rest);
1391
- case 'derive':
1392
- return deriveCommand(rest);
1393
- case 'act':
1394
- return actCommand(rest);
1395
- case 'check':
1396
- return checkCommand(rest);
1397
- case 'sync':
1398
- return syncCommand(rest);
1399
- case 'export':
1400
- return exportCommand(rest);
1401
- case 'report':
1402
- return reportCommand(rest);
1403
- case 'demo':
1404
- return demoCommand();
1405
- case 'version':
1406
- case '--version':
1407
- case '-v':
1408
- return versionCommand();
1409
- case 'help':
1410
- return helpCommand(rest);
1411
- case undefined:
1412
- case '--help':
1413
- case '-h':
1414
- return ok(`${usage}\n`);
1415
- default:
1416
- return fail(`cave: unknown command ${JSON.stringify(command)}\n\n${usage}\n`, 2);
1417
- }
1679
+ if (canonical !== undefined && canonical in commandHelp &&
1680
+ (rest.includes('--help') || rest.includes('-h'))) {
1681
+ return ok(`${commandHelp[canonical]}\n`);
1418
1682
  }
1419
- catch (error) {
1420
- return fail(`${error instanceof Error ? error.message : String(error)}\n`);
1683
+ switch (canonical) {
1684
+ case 'parse':
1685
+ return parseCommand(rest);
1686
+ case 'add':
1687
+ return addCommand(rest);
1688
+ case 'import':
1689
+ return importCommand(rest);
1690
+ case 'query':
1691
+ return queryCommand(rest);
1692
+ case 'resolve':
1693
+ return resolveCommand(rest);
1694
+ case 'derive':
1695
+ return deriveCommand(rest);
1696
+ case 'act':
1697
+ return actCommand(rest);
1698
+ case 'check':
1699
+ return checkCommand(rest);
1700
+ case 'backup':
1701
+ return backupCommand(rest);
1702
+ case 'restore':
1703
+ return restoreCommand(rest);
1704
+ case 'generate':
1705
+ return generateCommand(rest);
1706
+ case 'sync':
1707
+ return syncCommand(rest);
1708
+ case 'export':
1709
+ return exportCommand(rest);
1710
+ case 'report':
1711
+ return reportCommand(rest);
1712
+ case 'doctor':
1713
+ return doctorCommand(rest);
1714
+ case 'demo':
1715
+ return demoCommand();
1716
+ case 'version':
1717
+ case '--version':
1718
+ case '-v':
1719
+ return versionCommand();
1720
+ case 'help':
1721
+ return helpCommand(rest);
1722
+ case undefined:
1723
+ case '--help':
1724
+ case '-h':
1725
+ return ok(`${usage}\n`);
1726
+ default:
1727
+ return fail(`cave: unknown command ${JSON.stringify(command)}\n\n${usage}\n`, 2);
1421
1728
  }
1422
1729
  };
1423
1730
  //# sourceMappingURL=cli.js.map