@intentius/chant 0.91.0 → 0.93.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 (344) hide show
  1. package/dist/cli/commands/build.d.ts.map +1 -1
  2. package/dist/cli/commands/lint.d.ts.map +1 -1
  3. package/dist/cli/handlers/graph.d.ts.map +1 -1
  4. package/dist/cli/handlers/operator.d.ts +6 -0
  5. package/dist/cli/handlers/operator.d.ts.map +1 -1
  6. package/dist/cli/handlers/run.d.ts.map +1 -1
  7. package/dist/cli/main.d.ts.map +1 -1
  8. package/dist/cli/mcp/workspace-tools.d.ts +2 -1
  9. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  10. package/dist/cli/plugins.d.ts.map +1 -1
  11. package/dist/cli/registry.d.ts +12 -2
  12. package/dist/cli/registry.d.ts.map +1 -1
  13. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  14. package/dist/config.d.ts +22 -0
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/lexicon-module.d.ts +22 -0
  17. package/dist/lexicon-module.d.ts.map +1 -1
  18. package/dist/lifecycle/converge-ledger.d.ts +19 -0
  19. package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
  20. package/dist/lifecycle/run-ledger.d.ts +2 -0
  21. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  22. package/dist/lifecycle/symptoms.d.ts +51 -0
  23. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  24. package/dist/lint/rules/index.d.ts +4 -2
  25. package/dist/lint/rules/index.d.ts.map +1 -1
  26. package/dist/lint/rules/op/index.d.ts +14 -3
  27. package/dist/lint/rules/op/index.d.ts.map +1 -1
  28. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -1
  29. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts +29 -0
  30. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts.map +1 -0
  31. package/dist/lint/rules/sys001-no-literal-key.d.ts +3 -0
  32. package/dist/lint/rules/sys001-no-literal-key.d.ts.map +1 -0
  33. package/dist/op/__fixtures__/decide-stub-backend.d.ts +53 -0
  34. package/dist/op/__fixtures__/decide-stub-backend.d.ts.map +1 -0
  35. package/dist/op/activities/activity-contracts.d.ts +201 -0
  36. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  37. package/dist/op/activities/apply.d.ts.map +1 -1
  38. package/dist/op/activities/change-coverage.d.ts +44 -0
  39. package/dist/op/activities/change-coverage.d.ts.map +1 -0
  40. package/dist/op/activities/converge.d.ts +24 -1
  41. package/dist/op/activities/converge.d.ts.map +1 -1
  42. package/dist/op/activities/decide.d.ts +107 -0
  43. package/dist/op/activities/decide.d.ts.map +1 -0
  44. package/dist/op/activities/index.d.ts +10 -0
  45. package/dist/op/activities/index.d.ts.map +1 -1
  46. package/dist/op/activities/shell.d.ts +32 -0
  47. package/dist/op/activities/shell.d.ts.map +1 -1
  48. package/dist/op/activities/source-release.d.ts +86 -0
  49. package/dist/op/activities/source-release.d.ts.map +1 -0
  50. package/dist/op/activities/source-rollback.d.ts +100 -0
  51. package/dist/op/activities/source-rollback.d.ts.map +1 -0
  52. package/dist/op/activities/work-evidence.d.ts +42 -0
  53. package/dist/op/activities/work-evidence.d.ts.map +1 -0
  54. package/dist/op/activity-contract.d.ts +1 -0
  55. package/dist/op/activity-contract.d.ts.map +1 -1
  56. package/dist/op/activity-profiles.d.ts +16 -0
  57. package/dist/op/activity-profiles.d.ts.map +1 -1
  58. package/dist/op/builders.d.ts +84 -2
  59. package/dist/op/builders.d.ts.map +1 -1
  60. package/dist/op/composites/converge-op.d.ts +31 -4
  61. package/dist/op/composites/converge-op.d.ts.map +1 -1
  62. package/dist/op/decide-backend.d.ts +116 -0
  63. package/dist/op/decide-backend.d.ts.map +1 -0
  64. package/dist/op/decide-config.d.ts +120 -0
  65. package/dist/op/decide-config.d.ts.map +1 -0
  66. package/dist/op/decide-read-inputs.d.ts +40 -0
  67. package/dist/op/decide-read-inputs.d.ts.map +1 -0
  68. package/dist/op/gate-wait.d.ts +43 -0
  69. package/dist/op/gate-wait.d.ts.map +1 -0
  70. package/dist/op/gate.d.ts +5 -2
  71. package/dist/op/gate.d.ts.map +1 -1
  72. package/dist/op/index.d.ts +11 -4
  73. package/dist/op/index.d.ts.map +1 -1
  74. package/dist/op/local-executor.d.ts +29 -4
  75. package/dist/op/local-executor.d.ts.map +1 -1
  76. package/dist/op/local-output.d.ts +2 -0
  77. package/dist/op/local-output.d.ts.map +1 -1
  78. package/dist/op/op-ir.d.ts +2 -0
  79. package/dist/op/op-ir.d.ts.map +1 -1
  80. package/dist/op/op-verb-class.d.ts.map +1 -1
  81. package/dist/op/operator.d.ts +97 -1
  82. package/dist/op/operator.d.ts.map +1 -1
  83. package/dist/op/runtime.d.ts +37 -5
  84. package/dist/op/runtime.d.ts.map +1 -1
  85. package/dist/op/runtimes/local.d.ts.map +1 -1
  86. package/dist/op/source-archive.d.ts +63 -0
  87. package/dist/op/source-archive.d.ts.map +1 -0
  88. package/dist/op/steward-points.d.ts +136 -0
  89. package/dist/op/steward-points.d.ts.map +1 -0
  90. package/dist/op/steward-turn.d.ts +53 -0
  91. package/dist/op/steward-turn.d.ts.map +1 -0
  92. package/dist/op/steward.d.ts +22 -1
  93. package/dist/op/steward.d.ts.map +1 -1
  94. package/dist/op/types.d.ts +7 -0
  95. package/dist/op/types.d.ts.map +1 -1
  96. package/dist/op/work-lease-run.d.ts +1 -1
  97. package/dist/op/work-lease-run.d.ts.map +1 -1
  98. package/dist/workspace/__fixtures__/decide-workspace.d.ts +100 -0
  99. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -0
  100. package/dist/workspace/answers-ledger.d.ts +76 -0
  101. package/dist/workspace/answers-ledger.d.ts.map +1 -0
  102. package/dist/workspace/changes-cli.d.ts +16 -0
  103. package/dist/workspace/changes-cli.d.ts.map +1 -0
  104. package/dist/workspace/changes.d.ts +200 -0
  105. package/dist/workspace/changes.d.ts.map +1 -0
  106. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts +59 -0
  107. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts.map +1 -0
  108. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts +51 -0
  109. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts.map +1 -0
  110. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts +26 -0
  111. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts.map +1 -0
  112. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts +59 -0
  113. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts.map +1 -0
  114. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts +22 -0
  115. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts.map +1 -0
  116. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts +71 -0
  117. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -0
  118. package/dist/workspace/chant-migrations.d.ts +99 -0
  119. package/dist/workspace/chant-migrations.d.ts.map +1 -0
  120. package/dist/workspace/checks/boxes.d.ts +34 -1
  121. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  122. package/dist/workspace/checks/diagrams.d.ts +31 -0
  123. package/dist/workspace/checks/diagrams.d.ts.map +1 -0
  124. package/dist/workspace/checks/records.d.ts +7 -0
  125. package/dist/workspace/checks/records.d.ts.map +1 -1
  126. package/dist/workspace/checks.d.ts +3 -0
  127. package/dist/workspace/checks.d.ts.map +1 -1
  128. package/dist/workspace/compose-graph.d.ts +2 -0
  129. package/dist/workspace/compose-graph.d.ts.map +1 -1
  130. package/dist/workspace/conformance/index.d.ts.map +1 -1
  131. package/dist/workspace/decide.d.ts +49 -4
  132. package/dist/workspace/decide.d.ts.map +1 -1
  133. package/dist/workspace/declaration.d.ts +52 -0
  134. package/dist/workspace/declaration.d.ts.map +1 -1
  135. package/dist/workspace/declaration.schema.json +114 -1
  136. package/dist/workspace/declared-kinds.d.ts +20 -0
  137. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  138. package/dist/workspace/generated-files.d.ts +7 -0
  139. package/dist/workspace/generated-files.d.ts.map +1 -1
  140. package/dist/workspace/intent.d.ts.map +1 -1
  141. package/dist/workspace/lineage-check.d.ts +3 -0
  142. package/dist/workspace/lineage-check.d.ts.map +1 -1
  143. package/dist/workspace/lineage-upgrade.d.ts +6 -1
  144. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  145. package/dist/workspace/ls.d.ts +39 -1
  146. package/dist/workspace/ls.d.ts.map +1 -1
  147. package/dist/workspace/point-answer.schema.json +95 -0
  148. package/dist/workspace/points-cli.d.ts.map +1 -1
  149. package/dist/workspace/points.d.ts +1 -1
  150. package/dist/workspace/reason-codes.d.ts +14 -2
  151. package/dist/workspace/reason-codes.d.ts.map +1 -1
  152. package/dist/workspace/records-cli.d.ts +11 -1
  153. package/dist/workspace/records-cli.d.ts.map +1 -1
  154. package/dist/workspace/records-close.d.ts +1 -1
  155. package/dist/workspace/records-close.d.ts.map +1 -1
  156. package/dist/workspace/records-write.d.ts +11 -5
  157. package/dist/workspace/records-write.d.ts.map +1 -1
  158. package/dist/workspace/records.d.ts +34 -4
  159. package/dist/workspace/records.d.ts.map +1 -1
  160. package/dist/workspace/status-gates.d.ts +7 -2
  161. package/dist/workspace/status-gates.d.ts.map +1 -1
  162. package/dist/workspace/status-stewards.d.ts +59 -1
  163. package/dist/workspace/status-stewards.d.ts.map +1 -1
  164. package/dist/workspace/status.d.ts +16 -0
  165. package/dist/workspace/status.d.ts.map +1 -1
  166. package/dist/workspace/work-cli.d.ts +113 -2
  167. package/dist/workspace/work-cli.d.ts.map +1 -1
  168. package/dist/workspace/work-evidence.d.ts +74 -0
  169. package/dist/workspace/work-evidence.d.ts.map +1 -0
  170. package/dist/workspace/work.d.ts +43 -1
  171. package/dist/workspace/work.d.ts.map +1 -1
  172. package/package.json +1 -1
  173. package/src/cli/commands/audit.test.ts +9 -1
  174. package/src/cli/commands/build.ts +3 -2
  175. package/src/cli/commands/lint.ts +7 -1
  176. package/src/cli/handlers/graph.test.ts +20 -0
  177. package/src/cli/handlers/graph.ts +16 -2
  178. package/src/cli/handlers/operator.ts +34 -2
  179. package/src/cli/handlers/run.test.ts +154 -2
  180. package/src/cli/handlers/run.ts +121 -2
  181. package/src/cli/main.test.ts +10 -0
  182. package/src/cli/main.ts +19 -0
  183. package/src/cli/mcp/workspace-tools.test.ts +35 -19
  184. package/src/cli/mcp/workspace-tools.ts +62 -4
  185. package/src/cli/plugins.ts +11 -3
  186. package/src/cli/registry.ts +12 -2
  187. package/src/codegen/pinned-upgrade.ts +2 -1
  188. package/src/codegen/release-wiring.test.ts +49 -2
  189. package/src/components/capability-plugin-loader.ts +3 -3
  190. package/src/components/cli-support.ts +2 -2
  191. package/src/config.ts +11 -0
  192. package/src/lexicon-module.ts +56 -1
  193. package/src/lexicon-package.test.ts +96 -0
  194. package/src/lifecycle/converge-ledger.ts +20 -0
  195. package/src/lifecycle/run-ledger.ts +6 -0
  196. package/src/lifecycle/symptoms.ts +95 -0
  197. package/src/lint/rules/index.ts +6 -2
  198. package/src/lint/rules/op/index.ts +17 -4
  199. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +23 -1
  200. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +10 -6
  201. package/src/lint/rules/op/sys010-key-over-plain-http.test.ts +52 -0
  202. package/src/lint/rules/op/sys010-key-over-plain-http.ts +100 -0
  203. package/src/lint/rules/sys001-no-literal-key.test.ts +31 -0
  204. package/src/lint/rules/sys001-no-literal-key.ts +67 -0
  205. package/src/op/__fixtures__/decide-stub-backend.ts +143 -0
  206. package/src/op/activities/activity-contracts.ts +190 -1
  207. package/src/op/activities/apply.ts +7 -6
  208. package/src/op/activities/change-coverage.ts +60 -0
  209. package/src/op/activities/converge.test.ts +55 -1
  210. package/src/op/activities/converge.ts +208 -29
  211. package/src/op/activities/decide.live.test.ts +78 -0
  212. package/src/op/activities/decide.test.ts +300 -0
  213. package/src/op/activities/decide.ts +267 -0
  214. package/src/op/activities/index.ts +21 -0
  215. package/src/op/activities/shell.test.ts +91 -1
  216. package/src/op/activities/shell.ts +91 -2
  217. package/src/op/activities/source-release.test.ts +118 -0
  218. package/src/op/activities/source-release.ts +168 -0
  219. package/src/op/activities/source-rollback.test.ts +131 -0
  220. package/src/op/activities/source-rollback.ts +239 -0
  221. package/src/op/activities/work-evidence.ts +62 -0
  222. package/src/op/activity-contract-registry.test.ts +2 -0
  223. package/src/op/activity-contract-registry.ts +2 -2
  224. package/src/op/activity-contract.test.ts +12 -0
  225. package/src/op/activity-contract.ts +6 -1
  226. package/src/op/activity-profiles.ts +29 -0
  227. package/src/op/activity-registry.test.ts +1 -1
  228. package/src/op/activity-registry.ts +2 -2
  229. package/src/op/builders.ts +119 -5
  230. package/src/op/command-gate.test.ts +192 -0
  231. package/src/op/composites/converge-op.test.ts +27 -1
  232. package/src/op/composites/converge-op.ts +69 -15
  233. package/src/op/decide-backend.test.ts +44 -0
  234. package/src/op/decide-backend.ts +212 -0
  235. package/src/op/decide-config.test.ts +52 -0
  236. package/src/op/decide-config.ts +80 -0
  237. package/src/op/decide-read-inputs.ts +117 -0
  238. package/src/op/gate-approval.ts +2 -2
  239. package/src/op/gate-wait.ts +57 -0
  240. package/src/op/gate.ts +8 -3
  241. package/src/op/generate-pipeline.ts +2 -2
  242. package/src/op/index.ts +18 -3
  243. package/src/op/local-executor.ts +151 -15
  244. package/src/op/local-output.test.ts +33 -0
  245. package/src/op/local-output.ts +20 -2
  246. package/src/op/op-ir.test.ts +10 -0
  247. package/src/op/op-ir.ts +4 -0
  248. package/src/op/op-verb-class.ts +1 -0
  249. package/src/op/operator.ts +290 -11
  250. package/src/op/runtime.ts +31 -8
  251. package/src/op/runtimes/local.ts +3 -1
  252. package/src/op/source-archive.ts +161 -0
  253. package/src/op/steward-points.test.ts +313 -0
  254. package/src/op/steward-points.ts +194 -0
  255. package/src/op/steward-turn.ts +79 -0
  256. package/src/op/steward.test.ts +87 -3
  257. package/src/op/steward.ts +25 -1
  258. package/src/op/types.ts +7 -0
  259. package/src/op/work-lease-run.test.ts +20 -1
  260. package/src/op/work-lease-run.ts +2 -2
  261. package/src/workspace/__fixtures__/decide-workspace.ts +129 -0
  262. package/src/workspace/__fixtures__/studio-template-84c8b21.json +67 -0
  263. package/src/workspace/answers-ledger.ts +118 -0
  264. package/src/workspace/changes-cli.ts +56 -0
  265. package/src/workspace/changes.schema.json +472 -0
  266. package/src/workspace/changes.test.ts +315 -0
  267. package/src/workspace/changes.ts +512 -0
  268. package/src/workspace/chant-migrations/chud-lexicon-exit-fly-site.ts +234 -0
  269. package/src/workspace/chant-migrations/chud-lexicon-exit-live-names.ts +164 -0
  270. package/src/workspace/chant-migrations/chud-lexicon-exit-rollback.ts +223 -0
  271. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.ts +195 -0
  272. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-policy.ts +108 -0
  273. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +882 -0
  274. package/src/workspace/chant-migrations.e2e.test.ts +1089 -0
  275. package/src/workspace/chant-migrations.ts +164 -0
  276. package/src/workspace/check-contract.test.ts +4 -2
  277. package/src/workspace/check.schema.json +15 -5
  278. package/src/workspace/checks/boxes.test.ts +80 -1
  279. package/src/workspace/checks/boxes.ts +136 -1
  280. package/src/workspace/checks/diagrams.test.ts +134 -0
  281. package/src/workspace/checks/diagrams.ts +108 -0
  282. package/src/workspace/checks/records.ts +47 -0
  283. package/src/workspace/checks.ts +8 -1
  284. package/src/workspace/compose-graph.ts +2 -0
  285. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +10 -0
  286. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +26 -0
  287. package/src/workspace/conformance/index.ts +5 -0
  288. package/src/workspace/decide.test.ts +1 -1
  289. package/src/workspace/decide.ts +156 -23
  290. package/src/workspace/declaration.schema.json +114 -1
  291. package/src/workspace/declaration.test.ts +58 -0
  292. package/src/workspace/declaration.ts +104 -0
  293. package/src/workspace/declared-kinds.test.ts +5 -5
  294. package/src/workspace/declared-kinds.ts +46 -1
  295. package/src/workspace/generated-files.ts +14 -0
  296. package/src/workspace/graph-cli.ts +1 -1
  297. package/src/workspace/graph.schema.json +9 -1
  298. package/src/workspace/intent.schema.json +8 -2
  299. package/src/workspace/intent.ts +3 -10
  300. package/src/workspace/lineage-check.ts +16 -1
  301. package/src/workspace/lineage-upgrade-cli.ts +1 -1
  302. package/src/workspace/lineage-upgrade.test.ts +70 -0
  303. package/src/workspace/lineage-upgrade.ts +81 -10
  304. package/src/workspace/ls-contract.test.ts +57 -0
  305. package/src/workspace/ls.schema.json +46 -1
  306. package/src/workspace/ls.ts +62 -3
  307. package/src/workspace/points-cli.ts +14 -3
  308. package/src/workspace/points-write.schema.json +11 -1
  309. package/src/workspace/points.schema.json +33 -0
  310. package/src/workspace/points.test.ts +1 -1
  311. package/src/workspace/points.ts +1 -1
  312. package/src/workspace/read-contract.test.ts +1 -1
  313. package/src/workspace/reason-codes.test.ts +11 -1
  314. package/src/workspace/reason-codes.ts +18 -2
  315. package/src/workspace/records-amend.schema.json +2 -0
  316. package/src/workspace/records-cli.ts +13 -3
  317. package/src/workspace/records-close.schema.json +2 -0
  318. package/src/workspace/records-new.schema.json +2 -0
  319. package/src/workspace/records-review.schema.json +2 -0
  320. package/src/workspace/records-sessions-write.e2e.test.ts +77 -0
  321. package/src/workspace/records-sessions-write.test.ts +2 -34
  322. package/src/workspace/records-write.e2e.test.ts +108 -0
  323. package/src/workspace/records-write.test.ts +32 -41
  324. package/src/workspace/records-write.ts +33 -15
  325. package/src/workspace/records.schema.json +36 -2
  326. package/src/workspace/records.test.ts +48 -3
  327. package/src/workspace/records.ts +109 -3
  328. package/src/workspace/status-contract.test.ts +13 -8
  329. package/src/workspace/status-gates.ts +9 -4
  330. package/src/workspace/status-stewards.ts +108 -4
  331. package/src/workspace/status.schema.json +111 -7
  332. package/src/workspace/status.ts +36 -1
  333. package/src/workspace/work-acceptance.test.ts +274 -0
  334. package/src/workspace/{work-cli.test.ts → work-cli.e2e.test.ts} +1 -1
  335. package/src/workspace/work-cli.ts +193 -10
  336. package/src/workspace/work-evidence.schema.json +212 -0
  337. package/src/workspace/work-evidence.ts +169 -0
  338. package/src/workspace/work-history.schema.json +349 -0
  339. package/src/workspace/work-history.test.ts +156 -0
  340. package/src/workspace/work.test.ts +3 -2
  341. package/src/workspace/work.ts +100 -1
  342. /package/src/cli/{discovery-skip.test.ts → discovery-skip.e2e.test.ts} +0 -0
  343. /package/src/workspace/conformance/{conformance.test.ts → conformance.e2e.test.ts} +0 -0
  344. /package/src/workspace/{which-chant.test.ts → which-chant.e2e.test.ts} +0 -0
@@ -0,0 +1,315 @@
1
+ /**
2
+ * The forward coverage check (#2773): `chant workspace check --changes`, on a
3
+ * workspace built in a throwaway git repository.
4
+ *
5
+ * - c0 declares members app and docs, a changes block that ignores lockfiles
6
+ * and app/dist, and these records:
7
+ * - dec-001, decided: constrains `path:app/src`, puts `app/src/vendor` out of scope;
8
+ * - dec-002, proposed: constrains `path:docs`, so it covers nothing yet;
9
+ * - W-001, in progress, implements dec-001: constrains `path:app/lib`, puts
10
+ * `app/lib/legacy.mjs` out of scope;
11
+ * - W-002, done: constrains `path:tools`, so it covers nothing any more.
12
+ * - c1 changes a covered file, a file only a proposed decision names, a file
13
+ * only a done item names, a lockfile, a file under dist, a file dec-001
14
+ * puts out of scope, a file W-001 puts out of scope, and adds the work item
15
+ * W-003, a record file.
16
+ */
17
+
18
+ import { readFileSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ import Ajv from "ajv";
21
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
22
+ import { cleanScratch, contract, git, REPO, repo, writeFiles } from "./__fixtures__/contract-repo";
23
+ import { changeFindingSource, changeFindingTriageInputs, checkChanges, CHANGES_FINDING_CODES, workBranchChanges, type ChangesDocument } from "./changes";
24
+ import { formatChanges } from "./changes-cli";
25
+ import { changeCoverage } from "../op/activities/change-coverage";
26
+ import changesSchema from "./changes.schema.json";
27
+ import { parseFrontMatter } from "./records";
28
+ import { REASONS } from "./reason-codes";
29
+
30
+ const REF = join(REPO, "reference-workspace");
31
+ const DECISION = (() => {
32
+ const fm = parseFrontMatter(readFileSync(join(REF, "decisions", "ref-001-how-the-app-is-deployed.md"), "utf-8"));
33
+ if (!fm.ok) throw new Error(fm.message);
34
+ return fm.value;
35
+ })();
36
+
37
+ function decision(id: string, fields: { state?: string; constrains: string[]; out_of_scope?: string[] }): string {
38
+ const proposed = fields.state === "proposed";
39
+ const data = {
40
+ ...DECISION,
41
+ id,
42
+ title: `Decision ${id}`,
43
+ state: fields.state ?? "decided",
44
+ supersedes: [],
45
+ evidence: [],
46
+ constrains: fields.constrains,
47
+ ...(fields.out_of_scope ? { out_of_scope: fields.out_of_scope } : {}),
48
+ ...(proposed ? { choice: null, decided_by: null, decided_on: null } : {}),
49
+ };
50
+ return `---\n${JSON.stringify(data, null, 2)}\n---\n\n# ${id}\n`;
51
+ }
52
+
53
+ function work(id: string, fields: Record<string, unknown>): string {
54
+ const data = { schema: 1, id, title: `Work ${id}`, state: "open", implements: [], needs: [], constrains: ["path:app/lib"], evidence: [], opened_on: "2026-09-25", source: { kind: "workspace", member: "app" }, supersedes: [], ...fields };
55
+ return `---\n${JSON.stringify(data, null, 2)}\n---\n\n# ${id}\n`;
56
+ }
57
+
58
+ // The work schema is draft-07, like the decision schema.
59
+ const workSchema = new Ajv({ strict: false, allErrors: true }).compile(JSON.parse(readFileSync(join(REF, "work", "work.schema.json"), "utf-8")) as object);
60
+ const changes = contract(changesSchema);
61
+ type Result = Exclude<ChangesDocument, { error: unknown }>;
62
+
63
+ let root: string;
64
+ const sha: Record<string, string> = {};
65
+
66
+ function commit(message: string): string {
67
+ git(root, "add", "-A");
68
+ git(root, "commit", "-q", "-m", message);
69
+ return git(root, "rev-parse", "HEAD");
70
+ }
71
+
72
+ function declaration(changesBlock: Record<string, unknown>): string {
73
+ return JSON.stringify(
74
+ {
75
+ name: "studio",
76
+ schema: 1,
77
+ members: [
78
+ { name: "app", dir: "app", kind: "other", because: "a plain Node server" },
79
+ { name: "docs", dir: "docs", kind: "other", because: "prose" },
80
+ ],
81
+ records: [{ kind: "decisions/decision.kind.mjs" }, { kind: "work/work.kind.mjs" }],
82
+ changes: changesBlock,
83
+ },
84
+ null,
85
+ 2,
86
+ );
87
+ }
88
+
89
+ beforeAll(() => {
90
+ root = repo({
91
+ "chant.workspace.json": declaration({ ignore: ["**/package-lock.json", "app/dist/**"] }),
92
+ "decisions/decision.kind.mjs": readFileSync(join(REF, "decisions", "decision.kind.mjs"), "utf-8"),
93
+ "decisions/decision.schema.json": readFileSync(join(REF, "decisions", "decision.schema.json"), "utf-8"),
94
+ "decisions/dec-001-server.md": decision("dec-001", { constrains: ["path:app/src"], out_of_scope: ["app/src/vendor"] }),
95
+ "decisions/dec-002-docs.md": decision("dec-002", { state: "proposed", constrains: ["path:docs"] }),
96
+ "work/work.kind.mjs": readFileSync(join(REF, "work", "work.kind.mjs"), "utf-8"),
97
+ "work/work.schema.json": readFileSync(join(REF, "work", "work.schema.json"), "utf-8"),
98
+ "work/W-001-lib.md": work("W-001", { state: "in-progress", implements: ["dec-001"], out_of_scope: ["app/lib/legacy.mjs"] }),
99
+ "work/W-002-tools.md": work("W-002", { state: "done", closed_on: "2026-09-25", constrains: ["path:tools"], evidence: [{ title: "The run", url: "https://example.com/run" }] }),
100
+ "app/src/server.mjs": "export const port = 8080;\n",
101
+ "app/src/vendor/lib.mjs": "export const v = 1;\n",
102
+ "app/lib/a.mjs": "export const a = 1;\n",
103
+ "app/lib/legacy.mjs": "export const legacy = 1;\n",
104
+ "app/package-lock.json": "{}\n",
105
+ "app/dist/out.js": "1;\n",
106
+ "docs/readme.md": "# Docs\n",
107
+ "tools/t.mjs": "export const t = 1;\n",
108
+ });
109
+ sha.c0 = commit("the workspace");
110
+ writeFiles(root, {
111
+ "app/src/server.mjs": "export const port = 9090;\n",
112
+ "app/src/vendor/lib.mjs": "export const v = 2;\n",
113
+ "app/lib/legacy.mjs": "export const legacy = 2;\n",
114
+ "app/package-lock.json": '{ "lockfileVersion": 3 }\n',
115
+ "app/dist/out.js": "2;\n",
116
+ "docs/readme.md": "# Docs, edited\n",
117
+ "tools/t.mjs": "export const t = 2;\n",
118
+ "work/W-003-next.md": work("W-003", { constrains: ["path:app/lib/a.mjs"] }),
119
+ });
120
+ sha.c1 = commit("the change");
121
+ });
122
+ afterAll(cleanScratch);
123
+
124
+ async function check(range: string, options: { work?: string; severity?: "off" | "warn" | "fail"; kinds?: string[] } = {}): Promise<{ doc: Result; failed: boolean }> {
125
+ const { doc, failed } = await checkChanges({ cwd: root, range, ...options });
126
+ changes.expectValid(doc);
127
+ if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
128
+ return { doc, failed };
129
+ }
130
+
131
+ const statusOf = (doc: Result) => Object.fromEntries(doc.paths.map((p) => [p.path, p.status]));
132
+
133
+ describe("the forward coverage check (#2773)", () => {
134
+ test("maps each changed path to the current records covering it: covered, uncovered, out of scope, ignored and record", async () => {
135
+ const { doc, failed } = await check(`${sha.c0}..${sha.c1}`);
136
+ expect(failed).toBe(false);
137
+ expect(doc.range).toEqual({ spec: `${sha.c0}..${sha.c1}`, base: sha.c0, head: sha.c1 });
138
+ expect(doc.severity).toBe("warn");
139
+ expect(doc.kinds).toEqual([
140
+ { file: "decisions/decision.kind.mjs", name: "decision", role: "decision" },
141
+ { file: "work/work.kind.mjs", name: "work", role: "work" },
142
+ ]);
143
+ expect(statusOf(doc)).toEqual({
144
+ "app/dist/out.js": "ignored",
145
+ "app/lib/legacy.mjs": "out-of-scope",
146
+ "app/package-lock.json": "ignored",
147
+ "app/src/server.mjs": "covered",
148
+ "app/src/vendor/lib.mjs": "out-of-scope",
149
+ "docs/readme.md": "uncovered",
150
+ "tools/t.mjs": "uncovered",
151
+ "work/W-003-next.md": "record",
152
+ });
153
+ const server = doc.paths.find((p) => p.path === "app/src/server.mjs")!;
154
+ expect(server).toMatchObject({ change: "modified", member: "app", coveredBy: [{ record: "decision/dec-001", state: "decided", entry: "path:app/src" }] });
155
+ expect(doc.paths.find((p) => p.path === "app/package-lock.json")!.ignoredBy).toBe("**/package-lock.json");
156
+ expect(doc.paths.find((p) => p.path === "app/src/vendor/lib.mjs")!.outOfScopeBy).toEqual([{ record: "decision/dec-001", state: "decided", entry: "app/src/vendor" }]);
157
+ expect(doc.paths.find((p) => p.path === "app/lib/legacy.mjs")!.outOfScopeBy).toEqual([{ record: "work/W-001", state: "in-progress", entry: "app/lib/legacy.mjs" }]);
158
+ expect(doc.findings.map((f) => [f.code, f.path])).toEqual([
159
+ ["change-out-of-scope", "app/lib/legacy.mjs"],
160
+ ["change-out-of-scope", "app/src/vendor/lib.mjs"],
161
+ ["change-uncovered", "docs/readme.md"],
162
+ ["change-uncovered", "tools/t.mjs"],
163
+ ]);
164
+ expect(doc.findings.every((f) => f.severity === "warn")).toBe(true);
165
+ expect(doc.summary).toEqual({ paths: 8, covered: 1, uncovered: 2, outOfScope: 2, ignored: 2, records: 1 });
166
+ expect(doc.ok).toBe(true);
167
+ const text = formatChanges(doc);
168
+ expect(text).toContain("uncovered modified docs/readme.md");
169
+ expect(text).toContain("warning change-out-of-scope: app/src/vendor/lib.mjs is modified, and decision/dec-001 (app/src/vendor) puts it out of scope");
170
+ });
171
+
172
+ test("with --work, the records in hand are the item and the decisions it implements", async () => {
173
+ const { doc } = await check(`${sha.c0}..${sha.c1}`, { work: "W-001" });
174
+ expect(doc.work).toEqual({ record: "work/W-001", state: "in-progress" });
175
+ expect(statusOf(doc)["app/lib/legacy.mjs"]).toBe("out-of-scope");
176
+ expect(statusOf(doc)["app/src/vendor/lib.mjs"]).toBe("out-of-scope");
177
+ // W-002 is done, so nothing it puts out of scope counts, and nothing it constrains is covered.
178
+ const { doc: done } = await check(`${sha.c0}..${sha.c1}`, { work: "W-002" });
179
+ expect(statusOf(done)["app/lib/legacy.mjs"]).toBe("uncovered");
180
+ expect(statusOf(done)["app/src/vendor/lib.mjs"]).toBe("uncovered");
181
+ expect(statusOf(done)["app/src/server.mjs"]).toBe("covered");
182
+ });
183
+
184
+ test("severity: fail fails on a finding, off reports none, and the declaration sets the default", async () => {
185
+ const fail = await check(`${sha.c0}..${sha.c1}`, { severity: "fail" });
186
+ expect(fail.failed).toBe(true);
187
+ expect(fail.doc.ok).toBe(false);
188
+ expect(fail.doc.findings.every((f) => f.severity === "fail")).toBe(true);
189
+ const off = await check(`${sha.c0}..${sha.c1}`, { severity: "off" });
190
+ expect(off.failed).toBe(false);
191
+ expect(off.doc.findings).toEqual([]);
192
+ expect(statusOf(off.doc)["docs/readme.md"]).toBe("uncovered");
193
+
194
+ writeFiles(root, { "chant.workspace.json": declaration({ severity: "fail", ignore: ["**/package-lock.json", "app/dist/**"] }) });
195
+ const c2 = commit("fail on a gap");
196
+ const declared = await check(`${sha.c0}..${c2}`);
197
+ expect(declared.doc.severity).toBe("fail");
198
+ expect(declared.failed).toBe(true);
199
+ // The declaration itself changed, and no record covers it.
200
+ expect(statusOf(declared.doc)["chant.workspace.json"]).toBe("uncovered");
201
+ git(root, "reset", "-q", "--hard", sha.c1);
202
+ });
203
+
204
+ test("a range with no change, a head alone, and a merge base", async () => {
205
+ const empty = await check(`${sha.c1}..${sha.c1}`);
206
+ expect(empty.doc.paths).toEqual([]);
207
+ expect(empty.doc.findings).toEqual([]);
208
+ const toHead = await check(sha.c0);
209
+ expect(toHead.doc.range.head).toBe(sha.c1);
210
+ expect(toHead.doc.paths).toHaveLength(8);
211
+ const merge = await check(`${sha.c0}...${sha.c1}`);
212
+ expect(merge.doc.range.base).toBe(sha.c0);
213
+ });
214
+
215
+ test("an unknown revision or work item is an error document", async () => {
216
+ const bad = await checkChanges({ cwd: root, range: "nope..HEAD" });
217
+ changes.expectValid(bad.doc);
218
+ expect(bad.failed).toBe(true);
219
+ expect("error" in bad.doc && bad.doc.error.code).toBe("revision-unknown");
220
+ const unknown = await checkChanges({ cwd: root, range: `${sha.c0}..${sha.c1}`, work: "W-999" });
221
+ changes.expectValid(unknown.doc);
222
+ expect("error" in unknown.doc && unknown.doc.error.code).toBe("work-item-unknown");
223
+ });
224
+
225
+ test("a finding's triage is the source a work item seeded from it takes (#2741's seam)", async () => {
226
+ const { doc } = await check(`${sha.c0}..${sha.c1}`);
227
+ for (const f of doc.findings) {
228
+ expect(f.triage).toEqual(changeFindingSource(f));
229
+ const seeded = parseFrontMatter(work("W-010", { constrains: [`path:${f.path}`], source: f.triage }));
230
+ if (!seeded.ok) throw new Error(seeded.message);
231
+ expect(workSchema(seeded.value), JSON.stringify(workSchema.errors)).toBe(true);
232
+ }
233
+ for (const c of CHANGES_FINDING_CODES) expect(REASONS[c].length).toBeGreaterThan(10);
234
+ });
235
+
236
+ test("a finding reads as addressed when a work item's source names its gap, and maps to the finding-triage inputs (#2794)", async () => {
237
+ // Before any work item names a gap, every finding reads addressed false, and each path says whether it is generated.
238
+ const { doc: before } = await check(`${sha.c0}..${sha.c1}`);
239
+ expect(before.findings.map((f) => [f.path, f.addressed, f.addressedBy])).toEqual([
240
+ ["app/lib/legacy.mjs", false, []],
241
+ ["app/src/vendor/lib.mjs", false, []],
242
+ ["docs/readme.md", false, []],
243
+ ["tools/t.mjs", false, []],
244
+ ]);
245
+ expect(before.paths.every((p) => p.generated === false)).toBe(true);
246
+
247
+ // c2: W-004 came from the uncovered gap on docs, a directory above docs/readme.md, and is done; W-005 came from the
248
+ // out-of-scope gap on app/lib/legacy.mjs, by a line range; W-006 names the right path under the wrong code.
249
+ // A skill's SKILL.md is a generated file.
250
+ const gap = (finding: string, region: string) => ({ source: { finding, region } });
251
+ writeFiles(root, {
252
+ "work/W-004-docs.md": work("W-004", { state: "done", closed_on: "2026-09-25", constrains: ["path:docs"], evidence: [{ title: "The run", url: "https://example.com/run" }], ...gap("change-uncovered", "docs") }),
253
+ "work/W-005-legacy.md": work("W-005", { constrains: ["path:app/lib/a.mjs"], ...gap("change-out-of-scope", "app/lib/legacy.mjs:1-2") }),
254
+ "work/W-006-tools.md": work("W-006", { constrains: ["path:app/lib/a.mjs"], ...gap("change-out-of-scope", "tools/t.mjs") }),
255
+ "skills/review/SKILL.md": "# Review\n",
256
+ });
257
+ const c2 = commit("work from the gaps");
258
+ try {
259
+ const { doc } = await check(`${sha.c0}..${c2}`);
260
+ expect(doc.findings.map((f) => [f.path, f.addressed, f.addressedBy])).toEqual([
261
+ ["app/lib/legacy.mjs", true, [{ record: "work/W-005", state: "open" }]],
262
+ ["app/src/vendor/lib.mjs", false, []],
263
+ ["docs/readme.md", true, [{ record: "work/W-004", state: "done" }]],
264
+ ["skills/review/SKILL.md", false, []],
265
+ ["tools/t.mjs", false, []],
266
+ ]);
267
+ const skill = doc.paths.find((p) => p.path === "skills/review/SKILL.md")!;
268
+ expect([skill.status, skill.generated]).toEqual(["uncovered", true]);
269
+
270
+ // The point's inputs, by the names decisions/points.json declares, as graph --intent's finding and region give them.
271
+ const readme = doc.findings.find((f) => f.path === "docs/readme.md")!;
272
+ expect(changeFindingTriageInputs(readme, doc.paths.find((p) => p.path === readme.path)!)).toEqual({
273
+ "finding.code": "change-uncovered",
274
+ "finding.message": readme.message,
275
+ "finding.addressed": true,
276
+ "region.path": "docs/readme.md",
277
+ "region.member": "docs",
278
+ "region.generated": false,
279
+ });
280
+ const points = JSON.parse(readFileSync(join(REF, "decisions", "points.json"), "utf-8")) as { points: Record<string, { inputs: Record<string, string> }> };
281
+ expect(Object.keys(changeFindingTriageInputs(readme, doc.paths.find((p) => p.path === readme.path)!))).toEqual(Object.keys(points.points["finding-triage"].inputs));
282
+ expect(() => changeFindingTriageInputs(readme, skill)).toThrow(/fired on docs\/readme.md/);
283
+ } finally {
284
+ git(root, "reset", "-q", "--hard", sha.c1);
285
+ }
286
+
287
+ // With no work kind read, a finding has no addressed, and the inputs pass it as false.
288
+ const { doc: decisionsOnly } = await check(`${sha.c0}..${sha.c1}`, { kinds: ["decisions/decision.kind.mjs"] });
289
+ expect(decisionsOnly.findings.every((f) => !("addressed" in f) && !("addressedBy" in f))).toBe(true);
290
+ const f = decisionsOnly.findings[0];
291
+ expect(changeFindingTriageInputs(f, decisionsOnly.paths.find((p) => p.path === f.path)!)["finding.addressed"]).toBe(false);
292
+ });
293
+
294
+ test("on a work branch, the range and the item come from the branch (an Op under changesCheckout)", async () => {
295
+ const tree = join(root, ".git", "chant-work", "W-001");
296
+ git(root, "worktree", "add", "-q", "-b", "chant/work/W-001", tree, "HEAD");
297
+ writeFiles(tree, { "app/lib/a.mjs": "export const a = 2;\n", "docs/readme.md": "# Docs, again\n" });
298
+ git(tree, "add", "-A");
299
+ git(tree, "commit", "-q", "-m", "the work");
300
+ expect(workBranchChanges(root)).toBeUndefined();
301
+ const found = workBranchChanges(tree)!;
302
+ expect(found).toEqual({ range: `${sha.c1}..HEAD`, work: "W-001" });
303
+ const { doc } = await checkChanges({ cwd: tree, ...found });
304
+ changes.expectValid(doc);
305
+ if ("error" in doc) throw new Error(doc.error.message);
306
+ expect(statusOf(doc)).toEqual({ "app/lib/a.mjs": "covered", "docs/readme.md": "uncovered" });
307
+ // The Op activity reads the same from the worktree, and fails the step at severity fail.
308
+ const step = await changeCoverage({ cwd: tree });
309
+ expect(step).toMatchObject({ ok: true, work: "work/W-001", severity: "warn", summary: { paths: 2, covered: 1, uncovered: 1 } });
310
+ expect(step.findings).toEqual([expect.objectContaining({ code: "change-uncovered", path: "docs/readme.md", triage: { finding: "change-uncovered", region: "docs/readme.md" } })]);
311
+ await expect(changeCoverage({ cwd: tree, severity: "fail" })).rejects.toThrow(/1 change finding at severity fail: change-uncovered docs\/readme.md/);
312
+ await expect(changeCoverage({ cwd: root })).rejects.toThrow(/not on a chant\/work\/ branch/);
313
+ git(root, "worktree", "remove", "--force", tree);
314
+ });
315
+ });