@intentius/chant 0.79.0 → 0.81.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 (462) hide show
  1. package/dist/audit/core.d.ts.map +1 -1
  2. package/dist/audit/discover.d.ts +30 -0
  3. package/dist/audit/discover.d.ts.map +1 -1
  4. package/dist/audit/report-html.d.ts +5 -0
  5. package/dist/audit/report-html.d.ts.map +1 -1
  6. package/dist/audit/report-model.d.ts +13 -0
  7. package/dist/audit/report-model.d.ts.map +1 -1
  8. package/dist/audit/terraform-state.d.ts +13 -0
  9. package/dist/audit/terraform-state.d.ts.map +1 -1
  10. package/dist/cli/command-group.d.ts +6 -0
  11. package/dist/cli/command-group.d.ts.map +1 -1
  12. package/dist/cli/commands/audit.d.ts +44 -3
  13. package/dist/cli/commands/audit.d.ts.map +1 -1
  14. package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
  15. package/dist/cli/commands/check-lexicon.d.ts +8 -0
  16. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  17. package/dist/cli/commands/doctor.d.ts.map +1 -1
  18. package/dist/cli/commands/import-agents.d.ts.map +1 -1
  19. package/dist/cli/commands/init.d.ts +6 -0
  20. package/dist/cli/commands/init.d.ts.map +1 -1
  21. package/dist/cli/commands/lint.d.ts.map +1 -1
  22. package/dist/cli/commands/onboard.d.ts.map +1 -1
  23. package/dist/cli/commands/update.d.ts +6 -0
  24. package/dist/cli/commands/update.d.ts.map +1 -1
  25. package/dist/cli/commands/vendor.d.ts +5 -0
  26. package/dist/cli/commands/vendor.d.ts.map +1 -1
  27. package/dist/cli/conflict-check.d.ts +6 -0
  28. package/dist/cli/conflict-check.d.ts.map +1 -1
  29. package/dist/cli/handlers/build.d.ts.map +1 -1
  30. package/dist/cli/handlers/components.d.ts.map +1 -1
  31. package/dist/cli/handlers/dev.d.ts.map +1 -1
  32. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  33. package/dist/cli/handlers/init.d.ts.map +1 -1
  34. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  35. package/dist/cli/handlers/misc.d.ts.map +1 -1
  36. package/dist/cli/handlers/operator.d.ts +7 -0
  37. package/dist/cli/handlers/operator.d.ts.map +1 -1
  38. package/dist/cli/handlers/promote.d.ts +20 -0
  39. package/dist/cli/handlers/promote.d.ts.map +1 -0
  40. package/dist/cli/handlers/run-generate.d.ts +39 -0
  41. package/dist/cli/handlers/run-generate.d.ts.map +1 -0
  42. package/dist/cli/handlers/run.d.ts +4 -1
  43. package/dist/cli/handlers/run.d.ts.map +1 -1
  44. package/dist/cli/handlers/vendor.d.ts +8 -3
  45. package/dist/cli/handlers/vendor.d.ts.map +1 -1
  46. package/dist/cli/main.d.ts +12 -0
  47. package/dist/cli/main.d.ts.map +1 -1
  48. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  49. package/dist/cli/plugins.d.ts +14 -0
  50. package/dist/cli/plugins.d.ts.map +1 -1
  51. package/dist/cli/registry.d.ts +46 -1
  52. package/dist/cli/registry.d.ts.map +1 -1
  53. package/dist/codegen/fetch.d.ts +10 -0
  54. package/dist/codegen/fetch.d.ts.map +1 -1
  55. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  56. package/dist/components/cli-support.d.ts +3 -1
  57. package/dist/components/cli-support.d.ts.map +1 -1
  58. package/dist/components/discover.d.ts.map +1 -1
  59. package/dist/components/driver.d.ts +16 -2
  60. package/dist/components/driver.d.ts.map +1 -1
  61. package/dist/components/fan-out-support.d.ts.map +1 -1
  62. package/dist/components/gate-plan.d.ts +40 -0
  63. package/dist/components/gate-plan.d.ts.map +1 -0
  64. package/dist/components/pilots/alb-ecs.pilot.d.ts +7 -1
  65. package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -1
  66. package/dist/components/presets/ecs-fargate.d.ts +10 -4
  67. package/dist/components/presets/ecs-fargate.d.ts.map +1 -1
  68. package/dist/components/promote.d.ts +264 -0
  69. package/dist/components/promote.d.ts.map +1 -0
  70. package/dist/components/sandbox/driver.d.ts.map +1 -1
  71. package/dist/components/sandbox/run.d.ts.map +1 -1
  72. package/dist/components/verbs/build-archive.d.ts +22 -11
  73. package/dist/components/verbs/build-archive.d.ts.map +1 -1
  74. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  75. package/dist/config-static.d.ts +59 -0
  76. package/dist/config-static.d.ts.map +1 -0
  77. package/dist/config.d.ts +104 -5
  78. package/dist/config.d.ts.map +1 -1
  79. package/dist/content-digest.d.ts +17 -0
  80. package/dist/content-digest.d.ts.map +1 -0
  81. package/dist/discovery/files.d.ts +48 -4
  82. package/dist/discovery/files.d.ts.map +1 -1
  83. package/dist/discovery/fold-import.d.ts +32 -1
  84. package/dist/discovery/fold-import.d.ts.map +1 -1
  85. package/dist/discovery/index.d.ts.map +1 -1
  86. package/dist/discovery/sandbox/driver.d.ts +20 -0
  87. package/dist/discovery/sandbox/driver.d.ts.map +1 -1
  88. package/dist/discovery/sandbox/fork.d.ts +20 -0
  89. package/dist/discovery/sandbox/fork.d.ts.map +1 -1
  90. package/dist/discovery/walk.d.ts +119 -0
  91. package/dist/discovery/walk.d.ts.map +1 -0
  92. package/dist/graph-ir.d.ts +17 -1
  93. package/dist/graph-ir.d.ts.map +1 -1
  94. package/dist/join-key.d.ts +31 -0
  95. package/dist/join-key.d.ts.map +1 -0
  96. package/dist/lexicon-module.d.ts +110 -0
  97. package/dist/lexicon-module.d.ts.map +1 -0
  98. package/dist/lexicon.d.ts +82 -1
  99. package/dist/lexicon.d.ts.map +1 -1
  100. package/dist/lifecycle/build-ledger-store.d.ts.map +1 -1
  101. package/dist/lifecycle/digest.d.ts +5 -1
  102. package/dist/lifecycle/digest.d.ts.map +1 -1
  103. package/dist/lifecycle/gate-ledger.d.ts +20 -5
  104. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  105. package/dist/lifecycle/git.d.ts +64 -0
  106. package/dist/lifecycle/git.d.ts.map +1 -1
  107. package/dist/lifecycle/lease.d.ts +8 -1
  108. package/dist/lifecycle/lease.d.ts.map +1 -1
  109. package/dist/lifecycle/legacy-digest.d.ts +59 -0
  110. package/dist/lifecycle/legacy-digest.d.ts.map +1 -0
  111. package/dist/lifecycle/member-ledger.d.ts +67 -0
  112. package/dist/lifecycle/member-ledger.d.ts.map +1 -0
  113. package/dist/lifecycle/plan-digest.d.ts +4 -3
  114. package/dist/lifecycle/plan-digest.d.ts.map +1 -1
  115. package/dist/lifecycle/release-ledger.d.ts +42 -1
  116. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  117. package/dist/lifecycle/status.d.ts +90 -5
  118. package/dist/lifecycle/status.d.ts.map +1 -1
  119. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +2 -3
  120. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -1
  121. package/dist/op/activities/activity-contracts.d.ts +32 -0
  122. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  123. package/dist/op/activities/index.d.ts +2 -0
  124. package/dist/op/activities/index.d.ts.map +1 -1
  125. package/dist/op/activities/propose-upgrade.d.ts +75 -0
  126. package/dist/op/activities/propose-upgrade.d.ts.map +1 -0
  127. package/dist/op/activity-contract-registry.d.ts.map +1 -1
  128. package/dist/op/activity-registry.d.ts.map +1 -1
  129. package/dist/op/discover.d.ts.map +1 -1
  130. package/dist/op/gate-approval.d.ts.map +1 -1
  131. package/dist/op/gate-name.d.ts +7 -0
  132. package/dist/op/gate-name.d.ts.map +1 -1
  133. package/dist/op/gate-summary.d.ts +16 -0
  134. package/dist/op/gate-summary.d.ts.map +1 -1
  135. package/dist/op/gate.d.ts +16 -2
  136. package/dist/op/gate.d.ts.map +1 -1
  137. package/dist/op/generate-pipeline.d.ts.map +1 -1
  138. package/dist/op/runtimes/local.d.ts.map +1 -1
  139. package/dist/project-root.d.ts +26 -0
  140. package/dist/project-root.d.ts.map +1 -1
  141. package/dist/workspace/__fixtures__/contract-repo.d.ts +37 -0
  142. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -0
  143. package/dist/workspace/__fixtures__/two-member-workspace.d.ts +11 -0
  144. package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -0
  145. package/dist/workspace/checks/generated.d.ts +156 -0
  146. package/dist/workspace/checks/generated.d.ts.map +1 -0
  147. package/dist/workspace/checks/ledgers.d.ts +78 -0
  148. package/dist/workspace/checks/ledgers.d.ts.map +1 -0
  149. package/dist/workspace/checks/links.d.ts +25 -0
  150. package/dist/workspace/checks/links.d.ts.map +1 -0
  151. package/dist/workspace/checks/pipelines.d.ts +76 -0
  152. package/dist/workspace/checks/pipelines.d.ts.map +1 -0
  153. package/dist/workspace/checks.d.ts +161 -0
  154. package/dist/workspace/checks.d.ts.map +1 -0
  155. package/dist/workspace/compose-graph.d.ts +115 -0
  156. package/dist/workspace/compose-graph.d.ts.map +1 -0
  157. package/dist/workspace/compose-reports.d.ts +68 -0
  158. package/dist/workspace/compose-reports.d.ts.map +1 -0
  159. package/dist/workspace/declaration.d.ts +220 -0
  160. package/dist/workspace/declaration.d.ts.map +1 -0
  161. package/dist/workspace/declaration.schema.json +434 -0
  162. package/dist/workspace/generated-files.d.ts +65 -0
  163. package/dist/workspace/generated-files.d.ts.map +1 -0
  164. package/dist/workspace/glob.d.ts +13 -0
  165. package/dist/workspace/glob.d.ts.map +1 -0
  166. package/dist/workspace/graph-cli.d.ts +75 -0
  167. package/dist/workspace/graph-cli.d.ts.map +1 -0
  168. package/dist/workspace/init.d.ts +96 -0
  169. package/dist/workspace/init.d.ts.map +1 -0
  170. package/dist/workspace/jsonc.d.ts +38 -0
  171. package/dist/workspace/jsonc.d.ts.map +1 -0
  172. package/dist/workspace/kinds.d.ts +192 -0
  173. package/dist/workspace/kinds.d.ts.map +1 -0
  174. package/dist/workspace/lineage-check.d.ts +84 -0
  175. package/dist/workspace/lineage-check.d.ts.map +1 -0
  176. package/dist/workspace/lineage-cli.d.ts +31 -0
  177. package/dist/workspace/lineage-cli.d.ts.map +1 -0
  178. package/dist/workspace/lineage-init.d.ts +101 -0
  179. package/dist/workspace/lineage-init.d.ts.map +1 -0
  180. package/dist/workspace/lineage-lock.d.ts +258 -0
  181. package/dist/workspace/lineage-lock.d.ts.map +1 -0
  182. package/dist/workspace/lineage-merge.d.ts +14 -0
  183. package/dist/workspace/lineage-merge.d.ts.map +1 -0
  184. package/dist/workspace/lineage-migrations.d.ts +178 -0
  185. package/dist/workspace/lineage-migrations.d.ts.map +1 -0
  186. package/dist/workspace/lineage-update.d.ts +59 -0
  187. package/dist/workspace/lineage-update.d.ts.map +1 -0
  188. package/dist/workspace/lineage-upgrade-cli.d.ts +40 -0
  189. package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -0
  190. package/dist/workspace/lineage-upgrade.d.ts +146 -0
  191. package/dist/workspace/lineage-upgrade.d.ts.map +1 -0
  192. package/dist/workspace/lineage-vendor.d.ts +63 -0
  193. package/dist/workspace/lineage-vendor.d.ts.map +1 -0
  194. package/dist/workspace/lineage-version.d.ts +32 -0
  195. package/dist/workspace/lineage-version.d.ts.map +1 -0
  196. package/dist/workspace/links.d.ts +150 -0
  197. package/dist/workspace/links.d.ts.map +1 -0
  198. package/dist/workspace/ls.d.ts +99 -0
  199. package/dist/workspace/ls.d.ts.map +1 -0
  200. package/dist/workspace/member-commands.d.ts +139 -0
  201. package/dist/workspace/member-commands.d.ts.map +1 -0
  202. package/dist/workspace/member-handles.d.ts +54 -0
  203. package/dist/workspace/member-handles.d.ts.map +1 -0
  204. package/dist/workspace/member-pipeline.d.ts +143 -0
  205. package/dist/workspace/member-pipeline.d.ts.map +1 -0
  206. package/dist/workspace/member-run.d.ts +67 -0
  207. package/dist/workspace/member-run.d.ts.map +1 -0
  208. package/dist/workspace/reason-codes.d.ts +65 -0
  209. package/dist/workspace/reason-codes.d.ts.map +1 -0
  210. package/dist/workspace/record-source.d.ts +22 -0
  211. package/dist/workspace/record-source.d.ts.map +1 -0
  212. package/dist/workspace/records-cli.d.ts +81 -0
  213. package/dist/workspace/records-cli.d.ts.map +1 -0
  214. package/dist/workspace/records.d.ts +118 -0
  215. package/dist/workspace/records.d.ts.map +1 -0
  216. package/dist/workspace/root-refusal.d.ts +53 -0
  217. package/dist/workspace/root-refusal.d.ts.map +1 -0
  218. package/dist/workspace/status.d.ts +150 -0
  219. package/dist/workspace/status.d.ts.map +1 -0
  220. package/dist/workspace/template-manifest.d.ts +90 -0
  221. package/dist/workspace/template-manifest.d.ts.map +1 -0
  222. package/dist/workspace/tree.d.ts +36 -0
  223. package/dist/workspace/tree.d.ts.map +1 -0
  224. package/dist/workspace/trust/attestor.d.ts +67 -0
  225. package/dist/workspace/trust/attestor.d.ts.map +1 -0
  226. package/dist/workspace/trust/policy.d.ts +111 -0
  227. package/dist/workspace/trust/policy.d.ts.map +1 -0
  228. package/dist/workspace/trust/provenance.d.ts +67 -0
  229. package/dist/workspace/trust/provenance.d.ts.map +1 -0
  230. package/dist/workspace/trust/ssh-commit.d.ts +43 -0
  231. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -0
  232. package/dist/workspace/trust/test-repo.d.ts +42 -0
  233. package/dist/workspace/trust/test-repo.d.ts.map +1 -0
  234. package/dist/workspace/trust/verify-cli.d.ts +9 -0
  235. package/dist/workspace/trust/verify-cli.d.ts.map +1 -0
  236. package/dist/workspace/trust/verify.d.ts +66 -0
  237. package/dist/workspace/trust/verify.d.ts.map +1 -0
  238. package/dist/workspace/which-chant.d.ts +58 -0
  239. package/dist/workspace/which-chant.d.ts.map +1 -0
  240. package/dist/workspace/workspace-kinds.schema.json +133 -0
  241. package/package.json +4 -3
  242. package/src/audit/core.ts +13 -2
  243. package/src/audit/discover.ts +102 -36
  244. package/src/audit/report-html.ts +3 -1
  245. package/src/audit/report-model.ts +14 -1
  246. package/src/audit/terraform-state.test.ts +81 -1
  247. package/src/audit/terraform-state.ts +25 -0
  248. package/src/cli/command-group.ts +7 -0
  249. package/src/cli/commands/audit-truncation.test.ts +167 -0
  250. package/src/cli/commands/audit.ts +132 -29
  251. package/src/cli/commands/check-lexicon-examples.ts +2 -1
  252. package/src/cli/commands/check-lexicon.ts +24 -0
  253. package/src/cli/commands/doctor.test.ts +30 -0
  254. package/src/cli/commands/doctor.ts +21 -0
  255. package/src/cli/commands/import-agents.ts +15 -4
  256. package/src/cli/commands/init.ts +76 -25
  257. package/src/cli/commands/lint.test.ts +52 -1
  258. package/src/cli/commands/lint.ts +41 -71
  259. package/src/cli/commands/onboard.ts +15 -0
  260. package/src/cli/commands/update.ts +18 -5
  261. package/src/cli/commands/vendor.ts +11 -6
  262. package/src/cli/conflict-check.test.ts +18 -1
  263. package/src/cli/conflict-check.ts +13 -0
  264. package/src/cli/discovery-skip.test.ts +168 -0
  265. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +38 -0
  266. package/src/cli/handlers/build-member.test.ts +127 -0
  267. package/src/cli/handlers/build.test.ts +11 -0
  268. package/src/cli/handlers/build.ts +35 -1
  269. package/src/cli/handlers/components.test.ts +127 -0
  270. package/src/cli/handlers/components.ts +70 -8
  271. package/src/cli/handlers/dev.ts +6 -1
  272. package/src/cli/handlers/fan-out.ts +9 -2
  273. package/src/cli/handlers/graph.test.ts +20 -0
  274. package/src/cli/handlers/graph.ts +4 -2
  275. package/src/cli/handlers/init.ts +54 -1
  276. package/src/cli/handlers/lifecycle.ts +2 -1
  277. package/src/cli/handlers/misc.ts +16 -0
  278. package/src/cli/handlers/operator.test.ts +68 -0
  279. package/src/cli/handlers/operator.ts +39 -10
  280. package/src/cli/handlers/promote.test.ts +377 -0
  281. package/src/cli/handlers/promote.ts +391 -0
  282. package/src/cli/handlers/run-generate-member.test.ts +83 -0
  283. package/src/cli/handlers/run-generate.test.ts +158 -0
  284. package/src/cli/handlers/run-generate.ts +168 -0
  285. package/src/cli/handlers/run.test.ts +47 -0
  286. package/src/cli/handlers/run.ts +41 -6
  287. package/src/cli/handlers/vendor.ts +147 -43
  288. package/src/cli/main.test.ts +137 -1
  289. package/src/cli/main.ts +290 -24
  290. package/src/cli/mcp/op-tools.ts +2 -1
  291. package/src/cli/path-lexicon-messages.test.ts +152 -0
  292. package/src/cli/plugins.ts +43 -5
  293. package/src/cli/registry.ts +46 -1
  294. package/src/cli/static-config-read.test.ts +208 -0
  295. package/src/codegen/fetch.test.ts +63 -1
  296. package/src/codegen/fetch.ts +30 -1
  297. package/src/components/__fixtures__/alb-ecs-service.json +0 -8
  298. package/src/components/capability-plugin-loader.ts +3 -1
  299. package/src/components/cli-support.ts +9 -3
  300. package/src/components/discover.test.ts +27 -57
  301. package/src/components/discover.ts +20 -51
  302. package/src/components/driver.test.ts +90 -12
  303. package/src/components/driver.ts +43 -5
  304. package/src/components/fan-out-support.ts +2 -1
  305. package/src/components/gate-plan.ts +53 -0
  306. package/src/components/pilots/README.md +1 -1
  307. package/src/components/pilots/alb-ecs.pilot.ts +7 -7
  308. package/src/components/presets/ecs-fargate.ts +10 -5
  309. package/src/components/promote.test.ts +645 -0
  310. package/src/components/promote.ts +641 -0
  311. package/src/components/sandbox/driver.ts +8 -8
  312. package/src/components/sandbox/run.ts +4 -1
  313. package/src/components/verbs/build-archive.ts +27 -21
  314. package/src/components/verbs/run-agent.ts +3 -5
  315. package/src/config-static.test.ts +136 -0
  316. package/src/config-static.ts +232 -0
  317. package/src/config.test.ts +40 -0
  318. package/src/config.ts +161 -11
  319. package/src/content-digest.test.ts +120 -0
  320. package/src/content-digest.ts +21 -0
  321. package/src/discovery/files.test.ts +127 -1
  322. package/src/discovery/files.ts +91 -59
  323. package/src/discovery/fold-import.ts +127 -9
  324. package/src/discovery/index.ts +1 -0
  325. package/src/discovery/sandbox/driver.ts +73 -35
  326. package/src/discovery/sandbox/fork-diagnostic.test.ts +136 -13
  327. package/src/discovery/sandbox/fork.ts +83 -43
  328. package/src/discovery/sandbox/path-lexicon-multifile.test.ts +285 -0
  329. package/src/discovery/sandbox/path-lexicon-parity.test.ts +162 -0
  330. package/src/discovery/walk.test.ts +263 -0
  331. package/src/discovery/walk.ts +334 -0
  332. package/src/effect-receipt.ts +2 -2
  333. package/src/graph-ir.ts +18 -1
  334. package/src/join-key.test.ts +25 -0
  335. package/src/join-key.ts +39 -0
  336. package/src/lexicon-module.test.ts +203 -0
  337. package/src/lexicon-module.ts +215 -0
  338. package/src/lexicon.ts +105 -1
  339. package/src/lifecycle/build-ledger-store.ts +13 -2
  340. package/src/lifecycle/digest.ts +9 -5
  341. package/src/lifecycle/gate-ledger.test.ts +20 -0
  342. package/src/lifecycle/gate-ledger.ts +28 -6
  343. package/src/lifecycle/git.test.ts +13 -1
  344. package/src/lifecycle/git.ts +221 -176
  345. package/src/lifecycle/lease.ts +25 -10
  346. package/src/lifecycle/legacy-digest.test.ts +206 -0
  347. package/src/lifecycle/legacy-digest.ts +103 -0
  348. package/src/lifecycle/member-ledger.test.ts +319 -0
  349. package/src/lifecycle/member-ledger.ts +145 -0
  350. package/src/lifecycle/plan-digest.ts +7 -7
  351. package/src/lifecycle/release-ledger.test.ts +20 -0
  352. package/src/lifecycle/release-ledger.ts +53 -3
  353. package/src/lifecycle/status.test.ts +151 -1
  354. package/src/lifecycle/status.ts +228 -7
  355. package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +2 -3
  356. package/src/op/activities/activity-contracts.ts +37 -0
  357. package/src/op/activities/index.ts +10 -0
  358. package/src/op/activities/propose-upgrade.ts +206 -0
  359. package/src/op/activity-contract-registry.ts +18 -4
  360. package/src/op/activity-registry.ts +26 -0
  361. package/src/op/discover.test.ts +14 -4
  362. package/src/op/discover.ts +19 -23
  363. package/src/op/gate-approval.ts +3 -1
  364. package/src/op/gate-name.ts +8 -0
  365. package/src/op/gate-summary.test.ts +14 -0
  366. package/src/op/gate-summary.ts +25 -2
  367. package/src/op/gate.ts +36 -7
  368. package/src/op/generate-pipeline.test.ts +14 -5
  369. package/src/op/generate-pipeline.ts +2 -1
  370. package/src/op/runtimes/local.ts +2 -1
  371. package/src/project-root.test.ts +66 -1
  372. package/src/project-root.ts +48 -0
  373. package/src/workspace/__fixtures__/contract-repo.ts +100 -0
  374. package/src/workspace/__fixtures__/two-member-workspace.ts +35 -0
  375. package/src/workspace/behold-kinds.test.ts +162 -0
  376. package/src/workspace/check-contract.test.ts +127 -0
  377. package/src/workspace/check.schema.json +347 -0
  378. package/src/workspace/checks/generated.test.ts +287 -0
  379. package/src/workspace/checks/generated.ts +475 -0
  380. package/src/workspace/checks/ledgers.test.ts +139 -0
  381. package/src/workspace/checks/ledgers.ts +246 -0
  382. package/src/workspace/checks/links.ts +183 -0
  383. package/src/workspace/checks/pipelines.test.ts +133 -0
  384. package/src/workspace/checks/pipelines.ts +225 -0
  385. package/src/workspace/checks-repo.test.ts +71 -0
  386. package/src/workspace/checks.test.ts +450 -0
  387. package/src/workspace/checks.ts +461 -0
  388. package/src/workspace/compose-graph.test.ts +146 -0
  389. package/src/workspace/compose-graph.ts +228 -0
  390. package/src/workspace/compose-reports.test.ts +109 -0
  391. package/src/workspace/compose-reports.ts +186 -0
  392. package/src/workspace/declaration.schema.json +434 -0
  393. package/src/workspace/declaration.test.ts +347 -0
  394. package/src/workspace/declaration.ts +623 -0
  395. package/src/workspace/generated-files.test.ts +114 -0
  396. package/src/workspace/generated-files.ts +94 -0
  397. package/src/workspace/glob.ts +49 -0
  398. package/src/workspace/graph-cli.ts +220 -0
  399. package/src/workspace/graph-contract.test.ts +204 -0
  400. package/src/workspace/graph.schema.json +662 -0
  401. package/src/workspace/init.test.ts +223 -0
  402. package/src/workspace/init.ts +585 -0
  403. package/src/workspace/jsonc.test.ts +65 -0
  404. package/src/workspace/jsonc.ts +231 -0
  405. package/src/workspace/kinds.test.ts +288 -0
  406. package/src/workspace/kinds.ts +506 -0
  407. package/src/workspace/lineage-check.ts +264 -0
  408. package/src/workspace/lineage-cli.ts +114 -0
  409. package/src/workspace/lineage-init.test.ts +231 -0
  410. package/src/workspace/lineage-init.ts +365 -0
  411. package/src/workspace/lineage-lock.test.ts +224 -0
  412. package/src/workspace/lineage-lock.ts +357 -0
  413. package/src/workspace/lineage-merge.ts +44 -0
  414. package/src/workspace/lineage-migrations.test.ts +178 -0
  415. package/src/workspace/lineage-migrations.ts +343 -0
  416. package/src/workspace/lineage-update.ts +160 -0
  417. package/src/workspace/lineage-upgrade-cli.ts +151 -0
  418. package/src/workspace/lineage-upgrade.test.ts +474 -0
  419. package/src/workspace/lineage-upgrade.ts +624 -0
  420. package/src/workspace/lineage-vendor.test.ts +126 -0
  421. package/src/workspace/lineage-vendor.ts +215 -0
  422. package/src/workspace/lineage-version.ts +127 -0
  423. package/src/workspace/links.test.ts +344 -0
  424. package/src/workspace/links.ts +331 -0
  425. package/src/workspace/ls-contract.test.ts +200 -0
  426. package/src/workspace/ls.schema.json +190 -0
  427. package/src/workspace/ls.ts +254 -0
  428. package/src/workspace/member-commands.e2e.test.ts +244 -0
  429. package/src/workspace/member-commands.test.ts +217 -0
  430. package/src/workspace/member-commands.ts +540 -0
  431. package/src/workspace/member-handles.test.ts +76 -0
  432. package/src/workspace/member-handles.ts +228 -0
  433. package/src/workspace/member-pipeline.test.ts +135 -0
  434. package/src/workspace/member-pipeline.ts +317 -0
  435. package/src/workspace/member-run.ts +146 -0
  436. package/src/workspace/read-contract.test.ts +129 -0
  437. package/src/workspace/reason-codes.test.ts +113 -0
  438. package/src/workspace/reason-codes.ts +81 -0
  439. package/src/workspace/record-source.ts +117 -0
  440. package/src/workspace/records-cli.ts +198 -0
  441. package/src/workspace/records-contract.test.ts +93 -0
  442. package/src/workspace/records.schema.json +150 -0
  443. package/src/workspace/records.test.ts +243 -0
  444. package/src/workspace/records.ts +384 -0
  445. package/src/workspace/root-refusal.ts +97 -0
  446. package/src/workspace/status-contract.test.ts +308 -0
  447. package/src/workspace/status.schema.json +210 -0
  448. package/src/workspace/status.ts +412 -0
  449. package/src/workspace/template-manifest.test.ts +79 -0
  450. package/src/workspace/template-manifest.ts +221 -0
  451. package/src/workspace/tree.ts +132 -0
  452. package/src/workspace/trust/attestor.ts +93 -0
  453. package/src/workspace/trust/policy.ts +306 -0
  454. package/src/workspace/trust/provenance.ts +181 -0
  455. package/src/workspace/trust/ssh-commit.ts +138 -0
  456. package/src/workspace/trust/test-repo.ts +138 -0
  457. package/src/workspace/trust/trust.test.ts +397 -0
  458. package/src/workspace/trust/verify-cli.ts +56 -0
  459. package/src/workspace/trust/verify.ts +194 -0
  460. package/src/workspace/which-chant.test.ts +117 -0
  461. package/src/workspace/which-chant.ts +151 -0
  462. package/src/workspace/workspace-kinds.schema.json +133 -0
@@ -0,0 +1,168 @@
1
+ /**
2
+ * `chant run --generate <provider>` (#2533): the CLI for `generateOpsPipeline`
3
+ * (../../op/generate-pipeline.ts), the Op counterpart to `chant build
4
+ * --components --generate <lexicon>`.
5
+ *
6
+ * The generator is a library function with no caller of its own. A pipeline
7
+ * committed to a repository needs a named command that regenerates it, so a
8
+ * drift check (#2524 D14) can rerun that command and compare. This handler is
9
+ * that command and adds nothing to the generator's output beyond one header
10
+ * line: every file it writes is the generator's own `yaml`, byte for byte,
11
+ * under a first line carrying {@link GENERATED_MARKER} and the command that
12
+ * produced it.
13
+ *
14
+ * Where the specs come from:
15
+ *
16
+ * - by default, every discovered Op that declares its own `schedule`, as a
17
+ * bare `{ name }` spec. `generateOpsPipeline` fills the cron in from the Op
18
+ * (`withOpSchedules`), so the result is exactly what a direct call with
19
+ * those names returns;
20
+ * - with `--spec <file>`, a JSON file holding either a `ScheduledOpSpec[]`
21
+ * or `{ ops, options }`, for Ops triggered by a pull request or a push, a
22
+ * finding mode, setup steps, or generator options. The file is passed to
23
+ * the generator unchanged.
24
+ */
25
+
26
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
27
+ import { dirname, join, resolve } from "node:path";
28
+ import { discoverOps } from "../../op/discover";
29
+ import { generateOpsPipeline } from "../../op/generate-pipeline";
30
+ import { GENERATED_MARKER } from "../../discovery/files";
31
+ import { findWorkspaceRoot } from "../../project-root";
32
+ import type { ComponentPipelineOptions, ScheduledOpSpec } from "../../lexicon";
33
+ import { formatBold, formatError, formatSuccess } from "../format";
34
+ import type { CommandContext } from "../registry";
35
+
36
+ /** Where each forge reads its pipeline files from, relative to the repository root. `--output` overrides it. */
37
+ export const DEFAULT_OP_PIPELINE_DIRS: Record<string, string> = {
38
+ github: ".github/workflows",
39
+ forgejo: ".forgejo/workflows",
40
+ gitlab: ".",
41
+ };
42
+
43
+ /** The first line of every file this command writes. */
44
+ export function opPipelineHeader(provider: string, specFile?: string): string {
45
+ const command = `chant run --generate ${provider}${specFile ? ` --spec ${specFile}` : ""}`;
46
+ return `# ${GENERATED_MARKER}. Regenerate with: ${command}\n`;
47
+ }
48
+
49
+ interface SpecFile {
50
+ ops: ScheduledOpSpec[];
51
+ options?: ComponentPipelineOptions;
52
+ }
53
+
54
+ /** Read `--spec`: a `ScheduledOpSpec[]`, or `{ ops, options }`. Throws with the file named on anything else. */
55
+ export function readOpSpecFile(path: string): SpecFile {
56
+ let parsed: unknown;
57
+ try {
58
+ parsed = JSON.parse(readFileSync(path, "utf-8"));
59
+ } catch (err) {
60
+ throw new Error(`--spec ${path}: ${err instanceof Error ? err.message : String(err)}`);
61
+ }
62
+ const shape = "an array of Op specs, or { \"ops\": [...], \"options\": {...} }";
63
+ const ops = Array.isArray(parsed) ? parsed : (parsed as { ops?: unknown } | null)?.ops;
64
+ if (!Array.isArray(ops)) throw new Error(`--spec ${path} must hold ${shape}`);
65
+ for (const [i, spec] of ops.entries()) {
66
+ if (typeof (spec as { name?: unknown })?.name !== "string") {
67
+ throw new Error(`--spec ${path}: entry ${i} has no "name". Each entry names the Op it generates a pipeline for.`);
68
+ }
69
+ }
70
+ const options = Array.isArray(parsed) ? undefined : (parsed as { options?: ComponentPipelineOptions }).options;
71
+ return { ops: ops as ScheduledOpSpec[], ...(options ? { options } : {}) };
72
+ }
73
+
74
+ export async function runOpGenerate(ctx: CommandContext): Promise<number> {
75
+ const { args } = ctx;
76
+ const provider = args.generate as string;
77
+ const specPath = args.opsSpec;
78
+
79
+ let ops: ScheduledOpSpec[];
80
+ let options: ComponentPipelineOptions | undefined;
81
+ if (specPath) {
82
+ try {
83
+ ({ ops, options } = readOpSpecFile(resolve(specPath)));
84
+ } catch (err) {
85
+ console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
86
+ return 1;
87
+ }
88
+ } else {
89
+ const discovered = await discoverOps();
90
+ if (discovered.errors.length > 0) {
91
+ for (const e of discovered.errors) console.error(formatError({ message: e }));
92
+ return 1;
93
+ }
94
+ ops = [...discovered.ops.entries()]
95
+ .filter(([, op]) => op.config.schedule)
96
+ .map(([name]) => name)
97
+ .sort()
98
+ .map((name) => ({ name }));
99
+ if (ops.length === 0) {
100
+ console.error(formatError({
101
+ message: "No Op declares a schedule, so there is nothing to generate",
102
+ hint: "Give an Op a `schedule`, or pass --spec <file.json> naming the Ops and their triggers.",
103
+ }));
104
+ return 1;
105
+ }
106
+ }
107
+
108
+ // Inside a workspace member (#2542), the files are the member's: filtered
109
+ // to its paths, run in its directory, named after it at the repository
110
+ // root's forge directory, and recorded as its generated files. The
111
+ // workspace module loads only when a declaration was found.
112
+ const found = findWorkspaceRoot(process.cwd());
113
+ let plan: import("../../workspace/member-pipeline").MemberOpPlan | undefined;
114
+ let memberPipeline: typeof import("../../workspace/member-pipeline") | undefined;
115
+ if (found) {
116
+ memberPipeline = await import("../../workspace/member-pipeline");
117
+ try {
118
+ plan = memberPipeline.planMemberOpPipelines({ projectDir: process.cwd(), found, provider, output: args.output, specFile: specPath });
119
+ } catch (err) {
120
+ console.error(formatError({ message: memberPipeline.describeError(err) }));
121
+ return 1;
122
+ }
123
+ }
124
+
125
+ const result = await generateOpsPipeline(ops, provider, plan ? { ...options, member: plan.member } : options);
126
+ if (!result.success) {
127
+ console.error(formatError({ message: result.error ?? "Failed to generate Op pipelines" }));
128
+ return 1;
129
+ }
130
+
131
+ if (plan && memberPipeline) {
132
+ const header = memberPipeline.generatedHeader(plan.command);
133
+ if (args.format === "json") {
134
+ const files = (result.files ?? []).map((f) => ({
135
+ name: f.name,
136
+ path: memberPipeline.repoRelative(plan.ctx, join(plan.outDir, f.name)),
137
+ content: header + f.yaml,
138
+ }));
139
+ console.log(JSON.stringify({ member: plan.member.name, files, jobs: result.jobs ?? [] }, null, 2));
140
+ return 0;
141
+ }
142
+ for (const path of memberPipeline.writeMemberOpPipelines(plan, result.files ?? [])) {
143
+ console.error(formatSuccess(`wrote ${formatBold(path)} for member ${formatBold(plan.member.name)}`));
144
+ }
145
+ return 0;
146
+ }
147
+
148
+ const header = opPipelineHeader(provider, specPath);
149
+ const outDir = args.output ?? DEFAULT_OP_PIPELINE_DIRS[provider] ?? ".";
150
+ const files = (result.files ?? []).map((f) => ({
151
+ name: f.name,
152
+ path: join(outDir, f.name),
153
+ content: header + f.yaml,
154
+ }));
155
+
156
+ if (args.format === "json") {
157
+ console.log(JSON.stringify({ files, jobs: result.jobs ?? [] }, null, 2));
158
+ return 0;
159
+ }
160
+
161
+ for (const file of files) {
162
+ const target = resolve(file.path);
163
+ mkdirSync(dirname(target), { recursive: true });
164
+ writeFileSync(target, file.content);
165
+ console.error(formatSuccess(`wrote ${formatBold(file.path)}`));
166
+ }
167
+ return 0;
168
+ }
@@ -1157,6 +1157,53 @@ describe("runOpComponents", () => {
1157
1157
  vi.restoreAllMocks();
1158
1158
  });
1159
1159
 
1160
+ test("--digest-file writes <component>=<digest> for each release the run recorded (#2602)", async () => {
1161
+ runComponentsMock.mockResolvedValue({
1162
+ success: true,
1163
+ selected: ["svc", "infra"],
1164
+ run: {
1165
+ order: ["svc", "infra"],
1166
+ waves: [["svc", "infra"]],
1167
+ results: [
1168
+ { component: "svc", ok: true, status: "ok", records: [] },
1169
+ { component: "infra", ok: true, status: "ok", records: [] },
1170
+ ],
1171
+ ok: true,
1172
+ status: "ok",
1173
+ },
1174
+ });
1175
+ maybeRecordAutoReleaseMock.mockImplementation(async (info: { component: string }) =>
1176
+ info.component === "svc"
1177
+ ? { recorded: true, commit: "a".repeat(40), record: { version: 1, component: "svc", env: "staging", digest: "sha256:abc", gitSha: "x", runId: "local-1", timestamp: "t", actor: "a" } }
1178
+ : { recorded: false, reason: "no-digest" });
1179
+ writeFileSyncMock.mockClear();
1180
+ vi.spyOn(process.stderr, "write").mockImplementation(() => true);
1181
+
1182
+ const exit = await runOpComponents({ args: makeArgs({ path: "all", env: "staging", digestFile: "out/svc.digest" }), plugins: [], serializers: [] });
1183
+
1184
+ expect(exit).toBe(0);
1185
+ const call = writeFileSyncMock.mock.calls.find(([path]) => String(path).endsWith("out/svc.digest"));
1186
+ expect(call?.[1]).toBe("svc=sha256:abc\n");
1187
+ vi.restoreAllMocks();
1188
+ });
1189
+
1190
+ test("--digest-file is written empty when nothing was recorded (#2602)", async () => {
1191
+ runComponentsMock.mockResolvedValue({
1192
+ success: true,
1193
+ selected: ["svc"],
1194
+ run: { order: ["svc"], waves: [["svc"]], results: [{ component: "svc", ok: true, status: "ok", records: [] }], ok: true, status: "ok" },
1195
+ });
1196
+ maybeRecordAutoReleaseMock.mockResolvedValue({ recorded: false, reason: "opted-out" });
1197
+ writeFileSyncMock.mockClear();
1198
+ vi.spyOn(process.stderr, "write").mockImplementation(() => true);
1199
+
1200
+ await runOpComponents({ args: makeArgs({ path: "svc", digestFile: "svc.digest" }), plugins: [], serializers: [] });
1201
+
1202
+ const call = writeFileSyncMock.mock.calls.find(([path]) => String(path).endsWith("svc.digest"));
1203
+ expect(call?.[1]).toBe("");
1204
+ vi.restoreAllMocks();
1205
+ });
1206
+
1160
1207
  test("a release-write error is surfaced as a warning but does not change the exit code", async () => {
1161
1208
  runComponentsMock.mockResolvedValue({
1162
1209
  success: true,
@@ -1,3 +1,4 @@
1
+ import { lexiconNames } from "../../lexicon-module";
1
2
  import { resolve, dirname } from "node:path";
2
3
  import { writeFileSync, mkdirSync, readFileSync } from "node:fs";
3
4
  import { loadChantConfig, resolveAutoReleaseDisabled, type ChantConfig } from "../../config";
@@ -5,8 +6,8 @@ import { discoverOps } from "../../op/discover";
5
6
  import type { OpConfig } from "../../op/types";
6
7
  import { loadActivities, loadProfiles } from "../../op/activity-registry";
7
8
  import { runOpLocally, findPolicyGateStep, OpRunFailure, type StepRecord } from "../../op/local-executor";
8
- import { approveCommand } from "../../op/gate";
9
- import { writeGatedRunSummary, type GatedRunSummary } from "../../op/gate-summary";
9
+ import { approveCommand, describeGateMismatch } from "../../op/gate";
10
+ import { summaryLedgerPrefix, writeGatedRunSummary, type GatedRunSummary } from "../../op/gate-summary";
10
11
  import { createLocalOpRuntime } from "../../op/runtimes/local";
11
12
  import type { OpRuntimeProvider, OpRunStatus } from "../../op/runtime";
12
13
  import { renderHuman, renderJson } from "../../op/local-output";
@@ -20,6 +21,8 @@ import { ndjsonProgressSink } from "../../components/run-progress";
20
21
  import { maybeRecordAutoRelease } from "../../components/auto-release";
21
22
  import { maybePersistBuildManifest } from "../../components/manifest-persistence";
22
23
  import type { DriverComponentResult } from "../../components/driver";
24
+ import type { ReleaseRecord } from "../../lifecycle/release-ledger";
25
+ import { runOpGenerate } from "./run-generate";
23
26
 
24
27
  /**
25
28
  * The exit code a run that stopped at an unapproved gate uses (#2119).
@@ -124,7 +127,7 @@ async function resolveOpRuntime(ctx: CommandContext): Promise<OpRuntimeProvider
124
127
  // long wait to be told a name is wrong.
125
128
  let configured: string[] = [];
126
129
  try {
127
- configured = (await loadChantConfig(resolve("."))).config.lexicons ?? [];
130
+ configured = lexiconNames((await loadChantConfig(resolve("."))).config.lexicons ?? []);
128
131
  } catch {
129
132
  // No/unreadable chant.config.ts — the error below says so by listing nothing.
130
133
  }
@@ -524,6 +527,16 @@ function refusesPolicyGateUnderSandbox(ctx: CommandContext, config: OpConfig, op
524
527
  }
525
528
 
526
529
  export async function runOp(ctx: CommandContext): Promise<number> {
530
+ if (ctx.args.generate) {
531
+ if (ctx.args.components) {
532
+ console.error(formatError({
533
+ message: "`chant run --components --generate` is not a command",
534
+ hint: "Generate a component pipeline with `chant build --components --generate <lexicon>`, or Op pipelines with `chant run --generate <provider>`.",
535
+ }));
536
+ return 1;
537
+ }
538
+ return runOpGenerate(ctx);
539
+ }
527
540
  if (ctx.args.components && ctx.args.report) {
528
541
  console.error(formatError({
529
542
  message: "--report is not supported with --components",
@@ -576,13 +589,16 @@ export async function runOp(ctx: CommandContext): Promise<number> {
576
589
  * ledger for work `chant run --components` records. Each result is filtered on
577
590
  * its own `ok`, so a fan-out that partly succeeded records exactly the
578
591
  * components that did.
592
+ *
593
+ * Returns the records it wrote, for `--digest-file` (#2602).
579
594
  */
580
595
  export async function recordAutoReleasesForRun(
581
596
  results: DriverComponentResult[],
582
597
  env: string,
583
598
  runId: string,
584
599
  disabled: boolean,
585
- ): Promise<void> {
600
+ ): Promise<ReleaseRecord[]> {
601
+ const recorded: ReleaseRecord[] = [];
586
602
  for (const componentResult of results) {
587
603
  if (!componentResult.ok) continue;
588
604
  const outcome = await maybeRecordAutoRelease(
@@ -602,6 +618,7 @@ export async function recordAutoReleasesForRun(
602
618
  message: `release record for "${componentResult.component}"@${env} was not recorded: ${outcome.error}`,
603
619
  }));
604
620
  } else if (outcome.recorded) {
621
+ recorded.push(outcome.record);
605
622
  console.error(formatInfo(
606
623
  `Recorded release: ${formatBold(componentResult.component)}@${env} -> ${outcome.record.digest} (commit ${outcome.commit.slice(0, 7)})`,
607
624
  ));
@@ -621,6 +638,7 @@ export async function recordAutoReleasesForRun(
621
638
  ));
622
639
  }
623
640
  }
641
+ return recorded;
624
642
  }
625
643
 
626
644
  // ── chant run --components <name|all> ────────────────────────────────────────
@@ -748,7 +766,12 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
748
766
  console.error(formatWarning({
749
767
  message: `component "${result.gated.component}" is gated on "${gate.gate}" — pending approval`,
750
768
  }));
751
- console.error(formatInfo(`approve : ${approveCommand(gate.op, gate.gate)}`));
769
+ // #2574: an approval stands, but for another plan or for none. Say so,
770
+ // with the command that replaces it.
771
+ if (result.gated.mismatch) {
772
+ console.error(formatWarning({ message: describeGateMismatch(gate.op, gate.gate, result.gated.mismatch) }));
773
+ }
774
+ console.error(formatInfo(`approve : ${approveCommand(gate.op, gate.gate, gate.environment)}`));
752
775
  if (gate.url) console.error(formatInfo(`approve at: ${gate.url}`));
753
776
  console.error(formatInfo(`expires : ${gate.expiresAt}`));
754
777
  // #2310: this run's own append reached only the local chant/lifecycle
@@ -770,6 +793,8 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
770
793
  ...(gate.url ? { url: gate.url } : {}),
771
794
  ...(result.gated.pushed === false ? { pushed: false, pushWarning: result.gated.pushWarning } : {}),
772
795
  ...(gate.planDigest ? { planDigest: gate.planDigest } : {}),
796
+ ...(await summaryLedgerPrefix()),
797
+ ...(gate.environment ? { environment: gate.environment } : {}),
773
798
  },
774
799
  gatedExit,
775
800
  );
@@ -778,7 +803,16 @@ export async function runOpComponents(ctx: CommandContext): Promise<number> {
778
803
 
779
804
  if (result.success && result.run) {
780
805
  const disabled = resolveAutoReleaseDisabled(config, ctx.args.noReleaseRecord);
781
- await recordAutoReleasesForRun(result.run.results, env, `local-${Date.now()}`, disabled);
806
+ const recorded = await recordAutoReleasesForRun(result.run.results, env, `local-${Date.now()}`, disabled);
807
+ // `--digest-file` (#2602): the releases this run recorded, one
808
+ // `<component>=<digest>` line each, the form `chant components promote
809
+ // --digest` takes. A generated promote job reads it to promote exactly
810
+ // what this job built, not whatever is latest in the environment.
811
+ if (ctx.args.digestFile) {
812
+ const digestPath = resolve(ctx.args.digestFile);
813
+ mkdirSync(dirname(digestPath), { recursive: true });
814
+ writeFileSync(digestPath, recorded.map((r) => `${r.component}=${r.digest}\n`).join(""));
815
+ }
782
816
  }
783
817
 
784
818
  return result.success ? 0 : 1;
@@ -891,6 +925,7 @@ export async function runOpOnRuntime(ctx: CommandContext): Promise<number> {
891
925
  ? { pushed: false, pushWarning: status.result.gatePushWarning }
892
926
  : {}),
893
927
  ...(pending?.planDigest ? { planDigest: pending.planDigest } : {}),
928
+ ...(await summaryLedgerPrefix()),
894
929
  },
895
930
  gatedExit,
896
931
  );
@@ -1,61 +1,165 @@
1
- import { resolve } from "node:path";
2
- import { vendorPull, vendorCheck } from "../commands/vendor";
1
+ import { existsSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { vendorPull, vendorCheck, MANIFEST_FILE } from "../commands/vendor";
3
4
  import { formatError, formatSuccess, formatWarning } from "../format";
4
5
  import type { CommandContext } from "../registry";
5
6
 
6
7
  /**
7
- * `chant vendor [pull|check]` — pull pinned, checksummed patterns into the repo,
8
- * or check vendored targets against their recorded pin. Defaults to `pull`.
8
+ * Where the lineage lock lives (`../../workspace/lineage-lock.ts` owns the
9
+ * constant). Repeated here so that finding out whether a lock exists loads no
10
+ * workspace module: a project with only `vendor.json` must see today's
11
+ * `chant vendor` exactly (#2525 rule 2).
12
+ */
13
+ const LOCK_FILE = ".chant/workspace.lock.json";
14
+
15
+ /**
16
+ * `chant vendor [pull|check|migrate]` — pull pinned, checksummed patterns into
17
+ * the repo, check vendored targets against their pin, or move `vendor.json`
18
+ * into the lineage lock. Defaults to `pull`.
9
19
  *
10
- * The manifest (`vendor.json`) lives in the current directory.
20
+ * `vendor.json` and the lock both live in the current directory. With no lock,
21
+ * `pull` and `check` read `vendor.json` exactly as before. With a lock, its
22
+ * vendor scopes are pulled file by file (local edits are kept, conflicts
23
+ * become manual steps), and any `vendor.json` entries not yet migrated are
24
+ * still pulled the old way (#2540).
11
25
  */
12
26
  export async function runVendor(ctx: CommandContext): Promise<number> {
13
27
  const { args } = ctx;
14
- // `chant vendor` → pull; `chant vendor pull|check [name]`.
28
+ // `chant vendor` → pull; `chant vendor pull|check|migrate [name]`.
15
29
  const sub = args.path === "." ? "pull" : args.path;
16
30
  const manifestDir = resolve(".");
17
31
 
18
- if (sub === "pull") {
19
- try {
20
- const result = await vendorPull(manifestDir, args.extraPositional);
21
- if (!result.success) {
22
- console.error(formatError({ message: result.output }));
23
- return 1;
24
- }
25
- console.log(result.output);
26
- console.error(formatSuccess(`Vendored ${result.pulled.length} artifact(s)`));
27
- return 0;
28
- } catch (err) {
29
- console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
30
- return 1;
31
- }
32
- }
33
-
34
- if (sub === "check") {
35
- try {
36
- const result = vendorCheck(manifestDir);
37
- console.log(result.output);
38
- if (!result.drift) {
39
- console.error(formatSuccess("All vendored artifacts match their pin"));
40
- return 0;
41
- }
42
- // Drift is allowed (you may edit vendored files); fail only in CI so a
43
- // pipeline catches unrecorded changes, warn locally.
44
- if (process.env.CI) {
45
- console.error(formatError({ message: "Vendored content drifted from the manifest pin" }));
46
- return 1;
47
- }
48
- console.error(formatWarning({ message: "Vendored content drifted from the manifest pin (run `chant vendor pull` to re-pin)" }));
49
- return 0;
50
- } catch (err) {
51
- console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
52
- return 1;
53
- }
32
+ try {
33
+ if (sub === "migrate") return await runMigrate(manifestDir);
34
+ const locked = await hasVendorScopes(manifestDir);
35
+ if (sub === "pull") return locked ? await runLockedPull(manifestDir, args.extraPositional) : await runPull(manifestDir, args.extraPositional);
36
+ if (sub === "check") return locked ? await runLockedCheck(manifestDir) : runCheck(manifestDir);
37
+ } catch (err) {
38
+ console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
39
+ return 1;
54
40
  }
55
41
 
56
42
  console.error(formatError({
57
43
  message: `Unknown vendor subcommand: ${sub}`,
58
- hint: "Available: chant vendor pull [name], chant vendor check",
44
+ hint: "Available: chant vendor pull [name], chant vendor check, chant vendor migrate",
59
45
  }));
60
46
  return 1;
61
47
  }
48
+
49
+ /**
50
+ * Whether the lock holds any vendor scope. Without a lock file this loads
51
+ * nothing; a lock with no vendor scope (say, one `chant init --template`
52
+ * wrote) leaves `chant vendor` on `vendor.json` as before.
53
+ */
54
+ async function hasVendorScopes(root: string): Promise<boolean> {
55
+ if (!existsSync(join(root, LOCK_FILE))) return false;
56
+ const { readLock } = await import("../../workspace/lineage-lock");
57
+ const lock = readLock(root);
58
+ return !!lock && Object.values(lock.scopes).some((l) => l.kind === "vendor");
59
+ }
60
+
61
+ // ── vendor.json only: unchanged ──────────────────────────────────────────────
62
+
63
+ async function runPull(manifestDir: string, only?: string): Promise<number> {
64
+ const result = await vendorPull(manifestDir, only);
65
+ if (!result.success) {
66
+ console.error(formatError({ message: result.output }));
67
+ return 1;
68
+ }
69
+ console.log(result.output);
70
+ console.error(formatSuccess(`Vendored ${result.pulled.length} artifact(s)`));
71
+ return 0;
72
+ }
73
+
74
+ function runCheck(manifestDir: string): number {
75
+ const result = vendorCheck(manifestDir);
76
+ console.log(result.output);
77
+ if (!result.drift) {
78
+ console.error(formatSuccess("All vendored artifacts match their pin"));
79
+ return 0;
80
+ }
81
+ // Drift is allowed (you may edit vendored files); fail only in CI so a
82
+ // pipeline catches unrecorded changes, warn locally.
83
+ if (process.env.CI) {
84
+ console.error(formatError({ message: "Vendored content drifted from the manifest pin" }));
85
+ return 1;
86
+ }
87
+ console.error(formatWarning({ message: "Vendored content drifted from the manifest pin (run `chant vendor pull` to re-pin)" }));
88
+ return 0;
89
+ }
90
+
91
+ // ── With a lineage lock ──────────────────────────────────────────────────────
92
+
93
+ const MIGRATE_HINT = `${MANIFEST_FILE} entries are not in ${LOCK_FILE} yet, and pull still overwrites their targets. Run \`chant vendor migrate\` to move them.`;
94
+
95
+ async function runLockedPull(root: string, only?: string): Promise<number> {
96
+ const lv = await import("../../workspace/lineage-vendor");
97
+ const hasManifest = lv.manifestPresent(root);
98
+ const legacy = hasManifest ? (await import("../commands/vendor")).loadManifest(root).manifest.vendored : [];
99
+ const legacyMatch = only ? legacy.some((e) => e.name === only) : legacy.length > 0;
100
+
101
+ const pulled = await lv.pullLockedScopes(root, only);
102
+ if (only && pulled.length === 0 && !legacyMatch) {
103
+ console.error(formatError({ message: `no vendored entry named "${only}"` }));
104
+ return 1;
105
+ }
106
+ const lines = pulled.map(lv.formatLockedPull);
107
+ let count = pulled.length;
108
+ if (legacyMatch) {
109
+ const result = await vendorPull(root, only);
110
+ lines.push(result.output);
111
+ count += result.pulled.length;
112
+ }
113
+ if (lines.length > 0) console.log(lines.join("\n"));
114
+ if (legacyMatch) console.error(formatWarning({ message: MIGRATE_HINT }));
115
+
116
+ const steps = pulled.reduce((n, p) => n + p.manualSteps.length, 0);
117
+ if (steps > 0) {
118
+ console.error(formatWarning({
119
+ message: `Vendored ${count} artifact(s); ${steps} file(s) changed both here and upstream were left as they are`,
120
+ }));
121
+ console.error(" Merge each by hand, then run `chant workspace lineage resolve <path>`.");
122
+ return 0;
123
+ }
124
+ console.error(formatSuccess(`Vendored ${count} artifact(s)`));
125
+ return 0;
126
+ }
127
+
128
+ async function runLockedCheck(root: string): Promise<number> {
129
+ const lv = await import("../../workspace/lineage-vendor");
130
+ const checks = lv.checkLockedScopes(root);
131
+ const lines = checks.map(lv.formatLockedCheck);
132
+ let drift = checks.some(lv.lockedCheckFails);
133
+ if (lv.manifestPresent(root)) {
134
+ const legacy = vendorCheck(root);
135
+ if (legacy.output) lines.push(legacy.output);
136
+ drift ||= legacy.drift;
137
+ console.error(formatWarning({ message: MIGRATE_HINT }));
138
+ }
139
+ if (lines.length > 0) console.log(lines.join("\n"));
140
+ if (!drift) {
141
+ console.error(formatSuccess("All vendored artifacts match their pin, or carry local edits"));
142
+ return 0;
143
+ }
144
+ const message = "Vendored content has open manual steps or missing targets";
145
+ if (process.env.CI) {
146
+ console.error(formatError({ message }));
147
+ return 1;
148
+ }
149
+ console.error(formatWarning({ message: `${message} (see \`chant workspace lineage\`)` }));
150
+ return 0;
151
+ }
152
+
153
+ async function runMigrate(root: string): Promise<number> {
154
+ const { migrateVendorManifest } = await import("../../workspace/lineage-vendor");
155
+ const result = await migrateVendorManifest(root);
156
+ for (const m of result.migrated) {
157
+ console.log(` ${m.name} → ${m.target} (${m.pinned ? `${m.files} file(s)` : "unpinned"})`);
158
+ }
159
+ console.error(
160
+ formatSuccess(
161
+ `Moved ${result.migrated.length} vendored artifact(s) from ${MANIFEST_FILE} into ${LOCK_FILE}${result.lockCreated ? " (created)" : ""}; ${MANIFEST_FILE} removed`,
162
+ ),
163
+ );
164
+ return 0;
165
+ }