@xoxoai/checkmate 0.6.7 → 0.7.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 (381) hide show
  1. package/README.md +54 -257
  2. package/dist/checkmate.js +13375 -0
  3. package/package.json +30 -94
  4. package/plugin/.claude-plugin/plugin.json +10 -0
  5. package/plugin/bin/checkmate.ts +3 -0
  6. package/plugin/contracts/finding.schema.json +78 -0
  7. package/plugin/contracts/gap.schema.json +56 -0
  8. package/plugin/contracts/plan-candidate.schema.json +103 -0
  9. package/plugin/contracts/plan-delta.schema.json +63 -0
  10. package/plugin/contracts/plan-patch.schema.json +116 -0
  11. package/plugin/contracts/plan-request.schema.json +108 -0
  12. package/plugin/contracts/plan.schema.json +313 -0
  13. package/plugin/contracts/profile.schema.json +154 -0
  14. package/plugin/contracts/response.schema.json +25 -0
  15. package/plugin/contracts/result.schema.json +410 -0
  16. package/plugin/dist/checkmate.js +13012 -0
  17. package/plugin/package.json +4 -0
  18. package/plugin/references/browser.md +35 -0
  19. package/plugin/references/http.md +30 -0
  20. package/plugin/references/workflow.md +122 -0
  21. package/plugin/skills/execute/SKILL.md +92 -0
  22. package/plugin/skills/plan/SKILL.md +94 -0
  23. package/plugin/skills/report/SKILL.md +28 -0
  24. package/plugin/skills/revise/SKILL.md +43 -0
  25. package/plugin/skills/setup/SKILL.md +82 -0
  26. package/plugin/skills/status/SKILL.md +20 -0
  27. package/plugin/skills/ui/SKILL.md +26 -0
  28. package/plugin/src/actor.ts +21 -0
  29. package/plugin/src/cli.ts +1133 -0
  30. package/plugin/src/contracts.ts +342 -0
  31. package/plugin/src/index.ts +52 -0
  32. package/plugin/src/plans.ts +1028 -0
  33. package/plugin/src/render.ts +544 -0
  34. package/plugin/src/runs.ts +868 -0
  35. package/plugin/src/store.ts +384 -0
  36. package/plugin/src/targets.ts +205 -0
  37. package/plugin/src/types.ts +319 -0
  38. package/plugin/src/ui/assets/client.js +501 -0
  39. package/plugin/src/ui/assets/favicon.svg +1 -0
  40. package/plugin/src/ui/assets/fonts/inter-OFL.txt +93 -0
  41. package/plugin/src/ui/assets/fonts/inter.woff2 +0 -0
  42. package/plugin/src/ui/assets/fonts/jetbrains-mono-OFL.txt +93 -0
  43. package/plugin/src/ui/assets/fonts/jetbrains-mono.woff2 +0 -0
  44. package/plugin/src/ui/assets/fonts/space-grotesk-OFL.txt +93 -0
  45. package/plugin/src/ui/assets/fonts/space-grotesk.woff2 +0 -0
  46. package/plugin/src/ui/assets/style.css +1926 -0
  47. package/plugin/src/ui/edit.ts +305 -0
  48. package/plugin/src/ui/forms.ts +209 -0
  49. package/plugin/src/ui/html.ts +130 -0
  50. package/plugin/src/ui/layout.ts +198 -0
  51. package/plugin/src/ui/lists.ts +645 -0
  52. package/plugin/src/ui/plan.ts +577 -0
  53. package/plugin/src/ui/run.ts +489 -0
  54. package/plugin/src/ui/server.ts +684 -0
  55. package/plugin/src/validators.d.ts +13 -0
  56. package/plugin/src/validators.js +1 -0
  57. package/plugin/src/views.ts +839 -0
  58. package/bin/checkmate.js +0 -13
  59. package/dist/ai/client.d.ts +0 -64
  60. package/dist/ai/client.d.ts.map +0 -1
  61. package/dist/ai/client.js +0 -313
  62. package/dist/ai/client.js.map +0 -1
  63. package/dist/ai/message-handler.d.ts +0 -9
  64. package/dist/ai/message-handler.d.ts.map +0 -1
  65. package/dist/ai/message-handler.js +0 -50
  66. package/dist/ai/message-handler.js.map +0 -1
  67. package/dist/ai/message-history.d.ts +0 -17
  68. package/dist/ai/message-history.d.ts.map +0 -1
  69. package/dist/ai/message-history.js +0 -45
  70. package/dist/ai/message-history.js.map +0 -1
  71. package/dist/ai/prompts.d.ts +0 -4
  72. package/dist/ai/prompts.d.ts.map +0 -1
  73. package/dist/ai/prompts.js +0 -24
  74. package/dist/ai/prompts.js.map +0 -1
  75. package/dist/ai/rate-limit-policy.d.ts +0 -9
  76. package/dist/ai/rate-limit-policy.d.ts.map +0 -1
  77. package/dist/ai/rate-limit-policy.js +0 -34
  78. package/dist/ai/rate-limit-policy.js.map +0 -1
  79. package/dist/ai/tool-response-handler.d.ts +0 -16
  80. package/dist/ai/tool-response-handler.d.ts.map +0 -1
  81. package/dist/ai/tool-response-handler.js +0 -85
  82. package/dist/ai/tool-response-handler.js.map +0 -1
  83. package/dist/ai/turn-processor.d.ts +0 -34
  84. package/dist/ai/turn-processor.d.ts.map +0 -1
  85. package/dist/ai/turn-processor.js +0 -107
  86. package/dist/ai/turn-processor.js.map +0 -1
  87. package/dist/api/allocate-run-identity.d.ts +0 -8
  88. package/dist/api/allocate-run-identity.d.ts.map +0 -1
  89. package/dist/api/allocate-run-identity.js +0 -10
  90. package/dist/api/allocate-run-identity.js.map +0 -1
  91. package/dist/api/execute-prepared-run.d.ts +0 -22
  92. package/dist/api/execute-prepared-run.d.ts.map +0 -1
  93. package/dist/api/execute-prepared-run.js +0 -184
  94. package/dist/api/execute-prepared-run.js.map +0 -1
  95. package/dist/api/index.d.ts +0 -25
  96. package/dist/api/index.d.ts.map +0 -1
  97. package/dist/api/index.js +0 -69
  98. package/dist/api/index.js.map +0 -1
  99. package/dist/api/prepare-run.d.ts +0 -44
  100. package/dist/api/prepare-run.d.ts.map +0 -1
  101. package/dist/api/prepare-run.js +0 -261
  102. package/dist/api/prepare-run.js.map +0 -1
  103. package/dist/cli/diagnostic-relay.d.ts +0 -6
  104. package/dist/cli/diagnostic-relay.d.ts.map +0 -1
  105. package/dist/cli/diagnostic-relay.js +0 -6
  106. package/dist/cli/diagnostic-relay.js.map +0 -1
  107. package/dist/cli/main.d.ts +0 -21
  108. package/dist/cli/main.d.ts.map +0 -1
  109. package/dist/cli/main.js +0 -166
  110. package/dist/cli/main.js.map +0 -1
  111. package/dist/cli/protocol.d.ts +0 -57
  112. package/dist/cli/protocol.d.ts.map +0 -1
  113. package/dist/cli/protocol.js +0 -116
  114. package/dist/cli/protocol.js.map +0 -1
  115. package/dist/cli/reconcile-result.d.ts +0 -13
  116. package/dist/cli/reconcile-result.d.ts.map +0 -1
  117. package/dist/cli/reconcile-result.js +0 -300
  118. package/dist/cli/reconcile-result.js.map +0 -1
  119. package/dist/cli/run-parent.d.ts +0 -37
  120. package/dist/cli/run-parent.d.ts.map +0 -1
  121. package/dist/cli/run-parent.js +0 -491
  122. package/dist/cli/run-parent.js.map +0 -1
  123. package/dist/cli/signals.d.ts +0 -36
  124. package/dist/cli/signals.d.ts.map +0 -1
  125. package/dist/cli/signals.js +0 -85
  126. package/dist/cli/signals.js.map +0 -1
  127. package/dist/cli/static-commands.d.ts +0 -11
  128. package/dist/cli/static-commands.d.ts.map +0 -1
  129. package/dist/cli/static-commands.js +0 -83
  130. package/dist/cli/static-commands.js.map +0 -1
  131. package/dist/cli/worker-stream-guard.d.ts +0 -20
  132. package/dist/cli/worker-stream-guard.d.ts.map +0 -1
  133. package/dist/cli/worker-stream-guard.js +0 -41
  134. package/dist/cli/worker-stream-guard.js.map +0 -1
  135. package/dist/cli/worker.d.ts +0 -3
  136. package/dist/cli/worker.d.ts.map +0 -1
  137. package/dist/cli/worker.js +0 -111
  138. package/dist/cli/worker.js.map +0 -1
  139. package/dist/config/ingestion.d.ts +0 -58
  140. package/dist/config/ingestion.d.ts.map +0 -1
  141. package/dist/config/ingestion.js +0 -283
  142. package/dist/config/ingestion.js.map +0 -1
  143. package/dist/config/manifest.d.ts +0 -20
  144. package/dist/config/manifest.d.ts.map +0 -1
  145. package/dist/config/manifest.js +0 -91
  146. package/dist/config/manifest.js.map +0 -1
  147. package/dist/config/model-egress.d.ts +0 -10
  148. package/dist/config/model-egress.d.ts.map +0 -1
  149. package/dist/config/model-egress.js +0 -62
  150. package/dist/config/model-egress.js.map +0 -1
  151. package/dist/config/package-resolution.d.ts +0 -4
  152. package/dist/config/package-resolution.d.ts.map +0 -1
  153. package/dist/config/package-resolution.js +0 -24
  154. package/dist/config/package-resolution.js.map +0 -1
  155. package/dist/config/policy.d.ts +0 -44
  156. package/dist/config/policy.d.ts.map +0 -1
  157. package/dist/config/policy.js +0 -141
  158. package/dist/config/policy.js.map +0 -1
  159. package/dist/config/record.d.ts +0 -4
  160. package/dist/config/record.d.ts.map +0 -1
  161. package/dist/config/record.js +0 -10
  162. package/dist/config/record.js.map +0 -1
  163. package/dist/config/secrets.d.ts +0 -11
  164. package/dist/config/secrets.d.ts.map +0 -1
  165. package/dist/config/secrets.js +0 -42
  166. package/dist/config/secrets.js.map +0 -1
  167. package/dist/contracts/diagnostics.d.ts +0 -4
  168. package/dist/contracts/diagnostics.d.ts.map +0 -1
  169. package/dist/contracts/diagnostics.js +0 -31
  170. package/dist/contracts/diagnostics.js.map +0 -1
  171. package/dist/contracts/serialize.d.ts +0 -2
  172. package/dist/contracts/serialize.d.ts.map +0 -1
  173. package/dist/contracts/serialize.js +0 -90
  174. package/dist/contracts/serialize.js.map +0 -1
  175. package/dist/contracts/types.d.ts +0 -316
  176. package/dist/contracts/types.d.ts.map +0 -1
  177. package/dist/contracts/types.js +0 -74
  178. package/dist/contracts/types.js.map +0 -1
  179. package/dist/contracts/validator.d.ts +0 -8
  180. package/dist/contracts/validator.d.ts.map +0 -1
  181. package/dist/contracts/validator.js +0 -71
  182. package/dist/contracts/validator.js.map +0 -1
  183. package/dist/driver.d.ts +0 -97
  184. package/dist/driver.d.ts.map +0 -1
  185. package/dist/driver.js +0 -24
  186. package/dist/driver.js.map +0 -1
  187. package/dist/drivers/descriptor.d.ts +0 -15
  188. package/dist/drivers/descriptor.d.ts.map +0 -1
  189. package/dist/drivers/descriptor.js +0 -122
  190. package/dist/drivers/descriptor.js.map +0 -1
  191. package/dist/drivers/loader.d.ts +0 -18
  192. package/dist/drivers/loader.d.ts.map +0 -1
  193. package/dist/drivers/loader.js +0 -97
  194. package/dist/drivers/loader.js.map +0 -1
  195. package/dist/drivers/web/checkmate-driver.json +0 -47
  196. package/dist/drivers/web/index.d.ts +0 -6
  197. package/dist/drivers/web/index.d.ts.map +0 -1
  198. package/dist/drivers/web/index.js +0 -27
  199. package/dist/drivers/web/index.js.map +0 -1
  200. package/dist/drivers/web/session.d.ts +0 -25
  201. package/dist/drivers/web/session.d.ts.map +0 -1
  202. package/dist/drivers/web/session.js +0 -132
  203. package/dist/drivers/web/session.js.map +0 -1
  204. package/dist/drivers/web/tools/network-request-recorder.d.ts +0 -46
  205. package/dist/drivers/web/tools/network-request-recorder.d.ts.map +0 -1
  206. package/dist/drivers/web/tools/network-request-recorder.js +0 -164
  207. package/dist/drivers/web/tools/network-request-recorder.js.map +0 -1
  208. package/dist/drivers/web/tools/screenshot-service.d.ts +0 -10
  209. package/dist/drivers/web/tools/screenshot-service.d.ts.map +0 -1
  210. package/dist/drivers/web/tools/screenshot-service.js +0 -23
  211. package/dist/drivers/web/tools/screenshot-service.js.map +0 -1
  212. package/dist/drivers/web/tools/snapshot-filter/index.d.ts +0 -4
  213. package/dist/drivers/web/tools/snapshot-filter/index.d.ts.map +0 -1
  214. package/dist/drivers/web/tools/snapshot-filter/index.js +0 -4
  215. package/dist/drivers/web/tools/snapshot-filter/index.js.map +0 -1
  216. package/dist/drivers/web/tools/snapshot-filter/semantic-scorer.d.ts +0 -15
  217. package/dist/drivers/web/tools/snapshot-filter/semantic-scorer.d.ts.map +0 -1
  218. package/dist/drivers/web/tools/snapshot-filter/semantic-scorer.js +0 -99
  219. package/dist/drivers/web/tools/snapshot-filter/semantic-scorer.js.map +0 -1
  220. package/dist/drivers/web/tools/snapshot-filter/snapshot-filter.d.ts +0 -5
  221. package/dist/drivers/web/tools/snapshot-filter/snapshot-filter.d.ts.map +0 -1
  222. package/dist/drivers/web/tools/snapshot-filter/snapshot-filter.js +0 -56
  223. package/dist/drivers/web/tools/snapshot-filter/snapshot-filter.js.map +0 -1
  224. package/dist/drivers/web/tools/snapshot-filter/tree-reconstructor.d.ts +0 -3
  225. package/dist/drivers/web/tools/snapshot-filter/tree-reconstructor.d.ts.map +0 -1
  226. package/dist/drivers/web/tools/snapshot-filter/tree-reconstructor.js +0 -85
  227. package/dist/drivers/web/tools/snapshot-filter/tree-reconstructor.js.map +0 -1
  228. package/dist/drivers/web/tools/snapshot-service.d.ts +0 -31
  229. package/dist/drivers/web/tools/snapshot-service.d.ts.map +0 -1
  230. package/dist/drivers/web/tools/snapshot-service.js +0 -96
  231. package/dist/drivers/web/tools/snapshot-service.js.map +0 -1
  232. package/dist/drivers/web/tools/tool.d.ts +0 -76
  233. package/dist/drivers/web/tools/tool.d.ts.map +0 -1
  234. package/dist/drivers/web/tools/tool.js +0 -562
  235. package/dist/drivers/web/tools/tool.js.map +0 -1
  236. package/dist/drivers/web/tools/transient-state-tracker.d.ts +0 -26
  237. package/dist/drivers/web/tools/transient-state-tracker.d.ts.map +0 -1
  238. package/dist/drivers/web/tools/transient-state-tracker.js +0 -301
  239. package/dist/drivers/web/tools/transient-state-tracker.js.map +0 -1
  240. package/dist/drivers/web/tools/types.d.ts +0 -11
  241. package/dist/drivers/web/tools/types.d.ts.map +0 -1
  242. package/dist/drivers/web/tools/types.js +0 -2
  243. package/dist/drivers/web/tools/types.js.map +0 -1
  244. package/dist/evidence/atomic-file.d.ts +0 -23
  245. package/dist/evidence/atomic-file.d.ts.map +0 -1
  246. package/dist/evidence/atomic-file.js +0 -104
  247. package/dist/evidence/atomic-file.js.map +0 -1
  248. package/dist/evidence/layout.d.ts +0 -30
  249. package/dist/evidence/layout.d.ts.map +0 -1
  250. package/dist/evidence/layout.js +0 -152
  251. package/dist/evidence/layout.js.map +0 -1
  252. package/dist/evidence/retention.d.ts +0 -5
  253. package/dist/evidence/retention.d.ts.map +0 -1
  254. package/dist/evidence/retention.js +0 -10
  255. package/dist/evidence/retention.js.map +0 -1
  256. package/dist/evidence/store.d.ts +0 -101
  257. package/dist/evidence/store.d.ts.map +0 -1
  258. package/dist/evidence/store.js +0 -375
  259. package/dist/evidence/store.js.map +0 -1
  260. package/dist/evidence/terminal-finalizer.d.ts +0 -19
  261. package/dist/evidence/terminal-finalizer.d.ts.map +0 -1
  262. package/dist/evidence/terminal-finalizer.js +0 -56
  263. package/dist/evidence/terminal-finalizer.js.map +0 -1
  264. package/dist/index.d.ts +0 -9
  265. package/dist/index.d.ts.map +0 -1
  266. package/dist/index.js +0 -12
  267. package/dist/index.js.map +0 -1
  268. package/dist/logging/invocation-logger.d.ts +0 -4
  269. package/dist/logging/invocation-logger.d.ts.map +0 -1
  270. package/dist/logging/invocation-logger.js +0 -25
  271. package/dist/logging/invocation-logger.js.map +0 -1
  272. package/dist/logging/level-logger.d.ts +0 -11
  273. package/dist/logging/level-logger.d.ts.map +0 -1
  274. package/dist/logging/level-logger.js +0 -82
  275. package/dist/logging/level-logger.js.map +0 -1
  276. package/dist/logging/types.d.ts +0 -8
  277. package/dist/logging/types.d.ts.map +0 -1
  278. package/dist/logging/types.js +0 -7
  279. package/dist/logging/types.js.map +0 -1
  280. package/dist/redaction/diagnostic-sanitizer.d.ts +0 -7
  281. package/dist/redaction/diagnostic-sanitizer.d.ts.map +0 -1
  282. package/dist/redaction/diagnostic-sanitizer.js +0 -42
  283. package/dist/redaction/diagnostic-sanitizer.js.map +0 -1
  284. package/dist/redaction/redactor.d.ts +0 -40
  285. package/dist/redaction/redactor.d.ts.map +0 -1
  286. package/dist/redaction/redactor.js +0 -176
  287. package/dist/redaction/redactor.js.map +0 -1
  288. package/dist/redaction/scrub.d.ts +0 -29
  289. package/dist/redaction/scrub.d.ts.map +0 -1
  290. package/dist/redaction/scrub.js +0 -54
  291. package/dist/redaction/scrub.js.map +0 -1
  292. package/dist/runtime/config.d.ts +0 -16
  293. package/dist/runtime/config.d.ts.map +0 -1
  294. package/dist/runtime/config.js +0 -2
  295. package/dist/runtime/config.js.map +0 -1
  296. package/dist/runtime/driver-boundary.d.ts +0 -16
  297. package/dist/runtime/driver-boundary.d.ts.map +0 -1
  298. package/dist/runtime/driver-boundary.js +0 -71
  299. package/dist/runtime/driver-boundary.js.map +0 -1
  300. package/dist/runtime/driver-session.d.ts +0 -9
  301. package/dist/runtime/driver-session.d.ts.map +0 -1
  302. package/dist/runtime/driver-session.js +0 -64
  303. package/dist/runtime/driver-session.js.map +0 -1
  304. package/dist/runtime/internal-step-evidence.d.ts +0 -30
  305. package/dist/runtime/internal-step-evidence.d.ts.map +0 -1
  306. package/dist/runtime/internal-step-evidence.js +0 -82
  307. package/dist/runtime/internal-step-evidence.js.map +0 -1
  308. package/dist/runtime/result-adapter.d.ts +0 -9
  309. package/dist/runtime/result-adapter.d.ts.map +0 -1
  310. package/dist/runtime/result-adapter.js +0 -77
  311. package/dist/runtime/result-adapter.js.map +0 -1
  312. package/dist/runtime/runner.d.ts +0 -43
  313. package/dist/runtime/runner.d.ts.map +0 -1
  314. package/dist/runtime/runner.js +0 -72
  315. package/dist/runtime/runner.js.map +0 -1
  316. package/dist/runtime/scenario-control.d.ts +0 -40
  317. package/dist/runtime/scenario-control.d.ts.map +0 -1
  318. package/dist/runtime/scenario-control.js +0 -94
  319. package/dist/runtime/scenario-control.js.map +0 -1
  320. package/dist/runtime/scenario-runner.d.ts +0 -41
  321. package/dist/runtime/scenario-runner.d.ts.map +0 -1
  322. package/dist/runtime/scenario-runner.js +0 -274
  323. package/dist/runtime/scenario-runner.js.map +0 -1
  324. package/dist/runtime/scenario-state.d.ts +0 -55
  325. package/dist/runtime/scenario-state.d.ts.map +0 -1
  326. package/dist/runtime/scenario-state.js +0 -128
  327. package/dist/runtime/scenario-state.js.map +0 -1
  328. package/dist/runtime/step-execution.d.ts +0 -27
  329. package/dist/runtime/step-execution.d.ts.map +0 -1
  330. package/dist/runtime/step-execution.js +0 -143
  331. package/dist/runtime/step-execution.js.map +0 -1
  332. package/dist/runtime/text.d.ts +0 -10
  333. package/dist/runtime/text.d.ts.map +0 -1
  334. package/dist/runtime/text.js +0 -25
  335. package/dist/runtime/text.js.map +0 -1
  336. package/dist/runtime/types.d.ts +0 -53
  337. package/dist/runtime/types.d.ts.map +0 -1
  338. package/dist/runtime/types.js +0 -2
  339. package/dist/runtime/types.js.map +0 -1
  340. package/dist/runtime/usage-tracker.d.ts +0 -37
  341. package/dist/runtime/usage-tracker.d.ts.map +0 -1
  342. package/dist/runtime/usage-tracker.js +0 -110
  343. package/dist/runtime/usage-tracker.js.map +0 -1
  344. package/dist/tools/define-agent-tool.d.ts +0 -20
  345. package/dist/tools/define-agent-tool.d.ts.map +0 -1
  346. package/dist/tools/define-agent-tool.js +0 -30
  347. package/dist/tools/define-agent-tool.js.map +0 -1
  348. package/dist/tools/dispatcher.d.ts +0 -19
  349. package/dist/tools/dispatcher.d.ts.map +0 -1
  350. package/dist/tools/dispatcher.js +0 -91
  351. package/dist/tools/dispatcher.js.map +0 -1
  352. package/dist/tools/loop-detector.d.ts +0 -27
  353. package/dist/tools/loop-detector.d.ts.map +0 -1
  354. package/dist/tools/loop-detector.js +0 -76
  355. package/dist/tools/loop-detector.js.map +0 -1
  356. package/dist/tools/registry.d.ts +0 -19
  357. package/dist/tools/registry.d.ts.map +0 -1
  358. package/dist/tools/registry.js +0 -54
  359. package/dist/tools/registry.js.map +0 -1
  360. package/dist/tools/step/result-tool.d.ts +0 -7
  361. package/dist/tools/step/result-tool.d.ts.map +0 -1
  362. package/dist/tools/step/result-tool.js +0 -34
  363. package/dist/tools/step/result-tool.js.map +0 -1
  364. package/dist/tools/tool-contract.d.ts +0 -3
  365. package/dist/tools/tool-contract.d.ts.map +0 -1
  366. package/dist/tools/tool-contract.js +0 -2
  367. package/dist/tools/tool-contract.js.map +0 -1
  368. package/dist/tools/types.d.ts +0 -210
  369. package/dist/tools/types.d.ts.map +0 -1
  370. package/dist/tools/types.js +0 -4
  371. package/dist/tools/types.js.map +0 -1
  372. package/docs/CLI.md +0 -59
  373. package/docs/CONFIGURATION.md +0 -105
  374. package/docs/DRIVERS.md +0 -79
  375. package/docs/EVIDENCE.md +0 -64
  376. package/schemas/checkmate-config.v1.json +0 -176
  377. package/schemas/describe-result.v1.json +0 -414
  378. package/schemas/driver-descriptor.v1.json +0 -66
  379. package/schemas/run-request.v1.json +0 -67
  380. package/schemas/run-result.v1.json +0 -333
  381. package/schemas/validation-result.v1.json +0 -86
@@ -0,0 +1,1133 @@
1
+ import type { Reply, Store } from './store.ts'
2
+ import type { Plan, Profile } from './types.ts'
3
+ import type { RequestedTool, TargetRequest } from './targets.ts'
4
+ import type { PlanTool } from './plans.ts'
5
+ import type { EvidenceInput } from './runs.ts'
6
+ import { closeStore, completed, openReadOnlyStore, openStore } from './store.ts'
7
+ import { requireChoice, requireCondition, revisionReference } from './contracts.ts'
8
+ import { openTarget, saveTarget, showTarget, showTargetRequest } from './targets.ts'
9
+ import {
10
+ adoptGaps,
11
+ answerGap,
12
+ approvePlan,
13
+ diffPlan,
14
+ editPlan,
15
+ gate,
16
+ openPlan,
17
+ patchPlan,
18
+ rejectPlan,
19
+ revisePlan,
20
+ savePlan,
21
+ showPending,
22
+ showPlanRequest,
23
+ } from './plans.ts'
24
+ import {
25
+ acceptResult,
26
+ beginRun,
27
+ checkAssertion,
28
+ closeTest,
29
+ finishRun,
30
+ recordFinding,
31
+ recordGap,
32
+ rejectResult,
33
+ resumeRun,
34
+ } from './runs.ts'
35
+ import {
36
+ evidenceBytes,
37
+ inbox,
38
+ operationLog,
39
+ planDetail,
40
+ planList,
41
+ planStatus,
42
+ roles,
43
+ runDetail,
44
+ runList,
45
+ } from './views.ts'
46
+ import { exportPlan, exportRun } from './render.ts'
47
+ import { startUi } from './ui/server.ts'
48
+ import { defaultActor } from './actor.ts'
49
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
50
+ import { dirname, resolve } from 'node:path'
51
+
52
+ type FlagKind = 'value' | 'list' | 'repeat' | 'switch'
53
+ type Args = Record<string, string | string[] | boolean>
54
+ type Context = { actor: string | undefined; stdin: () => unknown; text: () => string }
55
+ type Printed = { facts?: Record<string, unknown>; lines?: unknown[][] }
56
+ type Command = {
57
+ positionals: string[]
58
+ flags?: Record<string, FlagKind>
59
+ required?: string[]
60
+ write?: boolean
61
+ stdin?: boolean
62
+ readOnly?: boolean
63
+ run: (store: Store, args: Args, context: Context) => Reply
64
+ print?: (reply: Reply) => Printed
65
+ }
66
+ export type Io = { cwd: string; env: Record<string, string | undefined>; stdin: () => string }
67
+ export type Outcome = { code: number; output: string }
68
+
69
+ const testFields = [
70
+ 'title',
71
+ 'readiness',
72
+ 'capability',
73
+ 'operations',
74
+ 'requirementIds',
75
+ 'gapIds',
76
+ 'dependsOn',
77
+ 'preconditions',
78
+ 'action',
79
+ 'assertions',
80
+ ]
81
+
82
+ function value(args: Args, name: string) {
83
+ const found = args[name]
84
+ return typeof found === 'string' ? found : undefined
85
+ }
86
+
87
+ function list(args: Args, name: string) {
88
+ const found = args[name]
89
+ return Array.isArray(found) ? found : undefined
90
+ }
91
+
92
+ function integer(args: Args, name: string) {
93
+ const text = value(args, name)
94
+ if (text === undefined) return undefined
95
+ requireCondition(/^[0-9]+$/.test(text), `Invalid --${name}: ${text}. Use a whole number.`)
96
+ return Number(text)
97
+ }
98
+
99
+ function evidence(args: Args, context: Context): EvidenceInput[] | undefined {
100
+ return list(args, 'evidence')?.map((spec) => {
101
+ const match = /^-(?::(.+))?$/.exec(spec)
102
+ return match ? { text: context.text(), kind: match[1] } : spec
103
+ })
104
+ }
105
+
106
+ function toolSpec(spec: string): RequestedTool {
107
+ const match = /^(.+?):(cli|mcp|skill):(.+)$/.exec(spec)
108
+ requireCondition(match, `Invalid --tool: ${spec}. Use <name>:<cli|mcp|skill>:<purpose>.`)
109
+ return { name: match[1], kind: match[2] as RequestedTool['kind'], purpose: match[3] }
110
+ }
111
+
112
+ function planRequestInput(args: Args, stdin: () => unknown) {
113
+ const objective = value(args, 'objective')
114
+ const sources = list(args, 'source')
115
+ const instructions = value(args, 'instructions')
116
+ if (objective === undefined) {
117
+ requireCondition(
118
+ !sources && instructions === undefined,
119
+ 'Give --objective with --source and --instructions, or send the request on standard input'
120
+ )
121
+ return stdin()
122
+ }
123
+ return { objective, context: { sources: sources ?? [], ...(instructions === undefined ? {} : { instructions }) } }
124
+ }
125
+
126
+ function leftOutLines(reply: Reply) {
127
+ const leftOut = (reply.data.leftOut ?? []) as Array<{ testId: string; reason: string }>
128
+ return leftOut.map((item) => [item.testId, 'left_out', item.reason])
129
+ }
130
+
131
+ function short(sha256: unknown) {
132
+ return typeof sha256 === 'string' ? sha256.slice(0, 8) : sha256
133
+ }
134
+
135
+ const commands: Record<string, Command> = {
136
+ init: {
137
+ positionals: [],
138
+ run: (store) => completed('.checkmate/checkmate.db', { path: store.path }),
139
+ },
140
+ inbox: {
141
+ positionals: [],
142
+ run: (store) => completed('inbox', inbox(store)),
143
+ print: (reply) => {
144
+ const waiting = reply.data as ReturnType<typeof inbox>
145
+ return {
146
+ facts: {
147
+ gaps: waiting.gaps.length,
148
+ plans: waiting.plans.length,
149
+ results: waiting.results.length,
150
+ requests: waiting.requests.length,
151
+ },
152
+ lines: [
153
+ ...waiting.gaps.map((gap) =>
154
+ gap.runId
155
+ ? [
156
+ `${gap.planId}@${gap.revision}/${gap.gapId}`,
157
+ 'run_gap',
158
+ gap.severity,
159
+ gap.runId,
160
+ gap.question,
161
+ ]
162
+ : [`${gap.planId}@${gap.revision}/${gap.gapId}`, 'gap', gap.severity, gap.question]
163
+ ),
164
+ ...waiting.plans.map((plan) => [
165
+ `${plan.planId}@${plan.revision}`,
166
+ 'plan',
167
+ short(plan.sha256),
168
+ plan.summary,
169
+ ]),
170
+ ...waiting.results.map((result) => [
171
+ result.runId,
172
+ 'result',
173
+ `${result.planId}@${result.revision}`,
174
+ result.build,
175
+ result.coverage,
176
+ Object.entries(result.counts).map(([outcome, count]) => `${outcome}=${count}`),
177
+ ]),
178
+ ...waiting.requests.map((request) => [
179
+ request.planId,
180
+ 'requested',
181
+ request.target,
182
+ request.objective,
183
+ ]),
184
+ ],
185
+ }
186
+ },
187
+ },
188
+ status: {
189
+ positionals: ['planId'],
190
+ run: (store, args) => {
191
+ const planId = value(args, 'planId')!
192
+ return completed(planId, planStatus(store, planId))
193
+ },
194
+ print: (reply) => {
195
+ const status = reply.data as ReturnType<typeof planStatus>
196
+ return {
197
+ facts: {
198
+ state: status.state,
199
+ waitsOn: status.waitsOn,
200
+ revision: status.revision ?? undefined,
201
+ approval: status.approval ?? undefined,
202
+ run: status.latestRun?.runId,
203
+ },
204
+ lines: [
205
+ ...status.why.map((line) => ['why', line]),
206
+ ...(status.next ? [['next', status.next]] : []),
207
+ ...status.gaps.map((gap) =>
208
+ gap.runId
209
+ ? [gap.gapId, 'run_gap', gap.severity, gap.runId, gap.question]
210
+ : [gap.gapId, 'gap', gap.severity, gap.question]
211
+ ),
212
+ ...status.pendingDecisions.map((decision) => [
213
+ decision.id,
214
+ 'pending',
215
+ decision.gapId,
216
+ decision.kind,
217
+ ]),
218
+ ...status.defects.map((defect) => [
219
+ defect.findingId,
220
+ 'defect',
221
+ defect.runId,
222
+ defect.severity,
223
+ defect.assertionIds,
224
+ defect.title,
225
+ ]),
226
+ ],
227
+ }
228
+ },
229
+ },
230
+ 'target open': {
231
+ positionals: ['targetId'],
232
+ flags: { 'base-url': 'value', capabilities: 'list', operations: 'list', tool: 'repeat' },
233
+ required: ['base-url', 'capabilities', 'operations'],
234
+ write: true,
235
+ run: (store, args, { actor }) =>
236
+ openTarget(store, actor, {
237
+ targetId: value(args, 'targetId')!,
238
+ baseUrl: value(args, 'base-url')!,
239
+ capabilities: list(args, 'capabilities') ?? [],
240
+ allowedOperations: list(args, 'operations') ?? [],
241
+ tools: (list(args, 'tool') ?? []).map(toolSpec),
242
+ }),
243
+ print: (reply) => ({ facts: { recorded: reply.data.recorded } }),
244
+ },
245
+ 'target request': {
246
+ positionals: ['targetId'],
247
+ run: (store, args) => showTargetRequest(store, value(args, 'targetId')!),
248
+ print: (reply) => {
249
+ const request = reply.data.request as TargetRequest
250
+ return {
251
+ lines: [
252
+ ['baseUrl', request.baseUrl],
253
+ ['capabilities', request.capabilities],
254
+ ['operations', request.allowedOperations],
255
+ ...request.tools.map((tool) => ['tool', tool.name, tool.kind, tool.purpose]),
256
+ ],
257
+ }
258
+ },
259
+ },
260
+ 'target save': {
261
+ positionals: ['targetId'],
262
+ write: true,
263
+ stdin: true,
264
+ run: (store, args, { actor, stdin }) =>
265
+ saveTarget(store, actor, { targetId: value(args, 'targetId')!, profile: stdin() }),
266
+ print: (reply) => ({ facts: { sha256: short(reply.data.sha256), recorded: reply.data.recorded } }),
267
+ },
268
+ 'target show': {
269
+ positionals: ['reference'],
270
+ run: (store, args) => showTarget(store, value(args, 'reference')!),
271
+ print: (reply) => {
272
+ const profile = reply.data.profile as Profile
273
+ const approvals = reply.data.approvals as { plan: string; result: string }
274
+ return {
275
+ facts: { sha256: short(reply.data.sha256), ready: profile.readyForLiveExecution },
276
+ lines: [
277
+ ['baseUrl', profile.baseUrl],
278
+ ['environment', profile.environment],
279
+ ['capabilities', profile.capabilities],
280
+ ['operations', profile.allowedOperations],
281
+ ['approvals', `plan=${approvals.plan}`, `result=${approvals.result}`],
282
+ ...profile.tools.map((tool) => [
283
+ 'tool',
284
+ tool.name,
285
+ tool.kind,
286
+ tool.version ?? '-',
287
+ tool.reachable ? 'reachable' : 'unreachable',
288
+ tool.purpose,
289
+ ]),
290
+ ],
291
+ }
292
+ },
293
+ },
294
+ 'plan open': {
295
+ positionals: ['planId'],
296
+ flags: { target: 'value', objective: 'value', source: 'repeat', instructions: 'value' },
297
+ required: ['target'],
298
+ write: true,
299
+ stdin: true,
300
+ run: (store, args, { actor, stdin }) =>
301
+ openPlan(store, actor, {
302
+ planId: value(args, 'planId')!,
303
+ target: value(args, 'target')!,
304
+ request: planRequestInput(args, stdin),
305
+ }),
306
+ print: (reply) => ({ facts: { target: reply.data.target, recorded: reply.data.recorded } }),
307
+ },
308
+ 'plan request': {
309
+ positionals: ['planId'],
310
+ run: (store, args) => showPlanRequest(store, value(args, 'planId')!),
311
+ print: (reply) => {
312
+ const data = reply.data as ReturnType<typeof showPlanRequest>['data'] & {
313
+ target: string
314
+ objective: string
315
+ requirements: Plan['requirements']
316
+ sourceContext: Array<{ uri: string; content: string }>
317
+ context: { sources: string[]; instructions?: string }
318
+ tools: PlanTool[]
319
+ capabilities: string[]
320
+ allowedOperations: string[]
321
+ }
322
+ return {
323
+ facts: { target: data.target },
324
+ lines: [
325
+ ['objective', data.objective],
326
+ ...data.requirements.map((requirement) => [requirement.id, requirement.text, requirement.source]),
327
+ ...data.context.sources.map((source) => ['source', source]),
328
+ ...(data.context.instructions ? [['instructions', data.context.instructions]] : []),
329
+ ...data.sourceContext.map((context) => ['context', context.uri, context.content]),
330
+ ...data.tools.map((tool) => ['tool', tool.name, tool.kind, tool.purpose]),
331
+ ['capabilities', data.capabilities],
332
+ ['operations', data.allowedOperations],
333
+ ],
334
+ }
335
+ },
336
+ },
337
+ 'plan save': {
338
+ positionals: ['planId'],
339
+ write: true,
340
+ stdin: true,
341
+ run: (store, args, { actor, stdin }) =>
342
+ savePlan(store, actor, { planId: value(args, 'planId')!, candidate: stdin() }),
343
+ print: (reply) => ({ facts: { sha256: short(reply.data.sha256) } }),
344
+ },
345
+ 'plan show': {
346
+ positionals: ['reference'],
347
+ flags: { tests: 'list', fields: 'list', gaps: 'value' },
348
+ run: (store, args) => showPlan(store, args),
349
+ print: printPlan,
350
+ },
351
+ 'plan list': {
352
+ positionals: [],
353
+ flags: { 'needs-decision': 'switch', 'awaiting-approval': 'switch', blocked: 'switch', 'waits-on': 'value' },
354
+ run: (store, args) => {
355
+ const waitsOn = value(args, 'waits-on')
356
+ if (waitsOn !== undefined) requireChoice(waitsOn, roles, 'role (--waits-on)')
357
+ const plans = planList(store).filter(
358
+ (plan) =>
359
+ (!args['needs-decision'] || plan.openBlockingGaps.length > 0) &&
360
+ (!args['awaiting-approval'] || plan.approval === 'pending') &&
361
+ (!args.blocked || plan.blocked.length > 0) &&
362
+ (waitsOn === undefined || plan.loop.waitsOn === waitsOn)
363
+ )
364
+ return completed('plans', { plans })
365
+ },
366
+ print: (reply) => ({
367
+ facts: { count: (reply.data.plans as unknown[]).length },
368
+ lines: (reply.data.plans as ReturnType<typeof planList>).map((plan) =>
369
+ plan.status === 'requested'
370
+ ? [plan.planId, 'status=requested', `state=${plan.loop.state}`, `waitsOn=${plan.loop.waitsOn}`]
371
+ : [
372
+ `${plan.planId}@${plan.revision}`,
373
+ `gaps=${cell(plan.openBlockingGaps)}`,
374
+ `approval=${plan.approval}`,
375
+ `blocked=${cell(plan.blocked.map((block) => `${block.testId}<-${block.dependency}: ${block.cause}`))}`,
376
+ `state=${plan.loop.state}`,
377
+ `waitsOn=${plan.loop.waitsOn}`,
378
+ ]
379
+ ),
380
+ }),
381
+ },
382
+ 'plan diff': {
383
+ positionals: ['planId', 'from', 'to'],
384
+ run: (store, args) =>
385
+ diffPlan(store, { planId: value(args, 'planId')!, from: value(args, 'from')!, to: value(args, 'to')! }),
386
+ print: (reply) => {
387
+ const patch = reply.data.patch as Array<{ op: string; path: string; value?: unknown }>
388
+ return {
389
+ facts: { operations: patch.length },
390
+ lines: [
391
+ ...(reply.data.changes as string[]).map((change) => ['change', change]),
392
+ ...patch.map((operation) =>
393
+ operation.op === 'remove'
394
+ ? [operation.op, operation.path]
395
+ : [operation.op, operation.path, JSON.stringify(operation.value)]
396
+ ),
397
+ ],
398
+ }
399
+ },
400
+ },
401
+ 'gap answer': {
402
+ positionals: ['planId', 'gapId'],
403
+ flags: { kind: 'value', value: 'value', source: 'value', rationale: 'value', 'expect-sha': 'value' },
404
+ required: ['kind', 'value', 'rationale'],
405
+ write: true,
406
+ run: (store, args, { actor }) =>
407
+ answerGap(store, actor, {
408
+ planId: value(args, 'planId')!,
409
+ gapId: value(args, 'gapId')!,
410
+ kind: value(args, 'kind')!,
411
+ value: value(args, 'value')!,
412
+ source: value(args, 'source') ?? null,
413
+ rationale: value(args, 'rationale')!,
414
+ expectSha256: value(args, 'expect-sha'),
415
+ }),
416
+ print: (reply) => {
417
+ const adopted = reply.data.adopted as { runId: string; revision: number; edit: string } | undefined
418
+ return {
419
+ facts: {
420
+ decision: reply.data.decisionId,
421
+ gap: reply.data.gapId,
422
+ from: adopted?.runId,
423
+ revision: adopted?.revision,
424
+ edit: adopted?.edit,
425
+ },
426
+ }
427
+ },
428
+ },
429
+ 'plan pending': {
430
+ positionals: ['planId'],
431
+ run: (store, args) => showPending(store, value(args, 'planId')!),
432
+ print: (reply) => {
433
+ const data = reply.data as ReturnType<typeof showPending>['data'] & {
434
+ decisions: Plan['decisions']
435
+ gaps: Plan['gaps']
436
+ tests: Plan['tests']
437
+ }
438
+ return {
439
+ facts: { sha256: short(data.sha256), decisions: data.decisions.length },
440
+ lines: [
441
+ ...data.decisions.map((decision) => [decision.id, decision.gapId, decision.kind, decision.value]),
442
+ ...data.gaps.map((gap) => [gap.id, gap.severity, gap.status, gap.question]),
443
+ ...data.tests.flatMap(testLines),
444
+ ],
445
+ }
446
+ },
447
+ },
448
+ 'plan revise': {
449
+ positionals: ['planId'],
450
+ flags: { 'expect-sha': 'value' },
451
+ write: true,
452
+ stdin: true,
453
+ run: (store, args, { actor, stdin }) =>
454
+ revisePlan(store, actor, {
455
+ planId: value(args, 'planId')!,
456
+ delta: stdin(),
457
+ expectSha256: value(args, 'expect-sha'),
458
+ }),
459
+ print: (reply) => ({ facts: { sha256: short(reply.data.sha256) } }),
460
+ },
461
+ 'plan edit': {
462
+ positionals: ['reference'],
463
+ flags: { patch: 'switch', 'expect-sha': 'value' },
464
+ write: true,
465
+ stdin: true,
466
+ run: (store, args, { actor, stdin }) => {
467
+ const reference = value(args, 'reference')!
468
+ const expectSha256 = value(args, 'expect-sha')
469
+ return args.patch
470
+ ? patchPlan(store, actor, { reference, patch: stdin(), expectSha256 })
471
+ : editPlan(store, actor, { reference, candidate: stdin(), expectSha256 })
472
+ },
473
+ print: (reply) => ({ facts: { sha256: short(reply.data.sha256), edit: reply.data.edit } }),
474
+ },
475
+ 'plan adopt-gaps': {
476
+ positionals: ['reference'],
477
+ flags: { run: 'value', gaps: 'list', 'expect-sha': 'value' },
478
+ required: ['run'],
479
+ write: true,
480
+ run: (store, args, { actor }) =>
481
+ adoptGaps(store, actor, {
482
+ reference: value(args, 'reference')!,
483
+ runId: value(args, 'run')!,
484
+ gapIds: list(args, 'gaps'),
485
+ expectSha256: value(args, 'expect-sha'),
486
+ }),
487
+ print: (reply) => ({
488
+ facts: { sha256: short(reply.data.sha256), edit: reply.data.edit, adopted: reply.data.adopted },
489
+ }),
490
+ },
491
+ 'plan approve': verdictCommand('reference', (store, actor, reference, rationale, expectSha256) =>
492
+ approvePlan(store, actor, { reference, rationale, expectSha256 })
493
+ ),
494
+ 'plan reject': verdictCommand('reference', (store, actor, reference, rationale, expectSha256) =>
495
+ rejectPlan(store, actor, { reference, rationale, expectSha256 })
496
+ ),
497
+ 'run gate': {
498
+ positionals: ['reference'],
499
+ flags: { tests: 'list', build: 'value', partial: 'value', 'retest-of': 'value' },
500
+ required: ['build'],
501
+ run: (store, args) =>
502
+ gate(store, {
503
+ reference: value(args, 'reference')!,
504
+ tests: list(args, 'tests'),
505
+ build: value(args, 'build')!,
506
+ partial: value(args, 'partial'),
507
+ retestOf: value(args, 'retest-of') ?? null,
508
+ }).reply,
509
+ print: (reply) => ({
510
+ facts: { sha256: short(reply.data.sha256), tests: reply.data.tests },
511
+ lines: leftOutLines(reply),
512
+ }),
513
+ },
514
+ 'run begin': {
515
+ positionals: ['runId', 'reference'],
516
+ flags: {
517
+ tests: 'list',
518
+ build: 'value',
519
+ partial: 'value',
520
+ 'on-new-blocker': 'value',
521
+ timeout: 'value',
522
+ 'retest-of': 'value',
523
+ },
524
+ required: ['build'],
525
+ write: true,
526
+ run: (store, args, { actor }) =>
527
+ beginRun(store, actor, {
528
+ runId: value(args, 'runId')!,
529
+ reference: value(args, 'reference')!,
530
+ tests: list(args, 'tests'),
531
+ build: value(args, 'build')!,
532
+ partial: value(args, 'partial'),
533
+ onNewBlocker: value(args, 'on-new-blocker'),
534
+ timeoutSeconds: integer(args, 'timeout'),
535
+ retestOf: value(args, 'retest-of') ?? null,
536
+ }),
537
+ print: (reply) => ({
538
+ facts: {
539
+ tests: reply.data.tests,
540
+ startedAt: reply.data.startedAt,
541
+ browserSession: reply.data.browserSession,
542
+ deadlineSeconds: reply.data.deadlineSeconds,
543
+ evidenceDirectory: reply.data.evidenceDirectory,
544
+ },
545
+ lines: leftOutLines(reply),
546
+ }),
547
+ },
548
+ 'run check': {
549
+ positionals: ['runId', 'testId', 'assertionId'],
550
+ flags: { result: 'value', observed: 'value', reason: 'value', evidence: 'repeat' },
551
+ required: ['result'],
552
+ write: true,
553
+ run: (store, args, context) =>
554
+ checkAssertion(store, context.actor, {
555
+ runId: value(args, 'runId')!,
556
+ testId: value(args, 'testId')!,
557
+ assertionId: value(args, 'assertionId')!,
558
+ result: value(args, 'result')!,
559
+ observed: value(args, 'observed'),
560
+ reason: value(args, 'reason'),
561
+ evidence: evidence(args, context),
562
+ }),
563
+ print: (reply) => ({
564
+ facts: { [`${reply.data.testId}.${reply.data.assertionId}`]: reply.data.result },
565
+ lines: (reply.data.evidenceIds as string[]).map((id) => [id, 'evidence']),
566
+ }),
567
+ },
568
+ 'run close': {
569
+ positionals: ['runId', 'testId'],
570
+ flags: { outcome: 'value', reason: 'value', evidence: 'repeat' },
571
+ write: true,
572
+ run: (store, args, context) =>
573
+ closeTest(store, context.actor, {
574
+ runId: value(args, 'runId')!,
575
+ testId: value(args, 'testId')!,
576
+ outcome: value(args, 'outcome'),
577
+ reason: value(args, 'reason'),
578
+ evidence: evidence(args, context),
579
+ }),
580
+ print: (reply) => ({
581
+ facts: { [String(reply.data.testId)]: reply.data.outcome },
582
+ lines: reply.data.reason ? [['reason', reply.data.reason]] : [],
583
+ }),
584
+ },
585
+ 'run finding': {
586
+ positionals: ['runId'],
587
+ write: true,
588
+ stdin: true,
589
+ run: (store, args, { actor, stdin }) =>
590
+ recordFinding(store, actor, { runId: value(args, 'runId')!, finding: stdin() }),
591
+ print: (reply) => ({ facts: { finding: reply.data.findingId, tests: reply.data.testIds } }),
592
+ },
593
+ 'run gap': {
594
+ positionals: ['runId'],
595
+ write: true,
596
+ stdin: true,
597
+ run: (store, args, { actor, stdin }) => recordGap(store, actor, { runId: value(args, 'runId')!, gap: stdin() }),
598
+ print: (reply) => ({ facts: { gap: reply.data.gapId } }),
599
+ },
600
+ 'run finish': {
601
+ positionals: ['runId'],
602
+ flags: { completion: 'value', cleanup: 'value', 'cleanup-reason': 'value' },
603
+ required: ['completion', 'cleanup', 'cleanup-reason'],
604
+ write: true,
605
+ run: (store, args, { actor }) =>
606
+ finishRun(store, actor, {
607
+ runId: value(args, 'runId')!,
608
+ completion: value(args, 'completion')!,
609
+ cleanup: value(args, 'cleanup')!,
610
+ cleanupReason: value(args, 'cleanup-reason')!,
611
+ }),
612
+ print: (reply) => ({
613
+ facts: { coverage: reply.data.coverage, sha256: short(reply.data.sha256) },
614
+ lines: Object.entries(reply.data.counts as Record<string, number>),
615
+ }),
616
+ },
617
+ 'run resume': {
618
+ positionals: ['runId'],
619
+ flags: { error: 'list', reason: 'value' },
620
+ write: true,
621
+ run: (store, args, { actor }) =>
622
+ resumeRun(store, actor, {
623
+ runId: value(args, 'runId')!,
624
+ error: list(args, 'error'),
625
+ reason: value(args, 'reason'),
626
+ }),
627
+ print: (reply) => {
628
+ const data = reply.data as {
629
+ attempt: number
630
+ browserSession: string
631
+ evidenceDirectory: string
632
+ closed: string[]
633
+ tests: Array<{ testId: string; warning: string; assertions: Plan['tests'][number]['assertions'] }>
634
+ }
635
+ return {
636
+ facts: {
637
+ attempt: data.attempt,
638
+ browserSession: data.browserSession,
639
+ evidenceDirectory: data.evidenceDirectory,
640
+ },
641
+ lines: [
642
+ ...data.closed.map((id) => [id, 'error']),
643
+ ...data.tests.flatMap((test) => [
644
+ [test.testId, 'unclosed', test.warning],
645
+ ...test.assertions.map((assertion) => [
646
+ `${test.testId}.${assertion.id}`,
647
+ assertion.checkKind,
648
+ assertion.check,
649
+ ]),
650
+ ]),
651
+ ],
652
+ }
653
+ },
654
+ },
655
+ 'run show': {
656
+ positionals: ['runId'],
657
+ flags: { tests: 'list' },
658
+ run: (store, args) => {
659
+ const detail = runDetail(store, value(args, 'runId')!)
660
+ const selected = list(args, 'tests')
661
+ for (const id of selected ?? [])
662
+ requireCondition(
663
+ detail.tests.some((test) => test.testId === id),
664
+ `Test ${id} is not selected in run ${detail.runId}`
665
+ )
666
+ const tests = detail.tests.filter((test) => !selected || selected.includes(test.testId))
667
+ return completed(detail.runId, { ...detail, tests })
668
+ },
669
+ print: (reply) => {
670
+ const detail = reply.data as ReturnType<typeof runDetail>
671
+ return {
672
+ facts: {
673
+ state: detail.state,
674
+ plan: `${detail.planId}@${detail.revision}`,
675
+ build: detail.build,
676
+ completion: detail.result?.completion,
677
+ coverage: detail.result?.coverage.status,
678
+ acceptance: detail.result?.acceptance,
679
+ },
680
+ lines: [
681
+ ...detail.tests.flatMap((test) => [
682
+ [test.testId, test.outcome ?? 'open', test.title, ...(test.reason ? [test.reason] : [])],
683
+ ...test.assertions.map((assertion) => [
684
+ `${test.testId}.${assertion.assertionId}`,
685
+ assertion.result ?? 'unchecked',
686
+ assertion.observed ?? assertion.check,
687
+ ...(assertion.reason ? [assertion.reason] : []),
688
+ ]),
689
+ ]),
690
+ ...detail.findings.map((finding) => [
691
+ finding.id,
692
+ finding.type,
693
+ finding.severity,
694
+ finding.assertionIds,
695
+ finding.title,
696
+ ]),
697
+ ...detail.newGaps.map((gap) => [gap.id, gap.severity, gap.status, gap.question]),
698
+ ],
699
+ }
700
+ },
701
+ },
702
+ 'run list': {
703
+ positionals: [],
704
+ flags: { plan: 'value', build: 'value' },
705
+ run: (store, args) =>
706
+ completed('runs', { runs: runList(store, { planId: value(args, 'plan'), build: value(args, 'build') }) }),
707
+ print: (reply) => ({
708
+ facts: { count: (reply.data.runs as unknown[]).length },
709
+ lines: (reply.data.runs as ReturnType<typeof runList>).map((run) => [
710
+ run.runId,
711
+ `${run.planId}@${run.revision}`,
712
+ run.build,
713
+ run.state,
714
+ run.completion,
715
+ run.coverage,
716
+ run.acceptance,
717
+ ]),
718
+ }),
719
+ },
720
+ 'result accept': verdictCommand('runId', (store, actor, runId, rationale, expectSha256) =>
721
+ acceptResult(store, actor, { runId, rationale, expectSha256 })
722
+ ),
723
+ 'result reject': verdictCommand('runId', (store, actor, runId, rationale, expectSha256) =>
724
+ rejectResult(store, actor, { runId, rationale, expectSha256 })
725
+ ),
726
+ 'evidence get': {
727
+ positionals: ['evidenceId'],
728
+ flags: { out: 'value' },
729
+ required: ['out'],
730
+ run: (store, args) => {
731
+ const evidence = evidenceBytes(store, value(args, 'evidenceId')!)
732
+ const out = writeOutput(store, value(args, 'out')!, evidence.bytes)
733
+ const { bytes, ...data } = evidence
734
+ return completed(evidence.id, { ...data, out })
735
+ },
736
+ print: (reply) => ({ facts: { out: reply.data.out, size: reply.data.size, mediaType: reply.data.mediaType } }),
737
+ },
738
+ 'export plan': exportCommand('reference', exportPlan),
739
+ 'export run': exportCommand('runId', exportRun),
740
+ log: {
741
+ positionals: [],
742
+ flags: { subject: 'value', limit: 'value' },
743
+ run: (store, args) => {
744
+ const subject = value(args, 'subject')
745
+ requireCondition(
746
+ subject === undefined || subject === 'inbox' || /^(target|plan|run):./.test(subject),
747
+ `Invalid --subject: ${subject}. Use target:<targetId>, plan:<planId>, run:<runId>, or inbox.`
748
+ )
749
+ return completed('log', {
750
+ operations: operationLog(store, { subject, limit: integer(args, 'limit') }),
751
+ })
752
+ },
753
+ print: (reply) => ({
754
+ lines: (reply.data.operations as ReturnType<typeof operationLog>).map((operation) => [
755
+ operation.id,
756
+ operation.at,
757
+ operation.actor,
758
+ operation.command,
759
+ operation.status,
760
+ operation.subjects,
761
+ ]),
762
+ }),
763
+ },
764
+ query: {
765
+ positionals: ['sql'],
766
+ readOnly: true,
767
+ run: (store, args) => {
768
+ const statement = store.db.prepare(value(args, 'sql')!)
769
+ const columns = statement.columns().map((column) => column.name)
770
+ const rows = statement.all().map((row) =>
771
+ Object.fromEntries(
772
+ columns.map((column) => {
773
+ const field = row[column]
774
+ return [column, field instanceof Uint8Array ? `<blob ${field.length} bytes>` : field]
775
+ })
776
+ )
777
+ )
778
+ return completed('query', { columns, rows })
779
+ },
780
+ print: (reply) => {
781
+ const columns = reply.data.columns as string[]
782
+ const rows = reply.data.rows as Array<Record<string, unknown>>
783
+ return {
784
+ facts: { rows: rows.length },
785
+ lines: [columns, ...rows.map((row) => columns.map((name) => row[name]))],
786
+ }
787
+ },
788
+ },
789
+ }
790
+
791
+ function exportCommand(positional: string, render: (store: Store, subject: string, format: string) => string): Command {
792
+ return {
793
+ positionals: [positional],
794
+ flags: { format: 'value', out: 'value' },
795
+ required: ['format'],
796
+ run: (store, args) => {
797
+ const subject = value(args, positional)!
798
+ const format = value(args, 'format')!
799
+ const content = render(store, subject, format)
800
+ const out = value(args, 'out')
801
+ if (out === undefined) return completed(subject, { format, content })
802
+ return completed(subject, { format, out: writeOutput(store, out, content) })
803
+ },
804
+ print: (reply) => ({ facts: { out: reply.data.out } }),
805
+ }
806
+ }
807
+
808
+ function verdictCommand(
809
+ positional: string,
810
+ decide: (
811
+ store: Store,
812
+ actor: string | undefined,
813
+ subject: string,
814
+ rationale: string,
815
+ expectSha256: string | undefined
816
+ ) => Reply
817
+ ): Command {
818
+ return {
819
+ positionals: [positional],
820
+ flags: { rationale: 'value', 'expect-sha': 'value' },
821
+ required: ['rationale'],
822
+ write: true,
823
+ run: (store, args, { actor }) =>
824
+ decide(store, actor, value(args, positional)!, value(args, 'rationale')!, value(args, 'expect-sha')),
825
+ print: (reply) => ({ facts: { verdict: reply.data.verdict, sha256: short(reply.data.sha256) } }),
826
+ }
827
+ }
828
+
829
+ function showPlan(store: Store, args: Args) {
830
+ const { id, revision } = revisionReference(value(args, 'reference')!, 'plan reference')
831
+ const detail = planDetail(store, id, revision)
832
+ const subject = `${id}@${detail.revision}`
833
+ const tests = list(args, 'tests')
834
+ const fields = list(args, 'fields')
835
+ const gaps = requireChoice(value(args, 'gaps') ?? 'all', ['open', 'all'] as const, 'gaps filter (--gaps)')
836
+ if (!tests && !fields) {
837
+ const plan = {
838
+ ...detail.plan,
839
+ gaps: detail.plan.gaps.filter((gap) => gaps === 'all' || gap.status === 'open'),
840
+ }
841
+ return completed(subject, { ...detail, plan })
842
+ }
843
+ for (const test of tests ?? [])
844
+ requireCondition(
845
+ detail.plan.tests.some((item) => item.id === test),
846
+ `Unknown test in ${subject}: ${test}`
847
+ )
848
+ for (const field of fields ?? [])
849
+ requireCondition(testFields.includes(field), `Unknown field: ${field}. Use: ${testFields.join(', ')}`)
850
+ const picked = detail.plan.tests
851
+ .filter((test) => !tests || tests.includes(test.id))
852
+ .map((test) =>
853
+ Object.fromEntries([
854
+ ['id', test.id],
855
+ ...(fields ?? ['readiness', 'capability', 'title']).map((field) => [
856
+ field,
857
+ test[field as keyof typeof test],
858
+ ]),
859
+ ])
860
+ )
861
+ return completed(subject, {
862
+ planId: id,
863
+ revision: detail.revision,
864
+ sha256: detail.sha256,
865
+ fields: fields ?? ['readiness', 'capability', 'title'],
866
+ tests: picked,
867
+ })
868
+ }
869
+
870
+ function testLines(test: Plan['tests'][number]) {
871
+ return [
872
+ [test.id, test.readiness, test.capability, test.title],
873
+ [test.id, 'action', test.action],
874
+ ...test.preconditions.map((precondition) => [test.id, 'precondition', precondition]),
875
+ ...test.assertions.map((assertion) => [`${test.id}.${assertion.id}`, assertion.checkKind, assertion.check]),
876
+ ]
877
+ }
878
+
879
+ function printPlan(reply: Reply): Printed {
880
+ const facts = { sha256: short(reply.data.sha256) }
881
+ if (reply.data.fields) {
882
+ const fields = reply.data.fields as string[]
883
+ const tests = reply.data.tests as Array<Record<string, unknown>>
884
+ return {
885
+ facts,
886
+ lines: tests.flatMap((test) => [
887
+ [test.id, ...fields.filter((field) => field !== 'assertions').map((field) => test[field])],
888
+ ...(fields.includes('assertions')
889
+ ? (test.assertions as Plan['tests'][number]['assertions']).map((assertion) => [
890
+ `${test.id}.${assertion.id}`,
891
+ assertion.checkKind,
892
+ assertion.check,
893
+ ])
894
+ : []),
895
+ ]),
896
+ }
897
+ }
898
+ const detail = reply.data as ReturnType<typeof planDetail>
899
+ const { plan } = detail
900
+ const coverage = new Map(plan.coverage.map((row) => [row.requirementId, row.status]))
901
+ return {
902
+ facts: {
903
+ ...facts,
904
+ approval: detail.approval.state,
905
+ ...(detail.latestRevision !== detail.revision ? { latest: detail.latestRevision } : {}),
906
+ },
907
+ lines: [
908
+ ...plan.requirements.map((requirement) => [requirement.id, coverage.get(requirement.id), requirement.text]),
909
+ ...plan.tests.map((test) => [test.id, test.readiness, test.capability, test.title]),
910
+ ...plan.gaps.map((gap) => [gap.id, gap.severity, gap.status, gap.question]),
911
+ ...plan.decisions.map((decision) => [decision.id, decision.gapId, decision.kind, decision.value]),
912
+ ...detail.pendingDecisions.map((decision) => [
913
+ decision.id,
914
+ decision.gapId,
915
+ `pending ${decision.kind}`,
916
+ decision.value,
917
+ ]),
918
+ ],
919
+ }
920
+ }
921
+
922
+ function writeOutput(store: Store, out: string, content: string | Uint8Array) {
923
+ const path = resolve(store.workspace, out)
924
+ mkdirSync(dirname(path), { recursive: true })
925
+ try {
926
+ writeFileSync(path, content, { flag: 'wx' })
927
+ } catch (error) {
928
+ if (error instanceof Error && 'code' in error && error.code === 'EEXIST')
929
+ throw new Error(`Output file exists: ${out}. Choose a new path.`, { cause: error })
930
+ throw error
931
+ }
932
+ return out
933
+ }
934
+
935
+ function cell(field: unknown): string {
936
+ if (field === null || field === undefined) return '-'
937
+ if (Array.isArray(field)) return field.length ? field.map(cell).join(',') : '-'
938
+ if (typeof field === 'object') return JSON.stringify(field)
939
+ return String(field).replace(/\\/g, '\\\\').replace(/\t/g, '\\t').replace(/\r/g, '\\r').replace(/\n/g, '\\n')
940
+ }
941
+
942
+ function compact(reply: Reply, printed: Printed) {
943
+ const facts = Object.entries(printed.facts ?? {})
944
+ .filter(([, field]) => field !== undefined)
945
+ .map(([key, field]) => `${key}=${cell(field)}`)
946
+ return [
947
+ [reply.status, reply.subject, ...facts].join(' '),
948
+ ...(printed.lines ?? []).map((line) => line.map(cell).join('\t')),
949
+ ...reply.reasons.map((reason) => `reason: ${cell(reason)}`),
950
+ ].join('\n')
951
+ }
952
+
953
+ function format(reply: Reply, json: boolean, print?: Command['print']) {
954
+ if (json)
955
+ return JSON.stringify({ status: reply.status, subject: reply.subject, reasons: reply.reasons, ...reply.data })
956
+ return compact(reply, print ? print(reply) : {})
957
+ }
958
+
959
+ const globalFlags: Record<string, FlagKind> = { workspace: 'value', json: 'switch' }
960
+
961
+ function commandName(argv: string[]) {
962
+ const words = argv.slice(0, 2).filter((word) => !word.startsWith('--'))
963
+ if (words.length === 2 && commands[words.join(' ')]) return words.join(' ')
964
+ if (commands[words[0]]) return words[0]
965
+ throw new Error(`Unknown command: ${words.join(' ') || '(none)'}. Run checkmate --help.`)
966
+ }
967
+
968
+ function parse(name: string, tokens: string[]) {
969
+ const command = commands[name]
970
+ const flags: Record<string, FlagKind> = {
971
+ ...globalFlags,
972
+ ...command.flags,
973
+ ...(command.write ? { actor: 'value' as const } : {}),
974
+ }
975
+ const args: Args = {}
976
+ const positionals: string[] = []
977
+ for (let i = 0; i < tokens.length; i++) {
978
+ const token = tokens[i]
979
+ if (!token.startsWith('--')) {
980
+ positionals.push(token)
981
+ continue
982
+ }
983
+ const flag = token.slice(2)
984
+ const kind = flags[flag]
985
+ if (!kind) throw new Error(`Unknown option ${token} for ${name}. ${usage(name)}`)
986
+ if (kind === 'switch') {
987
+ args[flag] = true
988
+ continue
989
+ }
990
+ const next = tokens[++i]
991
+ if (next === undefined || next.startsWith('--')) throw new Error(`Missing value for ${token}`)
992
+ if (kind === 'value') {
993
+ if (flag in args) throw new Error(`Option ${token} is given twice`)
994
+ args[flag] = next
995
+ } else if (kind === 'list') {
996
+ if (flag in args) throw new Error(`Option ${token} is given twice`)
997
+ args[flag] = next
998
+ .split(',')
999
+ .map((item) => item.trim())
1000
+ .filter(Boolean)
1001
+ } else args[flag] = [...((args[flag] as string[] | undefined) ?? []), next]
1002
+ }
1003
+ if (positionals.length !== command.positionals.length) throw new Error(usage(name))
1004
+ for (const flag of command.required ?? []) if (!(flag in args)) throw new Error(`Missing --${flag}. ${usage(name)}`)
1005
+ command.positionals.forEach((positional, i) => (args[positional] = positionals[i]))
1006
+ return args
1007
+ }
1008
+
1009
+ function usage(name: string) {
1010
+ const command = commands[name]
1011
+ const flags = Object.entries(command.flags ?? {}).map(([flag, kind]) => {
1012
+ const text = kind === 'switch' ? `--${flag}` : `--${flag} <${kind === 'list' ? 'a,b' : 'value'}>`
1013
+ return command.required?.includes(flag) ? text : `[${text}]`
1014
+ })
1015
+ return [
1016
+ 'Usage: checkmate',
1017
+ name,
1018
+ ...command.positionals.map((positional) => `<${positional}>`),
1019
+ ...flags,
1020
+ ...(command.write ? ['[--actor <name>]'] : []),
1021
+ ...(command.stdin ? ['< payload.json'] : []),
1022
+ ].join(' ')
1023
+ }
1024
+
1025
+ const uiUsage = 'Usage: checkmate ui [--port <value>]'
1026
+
1027
+ function help() {
1028
+ return [
1029
+ ...Object.keys(commands).map((name) => usage(name).replace('Usage: ', '')),
1030
+ uiUsage.replace('Usage: ', ''),
1031
+ 'Options for every command: --workspace <path>, --json',
1032
+ ].join('\n')
1033
+ }
1034
+
1035
+ function version() {
1036
+ return JSON.parse(readFileSync(new URL('../.claude-plugin/plugin.json', import.meta.url), 'utf8')).version as string
1037
+ }
1038
+
1039
+ function readJson(text: string) {
1040
+ requireCondition(text.trim().length > 0, 'Missing JSON payload on standard input')
1041
+ try {
1042
+ return JSON.parse(text) as unknown
1043
+ } catch (error) {
1044
+ throw new Error(`Standard input is not valid JSON: ${(error as Error).message}`, { cause: error })
1045
+ }
1046
+ }
1047
+
1048
+ export function run(argv: string[], io: Io): Outcome {
1049
+ if (argv[0] === '--version') return { code: 0, output: version() }
1050
+ if (!argv.length || argv[0] === '--help') return { code: 0, output: help() }
1051
+ const json = argv.includes('--json')
1052
+ let name = 'checkmate'
1053
+ let args: Args
1054
+ try {
1055
+ name = commandName(argv)
1056
+ args = parse(name, argv.slice(name.split(' ').length))
1057
+ } catch (error) {
1058
+ const reply: Reply = { status: 'invalid_request', subject: name, reasons: [(error as Error).message], data: {} }
1059
+ return { code: 1, output: format(reply, json) }
1060
+ }
1061
+ const command = commands[name]
1062
+ const subject = command.positionals.length && name !== 'query' ? String(args[command.positionals[0]]) : name
1063
+ const workspace = resolve(io.cwd, value(args, 'workspace') ?? '.')
1064
+ let store: Store | undefined
1065
+ try {
1066
+ store = command.readOnly ? openReadOnlyStore(workspace) : openStore(workspace)
1067
+ const actor = command.write ? (value(args, 'actor') ?? defaultActor(workspace, io.env)) : undefined
1068
+ const reply = command.run(store, args, { actor, stdin: () => readJson(io.stdin()), text: io.stdin })
1069
+ const code = reply.status === 'completed' || reply.status === 'needs_decision' ? 0 : 1
1070
+ if (!json && typeof reply.data.content === 'string')
1071
+ return { code, output: reply.data.content.replace(/\n$/, '') }
1072
+ return { code, output: format(reply, json, command.print) }
1073
+ } catch (error) {
1074
+ const reply: Reply = { status: 'rejected', subject, reasons: [(error as Error).message], data: {} }
1075
+ return { code: 1, output: format(reply, json) }
1076
+ } finally {
1077
+ if (store) closeStore(store)
1078
+ }
1079
+ }
1080
+
1081
+ function uiOptions(tokens: string[], cwd: string) {
1082
+ const values: Record<string, string> = {}
1083
+ for (let i = 0; i < tokens.length; i++) {
1084
+ const token = tokens[i]
1085
+ if (token === '--json') continue
1086
+ const flag = token.startsWith('--') ? token.slice(2) : ''
1087
+ if (!['workspace', 'port'].includes(flag)) throw new Error(`Unknown argument ${token} for ui. ${uiUsage}`)
1088
+ const next = tokens[++i]
1089
+ if (next === undefined || next.startsWith('--')) throw new Error(`Missing value for ${token}`)
1090
+ if (flag in values) throw new Error(`Option ${token} is given twice`)
1091
+ values[flag] = next
1092
+ }
1093
+ const port = values.port === undefined ? undefined : Number(values.port)
1094
+ requireCondition(
1095
+ port === undefined || (/^[0-9]+$/.test(values.port) && port <= 65535),
1096
+ `Invalid --port: ${values.port}. Use 0 to 65535.`
1097
+ )
1098
+ return { workspace: resolve(cwd, values.workspace ?? '.'), port }
1099
+ }
1100
+
1101
+ export async function serveUi(tokens: string[]) {
1102
+ const json = tokens.includes('--json')
1103
+ let options: ReturnType<typeof uiOptions>
1104
+ try {
1105
+ options = uiOptions(tokens, process.cwd())
1106
+ } catch (error) {
1107
+ const reply: Reply = { status: 'invalid_request', subject: 'ui', reasons: [(error as Error).message], data: {} }
1108
+ process.stdout.write(format(reply, json) + '\n')
1109
+ process.exitCode = 1
1110
+ return
1111
+ }
1112
+ const ui = await startUi(options)
1113
+ process.stdout.write(
1114
+ format(completed('ui', { url: ui.url }), json, (reply) => ({ facts: { url: reply.data.url } })) + '\n'
1115
+ )
1116
+ const stop = async () => {
1117
+ await ui.close()
1118
+ process.exit(0)
1119
+ }
1120
+ process.once('SIGINT', stop)
1121
+ process.once('SIGTERM', stop)
1122
+ }
1123
+
1124
+ export async function main(argv: string[]) {
1125
+ if (argv[0] === 'ui') return serveUi(argv.slice(1))
1126
+ const outcome = run(argv, {
1127
+ cwd: process.cwd(),
1128
+ env: process.env,
1129
+ stdin: () => readFileSync(0, 'utf8'),
1130
+ })
1131
+ process.stdout.write(outcome.output + '\n')
1132
+ process.exitCode = outcome.code
1133
+ }