@intentius/chant 0.90.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 (402) 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/components.d.ts +8 -0
  4. package/dist/cli/handlers/components.d.ts.map +1 -1
  5. package/dist/cli/handlers/graph.d.ts.map +1 -1
  6. package/dist/cli/handlers/operator.d.ts +20 -0
  7. package/dist/cli/handlers/operator.d.ts.map +1 -1
  8. package/dist/cli/handlers/run.d.ts.map +1 -1
  9. package/dist/cli/main.d.ts.map +1 -1
  10. package/dist/cli/mcp/workspace-tools.d.ts +7 -4
  11. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  12. package/dist/cli/plugins.d.ts.map +1 -1
  13. package/dist/cli/registry.d.ts +42 -1
  14. package/dist/cli/registry.d.ts.map +1 -1
  15. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  16. package/dist/components/verbs/vuln-scan.d.ts +72 -0
  17. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  18. package/dist/config.d.ts +22 -0
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/lexicon-module.d.ts +22 -0
  21. package/dist/lexicon-module.d.ts.map +1 -1
  22. package/dist/lifecycle/converge-ledger.d.ts +19 -0
  23. package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
  24. package/dist/lifecycle/git.d.ts +40 -5
  25. package/dist/lifecycle/git.d.ts.map +1 -1
  26. package/dist/lifecycle/lease.d.ts +72 -16
  27. package/dist/lifecycle/lease.d.ts.map +1 -1
  28. package/dist/lifecycle/member-ledger.d.ts +3 -2
  29. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  30. package/dist/lifecycle/plan-ledger.d.ts +114 -0
  31. package/dist/lifecycle/plan-ledger.d.ts.map +1 -0
  32. package/dist/lifecycle/run-ledger.d.ts +2 -0
  33. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  34. package/dist/lifecycle/symptoms.d.ts +51 -0
  35. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  36. package/dist/lifecycle/work-lease.d.ts +140 -0
  37. package/dist/lifecycle/work-lease.d.ts.map +1 -0
  38. package/dist/lint/rules/index.d.ts +4 -2
  39. package/dist/lint/rules/index.d.ts.map +1 -1
  40. package/dist/lint/rules/op/index.d.ts +14 -3
  41. package/dist/lint/rules/op/index.d.ts.map +1 -1
  42. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -1
  43. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts +29 -0
  44. package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts.map +1 -0
  45. package/dist/lint/rules/sys001-no-literal-key.d.ts +3 -0
  46. package/dist/lint/rules/sys001-no-literal-key.d.ts.map +1 -0
  47. package/dist/op/__fixtures__/decide-stub-backend.d.ts +53 -0
  48. package/dist/op/__fixtures__/decide-stub-backend.d.ts.map +1 -0
  49. package/dist/op/activities/activity-contracts.d.ts +203 -2
  50. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  51. package/dist/op/activities/apply.d.ts.map +1 -1
  52. package/dist/op/activities/change-coverage.d.ts +44 -0
  53. package/dist/op/activities/change-coverage.d.ts.map +1 -0
  54. package/dist/op/activities/converge.d.ts +24 -1
  55. package/dist/op/activities/converge.d.ts.map +1 -1
  56. package/dist/op/activities/decide.d.ts +107 -0
  57. package/dist/op/activities/decide.d.ts.map +1 -0
  58. package/dist/op/activities/index.d.ts +10 -0
  59. package/dist/op/activities/index.d.ts.map +1 -1
  60. package/dist/op/activities/shell.d.ts +32 -0
  61. package/dist/op/activities/shell.d.ts.map +1 -1
  62. package/dist/op/activities/source-release.d.ts +86 -0
  63. package/dist/op/activities/source-release.d.ts.map +1 -0
  64. package/dist/op/activities/source-rollback.d.ts +100 -0
  65. package/dist/op/activities/source-rollback.d.ts.map +1 -0
  66. package/dist/op/activities/work-evidence.d.ts +42 -0
  67. package/dist/op/activities/work-evidence.d.ts.map +1 -0
  68. package/dist/op/activity-contract.d.ts +1 -0
  69. package/dist/op/activity-contract.d.ts.map +1 -1
  70. package/dist/op/activity-profiles.d.ts +16 -0
  71. package/dist/op/activity-profiles.d.ts.map +1 -1
  72. package/dist/op/builders.d.ts +84 -2
  73. package/dist/op/builders.d.ts.map +1 -1
  74. package/dist/op/composites/converge-op.d.ts +31 -4
  75. package/dist/op/composites/converge-op.d.ts.map +1 -1
  76. package/dist/op/decide-backend.d.ts +116 -0
  77. package/dist/op/decide-backend.d.ts.map +1 -0
  78. package/dist/op/decide-config.d.ts +120 -0
  79. package/dist/op/decide-config.d.ts.map +1 -0
  80. package/dist/op/decide-read-inputs.d.ts +40 -0
  81. package/dist/op/decide-read-inputs.d.ts.map +1 -0
  82. package/dist/op/discover.d.ts +25 -0
  83. package/dist/op/discover.d.ts.map +1 -1
  84. package/dist/op/gate-wait.d.ts +43 -0
  85. package/dist/op/gate-wait.d.ts.map +1 -0
  86. package/dist/op/gate.d.ts +5 -2
  87. package/dist/op/gate.d.ts.map +1 -1
  88. package/dist/op/index.d.ts +18 -5
  89. package/dist/op/index.d.ts.map +1 -1
  90. package/dist/op/lifecycle-receipt-store.d.ts +34 -0
  91. package/dist/op/lifecycle-receipt-store.d.ts.map +1 -0
  92. package/dist/op/local-executor.d.ts +48 -4
  93. package/dist/op/local-executor.d.ts.map +1 -1
  94. package/dist/op/local-output.d.ts +2 -0
  95. package/dist/op/local-output.d.ts.map +1 -1
  96. package/dist/op/op-ir.d.ts +12 -1
  97. package/dist/op/op-ir.d.ts.map +1 -1
  98. package/dist/op/op-verb-class.d.ts.map +1 -1
  99. package/dist/op/operator.d.ts +126 -1
  100. package/dist/op/operator.d.ts.map +1 -1
  101. package/dist/op/runtime.d.ts +46 -5
  102. package/dist/op/runtime.d.ts.map +1 -1
  103. package/dist/op/runtimes/local.d.ts.map +1 -1
  104. package/dist/op/source-archive.d.ts +63 -0
  105. package/dist/op/source-archive.d.ts.map +1 -0
  106. package/dist/op/step-output-ref.d.ts +2 -2
  107. package/dist/op/step-output-ref.d.ts.map +1 -1
  108. package/dist/op/steward-points.d.ts +136 -0
  109. package/dist/op/steward-points.d.ts.map +1 -0
  110. package/dist/op/steward-turn.d.ts +53 -0
  111. package/dist/op/steward-turn.d.ts.map +1 -0
  112. package/dist/op/steward.d.ts +161 -0
  113. package/dist/op/steward.d.ts.map +1 -0
  114. package/dist/op/types.d.ts +58 -0
  115. package/dist/op/types.d.ts.map +1 -1
  116. package/dist/op/work-lease-decl.d.ts +18 -0
  117. package/dist/op/work-lease-decl.d.ts.map +1 -0
  118. package/dist/op/work-lease-run.d.ts +173 -0
  119. package/dist/op/work-lease-run.d.ts.map +1 -0
  120. package/dist/workspace/__fixtures__/decide-workspace.d.ts +100 -0
  121. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -0
  122. package/dist/workspace/answers-ledger.d.ts +76 -0
  123. package/dist/workspace/answers-ledger.d.ts.map +1 -0
  124. package/dist/workspace/box-isolation.d.ts +99 -0
  125. package/dist/workspace/box-isolation.d.ts.map +1 -0
  126. package/dist/workspace/changes-cli.d.ts +16 -0
  127. package/dist/workspace/changes-cli.d.ts.map +1 -0
  128. package/dist/workspace/changes.d.ts +200 -0
  129. package/dist/workspace/changes.d.ts.map +1 -0
  130. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts +59 -0
  131. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts.map +1 -0
  132. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts +51 -0
  133. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts.map +1 -0
  134. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts +26 -0
  135. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts.map +1 -0
  136. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts +59 -0
  137. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts.map +1 -0
  138. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts +22 -0
  139. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts.map +1 -0
  140. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts +71 -0
  141. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -0
  142. package/dist/workspace/chant-migrations.d.ts +99 -0
  143. package/dist/workspace/chant-migrations.d.ts.map +1 -0
  144. package/dist/workspace/checks/box-isolation.d.ts +18 -0
  145. package/dist/workspace/checks/box-isolation.d.ts.map +1 -0
  146. package/dist/workspace/checks/boxes.d.ts +104 -0
  147. package/dist/workspace/checks/boxes.d.ts.map +1 -0
  148. package/dist/workspace/checks/diagrams.d.ts +31 -0
  149. package/dist/workspace/checks/diagrams.d.ts.map +1 -0
  150. package/dist/workspace/checks/records.d.ts +8 -0
  151. package/dist/workspace/checks/records.d.ts.map +1 -1
  152. package/dist/workspace/checks.d.ts +13 -1
  153. package/dist/workspace/checks.d.ts.map +1 -1
  154. package/dist/workspace/compose-graph.d.ts +2 -0
  155. package/dist/workspace/compose-graph.d.ts.map +1 -1
  156. package/dist/workspace/conformance/index.d.ts.map +1 -1
  157. package/dist/workspace/decide.d.ts +229 -0
  158. package/dist/workspace/decide.d.ts.map +1 -0
  159. package/dist/workspace/decision-points.schema.json +137 -0
  160. package/dist/workspace/declaration.d.ts +103 -0
  161. package/dist/workspace/declaration.d.ts.map +1 -1
  162. package/dist/workspace/declaration.schema.json +285 -0
  163. package/dist/workspace/declared-kinds.d.ts +32 -0
  164. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  165. package/dist/workspace/generated-files.d.ts +7 -0
  166. package/dist/workspace/generated-files.d.ts.map +1 -1
  167. package/dist/workspace/intent.d.ts.map +1 -1
  168. package/dist/workspace/lineage-check.d.ts +3 -0
  169. package/dist/workspace/lineage-check.d.ts.map +1 -1
  170. package/dist/workspace/lineage-upgrade.d.ts +6 -1
  171. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  172. package/dist/workspace/ls.d.ts +39 -1
  173. package/dist/workspace/ls.d.ts.map +1 -1
  174. package/dist/workspace/point-answer.schema.json +95 -0
  175. package/dist/workspace/points-cli.d.ts +113 -0
  176. package/dist/workspace/points-cli.d.ts.map +1 -0
  177. package/dist/workspace/points.d.ts +320 -0
  178. package/dist/workspace/points.d.ts.map +1 -0
  179. package/dist/workspace/reason-codes.d.ts +38 -0
  180. package/dist/workspace/reason-codes.d.ts.map +1 -1
  181. package/dist/workspace/record-assets.d.ts.map +1 -1
  182. package/dist/workspace/records-cli.d.ts +23 -1
  183. package/dist/workspace/records-cli.d.ts.map +1 -1
  184. package/dist/workspace/records-close.d.ts +1 -1
  185. package/dist/workspace/records-close.d.ts.map +1 -1
  186. package/dist/workspace/records-write.d.ts +11 -5
  187. package/dist/workspace/records-write.d.ts.map +1 -1
  188. package/dist/workspace/records.d.ts +42 -6
  189. package/dist/workspace/records.d.ts.map +1 -1
  190. package/dist/workspace/status-gates.d.ts +7 -2
  191. package/dist/workspace/status-gates.d.ts.map +1 -1
  192. package/dist/workspace/status-stewards.d.ts +179 -0
  193. package/dist/workspace/status-stewards.d.ts.map +1 -0
  194. package/dist/workspace/status.d.ts +68 -1
  195. package/dist/workspace/status.d.ts.map +1 -1
  196. package/dist/workspace/work-cli.d.ts +189 -0
  197. package/dist/workspace/work-cli.d.ts.map +1 -0
  198. package/dist/workspace/work-evidence.d.ts +74 -0
  199. package/dist/workspace/work-evidence.d.ts.map +1 -0
  200. package/dist/workspace/work.d.ts +43 -1
  201. package/dist/workspace/work.d.ts.map +1 -1
  202. package/package.json +1 -1
  203. package/src/cli/commands/audit.test.ts +9 -1
  204. package/src/cli/commands/build.ts +3 -2
  205. package/src/cli/commands/lint.ts +7 -1
  206. package/src/cli/handlers/components.test.ts +93 -0
  207. package/src/cli/handlers/components.ts +44 -3
  208. package/src/cli/handlers/graph.test.ts +20 -0
  209. package/src/cli/handlers/graph.ts +16 -2
  210. package/src/cli/handlers/operator.ts +141 -4
  211. package/src/cli/handlers/run.test.ts +173 -2
  212. package/src/cli/handlers/run.ts +174 -3
  213. package/src/cli/main.test.ts +50 -0
  214. package/src/cli/main.ts +97 -2
  215. package/src/cli/mcp/workspace-tools.test.ts +48 -19
  216. package/src/cli/mcp/workspace-tools.ts +110 -8
  217. package/src/cli/plugins.ts +11 -3
  218. package/src/cli/registry.ts +42 -1
  219. package/src/codegen/pinned-upgrade.ts +2 -1
  220. package/src/codegen/release-wiring.test.ts +49 -2
  221. package/src/components/capability-plugin-loader.ts +3 -3
  222. package/src/components/cli-support.ts +2 -2
  223. package/src/components/verbs/vuln-scan.test.ts +124 -1
  224. package/src/components/verbs/vuln-scan.ts +142 -1
  225. package/src/config.ts +11 -0
  226. package/src/lexicon-module.ts +56 -1
  227. package/src/lexicon-package.test.ts +96 -0
  228. package/src/lifecycle/converge-ledger.ts +20 -0
  229. package/src/lifecycle/git.ts +65 -15
  230. package/src/lifecycle/lease.test.ts +22 -0
  231. package/src/lifecycle/lease.ts +133 -29
  232. package/src/lifecycle/member-ledger.ts +3 -2
  233. package/src/lifecycle/plan-ledger.test.ts +148 -0
  234. package/src/lifecycle/plan-ledger.ts +158 -0
  235. package/src/lifecycle/run-ledger.ts +6 -0
  236. package/src/lifecycle/symptoms.ts +95 -0
  237. package/src/lifecycle/work-lease.test.ts +236 -0
  238. package/src/lifecycle/work-lease.ts +426 -0
  239. package/src/lint/rules/index.ts +6 -2
  240. package/src/lint/rules/op/index.ts +17 -4
  241. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +23 -1
  242. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +10 -6
  243. package/src/lint/rules/op/sys010-key-over-plain-http.test.ts +52 -0
  244. package/src/lint/rules/op/sys010-key-over-plain-http.ts +100 -0
  245. package/src/lint/rules/sys001-no-literal-key.test.ts +31 -0
  246. package/src/lint/rules/sys001-no-literal-key.ts +67 -0
  247. package/src/op/__fixtures__/decide-stub-backend.ts +143 -0
  248. package/src/op/activities/activity-contracts.ts +190 -1
  249. package/src/op/activities/apply.ts +7 -6
  250. package/src/op/activities/change-coverage.ts +60 -0
  251. package/src/op/activities/converge.test.ts +55 -1
  252. package/src/op/activities/converge.ts +208 -29
  253. package/src/op/activities/decide.live.test.ts +78 -0
  254. package/src/op/activities/decide.test.ts +300 -0
  255. package/src/op/activities/decide.ts +267 -0
  256. package/src/op/activities/index.ts +21 -0
  257. package/src/op/activities/shell.test.ts +91 -1
  258. package/src/op/activities/shell.ts +91 -2
  259. package/src/op/activities/source-release.test.ts +118 -0
  260. package/src/op/activities/source-release.ts +168 -0
  261. package/src/op/activities/source-rollback.test.ts +131 -0
  262. package/src/op/activities/source-rollback.ts +239 -0
  263. package/src/op/activities/work-evidence.ts +62 -0
  264. package/src/op/activity-contract-registry.test.ts +2 -0
  265. package/src/op/activity-contract-registry.ts +2 -2
  266. package/src/op/activity-contract.test.ts +12 -0
  267. package/src/op/activity-contract.ts +6 -1
  268. package/src/op/activity-profiles.ts +29 -0
  269. package/src/op/activity-registry.test.ts +1 -1
  270. package/src/op/activity-registry.ts +2 -2
  271. package/src/op/builders.ts +124 -5
  272. package/src/op/command-gate.test.ts +192 -0
  273. package/src/op/composites/converge-op.test.ts +27 -1
  274. package/src/op/composites/converge-op.ts +69 -15
  275. package/src/op/decide-backend.test.ts +44 -0
  276. package/src/op/decide-backend.ts +212 -0
  277. package/src/op/decide-config.test.ts +52 -0
  278. package/src/op/decide-config.ts +80 -0
  279. package/src/op/decide-read-inputs.ts +117 -0
  280. package/src/op/discover.ts +71 -0
  281. package/src/op/gate-approval.ts +2 -2
  282. package/src/op/gate-wait.ts +57 -0
  283. package/src/op/gate.ts +8 -3
  284. package/src/op/generate-pipeline.ts +2 -2
  285. package/src/op/index.ts +34 -6
  286. package/src/op/lifecycle-receipt-store.test.ts +60 -0
  287. package/src/op/lifecycle-receipt-store.ts +61 -0
  288. package/src/op/local-executor.ts +367 -33
  289. package/src/op/local-output.test.ts +33 -0
  290. package/src/op/local-output.ts +33 -2
  291. package/src/op/op-ir.test.ts +10 -0
  292. package/src/op/op-ir.ts +18 -0
  293. package/src/op/op-verb-class.ts +1 -0
  294. package/src/op/operator.ts +360 -10
  295. package/src/op/runtime.ts +37 -8
  296. package/src/op/runtimes/local.ts +6 -1
  297. package/src/op/source-archive.ts +161 -0
  298. package/src/op/step-output-ref.ts +6 -2
  299. package/src/op/steward-points.test.ts +313 -0
  300. package/src/op/steward-points.ts +194 -0
  301. package/src/op/steward-turn.ts +79 -0
  302. package/src/op/steward.test.ts +296 -0
  303. package/src/op/steward.ts +277 -0
  304. package/src/op/types.ts +60 -0
  305. package/src/op/work-lease-decl.ts +80 -0
  306. package/src/op/work-lease-run.test.ts +345 -0
  307. package/src/op/work-lease-run.ts +395 -0
  308. package/src/workspace/__fixtures__/decide-workspace.ts +129 -0
  309. package/src/workspace/__fixtures__/studio-template-84c8b21.json +67 -0
  310. package/src/workspace/answers-ledger.ts +118 -0
  311. package/src/workspace/box-isolation.test.ts +261 -0
  312. package/src/workspace/box-isolation.ts +205 -0
  313. package/src/workspace/changes-cli.ts +56 -0
  314. package/src/workspace/changes.schema.json +472 -0
  315. package/src/workspace/changes.test.ts +315 -0
  316. package/src/workspace/changes.ts +512 -0
  317. package/src/workspace/chant-migrations/chud-lexicon-exit-fly-site.ts +234 -0
  318. package/src/workspace/chant-migrations/chud-lexicon-exit-live-names.ts +164 -0
  319. package/src/workspace/chant-migrations/chud-lexicon-exit-rollback.ts +223 -0
  320. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.ts +195 -0
  321. package/src/workspace/chant-migrations/chud-lexicon-exit-ship-policy.ts +108 -0
  322. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +882 -0
  323. package/src/workspace/chant-migrations.e2e.test.ts +1089 -0
  324. package/src/workspace/chant-migrations.ts +164 -0
  325. package/src/workspace/check-contract.test.ts +5 -1
  326. package/src/workspace/check.schema.json +25 -7
  327. package/src/workspace/checks/box-isolation.ts +68 -0
  328. package/src/workspace/checks/boxes.test.ts +276 -0
  329. package/src/workspace/checks/boxes.ts +442 -0
  330. package/src/workspace/checks/diagrams.test.ts +134 -0
  331. package/src/workspace/checks/diagrams.ts +108 -0
  332. package/src/workspace/checks/records.ts +72 -0
  333. package/src/workspace/checks.test.ts +7 -0
  334. package/src/workspace/checks.ts +27 -3
  335. package/src/workspace/compose-graph.ts +2 -0
  336. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +10 -0
  337. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +26 -0
  338. package/src/workspace/conformance/index.ts +5 -0
  339. package/src/workspace/decide.test.ts +224 -0
  340. package/src/workspace/decide.ts +709 -0
  341. package/src/workspace/decision-points.schema.json +137 -0
  342. package/src/workspace/declaration.schema.json +285 -0
  343. package/src/workspace/declaration.test.ts +58 -0
  344. package/src/workspace/declaration.ts +241 -0
  345. package/src/workspace/declared-kinds.test.ts +5 -5
  346. package/src/workspace/declared-kinds.ts +71 -3
  347. package/src/workspace/generated-files.ts +14 -0
  348. package/src/workspace/graph-cli.ts +1 -1
  349. package/src/workspace/graph.schema.json +9 -1
  350. package/src/workspace/intent.schema.json +12 -3
  351. package/src/workspace/intent.ts +3 -10
  352. package/src/workspace/lineage-check.ts +16 -1
  353. package/src/workspace/lineage-upgrade-cli.ts +1 -1
  354. package/src/workspace/lineage-upgrade.test.ts +70 -0
  355. package/src/workspace/lineage-upgrade.ts +81 -10
  356. package/src/workspace/ls-contract.test.ts +57 -0
  357. package/src/workspace/ls.schema.json +46 -1
  358. package/src/workspace/ls.ts +62 -3
  359. package/src/workspace/point-answer.schema.json +95 -0
  360. package/src/workspace/points-cli.ts +284 -0
  361. package/src/workspace/points-write.schema.json +499 -0
  362. package/src/workspace/points.schema.json +743 -0
  363. package/src/workspace/points.test.ts +264 -0
  364. package/src/workspace/points.ts +564 -0
  365. package/src/workspace/read-contract.test.ts +18 -1
  366. package/src/workspace/reason-codes.test.ts +24 -1
  367. package/src/workspace/reason-codes.ts +49 -0
  368. package/src/workspace/record-assets.test.ts +3 -2
  369. package/src/workspace/record-assets.ts +4 -1
  370. package/src/workspace/records-amend.schema.json +2 -0
  371. package/src/workspace/records-cli.ts +28 -5
  372. package/src/workspace/records-close.schema.json +2 -0
  373. package/src/workspace/records-contract.test.ts +3 -2
  374. package/src/workspace/records-new.schema.json +2 -0
  375. package/src/workspace/records-review.schema.json +2 -0
  376. package/src/workspace/records-sessions-write.e2e.test.ts +77 -0
  377. package/src/workspace/records-sessions-write.test.ts +2 -34
  378. package/src/workspace/records-write.e2e.test.ts +108 -0
  379. package/src/workspace/records-write.test.ts +32 -41
  380. package/src/workspace/records-write.ts +33 -15
  381. package/src/workspace/records.schema.json +53 -2
  382. package/src/workspace/records.test.ts +48 -3
  383. package/src/workspace/records.ts +142 -6
  384. package/src/workspace/status-contract.test.ts +189 -6
  385. package/src/workspace/status-gates.ts +9 -4
  386. package/src/workspace/status-stewards.ts +329 -0
  387. package/src/workspace/status.schema.json +335 -5
  388. package/src/workspace/status.ts +142 -6
  389. package/src/workspace/work-acceptance.test.ts +274 -0
  390. package/src/workspace/work-cli.e2e.test.ts +180 -0
  391. package/src/workspace/work-cli.ts +429 -0
  392. package/src/workspace/work-evidence.schema.json +212 -0
  393. package/src/workspace/work-evidence.ts +169 -0
  394. package/src/workspace/work-history.schema.json +349 -0
  395. package/src/workspace/work-history.test.ts +156 -0
  396. package/src/workspace/work-lease.schema.json +233 -0
  397. package/src/workspace/work-readiness-chud.test.ts +145 -0
  398. package/src/workspace/work.test.ts +3 -2
  399. package/src/workspace/work.ts +100 -1
  400. /package/src/cli/{discovery-skip.test.ts → discovery-skip.e2e.test.ts} +0 -0
  401. /package/src/workspace/conformance/{conformance.test.ts → conformance.e2e.test.ts} +0 -0
  402. /package/src/workspace/{which-chant.test.ts → which-chant.e2e.test.ts} +0 -0
@@ -0,0 +1,512 @@
1
+ /**
2
+ * The forward coverage check: `chant workspace check --changes
3
+ * <base>..<head>` (#2773).
4
+ *
5
+ * `graph --intent` reads the join between records and code backwards, from a
6
+ * region to the decisions and work items whose `constrains` cover it. This
7
+ * reads it forwards. Each path a diff changes is mapped to the current
8
+ * records whose `constrains` cover it, and two gaps are findings with closed
9
+ * codes:
10
+ *
11
+ * - `change-uncovered`: no current decided record and no open work item
12
+ * covers the path, by a `path:` entry that is the path or a directory
13
+ * above it, or by the `member:` of the member holding it.
14
+ * - `change-out-of-scope`: a record in hand for the change lists the path in
15
+ * its `out_of_scope` (the field its kind names in `outOfScope`). With
16
+ * `--work <id>`, the records in hand are that work item and the decisions it
17
+ * implements; without it, every current record that covers some path the
18
+ * diff changes.
19
+ *
20
+ * A path the declaration's `changes.ignore` globs match is listed as
21
+ * `ignored`, and a record file of a kind read as `record`: a change to the
22
+ * records is the coverage itself. The declaration's `changes.severity` says
23
+ * what a finding does: `off` reports none, `warn` (the default) reports them
24
+ * and passes, `fail` reports them and fails. Nothing here runs unless the
25
+ * check is asked for, so a workspace that never asks pays nothing.
26
+ *
27
+ * Records are read at `<head>`, so a change that adds the work item covering
28
+ * it is covered. Each finding carries `triage`, the `{ finding, region }` a
29
+ * work item's `source` takes (`work.schema.json`'s `sourceGap`), so the
30
+ * finding-triage decision point (#2741) can seed a work item or a decision
31
+ * from it; see {@link changeFindingSource}. When a work kind is read, each
32
+ * finding also says whether a work item already addresses it, the way `graph
33
+ * --intent` says it for its own findings (#2683), and
34
+ * {@link changeFindingTriageInputs} turns a finding into that point's inputs
35
+ * (#2794). Git is read through a local `git` subprocess only: no fetch, no
36
+ * network.
37
+ */
38
+
39
+ import { execFileSync } from "node:child_process";
40
+ import { relative, resolve, sep } from "node:path";
41
+ // @ts-ignore — picomatch has no types declaration
42
+ import picomatch from "picomatch";
43
+ import { declaredRecordKinds, readDeclaration, readerVersion, WORKSPACE_ERROR_CODES, WorkspaceReadError, type ChangeSeverity } from "./declaration";
44
+ import { declaredKindFile } from "./declared-kinds";
45
+ import { isGeneratedPath } from "./generated-files";
46
+ import { parseRegion } from "./intent";
47
+ import { constraintCovers, isWorkspacePath, memberHolding } from "./record-assets";
48
+ import { RecordReadError, type LoadedRecordKind, type RecordEntry } from "./records";
49
+ import { readRecordsFor } from "./records-cli";
50
+ import type { ReasonCode } from "./reason-codes";
51
+ import { joinPath } from "./tree";
52
+ import { locateWorkspace } from "./which-chant";
53
+ import { idList, isDecided } from "./work";
54
+
55
+ /** The version of the `changes` document this chant writes. */
56
+ export const CHANGES_CONTRACT_VERSION = 1;
57
+
58
+ /** `$id` of the JSON Schema for the document, shipped beside this file. */
59
+ export const CHANGES_OUTPUT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/changes/v1/changes.schema.json";
60
+
61
+ /** The finding codes. Closed: a reader may switch on them. */
62
+ export const CHANGES_FINDING_CODES = [
63
+ /** No current decided record and no open work item covers a changed path. */
64
+ "change-uncovered",
65
+ /** A record in hand for the change lists a changed path in its out_of_scope. */
66
+ "change-out-of-scope",
67
+ ] as const satisfies readonly ReasonCode[];
68
+ export type ChangesFindingCode = (typeof CHANGES_FINDING_CODES)[number];
69
+
70
+ /** Why the check failed as a whole: the declaration's codes, the record kind's, or a `--work` item no record has. */
71
+ export const CHANGES_ERROR_CODES = [
72
+ ...WORKSPACE_ERROR_CODES,
73
+ "kind-unreadable",
74
+ "kind-invalid",
75
+ "schema-unreadable",
76
+ "schema-id-mismatch",
77
+ "schema-invalid",
78
+ "location-missing",
79
+ "work-item-unknown",
80
+ ] as const satisfies readonly ReasonCode[];
81
+ export type ChangesErrorCode = (typeof CHANGES_ERROR_CODES)[number];
82
+
83
+ export const CHANGE_SEVERITIES = ["off", "warn", "fail"] as const satisfies readonly ChangeSeverity[];
84
+
85
+ class ChangesError extends Error {
86
+ constructor(
87
+ readonly code: ChangesErrorCode,
88
+ message: string,
89
+ ) {
90
+ super(message);
91
+ this.name = "ChangesError";
92
+ }
93
+ }
94
+
95
+ // ── The document ─────────────────────────────────────────────────────────────
96
+
97
+ /** A record, as `<kind name>/<id>`, and the entry that ties it to the path. */
98
+ export interface ChangeRecordRef {
99
+ record: string;
100
+ state: string | null;
101
+ /** The constrains or out_of_scope entry. */
102
+ entry: string;
103
+ }
104
+
105
+ export type ChangeStatus = "covered" | "uncovered" | "out-of-scope" | "ignored" | "record";
106
+
107
+ export interface ChangedPath {
108
+ /** From the workspace root. */
109
+ path: string;
110
+ change: "added" | "modified" | "deleted" | "renamed";
111
+ /** For a rename, the path it had at base. */
112
+ from: string | null;
113
+ /** The member whose directory holds the path, or null. */
114
+ member: string | null;
115
+ /** Whether the path is a generated file, as `graph --intent` reads a region's `generated` (#2794). */
116
+ generated: boolean;
117
+ status: ChangeStatus;
118
+ /** The current records covering the path, with the entry that covers it. */
119
+ coveredBy: ChangeRecordRef[];
120
+ /** The records in hand that put the path out of scope. */
121
+ outOfScopeBy: ChangeRecordRef[];
122
+ /** For an ignored path, the first glob that matched it. */
123
+ ignoredBy: string | null;
124
+ }
125
+
126
+ export interface ChangeFinding {
127
+ /** `finding:<code>:<path>`: one finding per path. */
128
+ id: string;
129
+ code: ChangesFindingCode;
130
+ path: string;
131
+ message: string;
132
+ severity: "warn" | "fail";
133
+ /** The records the finding concerns, as `<kind name>/<id>`: for out-of-scope, the records that put the path out. */
134
+ records: string[];
135
+ /** What a work item's `source` takes to name this gap (#2741): `{ finding, region }`. */
136
+ triage: { finding: ChangesFindingCode; region: string };
137
+ /**
138
+ * Present when a work kind was read (#2794): whether a work item addresses
139
+ * the finding, because its `source.finding` is this code and its
140
+ * `source.region` is this path or a directory above it.
141
+ */
142
+ addressed?: boolean;
143
+ /** Present when a work kind was read: the work items addressing the finding, as `<kind name>/<id>`, each with its state. */
144
+ addressedBy?: { record: string; state: string | null }[];
145
+ }
146
+
147
+ /** The inputs of the finding-triage decision point (#2741), by the names `decisions/points.json` declares. */
148
+ export type ChangeFindingTriageInputs = {
149
+ "finding.code": ChangesFindingCode;
150
+ "finding.message": string;
151
+ "finding.addressed": boolean;
152
+ "region.path": string;
153
+ "region.member": string | null;
154
+ "region.generated": boolean;
155
+ };
156
+
157
+ interface Head {
158
+ $schema: string;
159
+ contract: number;
160
+ chant: string;
161
+ }
162
+
163
+ export type ChangesDocument =
164
+ | (Head & {
165
+ workspace: { name: string; root: string };
166
+ range: { spec: string; base: string; head: string };
167
+ severity: ChangeSeverity;
168
+ ignore: string[];
169
+ /** The work item in hand, from `--work`, or null. */
170
+ work: { record: string; state: string | null } | null;
171
+ kinds: { file: string; name: string; role: "decision" | "work" | "other" }[];
172
+ paths: ChangedPath[];
173
+ findings: ChangeFinding[];
174
+ summary: { paths: number; covered: number; uncovered: number; outOfScope: number; ignored: number; records: number };
175
+ /** False only when severity is fail and there are findings. */
176
+ ok: boolean;
177
+ })
178
+ | (Head & { error: { code: ChangesErrorCode; message: string } });
179
+
180
+ export interface ChangesQuery {
181
+ /** Where the walk up to the declaration starts. */
182
+ cwd: string;
183
+ /** `<base>..<head>`, `<base>...<head>` (from their merge base), or `<base>` alone, to HEAD. */
184
+ range: string;
185
+ /** Kind files, relative to `cwd`. Left out, every record kind the declaration names. */
186
+ kinds?: string[];
187
+ /** The id of the work item in hand, such as the item an Op's work lease holds. */
188
+ work?: string;
189
+ /** In place of the declaration's `changes.severity`. */
190
+ severity?: ChangeSeverity;
191
+ }
192
+
193
+ export interface ChangesResult {
194
+ doc: ChangesDocument;
195
+ /** The read failed, or severity is fail and there are findings. */
196
+ failed: boolean;
197
+ }
198
+
199
+ /** The `source` a work item seeded from this finding carries (`work.schema.json`'s `sourceGap`): the seam for the finding-triage point (#2741). */
200
+ export function changeFindingSource(finding: Pick<ChangeFinding, "code" | "path">): { finding: ChangesFindingCode; region: string } {
201
+ return { finding: finding.code, region: finding.path };
202
+ }
203
+
204
+ /**
205
+ * The finding-triage point's inputs for a change finding and the entry in
206
+ * `paths` it fired on (#2794), for `askPoint` or the `decide` activity. The
207
+ * same names a caller fills from `graph --intent`'s finding and region nodes.
208
+ * The region is the changed file, so `region.generated` is never null. A
209
+ * document that read no work kind has no `addressed`, and it is passed as
210
+ * false: nothing that was read addresses the finding.
211
+ */
212
+ export function changeFindingTriageInputs(
213
+ finding: Pick<ChangeFinding, "code" | "message" | "path" | "addressed">,
214
+ path: Pick<ChangedPath, "path" | "member" | "generated">,
215
+ ): ChangeFindingTriageInputs {
216
+ if (path.path !== finding.path) throw new Error(`the finding fired on ${finding.path}, and the path given is ${path.path}`);
217
+ return {
218
+ "finding.code": finding.code,
219
+ "finding.message": finding.message,
220
+ "finding.addressed": finding.addressed ?? false,
221
+ "region.path": path.path,
222
+ "region.member": path.member,
223
+ "region.generated": path.generated,
224
+ };
225
+ }
226
+
227
+ // ── Git ──────────────────────────────────────────────────────────────────────
228
+
229
+ function git(top: string, args: string[]): string {
230
+ return execFileSync("git", args, { cwd: top, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 512 * 1024 * 1024 });
231
+ }
232
+
233
+ function commitOf(top: string, rev: string): string {
234
+ if (rev === "" || rev.startsWith("-")) throw new ChangesError("revision-unknown", `--changes needs <base>..<head>, and ${JSON.stringify(rev)} is not a revision`);
235
+ try {
236
+ return git(top, ["rev-parse", "--verify", "--quiet", `${rev}^{commit}`]).trim();
237
+ } catch {
238
+ throw new ChangesError("revision-unknown", `--changes names ${rev}, which is no commit in this repository`);
239
+ }
240
+ }
241
+
242
+ /** The base and head commits of `<base>..<head>`, `<base>...<head>` or `<base>`. */
243
+ function resolveRange(top: string, spec: string): { base: string; head: string } {
244
+ const three = spec.indexOf("...");
245
+ if (three >= 0) {
246
+ const head = commitOf(top, spec.slice(three + 3) || "HEAD");
247
+ const other = commitOf(top, spec.slice(0, three));
248
+ try {
249
+ return { base: git(top, ["merge-base", other, head]).trim(), head };
250
+ } catch {
251
+ throw new ChangesError("revision-unknown", `${spec}: the two revisions have no merge base`);
252
+ }
253
+ }
254
+ const two = spec.indexOf("..");
255
+ if (two >= 0) return { base: commitOf(top, spec.slice(0, two)), head: commitOf(top, spec.slice(two + 2) || "HEAD") };
256
+ return { base: commitOf(top, spec), head: commitOf(top, "HEAD") };
257
+ }
258
+
259
+ /** Each path the diff changes, from the repository root, with renames followed. */
260
+ function diffPaths(top: string, base: string, head: string, prefix: string): { path: string; change: ChangedPath["change"]; from: string | null }[] {
261
+ const out = git(top, ["diff", "--name-status", "-z", "-M", "--no-color", base, head, "--", prefix === "" ? "." : prefix]);
262
+ const tokens = out.split("\0");
263
+ const paths: { path: string; change: ChangedPath["change"]; from: string | null }[] = [];
264
+ for (let i = 0; i < tokens.length && tokens[i] !== ""; ) {
265
+ const status = tokens[i];
266
+ if (status.startsWith("R") || status.startsWith("C")) {
267
+ paths.push({ path: tokens[i + 2], change: status.startsWith("R") ? "renamed" : "added", from: status.startsWith("R") ? tokens[i + 1] : null });
268
+ i += 3;
269
+ } else {
270
+ paths.push({ path: tokens[i + 1], change: status === "A" ? "added" : status === "D" ? "deleted" : "modified", from: null });
271
+ i += 2;
272
+ }
273
+ }
274
+ return paths;
275
+ }
276
+
277
+ // ── The check ────────────────────────────────────────────────────────────────
278
+
279
+ const toPosix = (p: string) => (sep === "/" ? p : p.split(sep).join("/"));
280
+
281
+ interface ReadKind {
282
+ file: string;
283
+ loaded: LoadedRecordKind;
284
+ records: RecordEntry[];
285
+ /** The kind's workspace root, from the repository root; "" for the top. */
286
+ prefix: string;
287
+ role: "decision" | "work" | "other";
288
+ }
289
+
290
+ /** A work record's `source` when it names the gap it came from, as `{ finding, region }`, or null. */
291
+ function gapSource(data: Record<string, unknown> | null): { finding: string; region: string } | null {
292
+ const src = data?.source;
293
+ if (src === null || typeof src !== "object" || Array.isArray(src)) return null;
294
+ const s = src as Record<string, unknown>;
295
+ return typeof s.finding === "string" && typeof s.region === "string" ? { finding: s.finding, region: s.region } : null;
296
+ }
297
+
298
+ /** A record's list field, as strings. */
299
+ function stringList(data: Record<string, unknown> | null, field: string | undefined): string[] {
300
+ const list = field ? data?.[field] : undefined;
301
+ return Array.isArray(list) ? list.filter((x): x is string => typeof x === "string") : [];
302
+ }
303
+
304
+ /** Run the check and build the document. Never throws a {@link WorkspaceReadError} or {@link RecordReadError}. */
305
+ export async function checkChanges(query: ChangesQuery): Promise<ChangesResult> {
306
+ const head: Head = { $schema: CHANGES_OUTPUT_SCHEMA_ID, contract: CHANGES_CONTRACT_VERSION, chant: readerVersion() };
307
+ try {
308
+ const doc = await run(query, head);
309
+ return { doc, failed: !doc.ok };
310
+ } catch (err) {
311
+ if (err instanceof ChangesError || err instanceof WorkspaceReadError || err instanceof RecordReadError) {
312
+ return { doc: { ...head, error: { code: err.code as ChangesErrorCode, message: err.message } }, failed: true };
313
+ }
314
+ throw err;
315
+ }
316
+ }
317
+
318
+ async function run(query: ChangesQuery, head: Head): Promise<Exclude<ChangesDocument, { error: unknown }>> {
319
+ const top = locateWorkspace(query.cwd).top;
320
+ if (!top) throw new ChangesError("not-a-git-repository", "the change check reads a git diff, and this directory is not in a git repository");
321
+ const range = resolveRange(top, query.range);
322
+ // The declaration and the records as the change leaves them.
323
+ const located = locateWorkspace(query.cwd, range.head);
324
+ const declaration = readDeclaration(located.tree, "", { rootChant: true });
325
+ const severity = query.severity ?? declaration.changes?.severity ?? "warn";
326
+ const ignore = declaration.changes?.ignore ?? [];
327
+ const workspacePrefix = located.root === "." ? "" : located.root;
328
+
329
+ const kindFiles = query.kinds?.map((k) => resolve(query.cwd, k)) ?? declaredRecordKinds(declaration).map((d) => declaredKindFile(d, located.rootOnDisk));
330
+ const kinds: ReadKind[] = [];
331
+ for (const file of kindFiles) {
332
+ const read = await readRecordsFor({ kind: file, cwd: query.cwd, at: range.head });
333
+ const { kind } = read.loaded;
334
+ kinds.push({
335
+ file: toPosix(relative(located.rootOnDisk, file)),
336
+ loaded: read.loaded,
337
+ records: read.result.records,
338
+ prefix: read.workspaceRoot === "." ? "" : read.workspaceRoot,
339
+ role: kind.work ? "work" : kind.constrains ? "decision" : "other",
340
+ });
341
+ }
342
+ const ref = (k: ReadKind, r: RecordEntry) => `${k.loaded.kind.name}/${r.id}`;
343
+ /** Current: a decision not superseded and in a decided state, or a work item not closed. Invalid records cover nothing. */
344
+ const current = (k: ReadKind, r: RecordEntry): boolean => {
345
+ if (!r.valid || r.id === null || r.supersededBy !== null) return false;
346
+ if (k.role === "work") return r.state === null || !(k.loaded.kind.closedStates ?? []).includes(r.state);
347
+ return k.role === "decision" && isDecided(k.loaded.kind, r.state);
348
+ };
349
+ /** The `path:` and `member:` entries of a record that cover `full` (from the repository root). */
350
+ const covers = (k: ReadKind, r: RecordEntry, full: string, member: string | null): string[] =>
351
+ stringList(r.data, k.loaded.kind.constrains?.field).filter((entry) => {
352
+ if (entry.startsWith("path:")) {
353
+ const p = entry.slice("path:".length);
354
+ return isWorkspacePath(p) && constraintCovers(joinPath(k.prefix, p), full);
355
+ }
356
+ return entry.startsWith("member:") && member !== null && entry.slice("member:".length) === member;
357
+ });
358
+ /** The out_of_scope entries of a record that cover `full`. */
359
+ const excludes = (k: ReadKind, r: RecordEntry, full: string): string[] =>
360
+ stringList(r.data, k.loaded.kind.outOfScope?.field).filter((p) => isWorkspacePath(p) && constraintCovers(joinPath(k.prefix, p), full));
361
+
362
+ // The work item in hand, and the decisions it implements.
363
+ let work: { kind: ReadKind; record: RecordEntry } | null = null;
364
+ if (query.work !== undefined) {
365
+ for (const k of kinds.filter((x) => x.role === "work")) {
366
+ const r = k.records.find((x) => x.id === query.work);
367
+ if (r) {
368
+ work = { kind: k, record: r };
369
+ break;
370
+ }
371
+ }
372
+ if (!work) throw new ChangesError("work-item-unknown", `--work ${query.work}: no work record of the kinds read has that id`);
373
+ }
374
+
375
+ const isMatch = ignore.length > 0 ? ignore.map((g) => ({ glob: g, match: picomatch(g, { dot: true }) as (p: string) => boolean })) : [];
376
+ const recordFiles = new Set(kinds.flatMap((k) => k.records.map((r) => r.path)));
377
+ const changed = diffPaths(top, range.base, range.head, workspacePrefix);
378
+ const inWorkspace = (full: string) => (workspacePrefix === "" ? full : full.slice(workspacePrefix.length + 1));
379
+
380
+ const paths: ChangedPath[] = changed.map((c) => {
381
+ const path = inWorkspace(c.path);
382
+ const member = memberHolding(path, declaration.members);
383
+ const coveredBy: ChangeRecordRef[] = [];
384
+ for (const k of kinds) {
385
+ for (const r of k.records) {
386
+ if (!current(k, r)) continue;
387
+ const entries = covers(k, r, c.path, member);
388
+ if (entries.length > 0 && excludes(k, r, c.path).length === 0) coveredBy.push({ record: ref(k, r), state: r.state, entry: entries[0] });
389
+ }
390
+ }
391
+ const ignoredBy = isMatch.find((m) => m.match(path))?.glob ?? null;
392
+ const status: ChangeStatus = recordFiles.has(c.path) ? "record" : ignoredBy !== null ? "ignored" : coveredBy.length > 0 ? "covered" : "uncovered";
393
+ const generated = isGeneratedPath(declaration, path);
394
+ return { path, change: c.change, from: c.from === null ? null : inWorkspace(c.from), member, generated, status, coveredBy, outOfScopeBy: [], ignoredBy };
395
+ });
396
+
397
+ // The records in hand: the work item and what it implements, or every current record covering some changed path.
398
+ const inHand: { kind: ReadKind; record: RecordEntry }[] = [];
399
+ if (work) {
400
+ inHand.push(work);
401
+ const implemented = new Set(idList(work.record.data, work.kind.loaded.kind.work!.implements));
402
+ for (const k of kinds.filter((x) => x.role === "decision")) for (const r of k.records) if (r.id !== null && implemented.has(r.id)) inHand.push({ kind: k, record: r });
403
+ } else {
404
+ for (const k of kinds) {
405
+ for (const r of k.records) {
406
+ if (!current(k, r)) continue;
407
+ if (changed.some((c) => covers(k, r, c.path, memberHolding(inWorkspace(c.path), declaration.members)).length > 0)) inHand.push({ kind: k, record: r });
408
+ }
409
+ }
410
+ }
411
+ for (const [i, p] of paths.entries()) {
412
+ if (p.status === "record" || p.status === "ignored") continue;
413
+ for (const h of inHand) {
414
+ const entries = excludes(h.kind, h.record, changed[i].path);
415
+ if (entries.length > 0) p.outOfScopeBy.push({ record: ref(h.kind, h.record), state: h.record.state, entry: entries[0] });
416
+ }
417
+ if (p.outOfScopeBy.length > 0) p.status = "out-of-scope";
418
+ }
419
+
420
+ // A work item addresses a finding when it came from that gap (#2794), as
421
+ // graph --intent's fromGap reads it (#2683): the item's source names the
422
+ // finding's code, on a region that is the path or a directory above it.
423
+ // Every work item with an id counts, in any state, as there: a done item
424
+ // whose gap fires again still says the gap was taken once. Only the gap
425
+ // source counts. graph --intent also counts an item that implements a
426
+ // decision a pin finding concerns, and no change finding concerns a
427
+ // decision that way.
428
+ const workKinds = kinds.filter((k) => k.role === "work");
429
+ const addressing = (code: ChangesFindingCode, full: string): { record: string; state: string | null }[] => {
430
+ const by: { record: string; state: string | null }[] = [];
431
+ for (const k of workKinds) {
432
+ for (const r of k.records) {
433
+ if (r.id === null) continue;
434
+ const src = gapSource(r.data);
435
+ if (src === null || src.finding !== code) continue;
436
+ const parsed = parseRegion(src.region);
437
+ if ("error" in parsed) continue;
438
+ const named = joinPath(k.prefix, parsed.path);
439
+ // A change finding is about a whole file, so a line range on the same file still names it.
440
+ if (named === full || named === "" || full.startsWith(`${named}/`)) by.push({ record: ref(k, r), state: r.state });
441
+ }
442
+ }
443
+ return by;
444
+ };
445
+
446
+ const findings: ChangeFinding[] = [];
447
+ if (severity !== "off") {
448
+ for (const [i, p] of paths.entries()) {
449
+ let f: ChangeFinding | null = null;
450
+ if (p.status === "uncovered") {
451
+ const where = p.member ? `, in member ${p.member},` : "";
452
+ f = finding("change-uncovered", p, severity, `${p.path}${where} is ${p.change}, and no current decided record or open work item covers it by path or member`, []);
453
+ } else if (p.status === "out-of-scope") {
454
+ const by = p.outOfScopeBy.map((o) => `${o.record} (${o.entry})`).join(", ");
455
+ f = finding("change-out-of-scope", p, severity, `${p.path} is ${p.change}, and ${by} ${p.outOfScopeBy.length === 1 ? "puts" : "put"} it out of scope`, p.outOfScopeBy.map((o) => o.record));
456
+ }
457
+ if (f === null) continue;
458
+ if (workKinds.length > 0) {
459
+ f.addressedBy = addressing(f.code, changed[i].path);
460
+ f.addressed = f.addressedBy.length > 0;
461
+ }
462
+ findings.push(f);
463
+ }
464
+ }
465
+ const count = (s: ChangeStatus) => paths.filter((p) => p.status === s).length;
466
+ return {
467
+ ...head,
468
+ workspace: { name: declaration.name, root: located.root },
469
+ range: { spec: query.range, base: range.base, head: range.head },
470
+ severity,
471
+ ignore,
472
+ work: work ? { record: ref(work.kind, work.record), state: work.record.state } : null,
473
+ kinds: kinds.map((k) => ({ file: k.file, name: k.loaded.kind.name, role: k.role })),
474
+ paths,
475
+ findings,
476
+ summary: { paths: paths.length, covered: count("covered"), uncovered: count("uncovered"), outOfScope: count("out-of-scope"), ignored: count("ignored"), records: count("record") },
477
+ ok: !(severity === "fail" && findings.length > 0),
478
+ };
479
+ }
480
+
481
+ function finding(code: ChangesFindingCode, p: ChangedPath, severity: "warn" | "fail", message: string, records: string[]): ChangeFinding {
482
+ return { id: `finding:${code}:${p.path}`, code, path: p.path, message, severity, records, triage: changeFindingSource({ code, path: p.path }) };
483
+ }
484
+
485
+ // ── Under a work lease ───────────────────────────────────────────────────────
486
+
487
+ /**
488
+ * For a checkout on a work branch, `chant/work/[<member>/]<id>`, as an Op
489
+ * with `changesCheckout` works in (#2748): the range from where the branch
490
+ * left the main worktree's HEAD to its own HEAD, and the item's id. Undefined
491
+ * on any other branch.
492
+ */
493
+ export function workBranchChanges(cwd: string): { range: string; work: string } | undefined {
494
+ let branch: string;
495
+ try {
496
+ branch = git(cwd, ["symbolic-ref", "--quiet", "--short", "HEAD"]).trim();
497
+ } catch {
498
+ return undefined;
499
+ }
500
+ if (!branch.startsWith("chant/work/")) return undefined;
501
+ const id = branch.slice(branch.lastIndexOf("/") + 1);
502
+ // The main worktree comes first in the list.
503
+ const list = git(cwd, ["worktree", "list", "--porcelain"]);
504
+ const mainHead = list.split("\n").find((l) => l.startsWith("HEAD "))?.slice("HEAD ".length).trim();
505
+ if (!mainHead) return undefined;
506
+ try {
507
+ const base = git(cwd, ["merge-base", mainHead, "HEAD"]).trim();
508
+ return { range: `${base}..HEAD`, work: id };
509
+ } catch {
510
+ return undefined;
511
+ }
512
+ }