@intentius/chant 0.124.45 → 0.124.48

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 (772) hide show
  1. package/dist/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
  2. package/dist/__golden__/wave-loop/op-by-apply-hold.json +10 -0
  3. package/dist/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
  4. package/dist/__golden__/wave-loop/op-by-apply-pass.json +10 -0
  5. package/dist/__golden__/wave-loop/op-by-apply-skip.json +10 -0
  6. package/dist/__golden__/wave-loop/op-cost-member-clash.json +6 -0
  7. package/dist/__golden__/wave-loop/op-cost-member.json +13 -0
  8. package/dist/__golden__/wave-loop/op-denied-source.json +16 -0
  9. package/dist/__golden__/wave-loop/op-denied.json +16 -0
  10. package/dist/__golden__/wave-loop/op-discover-rest-short.json +6 -0
  11. package/dist/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
  12. package/dist/__golden__/wave-loop/op-discover-rest.json +26 -0
  13. package/dist/__golden__/wave-loop/op-discover-wide.json +26 -0
  14. package/dist/__golden__/wave-loop/op-held-on-again.json +16 -0
  15. package/dist/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
  16. package/dist/__golden__/wave-loop/op-held-on.json +16 -0
  17. package/dist/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
  18. package/dist/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
  19. package/dist/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
  20. package/dist/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
  21. package/dist/__golden__/wave-loop/op-hooks-held.json +13 -0
  22. package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
  23. package/dist/__golden__/wave-loop/op-hooks-pass.json +13 -0
  24. package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
  25. package/dist/__golden__/wave-loop/op-hooks-share.json +33 -0
  26. package/dist/__golden__/wave-loop/op-outcome-held.json +10 -0
  27. package/dist/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
  28. package/dist/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
  29. package/dist/__golden__/wave-loop/op-outcome-replan.json +10 -0
  30. package/dist/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
  31. package/dist/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
  32. package/dist/__golden__/wave-loop/op-overridden.json +16 -0
  33. package/dist/__golden__/wave-loop/op-per-run.json +20 -0
  34. package/dist/__golden__/wave-loop/op-point.json +13 -0
  35. package/dist/__golden__/wave-loop/op-pr-review.json +10 -0
  36. package/dist/__golden__/wave-loop/op-sealed.json +10 -0
  37. package/dist/__golden__/wave-loop/op-spend-change-only.json +10 -0
  38. package/dist/__golden__/wave-loop/op-spend.json +10 -0
  39. package/dist/__golden__/wave-loop/op-wave-commands.json +55 -0
  40. package/dist/__golden__/wave-loop/op-wide-refused.json +26 -0
  41. package/dist/__golden__/wave-loop/op-wide-waiting.json +29 -0
  42. package/dist/__golden__/wave-loop/op-wide.json +39 -0
  43. package/dist/action-pins.d.ts +61 -0
  44. package/dist/action-pins.d.ts.map +1 -0
  45. package/dist/action-pins.js +77 -0
  46. package/dist/capabilities/aws.paths +148 -0
  47. package/dist/capabilities/base-mappings.json +122 -0
  48. package/dist/capabilities/catalog.d.ts +89 -0
  49. package/dist/capabilities/catalog.d.ts.map +1 -0
  50. package/dist/capabilities/catalog.js +204 -0
  51. package/dist/capabilities/language.d.ts +103 -0
  52. package/dist/capabilities/language.d.ts.map +1 -0
  53. package/dist/capabilities/language.js +335 -0
  54. package/dist/capabilities/matcher.d.ts +65 -0
  55. package/dist/capabilities/matcher.d.ts.map +1 -0
  56. package/dist/capabilities/matcher.js +299 -0
  57. package/dist/capabilities/view.d.ts +207 -0
  58. package/dist/capabilities/view.d.ts.map +1 -0
  59. package/dist/capabilities/view.js +484 -0
  60. package/dist/cli/commands/carve.d.ts +3 -0
  61. package/dist/cli/commands/carve.d.ts.map +1 -1
  62. package/dist/cli/commands/carve.js +36 -7
  63. package/dist/cli/commands/import-report.d.ts +26 -0
  64. package/dist/cli/commands/import-report.d.ts.map +1 -0
  65. package/dist/cli/commands/import-report.js +65 -0
  66. package/dist/cli/commands/import.d.ts +11 -1
  67. package/dist/cli/commands/import.d.ts.map +1 -1
  68. package/dist/cli/commands/import.js +22 -3
  69. package/dist/cli/commands/lint.d.ts.map +1 -1
  70. package/dist/cli/commands/lint.js +1 -0
  71. package/dist/cli/commands/migrate.d.ts +13 -1
  72. package/dist/cli/commands/migrate.d.ts.map +1 -1
  73. package/dist/cli/commands/migrate.js +134 -10
  74. package/dist/cli/handlers/approve.d.ts.map +1 -1
  75. package/dist/cli/handlers/approve.js +21 -2
  76. package/dist/cli/handlers/build.d.ts.map +1 -1
  77. package/dist/cli/handlers/build.js +6 -1
  78. package/dist/cli/handlers/declared-graph.d.ts +44 -0
  79. package/dist/cli/handlers/declared-graph.d.ts.map +1 -0
  80. package/dist/cli/handlers/declared-graph.js +87 -0
  81. package/dist/cli/handlers/graph.d.ts +25 -0
  82. package/dist/cli/handlers/graph.d.ts.map +1 -1
  83. package/dist/cli/handlers/graph.js +93 -87
  84. package/dist/cli/handlers/ledger.d.ts +3 -0
  85. package/dist/cli/handlers/ledger.d.ts.map +1 -0
  86. package/dist/cli/handlers/ledger.js +55 -0
  87. package/dist/cli/handlers/migrate.d.ts.map +1 -1
  88. package/dist/cli/handlers/migrate.js +18 -11
  89. package/dist/cli/handlers/misc.d.ts.map +1 -1
  90. package/dist/cli/handlers/misc.js +12 -1
  91. package/dist/cli/handlers/pr-env.d.ts.map +1 -1
  92. package/dist/cli/handlers/pr-env.js +4 -3
  93. package/dist/cli/handlers/pr.d.ts +15 -1
  94. package/dist/cli/handlers/pr.d.ts.map +1 -1
  95. package/dist/cli/handlers/pr.js +143 -39
  96. package/dist/cli/handlers/query.d.ts +7 -2
  97. package/dist/cli/handlers/query.d.ts.map +1 -1
  98. package/dist/cli/handlers/query.js +16 -4
  99. package/dist/cli/handlers/release.d.ts.map +1 -1
  100. package/dist/cli/handlers/release.js +6 -3
  101. package/dist/cli/handlers/search-count.d.ts +89 -0
  102. package/dist/cli/handlers/search-count.d.ts.map +1 -0
  103. package/dist/cli/handlers/search-count.js +132 -0
  104. package/dist/cli/handlers/search.d.ts +356 -6
  105. package/dist/cli/handlers/search.d.ts.map +1 -1
  106. package/dist/cli/handlers/search.js +1039 -155
  107. package/dist/cli/main.d.ts.map +1 -1
  108. package/dist/cli/main.js +88 -17
  109. package/dist/cli/mcp/server.d.ts.map +1 -1
  110. package/dist/cli/mcp/server.js +3 -0
  111. package/dist/cli/mcp/tools/capabilities.d.ts +7 -0
  112. package/dist/cli/mcp/tools/capabilities.d.ts.map +1 -0
  113. package/dist/cli/mcp/tools/capabilities.js +27 -0
  114. package/dist/cli/mcp/workspace-tools.d.ts +3 -1
  115. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  116. package/dist/cli/mcp/workspace-tools.js +47 -2
  117. package/dist/cli/parse-args.d.ts.map +1 -1
  118. package/dist/cli/parse-args.js +100 -19
  119. package/dist/cli/registry.d.ts +36 -4
  120. package/dist/cli/registry.d.ts.map +1 -1
  121. package/dist/components/builders.d.ts +5 -1
  122. package/dist/components/builders.d.ts.map +1 -1
  123. package/dist/components/builders.js +5 -0
  124. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  125. package/dist/components/capability-plugin-loader.js +16 -1
  126. package/dist/components/capability-plugin.d.ts +14 -0
  127. package/dist/components/capability-plugin.d.ts.map +1 -1
  128. package/dist/components/pr-env-ports.d.ts +29 -0
  129. package/dist/components/pr-env-ports.d.ts.map +1 -0
  130. package/dist/components/pr-env-ports.js +77 -0
  131. package/dist/components/pr-env-run.d.ts +5 -21
  132. package/dist/components/pr-env-run.d.ts.map +1 -1
  133. package/dist/components/pr-env-run.js +6 -70
  134. package/dist/components/pr-environment.d.ts +2 -1
  135. package/dist/components/pr-environment.d.ts.map +1 -1
  136. package/dist/components/pr-environment.js +1 -1
  137. package/dist/components/pr-hooks.d.ts +21 -1
  138. package/dist/components/pr-hooks.d.ts.map +1 -1
  139. package/dist/components/pr-hooks.js +72 -4
  140. package/dist/components/pr-pipeline.d.ts +45 -10
  141. package/dist/components/pr-pipeline.d.ts.map +1 -1
  142. package/dist/components/pr-pipeline.js +47 -14
  143. package/dist/components/pr-policy.d.ts +36 -0
  144. package/dist/components/pr-policy.d.ts.map +1 -1
  145. package/dist/components/pr-policy.js +36 -0
  146. package/dist/components/starter-plugin.d.ts +1 -0
  147. package/dist/components/starter-plugin.d.ts.map +1 -1
  148. package/dist/components/starter-plugin.js +5 -1
  149. package/dist/components/verbs/index.d.ts +2 -0
  150. package/dist/components/verbs/index.d.ts.map +1 -1
  151. package/dist/components/verbs/index.js +2 -0
  152. package/dist/components/verbs/publish-artifact.d.ts +65 -0
  153. package/dist/components/verbs/publish-artifact.d.ts.map +1 -0
  154. package/dist/components/verbs/publish-artifact.js +74 -0
  155. package/dist/components/verbs/publish-image.d.ts +129 -0
  156. package/dist/components/verbs/publish-image.d.ts.map +1 -0
  157. package/dist/components/verbs/publish-image.js +145 -0
  158. package/dist/config-environments.d.ts +112 -0
  159. package/dist/config-environments.d.ts.map +1 -0
  160. package/dist/config-environments.js +114 -0
  161. package/dist/config.d.ts +32 -84
  162. package/dist/config.d.ts.map +1 -1
  163. package/dist/config.js +13 -96
  164. package/dist/gate-decision.d.ts +11 -2
  165. package/dist/gate-decision.d.ts.map +1 -1
  166. package/dist/gate-decision.js +15 -3
  167. package/dist/graph-detail.d.ts.map +1 -1
  168. package/dist/graph-detail.js +15 -3
  169. package/dist/graph-effective.d.ts +33 -2
  170. package/dist/graph-effective.d.ts.map +1 -1
  171. package/dist/graph-effective.js +215 -68
  172. package/dist/graph-ir.d.ts +48 -0
  173. package/dist/graph-ir.d.ts.map +1 -1
  174. package/dist/graph-ir.js +130 -0
  175. package/dist/graph-sub-template.d.ts +11 -0
  176. package/dist/graph-sub-template.d.ts.map +1 -0
  177. package/dist/graph-sub-template.js +19 -0
  178. package/dist/graph-walk.d.ts +66 -0
  179. package/dist/graph-walk.d.ts.map +1 -0
  180. package/dist/graph-walk.js +70 -0
  181. package/dist/import/parser.d.ts +19 -0
  182. package/dist/import/parser.d.ts.map +1 -1
  183. package/dist/lexicon.d.ts +80 -6
  184. package/dist/lexicon.d.ts.map +1 -1
  185. package/dist/lifecycle/gate-ledger.d.ts +11 -0
  186. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  187. package/dist/lifecycle/git.d.ts +2 -5
  188. package/dist/lifecycle/git.d.ts.map +1 -1
  189. package/dist/lifecycle/ledger-read.d.ts +65 -0
  190. package/dist/lifecycle/ledger-read.d.ts.map +1 -0
  191. package/dist/lifecycle/ledger-read.js +160 -0
  192. package/dist/lifecycle/observe.d.ts +28 -0
  193. package/dist/lifecycle/observe.d.ts.map +1 -1
  194. package/dist/lifecycle/observe.js +23 -1
  195. package/dist/lifecycle/release-workstation.d.ts +7 -0
  196. package/dist/lifecycle/release-workstation.d.ts.map +1 -1
  197. package/dist/lifecycle/release-workstation.js +25 -0
  198. package/dist/lifecycle/run-ledger.d.ts +4 -3
  199. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  200. package/dist/lifecycle/run-ledger.js +1 -0
  201. package/dist/lint/discover.js +3 -1
  202. package/dist/lint/post-synth.d.ts +127 -1
  203. package/dist/lint/post-synth.d.ts.map +1 -1
  204. package/dist/lint/post-synth.js +84 -2
  205. package/dist/lint/rule.d.ts +6 -0
  206. package/dist/lint/rule.d.ts.map +1 -1
  207. package/dist/lint/rules/op/index.d.ts +1 -0
  208. package/dist/lint/rules/op/index.d.ts.map +1 -1
  209. package/dist/lint/rules/op/index.js +2 -0
  210. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts +32 -0
  211. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts.map +1 -0
  212. package/dist/lint/rules/op/ops019-workspace-op-target.js +63 -0
  213. package/dist/live-endpoint.d.ts +1 -1
  214. package/dist/live-endpoint.d.ts.map +1 -1
  215. package/dist/live-endpoint.js +1 -1
  216. package/dist/observation.d.ts +48 -1
  217. package/dist/observation.d.ts.map +1 -1
  218. package/dist/observation.js +21 -5
  219. package/dist/op/activities/activity-contracts.d.ts +9 -6
  220. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  221. package/dist/op/activities/activity-contracts.js +5 -2
  222. package/dist/op/activities/index.d.ts +2 -2
  223. package/dist/op/activities/index.d.ts.map +1 -1
  224. package/dist/op/activities/index.js +1 -1
  225. package/dist/op/activities/review-agent.d.ts +90 -4
  226. package/dist/op/activities/review-agent.d.ts.map +1 -1
  227. package/dist/op/activities/review-agent.js +217 -19
  228. package/dist/op/activities/work-evidence.d.ts +8 -1
  229. package/dist/op/activities/work-evidence.d.ts.map +1 -1
  230. package/dist/op/activities/work-evidence.js +1 -0
  231. package/dist/op/builders.d.ts +9 -5
  232. package/dist/op/builders.d.ts.map +1 -1
  233. package/dist/op/builders.js +9 -5
  234. package/dist/op/composites/apply-op.d.ts +29 -2
  235. package/dist/op/composites/apply-op.d.ts.map +1 -1
  236. package/dist/op/composites/apply-op.js +20 -1
  237. package/dist/op/gate-approval.d.ts +11 -5
  238. package/dist/op/gate-approval.d.ts.map +1 -1
  239. package/dist/op/gate-approval.js +11 -6
  240. package/dist/op/gate-quorum.d.ts +100 -3
  241. package/dist/op/gate-quorum.d.ts.map +1 -1
  242. package/dist/op/gate-quorum.js +108 -6
  243. package/dist/op/gate-review.d.ts +9 -0
  244. package/dist/op/gate-review.d.ts.map +1 -1
  245. package/dist/op/gate-review.js +13 -0
  246. package/dist/op/gate.d.ts +44 -3
  247. package/dist/op/gate.d.ts.map +1 -1
  248. package/dist/op/gate.js +25 -4
  249. package/dist/op/index.d.ts +3 -3
  250. package/dist/op/index.d.ts.map +1 -1
  251. package/dist/op/index.js +1 -1
  252. package/dist/op/local-executor.d.ts +38 -3
  253. package/dist/op/local-executor.d.ts.map +1 -1
  254. package/dist/op/local-executor.js +48 -6
  255. package/dist/op/op-waves-run.d.ts +26 -1
  256. package/dist/op/op-waves-run.d.ts.map +1 -1
  257. package/dist/op/op-waves-run.js +259 -13
  258. package/dist/op/op-waves.d.ts +72 -5
  259. package/dist/op/op-waves.d.ts.map +1 -1
  260. package/dist/op/op-waves.js +110 -15
  261. package/dist/op/runtime.d.ts +11 -0
  262. package/dist/op/runtime.d.ts.map +1 -1
  263. package/dist/op/schedule-overdue.d.ts +16 -0
  264. package/dist/op/schedule-overdue.d.ts.map +1 -1
  265. package/dist/op/schedule-overdue.js +36 -0
  266. package/dist/op/stage-hooks.d.ts +9 -2
  267. package/dist/op/stage-hooks.d.ts.map +1 -1
  268. package/dist/op/stage-hooks.js +7 -5
  269. package/dist/op/workspace-run-env.d.ts +22 -0
  270. package/dist/op/workspace-run-env.d.ts.map +1 -0
  271. package/dist/op/workspace-run-env.js +21 -0
  272. package/dist/pr-forge.d.ts +24 -0
  273. package/dist/pr-forge.d.ts.map +1 -1
  274. package/dist/pr-forge.js +47 -0
  275. package/dist/pr-post.d.ts +42 -0
  276. package/dist/pr-post.d.ts.map +1 -1
  277. package/dist/pr-post.js +67 -1
  278. package/dist/read-filter.d.ts +65 -0
  279. package/dist/read-filter.d.ts.map +1 -0
  280. package/dist/read-filter.js +179 -0
  281. package/dist/run-event.d.ts +1 -1
  282. package/dist/run-event.d.ts.map +1 -1
  283. package/dist/terraform/carve.d.ts +24 -3
  284. package/dist/terraform/carve.d.ts.map +1 -1
  285. package/dist/terraform/carve.js +32 -14
  286. package/dist/terraform/cuts.d.ts +97 -0
  287. package/dist/terraform/cuts.d.ts.map +1 -0
  288. package/dist/terraform/cuts.js +138 -0
  289. package/dist/terraform/folds.d.ts +26 -0
  290. package/dist/terraform/folds.d.ts.map +1 -0
  291. package/dist/terraform/folds.js +38 -0
  292. package/dist/terraform/graph.d.ts.map +1 -1
  293. package/dist/terraform/graph.js +10 -0
  294. package/dist/terraform/reach.d.ts +140 -0
  295. package/dist/terraform/reach.d.ts.map +1 -0
  296. package/dist/terraform/reach.js +170 -0
  297. package/dist/terraform/score.d.ts +14 -0
  298. package/dist/terraform/score.d.ts.map +1 -1
  299. package/dist/terraform/score.js +14 -31
  300. package/dist/terraform/state.d.ts.map +1 -1
  301. package/dist/terraform/state.js +4 -2
  302. package/dist/terraform/types.d.ts +8 -0
  303. package/dist/terraform/types.d.ts.map +1 -1
  304. package/dist/wave-blocked.d.ts +10 -1
  305. package/dist/wave-blocked.d.ts.map +1 -1
  306. package/dist/wave-blocked.js +31 -3
  307. package/dist/wave-runtime.mjs +266 -256
  308. package/dist/workspace/capabilities-contract.d.ts +66 -0
  309. package/dist/workspace/capabilities-contract.d.ts.map +1 -0
  310. package/dist/workspace/capabilities-contract.js +107 -0
  311. package/dist/workspace/capabilities.schema.json +229 -0
  312. package/dist/workspace/check.schema.json +11 -2
  313. package/dist/workspace/checks/ci-branch.d.ts +44 -0
  314. package/dist/workspace/checks/ci-branch.d.ts.map +1 -0
  315. package/dist/workspace/checks/ci-branch.js +133 -0
  316. package/dist/workspace/checks/links.d.ts +3 -0
  317. package/dist/workspace/checks/links.d.ts.map +1 -1
  318. package/dist/workspace/checks/links.js +12 -1
  319. package/dist/workspace/checks/org.d.ts +48 -0
  320. package/dist/workspace/checks/org.d.ts.map +1 -0
  321. package/dist/workspace/checks/org.js +156 -0
  322. package/dist/workspace/checks.d.ts +11 -1
  323. package/dist/workspace/checks.d.ts.map +1 -1
  324. package/dist/workspace/checks.js +59 -7
  325. package/dist/workspace/ci-branches.d.ts +16 -0
  326. package/dist/workspace/ci-branches.d.ts.map +1 -1
  327. package/dist/workspace/ci-branches.js +27 -0
  328. package/dist/workspace/ci-green-cli.js +7 -7
  329. package/dist/workspace/ci-green-workflow.d.ts +1 -1
  330. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  331. package/dist/workspace/ci-green-workflow.js +13 -8
  332. package/dist/workspace/ci-green.d.ts +14 -0
  333. package/dist/workspace/ci-green.d.ts.map +1 -1
  334. package/dist/workspace/ci-green.js +44 -11
  335. package/dist/workspace/ci-run.d.ts +9 -1
  336. package/dist/workspace/ci-run.d.ts.map +1 -1
  337. package/dist/workspace/ci-run.js +67 -6
  338. package/dist/workspace/ci-serve.d.ts +9 -0
  339. package/dist/workspace/ci-serve.d.ts.map +1 -1
  340. package/dist/workspace/ci-serve.js +114 -76
  341. package/dist/workspace/compose-graph.d.ts +23 -0
  342. package/dist/workspace/compose-graph.d.ts.map +1 -1
  343. package/dist/workspace/compose-graph.js +3 -1
  344. package/dist/workspace/composites.d.ts +4 -1
  345. package/dist/workspace/composites.d.ts.map +1 -1
  346. package/dist/workspace/composites.js +5 -2
  347. package/dist/workspace/composites.schema.json +22 -0
  348. package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  349. package/dist/workspace/conformance/index.d.ts +1 -1
  350. package/dist/workspace/conformance/index.d.ts.map +1 -1
  351. package/dist/workspace/conformance/index.js +39 -2
  352. package/dist/workspace/cross-root-lint.d.ts +56 -0
  353. package/dist/workspace/cross-root-lint.d.ts.map +1 -0
  354. package/dist/workspace/cross-root-lint.js +111 -0
  355. package/dist/workspace/dbt-models-reader.d.ts +44 -0
  356. package/dist/workspace/dbt-models-reader.d.ts.map +1 -0
  357. package/dist/workspace/dbt-models-reader.js +356 -0
  358. package/dist/workspace/dbt-project-common.d.ts +40 -0
  359. package/dist/workspace/dbt-project-common.d.ts.map +1 -0
  360. package/dist/workspace/dbt-project-common.js +106 -0
  361. package/dist/workspace/dbt-project-reader.d.ts +4 -3
  362. package/dist/workspace/dbt-project-reader.d.ts.map +1 -1
  363. package/dist/workspace/dbt-project-reader.js +14 -108
  364. package/dist/workspace/decision-rank.d.ts +26 -0
  365. package/dist/workspace/decision-rank.d.ts.map +1 -0
  366. package/dist/workspace/decision-rank.js +16 -0
  367. package/dist/workspace/declaration.d.ts +43 -2
  368. package/dist/workspace/declaration.d.ts.map +1 -1
  369. package/dist/workspace/declaration.js +45 -11
  370. package/dist/workspace/declaration.schema.json +133 -24
  371. package/dist/workspace/environments.d.ts +26 -2
  372. package/dist/workspace/environments.d.ts.map +1 -1
  373. package/dist/workspace/environments.js +26 -2
  374. package/dist/workspace/flyway-dir.d.ts +39 -0
  375. package/dist/workspace/flyway-dir.d.ts.map +1 -0
  376. package/dist/workspace/flyway-dir.js +104 -0
  377. package/dist/workspace/flyway-layout.d.ts +96 -0
  378. package/dist/workspace/flyway-layout.d.ts.map +1 -0
  379. package/dist/workspace/flyway-layout.js +308 -0
  380. package/dist/workspace/graph-cache.d.ts +8 -0
  381. package/dist/workspace/graph-cache.d.ts.map +1 -1
  382. package/dist/workspace/graph-cache.js +27 -3
  383. package/dist/workspace/graph-cli.d.ts +39 -2
  384. package/dist/workspace/graph-cli.d.ts.map +1 -1
  385. package/dist/workspace/graph-cli.js +92 -32
  386. package/dist/workspace/graph.schema.json +182 -0
  387. package/dist/workspace/intent.d.ts +2 -3
  388. package/dist/workspace/intent.d.ts.map +1 -1
  389. package/dist/workspace/intent.js +5 -9
  390. package/dist/workspace/kind-link-readers.d.ts +59 -2
  391. package/dist/workspace/kind-link-readers.d.ts.map +1 -1
  392. package/dist/workspace/kind-link-readers.js +17 -1
  393. package/dist/workspace/kind-readers.d.ts +3 -0
  394. package/dist/workspace/kind-readers.d.ts.map +1 -1
  395. package/dist/workspace/kind-readers.js +7 -1
  396. package/dist/workspace/kinds.d.ts +66 -2
  397. package/dist/workspace/kinds.d.ts.map +1 -1
  398. package/dist/workspace/kinds.js +98 -4
  399. package/dist/workspace/ledger.schema.json +158 -0
  400. package/dist/workspace/lexicon-outputs.d.ts +20 -1
  401. package/dist/workspace/lexicon-outputs.d.ts.map +1 -1
  402. package/dist/workspace/lexicon-outputs.js +102 -10
  403. package/dist/workspace/lineage-check.d.ts +3 -0
  404. package/dist/workspace/lineage-check.d.ts.map +1 -1
  405. package/dist/workspace/lineage-check.js +23 -4
  406. package/dist/workspace/links.d.ts +8 -1
  407. package/dist/workspace/links.d.ts.map +1 -1
  408. package/dist/workspace/links.js +25 -1
  409. package/dist/workspace/ls.d.ts +8 -0
  410. package/dist/workspace/ls.d.ts.map +1 -1
  411. package/dist/workspace/ls.js +7 -1
  412. package/dist/workspace/ls.schema.json +7 -0
  413. package/dist/workspace/member-commands.d.ts +10 -1
  414. package/dist/workspace/member-commands.d.ts.map +1 -1
  415. package/dist/workspace/member-commands.js +71 -6
  416. package/dist/workspace/migrate-links.d.ts +45 -0
  417. package/dist/workspace/migrate-links.d.ts.map +1 -0
  418. package/dist/workspace/migrate-links.js +100 -0
  419. package/dist/workspace/migration-dirs.d.ts +74 -0
  420. package/dist/workspace/migration-dirs.d.ts.map +1 -0
  421. package/dist/workspace/migration-dirs.js +254 -0
  422. package/dist/workspace/nested-graph.d.ts +2 -0
  423. package/dist/workspace/nested-graph.d.ts.map +1 -1
  424. package/dist/workspace/nested-graph.js +32 -1
  425. package/dist/workspace/reason-codes.d.ts +18 -0
  426. package/dist/workspace/reason-codes.d.ts.map +1 -1
  427. package/dist/workspace/reason-codes.js +23 -0
  428. package/dist/workspace/records-cli.d.ts +11 -0
  429. package/dist/workspace/records-cli.d.ts.map +1 -1
  430. package/dist/workspace/records-cli.js +57 -5
  431. package/dist/workspace/records.schema.json +5 -0
  432. package/dist/workspace/release-candidates.d.ts +2 -0
  433. package/dist/workspace/release-candidates.d.ts.map +1 -1
  434. package/dist/workspace/release-candidates.js +5 -2
  435. package/dist/workspace/release-train.d.ts +53 -0
  436. package/dist/workspace/release-train.d.ts.map +1 -0
  437. package/dist/workspace/release-train.js +134 -0
  438. package/dist/workspace/release-workflow.d.ts.map +1 -1
  439. package/dist/workspace/release-workflow.js +4 -3
  440. package/dist/workspace/repo-ref.d.ts +45 -0
  441. package/dist/workspace/repo-ref.d.ts.map +1 -0
  442. package/dist/workspace/repo-ref.js +114 -0
  443. package/dist/workspace/run-event.schema.json +2 -2
  444. package/dist/workspace/runs-cli.d.ts +9 -0
  445. package/dist/workspace/runs-cli.d.ts.map +1 -1
  446. package/dist/workspace/runs-cli.js +48 -3
  447. package/dist/workspace/runs.schema.json +7 -0
  448. package/dist/workspace/search-cli.d.ts +204 -0
  449. package/dist/workspace/search-cli.d.ts.map +1 -0
  450. package/dist/workspace/search-cli.js +466 -0
  451. package/dist/workspace/search-contract.d.ts +56 -0
  452. package/dist/workspace/search-contract.d.ts.map +1 -0
  453. package/dist/workspace/search-contract.js +70 -0
  454. package/dist/workspace/search.schema.json +822 -0
  455. package/dist/workspace/status-decisions.d.ts +37 -0
  456. package/dist/workspace/status-decisions.d.ts.map +1 -0
  457. package/dist/workspace/status-decisions.js +147 -0
  458. package/dist/workspace/status-gates.d.ts +9 -0
  459. package/dist/workspace/status-gates.d.ts.map +1 -1
  460. package/dist/workspace/status.d.ts +4 -0
  461. package/dist/workspace/status.d.ts.map +1 -1
  462. package/dist/workspace/status.js +14 -2
  463. package/dist/workspace/status.schema.json +6 -0
  464. package/dist/workspace/work-cli.d.ts.map +1 -1
  465. package/dist/workspace/work-cli.js +5 -3
  466. package/dist/workspace/work-evidence-ops.d.ts +83 -0
  467. package/dist/workspace/work-evidence-ops.d.ts.map +1 -0
  468. package/dist/workspace/work-evidence-ops.js +169 -0
  469. package/dist/workspace/work-evidence.d.ts +12 -2
  470. package/dist/workspace/work-evidence.d.ts.map +1 -1
  471. package/dist/workspace/work-evidence.js +21 -3
  472. package/dist/workspace/work-evidence.schema.json +49 -2
  473. package/dist/workspace/workspace-gates.d.ts +110 -0
  474. package/dist/workspace/workspace-gates.d.ts.map +1 -0
  475. package/dist/workspace/workspace-gates.js +140 -0
  476. package/dist/workspace/workspace-kinds.schema.json +58 -3
  477. package/dist/workspace/workspace-ledger.d.ts +76 -0
  478. package/dist/workspace/workspace-ledger.d.ts.map +1 -0
  479. package/dist/workspace/workspace-ledger.js +91 -0
  480. package/dist/workspace/workspace-run.d.ts +112 -0
  481. package/dist/workspace/workspace-run.d.ts.map +1 -0
  482. package/dist/workspace/workspace-run.js +432 -0
  483. package/package.json +1 -1
  484. package/src/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
  485. package/src/__golden__/wave-loop/op-by-apply-hold.json +10 -0
  486. package/src/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
  487. package/src/__golden__/wave-loop/op-by-apply-pass.json +10 -0
  488. package/src/__golden__/wave-loop/op-by-apply-skip.json +10 -0
  489. package/src/__golden__/wave-loop/op-cost-member-clash.json +6 -0
  490. package/src/__golden__/wave-loop/op-cost-member.json +13 -0
  491. package/src/__golden__/wave-loop/op-denied-source.json +16 -0
  492. package/src/__golden__/wave-loop/op-denied.json +16 -0
  493. package/src/__golden__/wave-loop/op-discover-rest-short.json +6 -0
  494. package/src/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
  495. package/src/__golden__/wave-loop/op-discover-rest.json +26 -0
  496. package/src/__golden__/wave-loop/op-discover-wide.json +26 -0
  497. package/src/__golden__/wave-loop/op-held-on-again.json +16 -0
  498. package/src/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
  499. package/src/__golden__/wave-loop/op-held-on.json +16 -0
  500. package/src/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
  501. package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
  502. package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
  503. package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
  504. package/src/__golden__/wave-loop/op-hooks-held.json +13 -0
  505. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
  506. package/src/__golden__/wave-loop/op-hooks-pass.json +13 -0
  507. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
  508. package/src/__golden__/wave-loop/op-hooks-share.json +33 -0
  509. package/src/__golden__/wave-loop/op-outcome-held.json +10 -0
  510. package/src/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
  511. package/src/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
  512. package/src/__golden__/wave-loop/op-outcome-replan.json +10 -0
  513. package/src/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
  514. package/src/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
  515. package/src/__golden__/wave-loop/op-overridden.json +16 -0
  516. package/src/__golden__/wave-loop/op-per-run.json +20 -0
  517. package/src/__golden__/wave-loop/op-point.json +13 -0
  518. package/src/__golden__/wave-loop/op-pr-review.json +10 -0
  519. package/src/__golden__/wave-loop/op-sealed.json +10 -0
  520. package/src/__golden__/wave-loop/op-spend-change-only.json +10 -0
  521. package/src/__golden__/wave-loop/op-spend.json +10 -0
  522. package/src/__golden__/wave-loop/op-wave-commands.json +55 -0
  523. package/src/__golden__/wave-loop/op-wide-refused.json +26 -0
  524. package/src/__golden__/wave-loop/op-wide-waiting.json +29 -0
  525. package/src/__golden__/wave-loop/op-wide.json +39 -0
  526. package/src/__snapshots__/okf.test.ts.snap +10 -0
  527. package/src/action-pins.test.ts +78 -0
  528. package/src/action-pins.ts +92 -0
  529. package/src/capabilities/aws.paths +148 -0
  530. package/src/capabilities/base-mappings.json +122 -0
  531. package/src/capabilities/catalog.ts +230 -0
  532. package/src/capabilities/language.test.ts +191 -0
  533. package/src/capabilities/language.ts +373 -0
  534. package/src/capabilities/matcher.ts +315 -0
  535. package/src/capabilities/view-compose.test.ts +76 -0
  536. package/src/capabilities/view.ts +631 -0
  537. package/src/cli/commands/carve-score-once.test.ts +99 -0
  538. package/src/cli/commands/carve.ts +37 -6
  539. package/src/cli/commands/check-lexicon.test.ts +2 -2
  540. package/src/cli/commands/import-report.ts +71 -0
  541. package/src/cli/commands/import.ts +33 -3
  542. package/src/cli/commands/lint.ts +1 -0
  543. package/src/cli/commands/migrate-dialect.test.ts +145 -0
  544. package/src/cli/commands/migrate.ts +132 -11
  545. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +4 -4
  546. package/src/cli/handlers/approve.ts +23 -2
  547. package/src/cli/handlers/build.ts +7 -1
  548. package/src/cli/handlers/declared-graph.ts +99 -0
  549. package/src/cli/handlers/graph.test.ts +94 -3
  550. package/src/cli/handlers/graph.ts +123 -96
  551. package/src/cli/handlers/ledger.ts +54 -0
  552. package/src/cli/handlers/migrate.ts +21 -12
  553. package/src/cli/handlers/misc.ts +10 -1
  554. package/src/cli/handlers/pr-env.ts +3 -4
  555. package/src/cli/handlers/pr.ts +138 -36
  556. package/src/cli/handlers/query.ts +16 -4
  557. package/src/cli/handlers/release.ts +6 -3
  558. package/src/cli/handlers/search-build-roots.test.ts +144 -0
  559. package/src/cli/handlers/search-count.test.ts +191 -0
  560. package/src/cli/handlers/search-count.ts +165 -0
  561. package/src/cli/handlers/search-json.test.ts +333 -0
  562. package/src/cli/handlers/search-live.test.ts +387 -0
  563. package/src/cli/handlers/search-paths.test.ts +235 -0
  564. package/src/cli/handlers/search.ts +1291 -166
  565. package/src/cli/main.test.ts +43 -3
  566. package/src/cli/main.ts +88 -17
  567. package/src/cli/mcp/server.test.ts +3 -3
  568. package/src/cli/mcp/server.ts +3 -0
  569. package/src/cli/mcp/tools/capabilities.ts +32 -0
  570. package/src/cli/mcp/workspace-tools.e2e.test.ts +21 -1
  571. package/src/cli/mcp/workspace-tools.ts +47 -2
  572. package/src/cli/parse-args.ts +79 -15
  573. package/src/cli/registry.ts +36 -4
  574. package/src/components/builders.ts +8 -0
  575. package/src/components/capability-plugin-loader.ts +13 -1
  576. package/src/components/capability-plugin.ts +16 -0
  577. package/src/components/pr-env-ports.ts +89 -0
  578. package/src/components/pr-env-run-bundle.test.ts +54 -0
  579. package/src/components/pr-env-run.ts +5 -76
  580. package/src/components/pr-environment.ts +2 -1
  581. package/src/components/pr-hooks.test.ts +73 -5
  582. package/src/components/pr-hooks.ts +95 -5
  583. package/src/components/pr-pipeline.ts +64 -16
  584. package/src/components/pr-policy.test.ts +64 -1
  585. package/src/components/pr-policy.ts +53 -0
  586. package/src/components/registry.test.ts +1 -1
  587. package/src/components/starter-plugin.ts +6 -0
  588. package/src/components/verbs/index.ts +2 -0
  589. package/src/components/verbs/publish-artifact.test.ts +60 -0
  590. package/src/components/verbs/publish-artifact.ts +122 -0
  591. package/src/components/verbs/publish-image.test.ts +101 -0
  592. package/src/components/verbs/publish-image.ts +265 -0
  593. package/src/config-environments.ts +160 -0
  594. package/src/config.ts +44 -124
  595. package/src/gate-decision-bundle.test.ts +1 -0
  596. package/src/gate-decision-identity.test.ts +9 -0
  597. package/src/gate-decision.ts +26 -3
  598. package/src/graph-detail.ts +15 -3
  599. package/src/graph-effective.ts +226 -75
  600. package/src/graph-ir.test.ts +163 -0
  601. package/src/graph-ir.ts +174 -0
  602. package/src/graph-sub-template.ts +22 -0
  603. package/src/graph-walk.test.ts +44 -0
  604. package/src/graph-walk.ts +102 -0
  605. package/src/import/parser.ts +20 -0
  606. package/src/lexicon.ts +84 -6
  607. package/src/lifecycle/gate-ledger.ts +8 -0
  608. package/src/lifecycle/git.ts +2 -2
  609. package/src/lifecycle/ledger-read.test.ts +107 -0
  610. package/src/lifecycle/ledger-read.ts +195 -0
  611. package/src/lifecycle/observe.test.ts +27 -1
  612. package/src/lifecycle/observe.ts +43 -1
  613. package/src/lifecycle/release-workstation.ts +24 -0
  614. package/src/lifecycle/run-ledger.ts +5 -2
  615. package/src/lint/cross-root.test.ts +122 -0
  616. package/src/lint/discover.ts +3 -1
  617. package/src/lint/post-synth.ts +212 -2
  618. package/src/lint/rule.ts +6 -0
  619. package/src/lint/rules/op/index.ts +2 -0
  620. package/src/lint/rules/op/ops019-workspace-op-target.ts +89 -0
  621. package/src/live-endpoint.ts +1 -1
  622. package/src/observation.test.ts +25 -0
  623. package/src/observation.ts +69 -5
  624. package/src/op/activities/activity-contracts.ts +5 -2
  625. package/src/op/activities/index.ts +2 -2
  626. package/src/op/activities/review-agent.test.ts +221 -4
  627. package/src/op/activities/review-agent.ts +265 -22
  628. package/src/op/activities/work-evidence.ts +9 -1
  629. package/src/op/builders.ts +9 -5
  630. package/src/op/composites/apply-op.test.ts +108 -0
  631. package/src/op/composites/apply-op.ts +37 -2
  632. package/src/op/gate-approval.test.ts +6 -1
  633. package/src/op/gate-approval.ts +12 -6
  634. package/src/op/gate-quorum-at-base.test.ts +211 -0
  635. package/src/op/gate-quorum.ts +156 -8
  636. package/src/op/gate-review.ts +14 -0
  637. package/src/op/gate.ts +67 -8
  638. package/src/op/index.ts +3 -2
  639. package/src/op/local-executor.ts +69 -9
  640. package/src/op/op-waves-depends-on.test.ts +81 -0
  641. package/src/op/op-waves-hook-targets.test.ts +4 -2
  642. package/src/op/op-waves-quorum-product.test.ts +158 -0
  643. package/src/op/op-waves-run.ts +288 -14
  644. package/src/op/op-waves.ts +143 -14
  645. package/src/op/runtime.ts +8 -0
  646. package/src/op/schedule-overdue.ts +33 -0
  647. package/src/op/stage-hooks.ts +13 -5
  648. package/src/op/workspace-run-env.ts +29 -0
  649. package/src/pr-forge-bundle.test.ts +2 -2
  650. package/src/pr-forge.test.ts +47 -0
  651. package/src/pr-forge.ts +68 -0
  652. package/src/pr-post-open.e2e.test.ts +36 -0
  653. package/src/pr-post.test.ts +102 -3
  654. package/src/pr-post.ts +89 -1
  655. package/src/read-filter.test.ts +54 -0
  656. package/src/read-filter.ts +193 -0
  657. package/src/run-event.ts +1 -1
  658. package/src/stage-hooks-bundle.test.ts +1 -0
  659. package/src/terraform/carve.ts +52 -15
  660. package/src/terraform/cuts.test.ts +173 -0
  661. package/src/terraform/cuts.ts +188 -0
  662. package/src/terraform/folds.ts +44 -0
  663. package/src/terraform/graph.ts +9 -0
  664. package/src/terraform/radius.test.ts +90 -0
  665. package/src/terraform/reach.test.ts +146 -0
  666. package/src/terraform/reach.ts +247 -0
  667. package/src/terraform/score.ts +27 -29
  668. package/src/terraform/state.ts +3 -1
  669. package/src/terraform/types.ts +8 -0
  670. package/src/wave-blocked.ts +28 -3
  671. package/src/wave-loop-golden.test.ts +4 -3
  672. package/src/workspace/behold-kinds.test.ts +1 -1
  673. package/src/workspace/capabilities-contract.test.ts +88 -0
  674. package/src/workspace/capabilities-contract.ts +132 -0
  675. package/src/workspace/capabilities.schema.json +229 -0
  676. package/src/workspace/check-contract.test.ts +7 -6
  677. package/src/workspace/check.schema.json +11 -2
  678. package/src/workspace/checks/ci-branch.test.ts +128 -0
  679. package/src/workspace/checks/ci-branch.ts +141 -0
  680. package/src/workspace/checks/links.ts +12 -1
  681. package/src/workspace/checks/org.ts +169 -0
  682. package/src/workspace/checks.ts +64 -7
  683. package/src/workspace/ci-branches.ts +31 -0
  684. package/src/workspace/ci-green-branches.test.ts +153 -0
  685. package/src/workspace/ci-green-cli.ts +7 -7
  686. package/src/workspace/ci-green-workflow.test.ts +4 -3
  687. package/src/workspace/ci-green-workflow.ts +13 -9
  688. package/src/workspace/ci-green.ts +49 -12
  689. package/src/workspace/ci-run.test.ts +72 -1
  690. package/src/workspace/ci-run.ts +68 -8
  691. package/src/workspace/ci-serve.test.ts +95 -0
  692. package/src/workspace/ci-serve.ts +106 -65
  693. package/src/workspace/component-environments.test.ts +25 -2
  694. package/src/workspace/compose-graph.ts +19 -3
  695. package/src/workspace/composites.schema.json +22 -0
  696. package/src/workspace/composites.test.ts +39 -8
  697. package/src/workspace/composites.ts +6 -2
  698. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  699. package/src/workspace/conformance/conformance.e2e.test.ts +14 -1
  700. package/src/workspace/conformance/index.ts +31 -2
  701. package/src/workspace/cross-root-lint.test.ts +90 -0
  702. package/src/workspace/cross-root-lint.ts +126 -0
  703. package/src/workspace/dbt-kind.test.ts +3 -2
  704. package/src/workspace/dbt-models-reader.ts +362 -0
  705. package/src/workspace/dbt-models.test.ts +248 -0
  706. package/src/workspace/dbt-project-common.ts +113 -0
  707. package/src/workspace/dbt-project-reader.ts +29 -112
  708. package/src/workspace/decision-rank.ts +40 -0
  709. package/src/workspace/declaration.schema.json +133 -24
  710. package/src/workspace/declaration.ts +84 -15
  711. package/src/workspace/environments.ts +40 -4
  712. package/src/workspace/flyway-dir.ts +113 -0
  713. package/src/workspace/flyway-layout.ts +337 -0
  714. package/src/workspace/graph-cache.ts +27 -3
  715. package/src/workspace/graph-cli.ts +119 -32
  716. package/src/workspace/graph-contract.test.ts +4 -2
  717. package/src/workspace/graph.schema.json +182 -0
  718. package/src/workspace/identity.test.ts +14 -2
  719. package/src/workspace/intent.ts +6 -14
  720. package/src/workspace/kind-link-readers.ts +68 -3
  721. package/src/workspace/kind-readers.e2e.test.ts +28 -0
  722. package/src/workspace/kind-readers.test.ts +1 -1
  723. package/src/workspace/kind-readers.ts +7 -1
  724. package/src/workspace/kinds.test.ts +12 -7
  725. package/src/workspace/kinds.ts +132 -7
  726. package/src/workspace/ledger.schema.json +158 -0
  727. package/src/workspace/lexicon-outputs.ts +132 -13
  728. package/src/workspace/lineage-check.ts +30 -7
  729. package/src/workspace/links.ts +24 -1
  730. package/src/workspace/ls-contract.test.ts +1 -1
  731. package/src/workspace/ls.schema.json +7 -0
  732. package/src/workspace/ls.ts +10 -1
  733. package/src/workspace/member-commands.ts +82 -3
  734. package/src/workspace/member-file-schema.test.ts +124 -0
  735. package/src/workspace/migrate-links.test.ts +99 -0
  736. package/src/workspace/migrate-links.ts +108 -0
  737. package/src/workspace/migration-dirs.test.ts +272 -0
  738. package/src/workspace/migration-dirs.ts +267 -0
  739. package/src/workspace/nested-graph.ts +32 -1
  740. package/src/workspace/org-scope.test.ts +238 -0
  741. package/src/workspace/reason-codes.test.ts +13 -1
  742. package/src/workspace/reason-codes.ts +23 -0
  743. package/src/workspace/records-cli.ts +58 -5
  744. package/src/workspace/records.schema.json +5 -0
  745. package/src/workspace/release-candidates.ts +7 -2
  746. package/src/workspace/release-train.ts +157 -0
  747. package/src/workspace/release-workflow.ts +4 -3
  748. package/src/workspace/repo-ref.ts +113 -0
  749. package/src/workspace/run-event.schema.json +2 -2
  750. package/src/workspace/runs-cli.ts +48 -4
  751. package/src/workspace/runs.schema.json +7 -0
  752. package/src/workspace/search-cli.e2e.test.ts +305 -0
  753. package/src/workspace/search-cli.ts +591 -0
  754. package/src/workspace/search-contract.ts +95 -0
  755. package/src/workspace/search.schema.json +822 -0
  756. package/src/workspace/status-contract.test.ts +46 -1
  757. package/src/workspace/status-decisions.test.ts +100 -0
  758. package/src/workspace/status-decisions.ts +151 -0
  759. package/src/workspace/status-gates.ts +9 -0
  760. package/src/workspace/status.schema.json +6 -0
  761. package/src/workspace/status.ts +17 -3
  762. package/src/workspace/where-filters.test.ts +39 -0
  763. package/src/workspace/work-cli.ts +5 -3
  764. package/src/workspace/work-evidence-ops.test.ts +176 -0
  765. package/src/workspace/work-evidence-ops.ts +189 -0
  766. package/src/workspace/work-evidence.schema.json +49 -2
  767. package/src/workspace/work-evidence.ts +22 -3
  768. package/src/workspace/workspace-gates.ts +200 -0
  769. package/src/workspace/workspace-kinds.schema.json +58 -3
  770. package/src/workspace/workspace-ledger.ts +130 -0
  771. package/src/workspace/workspace-run.test.ts +322 -0
  772. package/src/workspace/workspace-run.ts +461 -0
@@ -0,0 +1,153 @@
1
+ /**
2
+ * #4325: `ci.green` names several branches when `ci.branches` routes members
3
+ * to branches of their own. Each is judged as the single branch always was, a
4
+ * one-branch declaration reads as before, and `release cut` takes a commit of
5
+ * any of them.
6
+ */
7
+
8
+ import { afterAll, describe, expect, test } from "vitest";
9
+ import { greenBranches, parseDeclaration } from "./declaration";
10
+ import { cleanScratch, declaration, git, scratchDir, writeFiles } from "./__fixtures__/contract-repo";
11
+ import { ciList, ciTick, formatTick, greenOn, lastGreen } from "./ci-green";
12
+ import type { CheckRun, CiForge } from "./ci-green-forge";
13
+ import { buildTriggerEdits, renderCiGreenWorkflow } from "./ci-green-workflow";
14
+ import { checkOnGreenBranches, checkOnTrunk, resolveTrunk, WorkstationCheckError } from "../lifecycle/release-workstation";
15
+
16
+ afterAll(cleanScratch);
17
+
18
+ const PHASES = { test: ["test"] };
19
+ const decl = (green: unknown) => JSON.stringify({ name: "w", schema: 1, members: [{ name: "app", dir: "app", kind: "other", because: "x" }], ci: { green } });
20
+ const parse = (branch: unknown) => parseDeclaration(decl({ branch, phases: PHASES, require: ["test"] }), "chant.workspace.json").ci!.green!;
21
+
22
+ describe("ci.green.branch as one branch or several (#4325)", () => {
23
+ test("a string reads exactly as it did: no branches field", () => {
24
+ const g = parse("main");
25
+ expect(g.branch).toBe("main");
26
+ expect("branches" in g).toBe(false);
27
+ expect(greenBranches(g)).toEqual(["main"]);
28
+ // A list of one is the same declaration.
29
+ expect(parse(["main"])).toEqual(g);
30
+ });
31
+
32
+ test("a list judges each branch, and the first is the trunk readers use", () => {
33
+ const g = parse(["main", "release"]);
34
+ expect(g.branch).toBe("main");
35
+ expect(g.branches).toEqual(["main", "release"]);
36
+ expect(greenBranches(g)).toEqual(["main", "release"]);
37
+ });
38
+
39
+ test("an empty list, a repeated branch and a malformed name are refused", () => {
40
+ for (const bad of [[], ["main", "main"], ["main", "has space"], [3]]) {
41
+ expect(() => parse(bad), JSON.stringify(bad)).toThrow();
42
+ }
43
+ });
44
+
45
+ test("greenOn picks a branch ci.green names and refuses one it doesn't", () => {
46
+ const g = parse(["main", "release"]);
47
+ expect(greenOn(g, undefined).branch).toBe("main");
48
+ expect(greenOn(g, "release").branch).toBe("release");
49
+ expect("branches" in greenOn(g, "release")).toBe(false);
50
+ expect(() => greenOn(g, "staging")).toThrow(/judges main, release, not staging/);
51
+ });
52
+ });
53
+
54
+ // ── The tick, against a real remote ──────────────────────────────────────────
55
+
56
+ const NOW = new Date();
57
+ const run = (id: number, name: string, conclusion: string): CheckRun => ({ id, name, status: "completed", conclusion, completedAt: null, url: `https://example.com/runs/${id}` });
58
+
59
+ function commit(root: string, file: string): string {
60
+ writeFiles(root, { [file]: `${file}\n` });
61
+ git(root, "add", "-A");
62
+ git(root, "commit", "-q", "-m", file);
63
+ return git(root, "rev-parse", "HEAD");
64
+ }
65
+
66
+ /** A checkout with `main` and `release` pushed to a bare origin, one commit each past a shared base. */
67
+ function twoBranches(green: unknown): { root: string; origin: string; main: string; release: string } {
68
+ const origin = scratchDir("chant-ci-branches-origin-");
69
+ git(origin, "init", "-q", "--bare");
70
+ const root = scratchDir("chant-ci-branches-work-");
71
+ git(root, "init", "-q");
72
+ git(root, "symbolic-ref", "HEAD", "refs/heads/main");
73
+ writeFiles(root, { "chant.workspace.json": declaration([{ name: "app", dir: "app", kind: "other", because: "the app" }], { ci: { green } }) });
74
+ git(root, "remote", "add", "origin", origin);
75
+ commit(root, "app/base.txt");
76
+ git(root, "checkout", "-q", "-b", "release");
77
+ const release = commit(root, "app/release.txt");
78
+ git(root, "checkout", "-q", "main");
79
+ const main = commit(root, "app/main.txt");
80
+ git(root, "push", "-q", "origin", "main", "release");
81
+ return { root, origin, main, release };
82
+ }
83
+
84
+ const remoteTags = (origin: string) => git(origin, "for-each-ref", "--format=%(refname:lstrip=2)", "refs/tags/").split("\n").filter(Boolean).sort();
85
+ const forge = (shas: string[]): CiForge => ({ kind: "github", checkRuns: async (sha) => (shas.includes(sha) ? [run(1, "test", "success")] : []) });
86
+
87
+ describe("the tick judges each branch ci.green names (#4325)", () => {
88
+ test("a commit of a routed branch gets a green tag, and last-green reads each branch", async () => {
89
+ const { root, origin, main, release } = twoBranches({ branch: ["main", "release"], phases: PHASES, require: ["test"] });
90
+ const result = await ciTick({ cwd: root, forge: forge([main, release]), now: NOW });
91
+ expect(result.branch).toBe("main");
92
+ expect(result.routed?.map((r) => r.branch)).toEqual(["release"]);
93
+ expect(result.made.sort()).toEqual([`ci/green/${main}`, `ci/green/${release}`].sort());
94
+ expect(remoteTags(origin)).toEqual([`ci/green/${main}`, `ci/green/${release}`].sort());
95
+ // The tag says which branch it judged.
96
+ const note = git(origin, "tag", "-l", "--format=%(contents)", `ci/green/${release}`);
97
+ expect(JSON.parse(note.slice(note.indexOf("{")))).toMatchObject({ tag: "green", commit: release, branch: "release" });
98
+
99
+ expect((lastGreen({ cwd: root }) as { commit: { sha: string } }).commit.sha).toBe(main);
100
+ const onRelease = lastGreen({ cwd: root, branch: "release" }) as { branch: string; commit: { sha: string } };
101
+ expect([onRelease.branch, onRelease.commit.sha]).toEqual(["release", release]);
102
+ expect(lastGreen({ cwd: root, branch: "staging" })).toMatchObject({ error: { code: "ci-branch-unknown" } });
103
+
104
+ const listed = await ciList({ cwd: root, branch: "release", now: NOW });
105
+ expect("commits" in listed && listed.commits.filter((c) => c.state === "green").map((c) => c.sha)).toEqual([release]);
106
+
107
+ const text = formatTick(result);
108
+ expect(text).toContain("main, commits within 24h");
109
+ expect(text).toContain("release, commits within 24h");
110
+ });
111
+
112
+ test("a one-branch declaration ticks one branch, with no routed results", async () => {
113
+ const { root, main, release } = twoBranches({ branch: "main", phases: PHASES, require: ["test"] });
114
+ const result = await ciTick({ cwd: root, forge: forge([main, release]), now: NOW });
115
+ expect(result.routed).toBeUndefined();
116
+ expect(result.made).toEqual([`ci/green/${main}`]);
117
+ expect(lastGreen({ cwd: root, branch: "release" })).toMatchObject({ error: { code: "ci-branch-unknown" } });
118
+ });
119
+ });
120
+
121
+ describe("the generated tick workflow follows the branches (#4325)", () => {
122
+ const green = (branch: string | string[]) => parse(branch);
123
+
124
+ test("workflow_run lists every branch, and one branch renders as before", () => {
125
+ const render = (g: ReturnType<typeof green>) => renderCiGreenWorkflow({ green: g, workflows: ["ci"], chantVersion: "0.0.0", workspaceRoot: ".", install: undefined, chant: undefined, npmLock: undefined, push: undefined });
126
+ const many = render(green(["main", "release"]));
127
+ expect(many).toMatch(/branches:\n\s+- "?main"?\n\s+- "?release"?\n/);
128
+ // The checkout stays on the first branch, which holds the declaration the tick reads.
129
+ expect(many).toMatch(/ref: "?main"?/);
130
+ expect(render(green("main"))).not.toContain("release");
131
+ });
132
+
133
+ test("a push-triggered build lists every branch", () => {
134
+ const sources = [{ path: ".github/workflows/ci.yml", text: "name: ci\non:\n push:\n branches: [main]\njobs:\n test:\n runs-on: ubuntu-latest\n steps: []\n" }];
135
+ const g = parseDeclaration(decl({ branch: ["main", "release"], phases: { test: { runs: ["test"], build: "newest" } }, require: ["test"] }), "chant.workspace.json").ci!.green!;
136
+ const [edit] = buildTriggerEdits(g, sources as never, { compose: false });
137
+ expect(edit!.text).toMatch(/branches:\n\s+- main\n\s+- release/);
138
+ });
139
+ });
140
+
141
+ describe("release cut takes a commit of any branch ci.green names (#4325)", () => {
142
+ test("a routed branch's commit is on trunk for the cut, and an unrelated one is not", () => {
143
+ const { root, main, release } = twoBranches({ branch: ["main", "release"], phases: PHASES, require: ["test"] });
144
+ git(root, "fetch", "-q", "origin");
145
+ const trunk = resolveTrunk(root, "main");
146
+ expect(() => checkOnTrunk(root, main, trunk)).not.toThrow();
147
+ expect(() => checkOnTrunk(root, release, trunk)).toThrow(WorkstationCheckError);
148
+ expect(checkOnGreenBranches(root, main, trunk, ["release"]).branch).toBe("main");
149
+ expect(checkOnGreenBranches(root, release, trunk, ["release"]).branch).toBe("release");
150
+ // Without the routed branch listed, the first branch's refusal stands.
151
+ expect(() => checkOnGreenBranches(root, release, trunk, [])).toThrow(WorkstationCheckError);
152
+ });
153
+ });
@@ -32,12 +32,12 @@ import { findWorkspaceRoot } from "../project-root";
32
32
  import { ciList, ciTick, CiGreenError, formatList, formatTick, lastGreen, RUNNER_KEY_ENV } from "./ci-green";
33
33
  import { ciForgeFromEnv } from "./ci-green-forge";
34
34
  import { ACTIONS_NAME, buildTriggerEdits, CI_GREEN_WORKFLOW_FILE, ciGreenHeader, type PushToken, readWorkflowSources, renderCiGreenWorkflow, workflowsHolding, writeWorkflowFile } from "./ci-green-workflow";
35
- import { readDeclaration, readerVersion, WorkspaceReadError, type CiSource } from "./declaration";
35
+ import { greenBranches, readDeclaration, readerVersion, WorkspaceReadError, type CiSource } from "./declaration";
36
36
  import { recordGeneratedFiles, resolveMemberContext, shellArg } from "./member-pipeline";
37
37
  import { gitTop, workingTree } from "./tree";
38
38
 
39
39
  const USAGE =
40
- "chant ci last-green [--json] [--from <rev>] | ci list [--json] [--limit <n>] [--since <rev>] [--forge github] [--source checks|runs] | ci tick [--dry-run] [--forge github] [--source checks|runs] [--key <runner key.pem>] | " +
40
+ "chant ci last-green [--json] [--from <rev>] [--branch <branch>] | ci list [--json] [--branch <branch>] [--limit <n>] [--since <rev>] [--forge github] [--source checks|runs] | ci tick [--dry-run] [--forge github] [--source checks|runs] [--key <runner key.pem>] | " +
41
41
  "ci workflow [--workflow <name>]... [--output <file>] [--chant <command>] [--install <command>] [--token-secret <NAME> | --app-id-var <VAR> --app-key-secret <NAME>] [--runner-key-secret <NAME>]";
42
42
 
43
43
  export { RUNNER_KEY_ENV };
@@ -56,7 +56,7 @@ function warnIgnored(ignored: readonly { tag: string; status: string; reason: st
56
56
  }
57
57
 
58
58
  export async function runCiLastGreen(ctx: CommandContext): Promise<number> {
59
- const doc = lastGreen({ cwd: process.cwd(), ...(ctx.args.migrateFrom !== undefined ? { from: ctx.args.migrateFrom } : {}) });
59
+ const doc = lastGreen({ cwd: process.cwd(), ...(ctx.args.branch !== undefined ? { branch: ctx.args.branch } : {}), ...(ctx.args.migrateFrom !== undefined ? { from: ctx.args.migrateFrom } : {}) });
60
60
  if (ctx.args.json) {
61
61
  console.log(JSON.stringify(doc, null, 2));
62
62
  return "error" in doc ? 1 : 0;
@@ -81,7 +81,7 @@ export async function runCiList(ctx: CommandContext): Promise<number> {
81
81
  // Only an explicit --forge reaches the network; without it the list reads local refs and tags.
82
82
  const forge = ctx.args.forge !== undefined ? ciForgeFromEnv(ctx.args.forge) : undefined;
83
83
  const source = sourceOf(ctx.args);
84
- const doc = await ciList({ cwd: process.cwd(), limit, since: ctx.args.since, forge, ...(source ? { source } : {}) });
84
+ const doc = await ciList({ cwd: process.cwd(), ...(ctx.args.branch !== undefined ? { branch: ctx.args.branch } : {}), limit, since: ctx.args.since, forge, ...(source ? { source } : {}) });
85
85
  if (ctx.args.json) {
86
86
  console.log(JSON.stringify(doc, null, 2));
87
87
  return "error" in doc ? 1 : 0;
@@ -185,14 +185,14 @@ export async function runCiWorkflow(ctx: CommandContext): Promise<number> {
185
185
  const yaml = renderCiGreenWorkflow({ green, workflows, chantVersion: readerVersion(), workspaceRoot, chant: ctx.args.ciChant, install: ctx.args.ciInstall, npmLock, push, ...(runnerKeySecret ? { runnerKeySecret } : {}), ...(compose ? { compose } : {}) });
186
186
  const written = writeWorkflowFile(target, ciGreenHeader(command, member ? "member" : "root") + yaml);
187
187
  if (member) recordGeneratedFiles(member.memberRoot, [{ path: fromRepo, command }]);
188
- console.log(`Wrote ${written}: ticks when ${workflows.join(", ")} complete${workflows.length === 1 ? "s" : ""} on ${green.branch}${compose ? " or a chant/compose/ branch" : ""}, and every 15 minutes.`);
188
+ console.log(`Wrote ${written}: ticks when ${workflows.join(", ")} complete${workflows.length === 1 ? "s" : ""} on ${greenBranches(green).join(", ")}${compose ? " or a chant/compose/ branch" : ""}, and every 15 minutes.`);
189
189
  for (const e of edits) {
190
190
  if (!e.changed) continue;
191
191
  const path = writeWorkflowFile(join(repoRoot, e.path), e.text);
192
192
  console.log(
193
193
  e.build === "newest"
194
- ? `Wrote ${path}'s triggers and concurrency: ${e.phases.join(", ")} build${e.phases.length === 1 ? "s" : ""} the newest commit on ${green.branch} when a run ends, and a ci/build/<sha> tag by hand.`
195
- : `Wrote ${path}'s triggers and concurrency: ${e.phases.join(", ")} build${e.phases.length === 1 ? "s" : ""} every commit on ${green.branch}, none cancelled, and a ci/build/<sha> tag by hand when the forge ran nothing.`,
194
+ ? `Wrote ${path}'s triggers and concurrency: ${e.phases.join(", ")} build${e.phases.length === 1 ? "s" : ""} the newest commit on ${greenBranches(green).join(", ")} when a run ends, and a ci/build/<sha> tag by hand.`
195
+ : `Wrote ${path}'s triggers and concurrency: ${e.phases.join(", ")} build${e.phases.length === 1 ? "s" : ""} every commit on ${greenBranches(green).join(", ")}, none cancelled, and a ci/build/<sha> tag by hand when the forge ran nothing.`,
196
196
  );
197
197
  }
198
198
  return 0;
@@ -14,6 +14,7 @@ import { runCiWorkflow } from "./ci-green-cli";
14
14
  import { BUILD_BLOCK_MARKER, buildTriggerEdits, CI_GREEN_WORKFLOW_FILE, dispatchableWorkflows, jobCheckRuns, renderCiGreenWorkflow, workflowsHolding, writeTopLevel } from "./ci-green-workflow";
15
15
  import { parseDeclaration } from "./declaration";
16
16
  import { cleanScratch, declaration, git, scratchDir, writeFiles } from "./__fixtures__/contract-repo";
17
+ import { ACTION_PINS, usesRef } from "../action-pins";
17
18
 
18
19
  afterAll(cleanScratch);
19
20
 
@@ -82,7 +83,7 @@ describe("the workflows that hold required check runs (#3573)", () => {
82
83
  expect(doc.concurrency).toEqual({ group: "chant-ci-green", "cancel-in-progress": false });
83
84
  expect(doc.permissions).toEqual({ contents: "write", checks: "read" });
84
85
  const steps = doc.jobs.tick.steps;
85
- expect(steps[0]).toEqual({ uses: "actions/checkout@v6", with: { ref: "main", "fetch-depth": 0 } });
86
+ expect(steps[0]).toEqual({ uses: usesRef(ACTION_PINS.checkout6), with: { ref: "main", "fetch-depth": 0 } });
86
87
  expect(steps[2].run).toBe("npx --yes @intentius/chant@0.108.0 ci tick");
87
88
  expect(steps[2]["working-directory"]).toBeUndefined();
88
89
  expect(steps[2].env.GITHUB_TOKEN).toBe("${{ github.token }}");
@@ -128,7 +129,7 @@ describe("the token the tick pushes its tags with (#3573)", () => {
128
129
  const plain = renderCiGreenWorkflow(base);
129
130
  const text = renderCiGreenWorkflow({ ...base, push: { secret: "CI_GREEN_TOKEN" } });
130
131
  const steps = (parseYAMLDocument(text) as Yaml).jobs.tick.steps;
131
- expect(steps[0]).toEqual({ uses: "actions/checkout@v6", with: { ref: "main", "fetch-depth": 0, token: "${{ secrets.CI_GREEN_TOKEN || github.token }}" } });
132
+ expect(steps[0]).toEqual({ uses: usesRef(ACTION_PINS.checkout6), with: { ref: "main", "fetch-depth": 0, token: "${{ secrets.CI_GREEN_TOKEN || github.token }}" } });
132
133
  // Check runs are still read with the Actions token: a fine-grained token can't call the Checks API.
133
134
  expect(steps[2].env.GITHUB_TOKEN).toBe("${{ github.token }}");
134
135
  expect((parseYAMLDocument(text) as Yaml).permissions).toEqual({ contents: "write", checks: "read" });
@@ -147,7 +148,7 @@ describe("the token the tick pushes its tags with (#3573)", () => {
147
148
  const steps = (parseYAMLDocument(renderCiGreenWorkflow({ ...base, push: { appIdVar: "CI_GREEN_APP_ID", appKeySecret: "CI_GREEN_APP_KEY" } })) as Yaml).jobs.tick.steps;
148
149
  expect(steps[0]).toMatchObject({
149
150
  id: "app-token",
150
- uses: "actions/create-github-app-token@v2",
151
+ uses: usesRef(ACTION_PINS.createGithubAppToken2),
151
152
  with: { "app-id": "${{ vars.CI_GREEN_APP_ID }}", "private-key": "${{ secrets.CI_GREEN_APP_KEY }}" },
152
153
  });
153
154
  expect(steps[1].with.token).toBe("${{ steps.app-token.outputs.token }}");
@@ -48,8 +48,9 @@
48
48
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
49
49
  import { dirname, join, relative, resolve, sep } from "node:path";
50
50
  import { GENERATED_MARKER } from "../discovery/files";
51
+ import { ACTION_PINS, tagComments, usesRef } from "../action-pins";
51
52
  import { emitYAMLEntry, parseYAMLDocument } from "../yaml";
52
- import type { CiBuild, CiGreen } from "./declaration";
53
+ import { greenBranches, type CiBuild, type CiGreen } from "./declaration";
53
54
  import { matchesCheckRun } from "./ci-green";
54
55
  import { githubSecretOrJobToken } from "../ci-jobs";
55
56
  import { COMPOSE_BRANCH_PREFIX } from "../lifecycle/compose-refs";
@@ -228,7 +229,7 @@ export function renderCiGreenWorkflow(input: WorkflowInput): string {
228
229
  ? [
229
230
  " - name: Mint a GitHub App token that may push tags to commits that change workflows",
230
231
  " id: app-token",
231
- " uses: actions/create-github-app-token@v2",
232
+ ` uses: ${usesRef(ACTION_PINS.createGithubAppToken2)}`,
232
233
  " with:",
233
234
  ` app-id: \${{ vars.${push.appIdVar} }}`,
234
235
  ` private-key: \${{ secrets.${push.appKeySecret} }}`,
@@ -247,7 +248,7 @@ export function renderCiGreenWorkflow(input: WorkflowInput): string {
247
248
  " types:",
248
249
  " - completed",
249
250
  " branches:",
250
- ` - ${q(green.branch)}`,
251
+ ...greenBranches(green).map((b) => ` - ${q(b)}`),
251
252
  // #4169: a build the tick asked for runs at a ci/build/<sha> tag, and its completion ticks too.
252
253
  ...(newestPhases(green) ? [` - ${q("ci/build/**")}`] : []),
253
254
  // #4220: a composed commit's phases run on its chant/compose/<name> branch, and their completion ticks too.
@@ -273,12 +274,12 @@ export function renderCiGreenWorkflow(input: WorkflowInput): string {
273
274
  " timeout-minutes: 15",
274
275
  " steps:",
275
276
  ...mint,
276
- " - uses: actions/checkout@v6",
277
+ ` - uses: ${usesRef(ACTION_PINS.checkout6)}`,
277
278
  " with:",
278
279
  ` ref: ${q(green.branch)}`,
279
280
  " fetch-depth: 0",
280
281
  ...token,
281
- " - uses: actions/setup-node@v6",
282
+ ` - uses: ${usesRef(ACTION_PINS.setupNode6)}`,
282
283
  " with:",
283
284
  " node-version: \"24\"",
284
285
  ...cache,
@@ -292,7 +293,7 @@ export function renderCiGreenWorkflow(input: WorkflowInput): string {
292
293
  ...(input.runnerKeySecret ? [` CHANT_RUNNER_KEY: \${{ secrets.${input.runnerKeySecret} }}`] : []),
293
294
  ` run: ${tick}`,
294
295
  ];
295
- return lines.join("\n") + "\n";
296
+ return tagComments(lines.join("\n") + "\n");
296
297
  }
297
298
 
298
299
  /** Whether any phase sets `build` (#4169): then the tick may re-run jobs and start workflows. */
@@ -370,7 +371,7 @@ export function buildTriggerEdits(green: CiGreen, sources: readonly WorkflowSour
370
371
  delete on.schedule;
371
372
  const push = isObject(on.push) ? { ...on.push } : {};
372
373
  const branches = Array.isArray(push.branches) ? push.branches.filter((b): b is string => typeof b === "string") : [];
373
- const withBranch = branches.includes(green.branch) ? branches : [...branches, green.branch];
374
+ const withBranch = [...branches, ...greenBranches(green).filter((b) => !branches.includes(b))];
374
375
  push.branches = compose && !withBranch.includes(COMPOSE_BRANCHES) ? [...withBranch, COMPOSE_BRANCHES] : withBranch;
375
376
  const { push: _push, workflow_dispatch: dispatch, ...rest } = on;
376
377
  text = writeTopLevel(text, "on", { push, workflow_dispatch: dispatch ?? {}, ...rest });
@@ -388,8 +389,11 @@ export function buildTriggerEdits(green: CiGreen, sources: readonly WorkflowSour
388
389
  if (!("workflow_dispatch" in next)) next = { ...next, workflow_dispatch: {} };
389
390
  if (next !== on) text = writeTopLevel(text, "on", next);
390
391
  // #4220: a compose branch's commits each get a verdict, so its runs are keyed by commit, as the trunk branch's are.
391
- const keyed = compose ? `(github.ref == '${main}' || startsWith(github.ref, 'refs/heads/${COMPOSE_BRANCH_PREFIX}'))` : `github.ref == '${main}'`;
392
- const unkeyed = compose ? `!${keyed}` : `github.ref != '${main}'`;
392
+ // #4325: each branch ci.green judges is keyed by commit.
393
+ const heads = greenBranches(green).map((b) => `github.ref == 'refs/heads/${b}'`);
394
+ const several = heads.length > 1;
395
+ const keyed = compose ? `(${[...heads, `startsWith(github.ref, 'refs/heads/${COMPOSE_BRANCH_PREFIX}')`].join(" || ")})` : several ? `(${heads.join(" || ")})` : `github.ref == '${main}'`;
396
+ const unkeyed = compose || several ? `!${keyed}` : `github.ref != '${main}'`;
393
397
  // Written as lines: the emitter would quote the expression, and GitHub reads it the same either way.
394
398
  text = writeTopLevel(text, "concurrency", [
395
399
  "concurrency:",
@@ -55,7 +55,7 @@
55
55
 
56
56
  import { judgeCommits, mergeCommits, readWorkIndex, type CommitLink } from "./commit-links";
57
57
  import { execFileSync } from "node:child_process";
58
- import { readDeclaration, readerVersion, WORKSPACE_ERROR_CODES, WorkspaceReadError, type CiGreen, type CiPhase, type CiSource } from "./declaration";
58
+ import { greenBranches, readDeclaration, readerVersion, WORKSPACE_ERROR_CODES, WorkspaceReadError, type CiGreen, type CiPhase, type CiSource } from "./declaration";
59
59
  import type { CheckRun, CiForge, OpRunRef } from "./ci-green-forge";
60
60
  import type { OpRunRecord } from "../op/runtime";
61
61
  import { readRunLedger } from "../lifecycle/run-ledger";
@@ -514,8 +514,25 @@ export function readGreen(cwd: string): { green: CiGreen; top: string; workspace
514
514
  return { green, top, workspace: { name: declaration.name, root: located.root, file: declaration.file } };
515
515
  }
516
516
 
517
+ /**
518
+ * `green` judging `branch` (#4325): `ci.green` names several branches when
519
+ * `ci.branches` routes members to branches of their own, and each is judged
520
+ * as the single branch always was. Without `branch` the first is judged.
521
+ * Refuses a branch `ci.green` does not name.
522
+ */
523
+ export function greenOn(green: CiGreen, branch: string | undefined): CiGreen {
524
+ const { branches: _all, ...rest } = green;
525
+ if (branch === undefined || branch === green.branch) return rest;
526
+ if (!greenBranches(green).includes(branch)) {
527
+ throw new CiGreenError("ci-branch-unknown", `ci.green judges ${greenBranches(green).join(", ")}, not ${branch}; add it to ci.green.branch to give it a green verdict`);
528
+ }
529
+ return { ...rest, branch };
530
+ }
531
+
517
532
  export interface TickRequest {
518
533
  cwd: string;
534
+ /** Judge this one branch of `ci.green.branch` (#4325). Without it a tick judges every branch the declaration names. */
535
+ branch?: string;
519
536
  /**
520
537
  * Where check runs come from. A function is called only when the source
521
538
  * is `checks`, so a tick from runs needs no forge token. Required for
@@ -547,6 +564,8 @@ export interface ComposedBranchTick {
547
564
 
548
565
  export interface TickResult {
549
566
  branch: string;
567
+ /** The other branches `ci.green` names (#4325), each judged as this one is. Their tags are in `made`. Absent for a one-branch declaration. */
568
+ routed?: TickResult[];
550
569
  window: string;
551
570
  /** The ref the commits were read from. */
552
571
  ref: string;
@@ -601,7 +620,16 @@ export function ciTagSigner(top: string, base: string, pem: string, what = "the
601
620
  export async function ciTick(req: TickRequest): Promise<TickResult> {
602
621
  const read = readGreen(req.cwd);
603
622
  const top = read.top;
604
- const green: CiGreen = { ...read.green, source: req.source ?? read.green.source };
623
+ const names = greenBranches(read.green);
624
+ if (req.branch === undefined && names.length > 1) {
625
+ const first = await ciTick({ ...req, branch: names[0]! });
626
+ const routed: TickResult[] = [];
627
+ for (const b of names.slice(1)) routed.push(await ciTick({ ...req, branch: b }));
628
+ return { ...first, routed, made: [...first.made, ...routed.flatMap((r) => r.made)] };
629
+ }
630
+ const green: CiGreen = { ...greenOn(read.green, req.branch), source: req.source ?? read.green.source };
631
+ // Compose branches are walked from trunk, so only the first branch's tick walks them.
632
+ const walksCompose = green.branch === read.green.branch;
605
633
  const forge = green.source === "checks" ? (typeof req.forge === "function" ? req.forge() : req.forge) : undefined;
606
634
  if (green.source === "checks" && !forge) throw new Error("ci.green.source is checks, so the tick reads check runs and needs a forge");
607
635
  const remote = req.remote ?? "origin";
@@ -671,7 +699,7 @@ export async function ciTick(req: TickRequest): Promise<TickResult> {
671
699
  if (links) for (const c of planned) c.work = links.get(c.sha);
672
700
  // Compose branches (#4168 4.3): each composed commit within the window is judged as trunk's are, and its tags name its branch.
673
701
  const composed: ComposedBranchTick[] = [];
674
- for (const b of remoteComposeBranches(top, remote)) {
702
+ for (const b of walksCompose ? remoteComposeBranches(top, remote) : []) {
675
703
  const own = git(top, ["log", "--first-parent", `--max-age=${cutoff}`, "--format=%H %ct", b.ref, `^${tracking}`])
676
704
  .split("\n")
677
705
  .filter(Boolean)
@@ -904,18 +932,20 @@ export interface RequestBuildResult {
904
932
  */
905
933
  export async function requestBuild(req: { cwd: string; at: string; remote?: string; forge?: CiForge; now?: Date }): Promise<RequestBuildResult> {
906
934
  const read = readGreen(req.cwd);
907
- const { green, top } = read;
935
+ const { top } = read;
936
+ const green = greenOn(read.green, undefined);
937
+ const names = greenBranches(read.green);
908
938
  const remote = req.remote ?? "origin";
909
- const tracking = `refs/remotes/${remote}/${green.branch}`;
939
+ const trackingOf = (b: string): string => `refs/remotes/${remote}/${b}`;
910
940
  try {
911
- git(top, ["fetch", "--quiet", "--no-tags", remote, `+refs/heads/${green.branch}:${tracking}`, `+refs/tags/${CI_BUILD_TAG_PREFIX}*:refs/tags/${CI_BUILD_TAG_PREFIX}*`]);
941
+ git(top, ["fetch", "--quiet", "--no-tags", remote, ...names.map((b) => `+refs/heads/${b}:${trackingOf(b)}`), `+refs/tags/${CI_BUILD_TAG_PREFIX}*:refs/tags/${CI_BUILD_TAG_PREFIX}*`]);
912
942
  } catch (err) {
913
- throw new Error(`could not fetch ${green.branch} and the build requests from ${remote}: ${stderrOf(err)}`);
943
+ throw new Error(`could not fetch ${names.join(", ")} and the build requests from ${remote}: ${stderrOf(err)}`);
914
944
  }
915
945
  if (req.at.startsWith("-") || tryGit(top, ["rev-parse", "--verify", "--quiet", `${req.at}^{commit}`]) === null) throw new WorkspaceReadError("revision-unknown", `--at ${req.at} names no commit in this repository`);
916
946
  const sha = git(top, ["rev-parse", `${req.at}^{commit}`]);
917
- const onBranch = git(top, ["rev-list", "--first-parent", tracking]).split("\n").includes(sha);
918
- if (!onBranch) throw new Error(`${sha.slice(0, 12)} is not a first-parent commit of ${remote}/${green.branch}, and only those are judged green`);
947
+ const onBranch = names.some((b) => tryGit(top, ["rev-list", "--first-parent", trackingOf(b)])?.split("\n").includes(sha));
948
+ if (!onBranch) throw new Error(`${sha.slice(0, 12)} is not a first-parent commit of ${names.map((b) => `${remote}/${b}`).join(" or ")}, and only those are judged green`);
919
949
  const existing = readBuildRequests(top).get(sha);
920
950
  const request = existing ?? requestedBuild(green, sha, green.require, req.now ?? new Date());
921
951
  const warnings: string[] = [];
@@ -1101,6 +1131,7 @@ export function whyWorkUnknown(work: CommitLink | null | undefined): string | un
1101
1131
 
1102
1132
  /** The text `chant ci tick` prints. */
1103
1133
  export function formatTick(r: TickResult, dryRun = false): string {
1134
+ if (r.routed) return [formatTick({ ...r, routed: undefined }, dryRun), ...r.routed.map((b) => formatTick(b, dryRun))].join("\n");
1104
1135
  const lines = [`${r.branch}, commits within ${r.window}${r.source === "runs" ? ", judged from chant runs" : ""}: ${r.commits.length}`];
1105
1136
  for (const c of r.commits) {
1106
1137
  const short = c.sha.slice(0, 12);
@@ -1187,10 +1218,12 @@ export const LAST_GREEN_NEWER = 50;
1187
1218
  * green tag and no revoked tag. It reads local refs and tags and never
1188
1219
  * fetches, so a reader fetches first (`git fetch origin --tags`).
1189
1220
  */
1190
- export function lastGreen(req: { cwd: string; remote?: string; from?: string }): LastGreenDocument {
1221
+ export function lastGreen(req: { cwd: string; remote?: string; from?: string; branch?: string }): LastGreenDocument {
1191
1222
  const head = { $schema: CI_LAST_GREEN_SCHEMA_ID, contract: CI_LAST_GREEN_CONTRACT_VERSION, chant: readerVersion() };
1192
1223
  try {
1193
- const { green, top, workspace } = readGreen(req.cwd);
1224
+ const read = readGreen(req.cwd);
1225
+ const { top, workspace } = read;
1226
+ const green = greenOn(read.green, req.branch);
1194
1227
  const branchRef = branchRefOf(top, green, req.remote);
1195
1228
  let ref = branchRef;
1196
1229
  if (req.from !== undefined) {
@@ -1355,6 +1388,8 @@ function recordedPhase(green: CountedCiTag | undefined, phase: string): CiListPh
1355
1388
 
1356
1389
  export interface ListRequest {
1357
1390
  cwd: string;
1391
+ /** List this branch of `ci.green.branch` (#4325). The first when absent. */
1392
+ branch?: string;
1358
1393
  remote?: string;
1359
1394
  /** Keep only the newest n commits. */
1360
1395
  limit?: number;
@@ -1380,7 +1415,9 @@ export interface ListRequest {
1380
1415
  export async function ciList(req: ListRequest): Promise<CiListDocument> {
1381
1416
  const head = { $schema: CI_LIST_SCHEMA_ID, contract: CI_LIST_CONTRACT_VERSION, chant: readerVersion() };
1382
1417
  try {
1383
- const { green, top, workspace } = readGreen(req.cwd);
1418
+ const read = readGreen(req.cwd);
1419
+ const { top, workspace } = read;
1420
+ const green = greenOn(read.green, req.branch);
1384
1421
  const ref = branchRefOf(top, green, req.remote);
1385
1422
 
1386
1423
  const logArgs = ["log", "--first-parent", "--format=%H %ct"];
@@ -17,7 +17,7 @@ import { join } from "node:path";
17
17
  import { afterAll, beforeAll, describe, expect, test } from "vitest";
18
18
  import { cleanScratch, declaration, git, repoFixture, scratchDir, writeFiles } from "./__fixtures__/contract-repo";
19
19
  import { checkRunEventLines } from "./__fixtures__/run-event-check";
20
- import { ciJobCommand, ciJobEnv, ciJobFailure, ciJobOp, ciLoopOptions, ciRun, CiRunError, ciStatusContext, ciStatusFromEnv, ciStatusUrl, formatCiRun } from "./ci-run";
20
+ import { chantNodeModules, ciJobCommand, ciJobEnv, ciJobFailure, ciJobOp, ciLoopOptions, ciRun, CiRunError, ciStatusContext, ciStatusFromEnv, ciStatusUrl, formatCiRun } from "./ci-run";
21
21
  import type { CommitStatus, ForgeStatus, PrForge } from "../pr-forge";
22
22
  import { FORGE_TOKEN_VARS } from "../ci-handover";
23
23
  import { ciList, ciTick, evaluateCommit, readOpRunSource, revokedAnnotation } from "./ci-green";
@@ -1040,6 +1040,77 @@ describe("the plan's steps hold no forge token (#4295)", () => {
1040
1040
  });
1041
1041
  });
1042
1042
 
1043
+ /** A scheduled watch Op's file, shaped as discovery reads one (entity.props = OpConfig), importing nothing. */
1044
+ const WATCH_OP = (name: string): string =>
1045
+ `export default { props: { name: ${JSON.stringify(name)}, overview: "t", schedule: { cron: "0 * * * *" }, labels: { Watch: "true" }, phases: [{ name: "Run", steps: [] }] } };\n`;
1046
+
1047
+ describe("chant ci run plan checks the scheduled Ops at --base for the note (#4328)", () => {
1048
+ /**
1049
+ * The base declares watch Op `drift`, with its generated workflow. The
1050
+ * change deletes it and adds `sneaky`. The note names the Op the base has,
1051
+ * since the check reads the Ops at --base, and never the change's own.
1052
+ */
1053
+ function stale(): { app: string; base: string; sha: string } {
1054
+ const { root, app } = workspace();
1055
+ writeFiles(root, {
1056
+ "app/chant.config.json": "{}",
1057
+ "app/ops/drift.op.ts": WATCH_OP("drift"),
1058
+ ".forgejo/workflows/app-drift.yml": "on: schedule\n",
1059
+ });
1060
+ const base = commitAt(root, 1, "app/b.txt");
1061
+ execFileSync("git", ["rm", "-q", "app/ops/drift.op.ts"], { cwd: root });
1062
+ writeFiles(root, { "app/ops/sneaky.op.ts": WATCH_OP("sneaky"), ".forgejo/workflows/app-sneaky.yml": "on: schedule\n" });
1063
+ const sha = commitAt(root, 0.5, "app/c.txt");
1064
+ return { app, base, sha };
1065
+ }
1066
+
1067
+ function overdueForge(): { forge: PrForge; notes: string[]; asked: string[] } {
1068
+ const notes: string[] = [];
1069
+ const asked: string[] = [];
1070
+ const forge = {
1071
+ kind: "forgejo",
1072
+ host: "forgejo.localhost",
1073
+ async setStatus() {},
1074
+ async upsertNote(_pr: number, _marker: string, body: string) {
1075
+ notes.push(body);
1076
+ return "1";
1077
+ },
1078
+ // Every Op last ran a year ago: overdue whichever is asked about.
1079
+ async scheduledRuns(q: { op: string }) {
1080
+ asked.push(q.op);
1081
+ return { last: "2025-10-01T00:00:00Z" };
1082
+ },
1083
+ } as unknown as PrForge;
1084
+ return { forge, notes, asked };
1085
+ }
1086
+
1087
+ test("on the runner, the note warns of the base's overdue Op and not of one the change added", async () => {
1088
+ const { app, base, sha } = stale();
1089
+ const forge = overdueForge();
1090
+ const r = await ciRun({ image: JOB_IMAGE, cwd: app, job: "plan", env: "prod", forge: "forgejo", base, pr: 7, chant: fakeChant(), hostEnv: { ...HOST, FAKE_REPORT: planReport(sha) }, passEnv: ["FAKE_REPORT"], planForge: forge.forge, push: false, output: join(scratchDir("chant-ci-overdue-"), "pr") });
1091
+ expect(r.status).toBe("ok");
1092
+ expect(forge.asked).toEqual(["drift"]);
1093
+ expect(forge.notes).toHaveLength(1);
1094
+ expect(forge.notes[0]).toContain("Op `drift` is overdue");
1095
+ expect(forge.notes[0]).not.toContain("sneaky");
1096
+ expect(r.warnings).toEqual([expect.stringMatching(/Op `drift` is overdue/)]);
1097
+ });
1098
+
1099
+ test("in place, the check still reads the base, not the checkout the change runs in", async () => {
1100
+ const { app, base, sha } = stale();
1101
+ const forge = overdueForge();
1102
+ const r = await ciRun({ cwd: app, job: "plan", env: "prod", forge: "forgejo", base, pr: 7, chant: fakeChant(), hostEnv: { ...HOST, GITHUB_JOB: "plan", FAKE_REPORT: planReport(sha) }, inPlace: true, push: false, planForge: forge.forge });
1103
+ expect(r.status).toBe("ok");
1104
+ expect(forge.asked).toEqual(["drift"]);
1105
+ expect(forge.notes[0]).toContain("Op `drift` is overdue");
1106
+ });
1107
+
1108
+ test("the base checkout resolves packages from the node_modules the running chant is installed in", () => {
1109
+ expect(chantNodeModules("/opt/ci/node_modules/@intentius/chant/dist/workspace/ci-run.js")).toBe("/opt/ci/node_modules");
1110
+ expect(chantNodeModules("/src/chant/packages/core/src/workspace/ci-run.ts")).toBeUndefined();
1111
+ });
1112
+ });
1113
+
1043
1114
  /**
1044
1115
  * #4330: a pull request environment's jobs run through `ci run` too. The
1045
1116
  * stand-in chant keeps each action's arguments, environment, directory and