@intentius/chant 0.91.0 → 0.94.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 (361) 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-plugins.d.ts +6 -6
  9. package/dist/cli/mcp/workspace-tools.d.ts +2 -1
  10. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  11. package/dist/cli/plugins.d.ts.map +1 -1
  12. package/dist/cli/registry.d.ts +12 -2
  13. package/dist/cli/registry.d.ts.map +1 -1
  14. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  15. package/dist/config.d.ts +22 -0
  16. package/dist/config.d.ts.map +1 -1
  17. package/dist/lexicon-module.d.ts +22 -0
  18. package/dist/lexicon-module.d.ts.map +1 -1
  19. package/dist/lifecycle/converge-ledger.d.ts +19 -0
  20. package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
  21. package/dist/lifecycle/run-ledger.d.ts +2 -0
  22. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  23. package/dist/lifecycle/symptoms.d.ts +51 -0
  24. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  25. package/dist/lint/rules/index.d.ts +4 -2
  26. package/dist/lint/rules/index.d.ts.map +1 -1
  27. package/dist/lint/rules/op/index.d.ts +14 -3
  28. package/dist/lint/rules/op/index.d.ts.map +1 -1
  29. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -1
  30. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts +29 -0
  31. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts.map +1 -0
  32. package/dist/lint/rules/sys001-no-literal-key.d.ts +3 -0
  33. package/dist/lint/rules/sys001-no-literal-key.d.ts.map +1 -0
  34. package/dist/op/__fixtures__/decide-stub-backend.d.ts +53 -0
  35. package/dist/op/__fixtures__/decide-stub-backend.d.ts.map +1 -0
  36. package/dist/op/activities/activity-contracts.d.ts +201 -0
  37. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  38. package/dist/op/activities/apply.d.ts.map +1 -1
  39. package/dist/op/activities/change-coverage.d.ts +44 -0
  40. package/dist/op/activities/change-coverage.d.ts.map +1 -0
  41. package/dist/op/activities/converge.d.ts +24 -1
  42. package/dist/op/activities/converge.d.ts.map +1 -1
  43. package/dist/op/activities/decide.d.ts +107 -0
  44. package/dist/op/activities/decide.d.ts.map +1 -0
  45. package/dist/op/activities/index.d.ts +10 -0
  46. package/dist/op/activities/index.d.ts.map +1 -1
  47. package/dist/op/activities/shell.d.ts +32 -0
  48. package/dist/op/activities/shell.d.ts.map +1 -1
  49. package/dist/op/activities/source-release.d.ts +86 -0
  50. package/dist/op/activities/source-release.d.ts.map +1 -0
  51. package/dist/op/activities/source-rollback.d.ts +100 -0
  52. package/dist/op/activities/source-rollback.d.ts.map +1 -0
  53. package/dist/op/activities/work-evidence.d.ts +42 -0
  54. package/dist/op/activities/work-evidence.d.ts.map +1 -0
  55. package/dist/op/activity-contract.d.ts +1 -0
  56. package/dist/op/activity-contract.d.ts.map +1 -1
  57. package/dist/op/activity-profiles.d.ts +16 -0
  58. package/dist/op/activity-profiles.d.ts.map +1 -1
  59. package/dist/op/builders.d.ts +84 -2
  60. package/dist/op/builders.d.ts.map +1 -1
  61. package/dist/op/composites/converge-op.d.ts +31 -4
  62. package/dist/op/composites/converge-op.d.ts.map +1 -1
  63. package/dist/op/decide-backend.d.ts +116 -0
  64. package/dist/op/decide-backend.d.ts.map +1 -0
  65. package/dist/op/decide-config.d.ts +120 -0
  66. package/dist/op/decide-config.d.ts.map +1 -0
  67. package/dist/op/decide-read-inputs.d.ts +40 -0
  68. package/dist/op/decide-read-inputs.d.ts.map +1 -0
  69. package/dist/op/gate-wait.d.ts +43 -0
  70. package/dist/op/gate-wait.d.ts.map +1 -0
  71. package/dist/op/gate.d.ts +5 -2
  72. package/dist/op/gate.d.ts.map +1 -1
  73. package/dist/op/index.d.ts +11 -4
  74. package/dist/op/index.d.ts.map +1 -1
  75. package/dist/op/local-executor.d.ts +29 -4
  76. package/dist/op/local-executor.d.ts.map +1 -1
  77. package/dist/op/local-output.d.ts +2 -0
  78. package/dist/op/local-output.d.ts.map +1 -1
  79. package/dist/op/op-ir.d.ts +2 -0
  80. package/dist/op/op-ir.d.ts.map +1 -1
  81. package/dist/op/op-verb-class.d.ts.map +1 -1
  82. package/dist/op/operator.d.ts +97 -1
  83. package/dist/op/operator.d.ts.map +1 -1
  84. package/dist/op/runtime.d.ts +37 -5
  85. package/dist/op/runtime.d.ts.map +1 -1
  86. package/dist/op/runtimes/local.d.ts.map +1 -1
  87. package/dist/op/source-archive.d.ts +63 -0
  88. package/dist/op/source-archive.d.ts.map +1 -0
  89. package/dist/op/steward-points.d.ts +136 -0
  90. package/dist/op/steward-points.d.ts.map +1 -0
  91. package/dist/op/steward-turn.d.ts +53 -0
  92. package/dist/op/steward-turn.d.ts.map +1 -0
  93. package/dist/op/steward.d.ts +22 -1
  94. package/dist/op/steward.d.ts.map +1 -1
  95. package/dist/op/types.d.ts +7 -0
  96. package/dist/op/types.d.ts.map +1 -1
  97. package/dist/op/work-lease-run.d.ts +1 -1
  98. package/dist/op/work-lease-run.d.ts.map +1 -1
  99. package/dist/workspace/__fixtures__/decide-workspace.d.ts +100 -0
  100. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -0
  101. package/dist/workspace/answers-ledger.d.ts +76 -0
  102. package/dist/workspace/answers-ledger.d.ts.map +1 -0
  103. package/dist/workspace/box-intent.d.ts +85 -0
  104. package/dist/workspace/box-intent.d.ts.map +1 -0
  105. package/dist/workspace/changes-cli.d.ts +16 -0
  106. package/dist/workspace/changes-cli.d.ts.map +1 -0
  107. package/dist/workspace/changes.d.ts +200 -0
  108. package/dist/workspace/changes.d.ts.map +1 -0
  109. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts +59 -0
  110. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts.map +1 -0
  111. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts +51 -0
  112. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts.map +1 -0
  113. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts +26 -0
  114. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts.map +1 -0
  115. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts +59 -0
  116. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts.map +1 -0
  117. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts +22 -0
  118. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts.map +1 -0
  119. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts +71 -0
  120. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -0
  121. package/dist/workspace/chant-migrations.d.ts +104 -0
  122. package/dist/workspace/chant-migrations.d.ts.map +1 -0
  123. package/dist/workspace/checks/boxes.d.ts +47 -1
  124. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  125. package/dist/workspace/checks/diagrams.d.ts +31 -0
  126. package/dist/workspace/checks/diagrams.d.ts.map +1 -0
  127. package/dist/workspace/checks/records.d.ts +7 -0
  128. package/dist/workspace/checks/records.d.ts.map +1 -1
  129. package/dist/workspace/checks.d.ts +7 -0
  130. package/dist/workspace/checks.d.ts.map +1 -1
  131. package/dist/workspace/compose-graph.d.ts +2 -0
  132. package/dist/workspace/compose-graph.d.ts.map +1 -1
  133. package/dist/workspace/conformance/index.d.ts.map +1 -1
  134. package/dist/workspace/decide.d.ts +49 -4
  135. package/dist/workspace/decide.d.ts.map +1 -1
  136. package/dist/workspace/decision-points.schema.json +3 -3
  137. package/dist/workspace/declaration.d.ts +58 -0
  138. package/dist/workspace/declaration.d.ts.map +1 -1
  139. package/dist/workspace/declaration.schema.json +119 -1
  140. package/dist/workspace/declared-kinds.d.ts +20 -0
  141. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  142. package/dist/workspace/generated-files.d.ts +7 -0
  143. package/dist/workspace/generated-files.d.ts.map +1 -1
  144. package/dist/workspace/intent-joins.d.ts +5 -5
  145. package/dist/workspace/intent.d.ts.map +1 -1
  146. package/dist/workspace/lineage-check.d.ts +3 -0
  147. package/dist/workspace/lineage-check.d.ts.map +1 -1
  148. package/dist/workspace/lineage-upgrade.d.ts +6 -1
  149. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  150. package/dist/workspace/ls.d.ts +39 -1
  151. package/dist/workspace/ls.d.ts.map +1 -1
  152. package/dist/workspace/point-answer.schema.json +95 -0
  153. package/dist/workspace/points-cli.d.ts +2 -0
  154. package/dist/workspace/points-cli.d.ts.map +1 -1
  155. package/dist/workspace/points.d.ts +4 -6
  156. package/dist/workspace/points.d.ts.map +1 -1
  157. package/dist/workspace/reason-codes.d.ts +16 -2
  158. package/dist/workspace/reason-codes.d.ts.map +1 -1
  159. package/dist/workspace/records-cli.d.ts +11 -1
  160. package/dist/workspace/records-cli.d.ts.map +1 -1
  161. package/dist/workspace/records-close.d.ts +1 -1
  162. package/dist/workspace/records-close.d.ts.map +1 -1
  163. package/dist/workspace/records-write.d.ts +11 -5
  164. package/dist/workspace/records-write.d.ts.map +1 -1
  165. package/dist/workspace/records.d.ts +34 -4
  166. package/dist/workspace/records.d.ts.map +1 -1
  167. package/dist/workspace/status-gates.d.ts +7 -2
  168. package/dist/workspace/status-gates.d.ts.map +1 -1
  169. package/dist/workspace/status-stewards.d.ts +65 -1
  170. package/dist/workspace/status-stewards.d.ts.map +1 -1
  171. package/dist/workspace/status.d.ts +24 -0
  172. package/dist/workspace/status.d.ts.map +1 -1
  173. package/dist/workspace/work-cli.d.ts +113 -2
  174. package/dist/workspace/work-cli.d.ts.map +1 -1
  175. package/dist/workspace/work-evidence.d.ts +74 -0
  176. package/dist/workspace/work-evidence.d.ts.map +1 -0
  177. package/dist/workspace/work.d.ts +43 -1
  178. package/dist/workspace/work.d.ts.map +1 -1
  179. package/package.json +1 -1
  180. package/src/cli/commands/audit.test.ts +9 -1
  181. package/src/cli/commands/build.ts +3 -2
  182. package/src/cli/commands/lint.ts +7 -1
  183. package/src/cli/handlers/graph.test.ts +20 -0
  184. package/src/cli/handlers/graph.ts +16 -2
  185. package/src/cli/handlers/operator.ts +34 -2
  186. package/src/cli/handlers/run.test.ts +154 -2
  187. package/src/cli/handlers/run.ts +121 -2
  188. package/src/cli/main.test.ts +10 -0
  189. package/src/cli/main.ts +23 -3
  190. package/src/cli/mcp/workspace-plugins.ts +6 -6
  191. package/src/cli/mcp/workspace-tools.test.ts +35 -19
  192. package/src/cli/mcp/workspace-tools.ts +62 -4
  193. package/src/cli/plugins.ts +11 -3
  194. package/src/cli/registry.ts +12 -2
  195. package/src/cli/serve-mcp-workspace.test.ts +6 -6
  196. package/src/codegen/pinned-upgrade.ts +2 -1
  197. package/src/codegen/release-wiring.test.ts +49 -2
  198. package/src/components/capability-plugin-loader.ts +3 -3
  199. package/src/components/cli-support.ts +2 -2
  200. package/src/config.ts +11 -0
  201. package/src/lexicon-module.ts +56 -1
  202. package/src/lexicon-package.test.ts +96 -0
  203. package/src/lifecycle/converge-ledger.ts +20 -0
  204. package/src/lifecycle/run-ledger.ts +6 -0
  205. package/src/lifecycle/symptoms.ts +95 -0
  206. package/src/lint/rules/index.ts +6 -2
  207. package/src/lint/rules/op/index.ts +17 -4
  208. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +23 -1
  209. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +10 -6
  210. package/src/lint/rules/op/sys010-key-over-plain-http.test.ts +52 -0
  211. package/src/lint/rules/op/sys010-key-over-plain-http.ts +100 -0
  212. package/src/lint/rules/sys001-no-literal-key.test.ts +31 -0
  213. package/src/lint/rules/sys001-no-literal-key.ts +67 -0
  214. package/src/op/__fixtures__/decide-stub-backend.ts +143 -0
  215. package/src/op/activities/activity-contracts.ts +190 -1
  216. package/src/op/activities/apply.ts +7 -6
  217. package/src/op/activities/change-coverage.ts +60 -0
  218. package/src/op/activities/converge.test.ts +55 -1
  219. package/src/op/activities/converge.ts +208 -29
  220. package/src/op/activities/decide.live.test.ts +78 -0
  221. package/src/op/activities/decide.test.ts +308 -0
  222. package/src/op/activities/decide.ts +267 -0
  223. package/src/op/activities/index.ts +21 -0
  224. package/src/op/activities/shell.test.ts +91 -1
  225. package/src/op/activities/shell.ts +91 -2
  226. package/src/op/activities/source-release.test.ts +118 -0
  227. package/src/op/activities/source-release.ts +168 -0
  228. package/src/op/activities/source-rollback.test.ts +131 -0
  229. package/src/op/activities/source-rollback.ts +239 -0
  230. package/src/op/activities/work-evidence.ts +62 -0
  231. package/src/op/activity-contract-registry.test.ts +2 -0
  232. package/src/op/activity-contract-registry.ts +2 -2
  233. package/src/op/activity-contract.test.ts +12 -0
  234. package/src/op/activity-contract.ts +6 -1
  235. package/src/op/activity-profiles.ts +29 -0
  236. package/src/op/activity-registry.test.ts +1 -1
  237. package/src/op/activity-registry.ts +2 -2
  238. package/src/op/builders.ts +119 -5
  239. package/src/op/command-gate.test.ts +192 -0
  240. package/src/op/composites/converge-op.test.ts +27 -1
  241. package/src/op/composites/converge-op.ts +69 -15
  242. package/src/op/decide-backend.test.ts +44 -0
  243. package/src/op/decide-backend.ts +212 -0
  244. package/src/op/decide-config.test.ts +52 -0
  245. package/src/op/decide-config.ts +80 -0
  246. package/src/op/decide-read-inputs.ts +117 -0
  247. package/src/op/gate-approval.ts +2 -2
  248. package/src/op/gate-wait.ts +57 -0
  249. package/src/op/gate.ts +8 -3
  250. package/src/op/generate-pipeline.ts +2 -2
  251. package/src/op/index.ts +18 -3
  252. package/src/op/local-executor.ts +151 -15
  253. package/src/op/local-output.test.ts +33 -0
  254. package/src/op/local-output.ts +20 -2
  255. package/src/op/op-ir.test.ts +10 -0
  256. package/src/op/op-ir.ts +4 -0
  257. package/src/op/op-verb-class.ts +1 -0
  258. package/src/op/operator.ts +290 -11
  259. package/src/op/runtime.ts +31 -8
  260. package/src/op/runtimes/local.ts +3 -1
  261. package/src/op/source-archive.ts +161 -0
  262. package/src/op/steward-points.test.ts +364 -0
  263. package/src/op/steward-points.ts +194 -0
  264. package/src/op/steward-turn.ts +79 -0
  265. package/src/op/steward.test.ts +87 -3
  266. package/src/op/steward.ts +25 -1
  267. package/src/op/types.ts +7 -0
  268. package/src/op/work-lease-run.test.ts +20 -1
  269. package/src/op/work-lease-run.ts +2 -2
  270. package/src/workspace/__fixtures__/decide-workspace.ts +129 -0
  271. package/src/workspace/__fixtures__/studio-template-84c8b21.json +67 -0
  272. package/src/workspace/answers-ledger.ts +118 -0
  273. package/src/workspace/box-intent.test.ts +205 -0
  274. package/src/workspace/box-intent.ts +159 -0
  275. package/src/workspace/changes-cli.ts +56 -0
  276. package/src/workspace/changes.schema.json +472 -0
  277. package/src/workspace/changes.test.ts +315 -0
  278. package/src/workspace/changes.ts +512 -0
  279. package/src/workspace/chant-migrations/chud-lexicon-exit-fly-site.ts +234 -0
  280. package/src/workspace/chant-migrations/chud-lexicon-exit-live-names.ts +164 -0
  281. package/src/workspace/chant-migrations/chud-lexicon-exit-rollback.ts +223 -0
  282. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.ts +195 -0
  283. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-policy.ts +108 -0
  284. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +882 -0
  285. package/src/workspace/chant-migrations.e2e.test.ts +1089 -0
  286. package/src/workspace/chant-migrations.ts +169 -0
  287. package/src/workspace/check-contract.test.ts +4 -2
  288. package/src/workspace/check.schema.json +19 -5
  289. package/src/workspace/checks/boxes.test.ts +82 -2
  290. package/src/workspace/checks/boxes.ts +202 -1
  291. package/src/workspace/checks/diagrams.test.ts +134 -0
  292. package/src/workspace/checks/diagrams.ts +108 -0
  293. package/src/workspace/checks/records.ts +47 -0
  294. package/src/workspace/checks.ts +20 -2
  295. package/src/workspace/compose-graph.ts +2 -0
  296. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +10 -0
  297. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +26 -0
  298. package/src/workspace/conformance/index.ts +5 -0
  299. package/src/workspace/decide.test.ts +1 -1
  300. package/src/workspace/decide.ts +156 -23
  301. package/src/workspace/decision-points.schema.json +3 -3
  302. package/src/workspace/declaration.schema.json +119 -1
  303. package/src/workspace/declaration.test.ts +58 -0
  304. package/src/workspace/declaration.ts +112 -0
  305. package/src/workspace/declared-kinds.test.ts +31 -5
  306. package/src/workspace/declared-kinds.ts +46 -1
  307. package/src/workspace/generated-files.ts +14 -0
  308. package/src/workspace/graph-cli.ts +1 -1
  309. package/src/workspace/graph.schema.json +9 -1
  310. package/src/workspace/intent-joins.test.ts +5 -5
  311. package/src/workspace/intent-joins.ts +5 -5
  312. package/src/workspace/intent.schema.json +8 -2
  313. package/src/workspace/intent.ts +3 -10
  314. package/src/workspace/lineage-check.ts +16 -1
  315. package/src/workspace/lineage-upgrade-cli.ts +1 -1
  316. package/src/workspace/lineage-upgrade.test.ts +70 -0
  317. package/src/workspace/lineage-upgrade.ts +81 -10
  318. package/src/workspace/ls-contract.test.ts +57 -0
  319. package/src/workspace/ls.schema.json +46 -1
  320. package/src/workspace/ls.ts +62 -3
  321. package/src/workspace/points-cli.ts +17 -3
  322. package/src/workspace/points-write.schema.json +11 -1
  323. package/src/workspace/points.schema.json +51 -0
  324. package/src/workspace/points.test.ts +16 -15
  325. package/src/workspace/points.ts +5 -17
  326. package/src/workspace/read-contract.test.ts +9 -1
  327. package/src/workspace/reason-codes.test.ts +18 -8
  328. package/src/workspace/reason-codes.ts +21 -2
  329. package/src/workspace/records-amend.schema.json +2 -0
  330. package/src/workspace/records-cli.ts +27 -4
  331. package/src/workspace/records-close.schema.json +2 -0
  332. package/src/workspace/records-formats.test.ts +15 -15
  333. package/src/workspace/records-new.schema.json +2 -0
  334. package/src/workspace/records-review.schema.json +2 -0
  335. package/src/workspace/records-sessions-write.e2e.test.ts +77 -0
  336. package/src/workspace/records-sessions-write.test.ts +2 -34
  337. package/src/workspace/records-since.test.ts +8 -7
  338. package/src/workspace/records-write.e2e.test.ts +108 -0
  339. package/src/workspace/records-write.test.ts +32 -41
  340. package/src/workspace/records-write.ts +33 -15
  341. package/src/workspace/records.schema.json +36 -2
  342. package/src/workspace/records.test.ts +49 -4
  343. package/src/workspace/records.ts +109 -3
  344. package/src/workspace/status-contract.test.ts +14 -8
  345. package/src/workspace/status-gates.ts +9 -4
  346. package/src/workspace/status-stewards.ts +155 -5
  347. package/src/workspace/status.schema.json +129 -8
  348. package/src/workspace/status.ts +46 -1
  349. package/src/workspace/work-acceptance.test.ts +274 -0
  350. package/src/workspace/{work-cli.test.ts → work-cli.e2e.test.ts} +1 -1
  351. package/src/workspace/work-cli.ts +193 -10
  352. package/src/workspace/work-evidence.schema.json +212 -0
  353. package/src/workspace/work-evidence.ts +169 -0
  354. package/src/workspace/work-history.schema.json +349 -0
  355. package/src/workspace/work-history.test.ts +156 -0
  356. package/src/workspace/{work-readiness-chud.test.ts → work-readiness.test.ts} +30 -32
  357. package/src/workspace/work.test.ts +20 -2
  358. package/src/workspace/work.ts +100 -1
  359. /package/src/cli/{discovery-skip.test.ts → discovery-skip.e2e.test.ts} +0 -0
  360. /package/src/workspace/conformance/{conformance.test.ts → conformance.e2e.test.ts} +0 -0
  361. /package/src/workspace/{which-chant.test.ts → which-chant.e2e.test.ts} +0 -0
@@ -96,6 +96,17 @@ export interface Suppression {
96
96
  /** What `checks` may set a declaration check's severity to. */
97
97
  export type CheckSeverity = "error" | "warning" | "info" | "off";
98
98
 
99
+ /** How `chant workspace check --changes` treats its findings (#2773): not at all, as warnings, or as failures. */
100
+ export type ChangeSeverity = "off" | "warn" | "fail";
101
+
102
+ /** The declaration's `changes` block (#2773): the severity of the forward coverage check, and the paths it never reports. */
103
+ export interface ChangesPolicy {
104
+ /** Absent from the declaration, `warn`. */
105
+ severity: ChangeSeverity;
106
+ /** Globs over files, relative to the workspace root, whose changes need no record: lockfiles, generated output. */
107
+ ignore: string[];
108
+ }
109
+
99
110
  export interface MemberRole {
100
111
  name: string;
101
112
  /** Relative to the member's directory, or null for the whole member. */
@@ -128,6 +139,40 @@ export interface LinkDeclaration {
128
139
  pointer: string;
129
140
  }
130
141
 
142
+ /** A renderer a diagram is pinned to (#2764): a closed list of tools chant does not run. */
143
+ export const DIAGRAM_TOOLS = ["d2", "mermaid", "graphviz"] as const;
144
+ export type DiagramTool = (typeof DIAGRAM_TOOLS)[number];
145
+
146
+ /** The renderer a diagram's render was made with (#2764). chant never runs it; it is recorded so a reader can. */
147
+ export interface DiagramRenderer {
148
+ tool: DiagramTool;
149
+ /** The exact release the render was made with, such as "0.9.0". */
150
+ version: string;
151
+ /** Passed before the input and output paths, in order. */
152
+ args: string[];
153
+ }
154
+
155
+ /**
156
+ * A diagram artifact the declaration names (#2764): the workspace's own, in
157
+ * the top-level `diagrams`, or a member's, in that member's `diagrams`. A
158
+ * name is given once across the declaration, like a record kind's.
159
+ */
160
+ export interface DiagramDeclaration {
161
+ name: string;
162
+ title: string;
163
+ /** From the workspace root, with / separators. Null for an SVG with no source. */
164
+ source: string | null;
165
+ /** From the workspace root, with / separators. */
166
+ render: string;
167
+ renderer: DiagramRenderer;
168
+ /** sha256 hex of the source's bytes when the render was last produced, for `chant workspace check`'s drift finding. Null when not recorded, or when source is null: the render is then never checked for drift. */
169
+ sourceHash: string | null;
170
+ /** The member that declares it, or null for the workspace's own. */
171
+ member: string | null;
172
+ /** The entry's JSON Pointer in the file, for messages. */
173
+ pointer: string;
174
+ }
175
+
131
176
  /**
132
177
  * A record kind the declaration names (#2680): the workspace's own, in the
133
178
  * top-level `records`, or a member's, in that member's `records`.
@@ -165,6 +210,12 @@ export interface BoxDeclaration {
165
210
  capabilities: BoxCapability[];
166
211
  /** The host and slot of the box's isolation, or null when the block declares none (#2727). The values are derived in `boxes.ts`. */
167
212
  isolation: BoxIsolationDeclaration | null;
213
+ /**
214
+ * The id of the decision record that says what the box is for, or null when
215
+ * the block names none (#2850). A box starts as a question: the record is
216
+ * proposed with no choice until the person who answers it decides it.
217
+ */
218
+ intent: string | null;
168
219
  /** The block's JSON Pointer in the file, for messages. */
169
220
  pointer: string;
170
221
  }
@@ -208,6 +259,8 @@ export interface Member {
208
259
  links: LinkDeclaration[];
209
260
  /** The record kinds this member declares, in file order (#2680). */
210
261
  records: RecordKindDeclaration[];
262
+ /** The diagram artifacts this member declares, in file order (#2764). */
263
+ diagrams: DiagramDeclaration[];
211
264
  /** The member's box block, or null when it declares none (#2726). */
212
265
  box: BoxDeclaration | null;
213
266
  upstream: string | null;
@@ -252,8 +305,12 @@ export interface Declaration {
252
305
  quorum: number | null;
253
306
  /** The workspace's own record kinds, from the top-level `records`, in file order (#2680). A member's are on the member. */
254
307
  records: RecordKindDeclaration[];
308
+ /** The workspace's own diagram artifacts, from the top-level `diagrams`, in file order (#2764). A member's are on the member. */
309
+ diagrams: DiagramDeclaration[];
255
310
  /** The hosts boxes run on, in file order (#2727). */
256
311
  hosts: Host[];
312
+ /** The forward coverage check's policy (#2773), or null when the declaration has no `changes` block. */
313
+ changes: ChangesPolicy | null;
257
314
  /** The file, relative to the workspace root's tree (`chant.workspace.json` or `.jsonc`). */
258
315
  file: string;
259
316
  }
@@ -466,6 +523,7 @@ export function parseDeclaration(text: string, file: string, reader: string = re
466
523
  pointer: `${pointer}/links/${j}`,
467
524
  }));
468
525
  const records = recordKindsOf(e.records, e.dir as string, e.name as string, `${pointer}/records`);
526
+ const diagrams = diagramsOf(e.diagrams, e.name as string, `${pointer}/diagrams`);
469
527
  const box = boxOf(e.box, `${pointer}/box`);
470
528
  return {
471
529
  type: "member",
@@ -477,6 +535,7 @@ export function parseDeclaration(text: string, file: string, reader: string = re
477
535
  outputs: e.outputs === undefined ? null : [...(e.outputs as string[])],
478
536
  links,
479
537
  records,
538
+ diagrams,
480
539
  box,
481
540
  upstream: (e.upstream as string | undefined) ?? null,
482
541
  because: (e.because as string | undefined) ?? null,
@@ -572,6 +631,17 @@ export function parseDeclaration(text: string, file: string, reader: string = re
572
631
  }
573
632
  }
574
633
 
634
+ // A diagram name is given once across the declaration (#2764): a reader keys diagrams by name.
635
+ const ownDiagrams = diagramsOf(obj.diagrams, null, "/diagrams");
636
+ const byDiagramName = new Map<string, DiagramDeclaration>();
637
+ for (const d of [...ownDiagrams, ...members.flatMap((m) => m.diagrams)]) {
638
+ const first = byDiagramName.get(d.name);
639
+ if (first) {
640
+ throw new WorkspaceReadError("declaration-invalid", `the diagram name ${JSON.stringify(d.name)} is already used by the diagram at ${first.pointer}`, at(`${d.pointer}/name`));
641
+ }
642
+ byDiagramName.set(d.name, d);
643
+ }
644
+
575
645
  const hosts = hostsOf(obj, members, at);
576
646
 
577
647
  const pins = ((obj.pins as Record<string, string>[] | undefined) ?? []).map((p) => ({
@@ -592,11 +662,19 @@ export function parseDeclaration(text: string, file: string, reader: string = re
592
662
  checks: { ...((obj.checks as Record<string, CheckSeverity> | undefined) ?? {}) },
593
663
  quorum: typeof obj.quorum === "number" ? obj.quorum : null,
594
664
  records: ownRecords,
665
+ diagrams: ownDiagrams,
595
666
  hosts,
667
+ changes: changesOf(obj.changes as Record<string, unknown> | undefined),
596
668
  file,
597
669
  };
598
670
  }
599
671
 
672
+ /** The `changes` block, already validated, with its defaults (#2773). */
673
+ function changesOf(block: Record<string, unknown> | undefined): ChangesPolicy | null {
674
+ if (block === undefined) return null;
675
+ return { severity: (block.severity as ChangeSeverity | undefined) ?? "warn", ignore: [...((block.ignore as string[] | undefined) ?? [])] };
676
+ }
677
+
600
678
  /** The `records` list at `pointer`, already validated, with each kind file's path from the workspace root. */
601
679
  function recordKindsOf(raw: unknown, dir: string, member: string | null, pointer: string): RecordKindDeclaration[] {
602
680
  return ((raw as { kind: string; name?: string }[] | undefined) ?? []).map((r, i) => ({
@@ -608,6 +686,29 @@ function recordKindsOf(raw: unknown, dir: string, member: string | null, pointer
608
686
  }));
609
687
  }
610
688
 
689
+ /**
690
+ * The `diagrams` list at `pointer`, already validated. `source` and `render`
691
+ * are workspace-root-relative as written: unlike a generated file or a
692
+ * record kind, a diagram's files need not sit inside the declaring member's
693
+ * directory, so there is no member-relative form to resolve (#2764).
694
+ */
695
+ function diagramsOf(raw: unknown, member: string | null, pointer: string): DiagramDeclaration[] {
696
+ return (
697
+ (raw as
698
+ | { name: string; title: string; source?: string | null; render: string; renderer: { tool: DiagramTool; version: string; args?: string[] }; sourceHash?: string | null }[]
699
+ | undefined) ?? []
700
+ ).map((d, i) => ({
701
+ name: d.name,
702
+ title: d.title,
703
+ source: d.source ?? null,
704
+ render: d.render,
705
+ renderer: { tool: d.renderer.tool, version: d.renderer.version, args: [...(d.renderer.args ?? [])] },
706
+ sourceHash: d.sourceHash ?? null,
707
+ member,
708
+ pointer: `${pointer}/${i}`,
709
+ }));
710
+ }
711
+
611
712
  /** The `box` block at `pointer`, already validated, or null when there is none (#2726, #2727). */
612
713
  function boxOf(raw: unknown, pointer: string): BoxDeclaration | null {
613
714
  if (raw === undefined) return null;
@@ -618,6 +719,7 @@ function boxOf(raw: unknown, pointer: string): BoxDeclaration | null {
618
719
  ports?: Record<string, number>;
619
720
  state?: Record<string, string>;
620
721
  cookies?: string[];
722
+ intent?: string;
621
723
  };
622
724
  return {
623
725
  capabilities: (b.capabilities ?? []).map((c, i) => ({ name: c.name, broker: c.broker ?? null, scope: [...(c.scope ?? [])], pointer: `${pointer}/capabilities/${i}` })),
@@ -626,6 +728,7 @@ function boxOf(raw: unknown, pointer: string): BoxDeclaration | null {
626
728
  b.host === undefined
627
729
  ? null
628
730
  : { host: b.host, slot: b.slot!, ports: { ...(b.ports ?? {}) }, state: { ...(b.state ?? {}) }, cookies: [...(b.cookies ?? [])] },
731
+ intent: b.intent ?? null,
629
732
  pointer,
630
733
  };
631
734
  }
@@ -687,6 +790,15 @@ export function declaredRecordKinds(declaration: Declaration): RecordKindDeclara
687
790
  return [...declaration.records, ...declaration.members.flatMap((m) => m.records)];
688
791
  }
689
792
 
793
+ /**
794
+ * Every diagram artifact the declaration names (#2764), in the order readers
795
+ * use them: the workspace's own first, then each member's, members in file
796
+ * order and each list in its own order.
797
+ */
798
+ export function declaredDiagrams(declaration: Declaration): DiagramDeclaration[] {
799
+ return [...declaration.diagrams, ...declaration.members.flatMap((m) => m.diagrams)];
800
+ }
801
+
690
802
  /** Whether `path` is `dir` or sits under it. Both are tree-relative; `"."` is the root. */
691
803
  export function isInside(path: string, dir: string): boolean {
692
804
  if (dir === ".") return true;
@@ -111,20 +111,20 @@ describe("workspace ls lists the declared kinds (#2680)", () => {
111
111
  ]);
112
112
  const doc = result(await listWorkspaceWithKinds({ cwd: root }));
113
113
  expect(doc.contract).toBe(1);
114
- expect(doc.workspace.records).toEqual([{ name: "decision", path: "decisions/decision.kind.mjs", kind: "decision", reason: null }]);
114
+ expect(doc.workspace.records).toEqual([{ name: "decision", path: "decisions/decision.kind.mjs", kind: "decision", reason: null, acceptance: null }]);
115
115
  const design = doc.members.find((m) => m.name === "design")!;
116
116
  expect(design.readable).toBe(true);
117
117
  expect(design.records).toEqual([
118
- { name: "notes", path: "design/notes/note.kind.mjs", kind: "note", reason: null },
119
- { name: null, path: "design/gone.kind.mjs", kind: null, reason: { code: "kind-unreadable", message: "kind file design/gone.kind.mjs does not exist" } },
120
- { name: null, path: "design/broken.kind.mjs", kind: null, reason: { code: "kind-invalid", message: "kind file design/broken.kind.mjs is not a record kind: it has no recordKind export" } },
118
+ { name: "notes", path: "design/notes/note.kind.mjs", kind: "note", reason: null, acceptance: null },
119
+ { name: null, path: "design/gone.kind.mjs", kind: null, reason: { code: "kind-unreadable", message: "kind file design/gone.kind.mjs does not exist" }, acceptance: null },
120
+ { name: null, path: "design/broken.kind.mjs", kind: null, reason: { code: "kind-invalid", message: "kind file design/broken.kind.mjs is not a record kind: it has no recordKind export" }, acceptance: null },
121
121
  ]);
122
122
  expect(doc.members.find((m) => m.name === "app")!.records).toEqual([]);
123
123
  });
124
124
 
125
125
  test("listWorkspace lists them unloaded, and a workspace that names none lists empty arrays", async () => {
126
126
  const doc = result(listWorkspace({ cwd: workspace() }));
127
- expect(doc.workspace.records).toEqual([{ name: null, path: "decisions/decision.kind.mjs", kind: null, reason: null }]);
127
+ expect(doc.workspace.records).toEqual([{ name: null, path: "decisions/decision.kind.mjs", kind: null, reason: null, acceptance: null }]);
128
128
  const none = result(await listWorkspaceWithKinds({ cwd: repo({ "chant.workspace.json": decl(members()), "app/x": "", "design/x": "" }) }));
129
129
  expect(none.workspace.records).toEqual([]);
130
130
  expect(none.members.map((m) => m.records)).toEqual([[], []]);
@@ -242,6 +242,32 @@ describe("records without --kind reads every declared kind (#2680)", () => {
242
242
  expect(await run(workspace(), "--current")).toBe(0);
243
243
  expect(out.filter((l) => l.includes("("))).toEqual(["decision (decisions/decision.kind.mjs)", "notes (design/notes/note.kind.mjs)"]);
244
244
  });
245
+
246
+ test("--at an unknown revision with --json prints the error document, not just text on stderr (#2860)", async () => {
247
+ const root = workspace();
248
+ expect(await run(root, "--at", "refs/heads/no-such-branch", "--json")).toBe(1);
249
+ expect(err).toEqual([]);
250
+ const printed = JSON.parse(out.join("\n"));
251
+ expectValid(printed);
252
+ expect(printed).toEqual({
253
+ $schema: recordsSchema.$id,
254
+ contract: 1,
255
+ error: { code: "revision-unknown", message: expect.stringContaining("refs/heads/no-such-branch") as unknown as string },
256
+ });
257
+
258
+ out.length = 0;
259
+ err.length = 0;
260
+ expect(await run(root, "--at", "refs/heads/no-such-branch")).toBe(1);
261
+ expect(out).toEqual([]);
262
+ expect(err.join("\n")).toContain("revision-unknown");
263
+ });
264
+
265
+ test("a declaration-level code the records schema doesn't carry, such as declaration-invalid, stays text-only even with --json (#2860)", async () => {
266
+ const root = repo({ "chant.workspace.json": decl(members(), { records: [{ kind: "a.kind.mjs", file: "b" }] }), "app/x": "", "design/x": "" });
267
+ expect(await run(root, "--json")).toBe(1);
268
+ expect(out).toEqual([]);
269
+ expect(err.join("\n")).toContain("declaration-invalid");
270
+ });
245
271
  });
246
272
 
247
273
  describe("records new, amend and review without --kind (#2680)", () => {
@@ -36,6 +36,25 @@ export interface DeclaredKind {
36
36
  * other kind, and when the kind wasn't loaded.
37
37
  */
38
38
  points?: { file: string; problems: { field: string | null; message: string }[] };
39
+ /**
40
+ * For a work kind with acceptance criteria (#2772), read with
41
+ * `acceptance: true`: each current record that lists criteria, with how
42
+ * many are met. Absent for any other kind, when the kind wasn't loaded,
43
+ * and when its records can't be read (`records` reports why).
44
+ */
45
+ acceptance?: DeclaredAcceptance[];
46
+ }
47
+
48
+ /** One work record's acceptance criteria, counted, as `ls`, `status` and `check` read them (#2772). */
49
+ export interface DeclaredAcceptance {
50
+ item: string;
51
+ state: string | null;
52
+ met: number;
53
+ total: number;
54
+ /** The record file on disk, absolute. */
55
+ file: string;
56
+ /** The record's `work-acceptance-unmet` warning, when it is done with a criterion unmet; null otherwise. */
57
+ unmet: string | null;
39
58
  }
40
59
 
41
60
  /** The kind file of `declared` on disk, under the workspace root `rootOnDisk`. */
@@ -53,7 +72,7 @@ export async function loadDeclaredKinds(
53
72
  declaration: Declaration,
54
73
  tree: WorkspaceTree,
55
74
  rootOnDisk: string,
56
- options: { load?: boolean } = {},
75
+ options: { load?: boolean; acceptance?: boolean; at?: string } = {},
57
76
  ): Promise<DeclaredKind[]> {
58
77
  const declared = declaredRecordKinds(declaration);
59
78
  if (declared.length === 0) return [];
@@ -73,6 +92,10 @@ export async function loadDeclaredKinds(
73
92
  const loaded = await records.loadRecordKind(file);
74
93
  const entry: DeclaredKind = { declared: d, file, kind: loaded.kind.name, reason: null };
75
94
  if (loaded.kind.answers) entry.points = await checkPoints(resolve(dirname(file), loaded.kind.answers.points), rootOnDisk, tree);
95
+ if (loaded.kind.work?.acceptance && options.acceptance) {
96
+ const counted = await countAcceptance(file, rootOnDisk, options.at);
97
+ if (counted) entry.acceptance = counted;
98
+ }
76
99
  out.push(entry);
77
100
  } catch (err) {
78
101
  if (!(err instanceof records.RecordReadError)) throw err;
@@ -83,6 +106,28 @@ export async function loadDeclaredKinds(
83
106
  return out;
84
107
  }
85
108
 
109
+ /** The acceptance counts of a work kind's current records, or undefined when they can't be read. */
110
+ async function countAcceptance(file: string, rootOnDisk: string, at: string | undefined): Promise<DeclaredAcceptance[] | undefined> {
111
+ const { readRecordsFor } = await import("./records-cli");
112
+ const { RecordReadError } = await import("./records");
113
+ try {
114
+ const read = await readRecordsFor({ kind: file, cwd: rootOnDisk, current: true, ...(at !== undefined ? { at } : {}) });
115
+ return read.result.records
116
+ .filter((r): r is typeof r & { id: string; acceptance: NonNullable<typeof r.acceptance> } => r.id !== null && r.acceptance != null)
117
+ .map((r) => ({
118
+ item: r.id,
119
+ state: r.state,
120
+ met: r.acceptance.met,
121
+ total: r.acceptance.total,
122
+ file: join(read.root, ...r.path.split("/")),
123
+ unmet: r.warnings.find((w) => w.code === "work-acceptance-unmet")?.message ?? null,
124
+ }));
125
+ } catch (err) {
126
+ if (err instanceof RecordReadError) return undefined;
127
+ throw err;
128
+ }
129
+ }
130
+
86
131
  /** An answer kind's points file (ws-058), read from `tree` and validated as `points ask` reads it. */
87
132
  async function checkPoints(abs: string, rootOnDisk: string, tree: WorkspaceTree): Promise<NonNullable<DeclaredKind["points"]>> {
88
133
  const file = relative(rootOnDisk, abs).split(sep).join("/");
@@ -22,6 +22,7 @@
22
22
  import { relative, resolve, sep } from "node:path";
23
23
  import { findWorkspaceRoot } from "../project-root";
24
24
  import { isInside, readDeclaration, type Declaration } from "./declaration";
25
+ import { memberHolding } from "./record-assets";
25
26
  import { workingTree } from "./tree";
26
27
 
27
28
  /** What `chant update` rewrites, registered as implicit generated entries. */
@@ -79,6 +80,19 @@ export function declaredFilesUnder(declaration: Declaration, dir: string): Map<s
79
80
  return out;
80
81
  }
81
82
 
83
+ /**
84
+ * Whether a workspace path (from the workspace root) is a generated file:
85
+ * declared in its member's `generated` entries, or one core registers. The
86
+ * `generated` of `graph --intent`'s region and file nodes and of `check
87
+ * --changes`'s paths.
88
+ */
89
+ export function isGeneratedPath(declaration: Declaration, path: string): boolean {
90
+ const m = declaration.members.find((x) => x.name === memberHolding(path, declaration.members));
91
+ const dir = m?.dir ?? ".";
92
+ const rel = dir === "." ? path : path.slice(dir.length + 1);
93
+ return classifyFile(rel, declaredFilesUnder(declaration, dir)).class === "generated";
94
+ }
95
+
82
96
  /**
83
97
  * The declared generated files for the absolute directory `absDir`, from the
84
98
  * nearest `chant.workspace.json` above it. Empty when there is none, which is
@@ -220,7 +220,7 @@ export async function workspaceGraph(query: GraphQuery): Promise<GraphResult> {
220
220
  try {
221
221
  const read = await readRecordsFor({ kind: query.kind, cwd: query.cwd, at: query.at });
222
222
  const name = read.loaded.kind.name;
223
- graph.records = read.result.records.map((r) => ({ kind: name, id: r.id, path: r.path, state: r.state, valid: r.valid, supersededBy: r.supersededBy }));
223
+ graph.records = read.result.records.map((r) => ({ kind: name, id: r.id, path: r.path, state: r.state, valid: r.valid, supersededBy: r.supersededBy, remediatedBy: r.remediatedBy }));
224
224
  graph.links.push(...recordLinkRows(name, read.result.records, read.loaded.kind.constrains?.field, located.tree, declaration.members));
225
225
  } catch (err) {
226
226
  if (!(err instanceof RecordReadError)) throw err;
@@ -752,7 +752,8 @@
752
752
  "path",
753
753
  "state",
754
754
  "valid",
755
- "supersededBy"
755
+ "supersededBy",
756
+ "remediatedBy"
756
757
  ],
757
758
  "properties": {
758
759
  "kind": {
@@ -784,6 +785,13 @@
784
785
  "string",
785
786
  "null"
786
787
  ]
788
+ },
789
+ "remediatedBy": {
790
+ "description": "Added in contract 1 by #2774. The ids of records whose remediates link names this one.",
791
+ "type": "array",
792
+ "items": {
793
+ "type": "string"
794
+ }
787
795
  }
788
796
  }
789
797
  }
@@ -36,7 +36,7 @@ describe("the data form reads every trailer value (#2663)", () => {
36
36
  });
37
37
 
38
38
  test("trailerValues keeps git's order, trims, and drops empty values and repeats", () => {
39
- expect(trailerValues({ "Chud-Evidence": ["h1", "h2"], "chud-evidence": ["h2", " h3", " "] }, "CHUD-EVIDENCE")).toEqual(["h1", "h2", "h3"]);
39
+ expect(trailerValues({ "Acme-Evidence": ["h1", "h2"], "acme-evidence": ["h2", " h3", " "] }, "ACME-EVIDENCE")).toEqual(["h1", "h2", "h3"]);
40
40
  expect(trailerValues({}, "X")).toEqual([]);
41
41
  expect(trailerValue({ X: [" "] }, "x")).toBeUndefined();
42
42
  });
@@ -45,16 +45,16 @@ describe("the data form reads every trailer value (#2663)", () => {
45
45
  describe("commitJoinsName (#2663)", () => {
46
46
  test("names the findings of either form, and leaves the data form's own keys alone", () => {
47
47
  const join = () => undefined;
48
- expect(readCommitJoins({ commitJoins: join, commitJoinsName: "chud" })).toEqual({ form: "function", join, name: "chud" });
49
- expect(readCommitJoins({ commitJoins: { trailers: { unit: "Unit" } }, commitJoinsName: "chud" })).toEqual({ form: "data", data: { trailers: { unit: "Unit" } }, name: "chud" });
48
+ expect(readCommitJoins({ commitJoins: join, commitJoinsName: "acme" })).toEqual({ form: "function", join, name: "acme" });
49
+ expect(readCommitJoins({ commitJoins: { trailers: { unit: "Unit" } }, commitJoinsName: "acme" })).toEqual({ form: "data", data: { trailers: { unit: "Unit" } }, name: "acme" });
50
50
  expect(readCommitJoins({ commitJoins: join })).toEqual({ form: "function", join });
51
51
  // A name inside the data form is still a key the data form does not have.
52
- expect(readCommitJoins({ commitJoins: { name: "chud", trailers: {} } })).toMatch(/Unrecognized key/);
52
+ expect(readCommitJoins({ commitJoins: { name: "acme", trailers: {} } })).toMatch(/Unrecognized key/);
53
53
  });
54
54
 
55
55
  test("a name that can't be a plugin:<name>: segment, or one with no joins, is refused", () => {
56
56
  for (const bad of ["a:b", "a b", "", 7]) expect(readCommitJoins({ commitJoins: () => undefined, commitJoinsName: bad }), String(bad)).toMatch(/^commitJoinsName:/);
57
- expect(readCommitJoins({ commitJoinsName: "chud" })).toMatch(/no commitJoins/);
57
+ expect(readCommitJoins({ commitJoinsName: "acme" })).toMatch(/no commitJoins/);
58
58
  expect(readCommitJoins({})).toBeUndefined();
59
59
  });
60
60
  });
@@ -2,10 +2,10 @@
2
2
  * The commit-join hook of the intent graph (#2651; #2650 C1 and C13).
3
3
  *
4
4
  * Commits, decisions and artifacts come from core. Units of work, contracts
5
- * and evidence come from a plugin, such as chud's development model, because
6
- * core ships no model of them (#2555, "Core ships no decision kind"). A kind
7
- * file passed to `chant workspace graph --intent --kind <file>` supplies them
8
- * through one export, `commitJoins`, in one of two forms:
5
+ * and evidence come from a plugin, such as a workspace's own development
6
+ * model, because core ships no model of them (#2555, "Core ships no decision
7
+ * kind"). A kind file passed to `chant workspace graph --intent --kind <file>`
8
+ * supplies them through one export, `commitJoins`, in one of two forms:
9
9
  *
10
10
  * - A function `commitJoins(commit, context)` that returns the unit, contract
11
11
  * and evidence for one commit, or nothing. It is given the commit's sha,
@@ -36,7 +36,7 @@
36
36
  * `<name>` is the kind's name by default: its record kind's name, or the
37
37
  * file's name without `.kind.mjs`. A kind file may name its findings itself
38
38
  * with a sibling export, `commitJoinsName` (#2663), so joins that live beside
39
- * a record kind called `decision` can still report `plugin:chud:<code>`. It is
39
+ * a record kind called `decision` can still report `plugin:acme:<code>`. It is
40
40
  * a sibling export rather than a property because the data form is a closed
41
41
  * object whose keys are all joins, and a function's `name` property is
42
42
  * already its own name.
@@ -600,7 +600,9 @@
600
600
  "record-schema-invalid",
601
601
  "record-id-duplicate",
602
602
  "record-supersedes-unknown",
603
- "record-supersedes-conflict"
603
+ "record-supersedes-conflict",
604
+ "record-remediates-unknown",
605
+ "record-remediates-not-closed"
604
606
  ]
605
607
  },
606
608
  "message": {
@@ -794,7 +796,9 @@
794
796
  "record-schema-invalid",
795
797
  "record-id-duplicate",
796
798
  "record-supersedes-unknown",
797
- "record-supersedes-conflict"
799
+ "record-supersedes-conflict",
800
+ "record-remediates-unknown",
801
+ "record-remediates-not-closed"
798
802
  ]
799
803
  },
800
804
  "message": {
@@ -941,6 +945,8 @@
941
945
  "work-done-unpinned",
942
946
  "work-closed-without-date",
943
947
  "work-done-gap-open",
948
+ "work-acceptance-unmet",
949
+ "work-acceptance-self-verified",
944
950
  "answer-points-unreadable",
945
951
  "answer-point-unknown",
946
952
  "answer-point-changed"
@@ -38,7 +38,7 @@ import { realpathSync } from "node:fs";
38
38
  import { basename, dirname, relative, resolve, sep } from "node:path";
39
39
  import { declaredRecordKinds, readDeclaration, readerVersion, resolveGroups, WORKSPACE_ERROR_CODES, WorkspaceReadError, type Declaration } from "./declaration";
40
40
  import { declaredKindFile } from "./declared-kinds";
41
- import { classifyFile, declaredFilesUnder } from "./generated-files";
41
+ import { isGeneratedPath } from "./generated-files";
42
42
  import { entityDecisions, hasTrailer, readCommitJoins, runCommitJoins, type CommitJoins, type IntentCommit, type JoinedEntity, type PluginFinding } from "./intent-joins";
43
43
  import { loadKindRegistry } from "./kinds";
44
44
  import { resolveLinks, type LinkTableRow } from "./links";
@@ -541,13 +541,6 @@ function filesUnder(tree: WorkspaceTree, dir: string): string[] {
541
541
  return out;
542
542
  }
543
543
 
544
- function isGenerated(declaration: Declaration, path: string): boolean {
545
- const m = declaration.members.find((x) => x.name === memberHolding(path, declaration.members));
546
- const dir = m?.dir ?? ".";
547
- const rel = dir === "." ? path : path.slice(dir.length + 1);
548
- return classifyFile(rel, declaredFilesUnder(declaration, dir)).class === "generated";
549
- }
550
-
551
544
  function stringOr(v: unknown): string | null {
552
545
  return typeof v === "string" ? v : null;
553
546
  }
@@ -707,9 +700,9 @@ async function walk(query: IntentQuery, head: Head): Promise<IntentResult> {
707
700
  // The region, its files and its member.
708
701
  const member = region.path === "." ? (declaration.members.find((m) => m.dir === ".")?.name ?? null) : memberHolding(region.path, declaration.members);
709
702
  const rid = regionId(region.path, region.lines);
710
- add<RegionNode>({ id: rid, kind: "region", path: region.path, lines: region.lines, member, at: located.at, type, generated: type === "file" ? isGenerated(declaration, region.path) : null, node: region.node });
703
+ add<RegionNode>({ id: rid, kind: "region", path: region.path, lines: region.lines, member, at: located.at, type, generated: type === "file" ? isGeneratedPath(declaration, region.path) : null, node: region.node });
711
704
  const files = type === "dir" ? filesUnder(located.tree, region.path) : [];
712
- for (const f of files) add<FileNode>({ id: `file:${f}`, kind: "file", path: f, member: memberHolding(f, declaration.members), generated: isGenerated(declaration, f) });
705
+ for (const f of files) add<FileNode>({ id: `file:${f}`, kind: "file", path: f, member: memberHolding(f, declaration.members), generated: isGeneratedPath(declaration, f) });
713
706
  const memberNode = (name: string) => {
714
707
  const m = declaration.members.find((x) => x.name === name);
715
708
  return add<MemberNode>({ id: `member:${name}`, kind: "member", name, dir: m?.dir ?? "", memberKind: m?.kind ?? "" });
@@ -16,6 +16,9 @@
16
16
  *
17
17
  * `chant workspace upgrade` runs the same checks in its staging worktree
18
18
  * before it reaches its gate.
19
+ *
20
+ * `--changes <base>..<head>` runs the forward coverage check instead
21
+ * (`changes-cli.ts`, #2773), which prints a document of its own.
19
22
  */
20
23
 
21
24
  import { realpathSync } from "node:fs";
@@ -84,7 +87,8 @@ export function findingKey(f: CheckFinding): string {
84
87
  return `${f.code}\0${f.scope ?? ""}\0${f.path ?? ""}`;
85
88
  }
86
89
 
87
- const USAGE = "chant workspace check [--at <rev>] [--json] [--format stylish|json|sarif] [--generated] [--kind <kind file>]";
90
+ const USAGE =
91
+ "chant workspace check [--at <rev>] [--json] [--format stylish|json|sarif] [--generated] [--kind <kind file>]\n chant workspace check --changes <base>..<head> [--work <id>] [--severity off|warn|fail] [--kind <kind file>...] [--json]";
88
92
  const FORMATS = ["stylish", "json", "sarif"] as const;
89
93
 
90
94
  /** A lock finding as a lint diagnostic, for `--format json` and `--format sarif`. */
@@ -223,6 +227,17 @@ export async function runWorkspaceCheck(ctx: CommandContext): Promise<number> {
223
227
  console.error(formatError({ message: `chant workspace check takes no argument (got ${ctx.args.extraPositional})`, hint: USAGE }));
224
228
  return 1;
225
229
  }
230
+ // The forward coverage check (#2773) is its own document.
231
+ if (ctx.args.changes !== undefined) {
232
+ if (ctx.args.at !== undefined) {
233
+ console.error(formatError({ message: "--changes reads the records at the range's head, so it takes no --at", hint: USAGE }));
234
+ return 1;
235
+ }
236
+ const { handToRootChant } = await import("./which-chant");
237
+ const handed = await handToRootChant(root, undefined);
238
+ if (handed !== undefined) return handed;
239
+ return (await import("./changes-cli")).runWorkspaceChanges(ctx, root);
240
+ }
226
241
  const format = (ctx.args.format || "stylish") as (typeof FORMATS)[number];
227
242
  if (!FORMATS.includes(format)) {
228
243
  console.error(formatError({ message: `--format ${format} is not a check format; use stylish, json or sarif`, hint: USAGE }));
@@ -71,7 +71,7 @@ export async function upgradeCommand(opts: UpgradeCommandOptions): Promise<Upgra
71
71
  }),
72
72
  );
73
73
  }
74
- if (!staged.changed) {
74
+ if (!staged.changed && staged.checksOk) {
75
75
  if (!opts.json) console.error(formatSuccess(`scope "${staged.scope}" is already at ${staged.to ?? "its source"}; nothing to upgrade`));
76
76
  return report("up-to-date", 0);
77
77
  }
@@ -124,6 +124,76 @@ describe("chant workspace upgrade", () => {
124
124
  expect(git(proj, ["worktree", "list"]).split("\n")).toHaveLength(1);
125
125
  });
126
126
 
127
+ test("builds and lints the chant member chant.workspace.json declares, not the scope root (#2804)", async () => {
128
+ release("v2.0.0", {
129
+ "chant.workspace.json": JSON.stringify({ name: "proj", schema: 1, members: [{ name: "delivery", dir: "delivery", kind: "chant" }] }, null, 2) + "\n",
130
+ "delivery/chant.config.ts": "export default {};\n",
131
+ });
132
+ const calls: Array<{ command: string; cwd: string }> = [];
133
+ const recording: ChantRunner = async (command, cwd) => {
134
+ calls.push({ command, cwd });
135
+ return { exitCode: 0, output: "" };
136
+ };
137
+
138
+ const staged = await stageUpgrade({ root: proj, to: "v2.0.0", runChant: recording });
139
+ try {
140
+ // Never at the scope root: it has no chant.config once the project is a workspace.
141
+ expect(calls.every((c) => c.cwd === join(staged.worktreeProject, "delivery"))).toBe(true);
142
+ expect(calls.map((c) => c.command).sort()).toEqual(["build", "lint"]);
143
+ expect(staged.checks).toEqual(
144
+ expect.arrayContaining([
145
+ { name: "build", status: "passed", member: "delivery" },
146
+ { name: "lint", status: "passed", member: "delivery" },
147
+ ]),
148
+ );
149
+ } finally {
150
+ staged.dispose();
151
+ }
152
+ });
153
+
154
+ test("a member's own node_modules is linked into the staging worktree for its checks, and stays out of the patch (#2847)", async () => {
155
+ release("v2.0.0", {
156
+ "chant.workspace.json": JSON.stringify({ name: "proj", schema: 1, members: [{ name: "delivery", dir: "delivery", kind: "chant" }] }, null, 2) + "\n",
157
+ "delivery/chant.config.ts": "export default {};\n",
158
+ });
159
+ // The member installs its packages itself; git never sees them.
160
+ put(proj, "delivery/node_modules/member-only/package.json", JSON.stringify({ name: "member-only", version: "1.0.0" }) + "\n");
161
+ writeFileSync(join(proj, ".git", "info", "exclude"), "node_modules\n");
162
+ const seen: string[] = [];
163
+ const recording: ChantRunner = async (command, cwd) => {
164
+ if (existsSync(join(cwd, "node_modules", "member-only", "package.json"))) seen.push(command);
165
+ return { exitCode: 0, output: "" };
166
+ };
167
+
168
+ const staged = await stageUpgrade({ root: proj, to: "v2.0.0", runChant: recording });
169
+ try {
170
+ expect(seen.sort()).toEqual(["build", "lint"]);
171
+ expect(staged.changedPaths.some((p) => p.includes("node_modules"))).toBe(false);
172
+ expect(staged.patch).not.toContain("node_modules");
173
+ } finally {
174
+ staged.dispose();
175
+ }
176
+ // Disposing the worktree removes the link, never the member's own packages.
177
+ expect(existsSync(join(proj, "delivery", "node_modules", "member-only", "package.json"))).toBe(true);
178
+ });
179
+
180
+ test("a member's failing build fails the upgrade's checks before the gate (#2804)", async () => {
181
+ release("v2.0.0", {
182
+ "chant.workspace.json": JSON.stringify({ name: "proj", schema: 1, members: [{ name: "delivery", dir: "delivery", kind: "chant" }] }, null, 2) + "\n",
183
+ "delivery/chant.config.ts": "export default {};\n",
184
+ });
185
+ const failingBuild: ChantRunner = async (command) => (command === "build" ? { exitCode: 1, output: "boom\n" } : { exitCode: 0, output: "" });
186
+
187
+ const result = await upgradeCommand({ root: proj, to: "v2.0.0", runChant: failingBuild });
188
+ expect(result.outcome).toBe("checks-failed");
189
+ expect(result.exitCode).toBe(1);
190
+ expect(result.staged!.checks).toEqual(
191
+ expect.arrayContaining([{ name: "build", status: "failed", detail: "boom", member: "delivery" }]),
192
+ );
193
+ // The tree is unchanged: an unbuildable migration never reaches the gate.
194
+ expect(git(proj, ["status", "--porcelain"])).toBe("");
195
+ });
196
+
127
197
  test("gates on the patch digest, then applies exactly the approved patch", async () => {
128
198
  release("v2.0.0", { "README.md": "starter v2\n" });
129
199
  const port = ledger();