@intentius/chant 0.80.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 (414) hide show
  1. package/dist/audit/discover.d.ts +8 -16
  2. package/dist/audit/discover.d.ts.map +1 -1
  3. package/dist/audit/report-html.d.ts +5 -0
  4. package/dist/audit/report-html.d.ts.map +1 -1
  5. package/dist/audit/report-model.d.ts +13 -0
  6. package/dist/audit/report-model.d.ts.map +1 -1
  7. package/dist/audit/terraform-state.d.ts +4 -5
  8. package/dist/audit/terraform-state.d.ts.map +1 -1
  9. package/dist/cli/commands/audit.d.ts +25 -9
  10. package/dist/cli/commands/audit.d.ts.map +1 -1
  11. package/dist/cli/commands/check-lexicon.d.ts +8 -0
  12. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  13. package/dist/cli/commands/doctor.d.ts.map +1 -1
  14. package/dist/cli/commands/import-agents.d.ts.map +1 -1
  15. package/dist/cli/commands/init.d.ts.map +1 -1
  16. package/dist/cli/commands/lint.d.ts.map +1 -1
  17. package/dist/cli/commands/update.d.ts +6 -0
  18. package/dist/cli/commands/update.d.ts.map +1 -1
  19. package/dist/cli/commands/vendor.d.ts +5 -0
  20. package/dist/cli/commands/vendor.d.ts.map +1 -1
  21. package/dist/cli/handlers/build.d.ts.map +1 -1
  22. package/dist/cli/handlers/components.d.ts.map +1 -1
  23. package/dist/cli/handlers/dev.d.ts.map +1 -1
  24. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  25. package/dist/cli/handlers/init.d.ts.map +1 -1
  26. package/dist/cli/handlers/operator.d.ts +7 -0
  27. package/dist/cli/handlers/operator.d.ts.map +1 -1
  28. package/dist/cli/handlers/promote.d.ts +7 -4
  29. package/dist/cli/handlers/promote.d.ts.map +1 -1
  30. package/dist/cli/handlers/run-generate.d.ts.map +1 -1
  31. package/dist/cli/handlers/run.d.ts +4 -1
  32. package/dist/cli/handlers/run.d.ts.map +1 -1
  33. package/dist/cli/handlers/vendor.d.ts +8 -3
  34. package/dist/cli/handlers/vendor.d.ts.map +1 -1
  35. package/dist/cli/main.d.ts +12 -0
  36. package/dist/cli/main.d.ts.map +1 -1
  37. package/dist/cli/plugins.d.ts +12 -7
  38. package/dist/cli/plugins.d.ts.map +1 -1
  39. package/dist/cli/registry.d.ts +32 -0
  40. package/dist/cli/registry.d.ts.map +1 -1
  41. package/dist/codegen/fetch.d.ts +10 -0
  42. package/dist/codegen/fetch.d.ts.map +1 -1
  43. package/dist/components/cli-support.d.ts +3 -1
  44. package/dist/components/cli-support.d.ts.map +1 -1
  45. package/dist/components/discover.d.ts.map +1 -1
  46. package/dist/components/driver.d.ts +16 -2
  47. package/dist/components/driver.d.ts.map +1 -1
  48. package/dist/components/gate-plan.d.ts +40 -0
  49. package/dist/components/gate-plan.d.ts.map +1 -0
  50. package/dist/components/promote.d.ts +60 -7
  51. package/dist/components/promote.d.ts.map +1 -1
  52. package/dist/components/sandbox/driver.d.ts.map +1 -1
  53. package/dist/components/sandbox/run.d.ts.map +1 -1
  54. package/dist/components/verbs/build-archive.d.ts +22 -11
  55. package/dist/components/verbs/build-archive.d.ts.map +1 -1
  56. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  57. package/dist/config-static.d.ts +59 -0
  58. package/dist/config-static.d.ts.map +1 -0
  59. package/dist/config.d.ts +32 -10
  60. package/dist/config.d.ts.map +1 -1
  61. package/dist/content-digest.d.ts +17 -0
  62. package/dist/content-digest.d.ts.map +1 -0
  63. package/dist/discovery/files.d.ts +10 -5
  64. package/dist/discovery/files.d.ts.map +1 -1
  65. package/dist/discovery/fold-import.d.ts +20 -4
  66. package/dist/discovery/fold-import.d.ts.map +1 -1
  67. package/dist/discovery/index.d.ts.map +1 -1
  68. package/dist/discovery/sandbox/driver.d.ts +20 -0
  69. package/dist/discovery/sandbox/driver.d.ts.map +1 -1
  70. package/dist/discovery/sandbox/fork.d.ts +20 -0
  71. package/dist/discovery/sandbox/fork.d.ts.map +1 -1
  72. package/dist/discovery/walk.d.ts +119 -0
  73. package/dist/discovery/walk.d.ts.map +1 -0
  74. package/dist/graph-ir.d.ts +4 -1
  75. package/dist/graph-ir.d.ts.map +1 -1
  76. package/dist/join-key.d.ts +31 -0
  77. package/dist/join-key.d.ts.map +1 -0
  78. package/dist/lexicon-module.d.ts +51 -6
  79. package/dist/lexicon-module.d.ts.map +1 -1
  80. package/dist/lexicon.d.ts +58 -1
  81. package/dist/lexicon.d.ts.map +1 -1
  82. package/dist/lifecycle/build-ledger-store.d.ts.map +1 -1
  83. package/dist/lifecycle/digest.d.ts +5 -1
  84. package/dist/lifecycle/digest.d.ts.map +1 -1
  85. package/dist/lifecycle/gate-ledger.d.ts +20 -5
  86. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  87. package/dist/lifecycle/git.d.ts +64 -0
  88. package/dist/lifecycle/git.d.ts.map +1 -1
  89. package/dist/lifecycle/lease.d.ts +8 -1
  90. package/dist/lifecycle/lease.d.ts.map +1 -1
  91. package/dist/lifecycle/legacy-digest.d.ts +41 -8
  92. package/dist/lifecycle/legacy-digest.d.ts.map +1 -1
  93. package/dist/lifecycle/member-ledger.d.ts +67 -0
  94. package/dist/lifecycle/member-ledger.d.ts.map +1 -0
  95. package/dist/lifecycle/plan-digest.d.ts +4 -3
  96. package/dist/lifecycle/plan-digest.d.ts.map +1 -1
  97. package/dist/lifecycle/release-ledger.d.ts +18 -2
  98. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  99. package/dist/lifecycle/status.d.ts +90 -5
  100. package/dist/lifecycle/status.d.ts.map +1 -1
  101. package/dist/op/activities/activity-contracts.d.ts +32 -0
  102. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  103. package/dist/op/activities/index.d.ts +2 -0
  104. package/dist/op/activities/index.d.ts.map +1 -1
  105. package/dist/op/activities/propose-upgrade.d.ts +75 -0
  106. package/dist/op/activities/propose-upgrade.d.ts.map +1 -0
  107. package/dist/op/discover.d.ts.map +1 -1
  108. package/dist/op/gate-name.d.ts +7 -0
  109. package/dist/op/gate-name.d.ts.map +1 -1
  110. package/dist/op/gate-summary.d.ts +16 -0
  111. package/dist/op/gate-summary.d.ts.map +1 -1
  112. package/dist/op/gate.d.ts +16 -2
  113. package/dist/op/gate.d.ts.map +1 -1
  114. package/dist/project-root.d.ts +26 -0
  115. package/dist/project-root.d.ts.map +1 -1
  116. package/dist/workspace/__fixtures__/contract-repo.d.ts +37 -0
  117. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -0
  118. package/dist/workspace/__fixtures__/two-member-workspace.d.ts +11 -0
  119. package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -0
  120. package/dist/workspace/checks/generated.d.ts +156 -0
  121. package/dist/workspace/checks/generated.d.ts.map +1 -0
  122. package/dist/workspace/checks/ledgers.d.ts +78 -0
  123. package/dist/workspace/checks/ledgers.d.ts.map +1 -0
  124. package/dist/workspace/checks/links.d.ts +25 -0
  125. package/dist/workspace/checks/links.d.ts.map +1 -0
  126. package/dist/workspace/checks/pipelines.d.ts +76 -0
  127. package/dist/workspace/checks/pipelines.d.ts.map +1 -0
  128. package/dist/workspace/checks.d.ts +161 -0
  129. package/dist/workspace/checks.d.ts.map +1 -0
  130. package/dist/workspace/compose-graph.d.ts +115 -0
  131. package/dist/workspace/compose-graph.d.ts.map +1 -0
  132. package/dist/workspace/compose-reports.d.ts +68 -0
  133. package/dist/workspace/compose-reports.d.ts.map +1 -0
  134. package/dist/workspace/declaration.d.ts +220 -0
  135. package/dist/workspace/declaration.d.ts.map +1 -0
  136. package/dist/workspace/declaration.schema.json +434 -0
  137. package/dist/workspace/generated-files.d.ts +65 -0
  138. package/dist/workspace/generated-files.d.ts.map +1 -0
  139. package/dist/workspace/glob.d.ts +13 -0
  140. package/dist/workspace/glob.d.ts.map +1 -0
  141. package/dist/workspace/graph-cli.d.ts +75 -0
  142. package/dist/workspace/graph-cli.d.ts.map +1 -0
  143. package/dist/workspace/init.d.ts +96 -0
  144. package/dist/workspace/init.d.ts.map +1 -0
  145. package/dist/workspace/jsonc.d.ts +38 -0
  146. package/dist/workspace/jsonc.d.ts.map +1 -0
  147. package/dist/workspace/kinds.d.ts +192 -0
  148. package/dist/workspace/kinds.d.ts.map +1 -0
  149. package/dist/workspace/lineage-check.d.ts +84 -0
  150. package/dist/workspace/lineage-check.d.ts.map +1 -0
  151. package/dist/workspace/lineage-cli.d.ts +31 -0
  152. package/dist/workspace/lineage-cli.d.ts.map +1 -0
  153. package/dist/workspace/lineage-init.d.ts +101 -0
  154. package/dist/workspace/lineage-init.d.ts.map +1 -0
  155. package/dist/workspace/lineage-lock.d.ts +258 -0
  156. package/dist/workspace/lineage-lock.d.ts.map +1 -0
  157. package/dist/workspace/lineage-merge.d.ts +14 -0
  158. package/dist/workspace/lineage-merge.d.ts.map +1 -0
  159. package/dist/workspace/lineage-migrations.d.ts +178 -0
  160. package/dist/workspace/lineage-migrations.d.ts.map +1 -0
  161. package/dist/workspace/lineage-update.d.ts +59 -0
  162. package/dist/workspace/lineage-update.d.ts.map +1 -0
  163. package/dist/workspace/lineage-upgrade-cli.d.ts +40 -0
  164. package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -0
  165. package/dist/workspace/lineage-upgrade.d.ts +146 -0
  166. package/dist/workspace/lineage-upgrade.d.ts.map +1 -0
  167. package/dist/workspace/lineage-vendor.d.ts +63 -0
  168. package/dist/workspace/lineage-vendor.d.ts.map +1 -0
  169. package/dist/workspace/lineage-version.d.ts +32 -0
  170. package/dist/workspace/lineage-version.d.ts.map +1 -0
  171. package/dist/workspace/links.d.ts +150 -0
  172. package/dist/workspace/links.d.ts.map +1 -0
  173. package/dist/workspace/ls.d.ts +99 -0
  174. package/dist/workspace/ls.d.ts.map +1 -0
  175. package/dist/workspace/member-commands.d.ts +139 -0
  176. package/dist/workspace/member-commands.d.ts.map +1 -0
  177. package/dist/workspace/member-handles.d.ts +54 -0
  178. package/dist/workspace/member-handles.d.ts.map +1 -0
  179. package/dist/workspace/member-pipeline.d.ts +143 -0
  180. package/dist/workspace/member-pipeline.d.ts.map +1 -0
  181. package/dist/workspace/member-run.d.ts +67 -0
  182. package/dist/workspace/member-run.d.ts.map +1 -0
  183. package/dist/workspace/reason-codes.d.ts +65 -0
  184. package/dist/workspace/reason-codes.d.ts.map +1 -0
  185. package/dist/workspace/records-cli.d.ts +28 -4
  186. package/dist/workspace/records-cli.d.ts.map +1 -1
  187. package/dist/workspace/records.d.ts.map +1 -1
  188. package/dist/workspace/root-refusal.d.ts +53 -0
  189. package/dist/workspace/root-refusal.d.ts.map +1 -0
  190. package/dist/workspace/status.d.ts +150 -0
  191. package/dist/workspace/status.d.ts.map +1 -0
  192. package/dist/workspace/template-manifest.d.ts +90 -0
  193. package/dist/workspace/template-manifest.d.ts.map +1 -0
  194. package/dist/workspace/tree.d.ts +36 -0
  195. package/dist/workspace/tree.d.ts.map +1 -0
  196. package/dist/workspace/trust/attestor.d.ts +67 -0
  197. package/dist/workspace/trust/attestor.d.ts.map +1 -0
  198. package/dist/workspace/trust/policy.d.ts +111 -0
  199. package/dist/workspace/trust/policy.d.ts.map +1 -0
  200. package/dist/workspace/trust/provenance.d.ts +67 -0
  201. package/dist/workspace/trust/provenance.d.ts.map +1 -0
  202. package/dist/workspace/trust/ssh-commit.d.ts +43 -0
  203. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -0
  204. package/dist/workspace/trust/test-repo.d.ts +42 -0
  205. package/dist/workspace/trust/test-repo.d.ts.map +1 -0
  206. package/dist/workspace/trust/verify-cli.d.ts +9 -0
  207. package/dist/workspace/trust/verify-cli.d.ts.map +1 -0
  208. package/dist/workspace/trust/verify.d.ts +66 -0
  209. package/dist/workspace/trust/verify.d.ts.map +1 -0
  210. package/dist/workspace/which-chant.d.ts +58 -0
  211. package/dist/workspace/which-chant.d.ts.map +1 -0
  212. package/dist/workspace/workspace-kinds.schema.json +133 -0
  213. package/package.json +2 -1
  214. package/src/audit/discover.ts +66 -78
  215. package/src/audit/report-html.ts +3 -1
  216. package/src/audit/report-model.ts +14 -1
  217. package/src/audit/terraform-state.test.ts +4 -9
  218. package/src/audit/terraform-state.ts +4 -5
  219. package/src/cli/commands/audit-truncation.test.ts +167 -0
  220. package/src/cli/commands/audit.ts +89 -60
  221. package/src/cli/commands/check-lexicon.ts +24 -0
  222. package/src/cli/commands/doctor.test.ts +30 -0
  223. package/src/cli/commands/doctor.ts +18 -0
  224. package/src/cli/commands/import-agents.ts +6 -2
  225. package/src/cli/commands/init.ts +22 -3
  226. package/src/cli/commands/lint.test.ts +17 -23
  227. package/src/cli/commands/lint.ts +24 -84
  228. package/src/cli/commands/update.ts +13 -4
  229. package/src/cli/commands/vendor.ts +11 -6
  230. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +38 -0
  231. package/src/cli/handlers/build-member.test.ts +127 -0
  232. package/src/cli/handlers/build.ts +35 -1
  233. package/src/cli/handlers/components.test.ts +127 -0
  234. package/src/cli/handlers/components.ts +70 -8
  235. package/src/cli/handlers/dev.ts +5 -3
  236. package/src/cli/handlers/fan-out.ts +9 -2
  237. package/src/cli/handlers/init.ts +54 -1
  238. package/src/cli/handlers/operator.test.ts +68 -0
  239. package/src/cli/handlers/operator.ts +37 -9
  240. package/src/cli/handlers/promote.test.ts +118 -2
  241. package/src/cli/handlers/promote.ts +82 -16
  242. package/src/cli/handlers/run-generate-member.test.ts +83 -0
  243. package/src/cli/handlers/run-generate.test.ts +6 -8
  244. package/src/cli/handlers/run-generate.ts +36 -1
  245. package/src/cli/handlers/run.test.ts +47 -0
  246. package/src/cli/handlers/run.ts +28 -5
  247. package/src/cli/handlers/vendor.ts +147 -43
  248. package/src/cli/main.test.ts +105 -1
  249. package/src/cli/main.ts +230 -17
  250. package/src/cli/plugins.ts +17 -17
  251. package/src/cli/registry.ts +32 -0
  252. package/src/cli/static-config-read.test.ts +208 -0
  253. package/src/codegen/fetch.test.ts +63 -1
  254. package/src/codegen/fetch.ts +30 -1
  255. package/src/components/cli-support.ts +6 -1
  256. package/src/components/discover.test.ts +27 -57
  257. package/src/components/discover.ts +13 -68
  258. package/src/components/driver.test.ts +78 -1
  259. package/src/components/driver.ts +43 -7
  260. package/src/components/gate-plan.ts +53 -0
  261. package/src/components/promote.test.ts +276 -2
  262. package/src/components/promote.ts +119 -15
  263. package/src/components/sandbox/driver.ts +8 -8
  264. package/src/components/sandbox/run.ts +4 -1
  265. package/src/components/verbs/build-archive.ts +27 -21
  266. package/src/components/verbs/run-agent.ts +3 -5
  267. package/src/config-static.test.ts +136 -0
  268. package/src/config-static.ts +232 -0
  269. package/src/config.test.ts +2 -2
  270. package/src/config.ts +48 -15
  271. package/src/content-digest.test.ts +120 -0
  272. package/src/content-digest.ts +21 -0
  273. package/src/discovery/files.test.ts +10 -10
  274. package/src/discovery/files.ts +25 -81
  275. package/src/discovery/fold-import.ts +49 -5
  276. package/src/discovery/index.ts +1 -0
  277. package/src/discovery/sandbox/driver.ts +73 -35
  278. package/src/discovery/sandbox/fork-diagnostic.test.ts +136 -13
  279. package/src/discovery/sandbox/fork.ts +83 -43
  280. package/src/discovery/sandbox/path-lexicon-multifile.test.ts +285 -0
  281. package/src/discovery/walk.test.ts +263 -0
  282. package/src/discovery/walk.ts +334 -0
  283. package/src/effect-receipt.ts +2 -2
  284. package/src/graph-ir.ts +4 -1
  285. package/src/join-key.test.ts +25 -0
  286. package/src/join-key.ts +39 -0
  287. package/src/lexicon-module.ts +108 -10
  288. package/src/lexicon.ts +79 -1
  289. package/src/lifecycle/build-ledger-store.ts +11 -10
  290. package/src/lifecycle/digest.ts +9 -5
  291. package/src/lifecycle/gate-ledger.test.ts +20 -0
  292. package/src/lifecycle/gate-ledger.ts +28 -6
  293. package/src/lifecycle/git.test.ts +13 -1
  294. package/src/lifecycle/git.ts +221 -176
  295. package/src/lifecycle/lease.ts +25 -10
  296. package/src/lifecycle/legacy-digest.test.ts +115 -23
  297. package/src/lifecycle/legacy-digest.ts +60 -19
  298. package/src/lifecycle/member-ledger.test.ts +319 -0
  299. package/src/lifecycle/member-ledger.ts +145 -0
  300. package/src/lifecycle/plan-digest.ts +7 -7
  301. package/src/lifecycle/release-ledger.test.ts +20 -0
  302. package/src/lifecycle/release-ledger.ts +23 -7
  303. package/src/lifecycle/status.test.ts +151 -1
  304. package/src/lifecycle/status.ts +228 -7
  305. package/src/op/activities/activity-contracts.ts +37 -0
  306. package/src/op/activities/index.ts +10 -0
  307. package/src/op/activities/propose-upgrade.ts +206 -0
  308. package/src/op/discover.test.ts +14 -4
  309. package/src/op/discover.ts +19 -27
  310. package/src/op/gate-name.ts +8 -0
  311. package/src/op/gate-summary.test.ts +14 -0
  312. package/src/op/gate-summary.ts +25 -2
  313. package/src/op/gate.ts +36 -7
  314. package/src/op/generate-pipeline.test.ts +14 -5
  315. package/src/project-root.test.ts +66 -1
  316. package/src/project-root.ts +48 -0
  317. package/src/workspace/__fixtures__/contract-repo.ts +100 -0
  318. package/src/workspace/__fixtures__/two-member-workspace.ts +35 -0
  319. package/src/workspace/behold-kinds.test.ts +162 -0
  320. package/src/workspace/check-contract.test.ts +127 -0
  321. package/src/workspace/check.schema.json +347 -0
  322. package/src/workspace/checks/generated.test.ts +287 -0
  323. package/src/workspace/checks/generated.ts +475 -0
  324. package/src/workspace/checks/ledgers.test.ts +139 -0
  325. package/src/workspace/checks/ledgers.ts +246 -0
  326. package/src/workspace/checks/links.ts +183 -0
  327. package/src/workspace/checks/pipelines.test.ts +133 -0
  328. package/src/workspace/checks/pipelines.ts +225 -0
  329. package/src/workspace/checks-repo.test.ts +71 -0
  330. package/src/workspace/checks.test.ts +450 -0
  331. package/src/workspace/checks.ts +461 -0
  332. package/src/workspace/compose-graph.test.ts +146 -0
  333. package/src/workspace/compose-graph.ts +228 -0
  334. package/src/workspace/compose-reports.test.ts +109 -0
  335. package/src/workspace/compose-reports.ts +186 -0
  336. package/src/workspace/declaration.schema.json +434 -0
  337. package/src/workspace/declaration.test.ts +347 -0
  338. package/src/workspace/declaration.ts +623 -0
  339. package/src/workspace/generated-files.test.ts +114 -0
  340. package/src/workspace/generated-files.ts +94 -0
  341. package/src/workspace/glob.ts +49 -0
  342. package/src/workspace/graph-cli.ts +220 -0
  343. package/src/workspace/graph-contract.test.ts +204 -0
  344. package/src/workspace/graph.schema.json +662 -0
  345. package/src/workspace/init.test.ts +223 -0
  346. package/src/workspace/init.ts +585 -0
  347. package/src/workspace/jsonc.test.ts +65 -0
  348. package/src/workspace/jsonc.ts +231 -0
  349. package/src/workspace/kinds.test.ts +288 -0
  350. package/src/workspace/kinds.ts +506 -0
  351. package/src/workspace/lineage-check.ts +264 -0
  352. package/src/workspace/lineage-cli.ts +114 -0
  353. package/src/workspace/lineage-init.test.ts +231 -0
  354. package/src/workspace/lineage-init.ts +365 -0
  355. package/src/workspace/lineage-lock.test.ts +224 -0
  356. package/src/workspace/lineage-lock.ts +357 -0
  357. package/src/workspace/lineage-merge.ts +44 -0
  358. package/src/workspace/lineage-migrations.test.ts +178 -0
  359. package/src/workspace/lineage-migrations.ts +343 -0
  360. package/src/workspace/lineage-update.ts +160 -0
  361. package/src/workspace/lineage-upgrade-cli.ts +151 -0
  362. package/src/workspace/lineage-upgrade.test.ts +474 -0
  363. package/src/workspace/lineage-upgrade.ts +624 -0
  364. package/src/workspace/lineage-vendor.test.ts +126 -0
  365. package/src/workspace/lineage-vendor.ts +215 -0
  366. package/src/workspace/lineage-version.ts +127 -0
  367. package/src/workspace/links.test.ts +344 -0
  368. package/src/workspace/links.ts +331 -0
  369. package/src/workspace/ls-contract.test.ts +200 -0
  370. package/src/workspace/ls.schema.json +190 -0
  371. package/src/workspace/ls.ts +254 -0
  372. package/src/workspace/member-commands.e2e.test.ts +244 -0
  373. package/src/workspace/member-commands.test.ts +217 -0
  374. package/src/workspace/member-commands.ts +540 -0
  375. package/src/workspace/member-handles.test.ts +76 -0
  376. package/src/workspace/member-handles.ts +228 -0
  377. package/src/workspace/member-pipeline.test.ts +135 -0
  378. package/src/workspace/member-pipeline.ts +317 -0
  379. package/src/workspace/member-run.ts +146 -0
  380. package/src/workspace/read-contract.test.ts +129 -0
  381. package/src/workspace/reason-codes.test.ts +113 -0
  382. package/src/workspace/reason-codes.ts +81 -0
  383. package/src/workspace/records-cli.ts +86 -14
  384. package/src/workspace/records-contract.test.ts +5 -0
  385. package/src/workspace/records.schema.json +32 -2
  386. package/src/workspace/records.ts +3 -2
  387. package/src/workspace/root-refusal.ts +97 -0
  388. package/src/workspace/status-contract.test.ts +308 -0
  389. package/src/workspace/status.schema.json +210 -0
  390. package/src/workspace/status.ts +412 -0
  391. package/src/workspace/template-manifest.test.ts +79 -0
  392. package/src/workspace/template-manifest.ts +221 -0
  393. package/src/workspace/tree.ts +132 -0
  394. package/src/workspace/trust/attestor.ts +93 -0
  395. package/src/workspace/trust/policy.ts +306 -0
  396. package/src/workspace/trust/provenance.ts +181 -0
  397. package/src/workspace/trust/ssh-commit.ts +138 -0
  398. package/src/workspace/trust/test-repo.ts +138 -0
  399. package/src/workspace/trust/trust.test.ts +397 -0
  400. package/src/workspace/trust/verify-cli.ts +56 -0
  401. package/src/workspace/trust/verify.ts +194 -0
  402. package/src/workspace/which-chant.test.ts +117 -0
  403. package/src/workspace/which-chant.ts +151 -0
  404. package/src/workspace/workspace-kinds.schema.json +133 -0
  405. package/dist/components/unbound-gate-approval.d.ts +0 -24
  406. package/dist/components/unbound-gate-approval.d.ts.map +0 -1
  407. package/dist/discovery/convergence.d.ts +0 -97
  408. package/dist/discovery/convergence.d.ts.map +0 -1
  409. package/src/cli/commands/audit-walk-warnings.test.ts +0 -156
  410. package/src/components/unbound-gate-approval.test.ts +0 -82
  411. package/src/components/unbound-gate-approval.ts +0 -51
  412. package/src/discovery/convergence.test.ts +0 -231
  413. package/src/discovery/convergence.ts +0 -399
  414. package/src/op/discover-convergence.test.ts +0 -70
@@ -6,7 +6,6 @@ import { join, resolve } from "node:path";
6
6
  import { tmpdir } from "node:os";
7
7
  import { execFileSync } from "node:child_process";
8
8
  import { params, setBuildParams } from "../../params";
9
- import { resetDiscoveryWarnings } from "../../discovery/convergence";
10
9
 
11
10
  describe("lintCommand", () => {
12
11
  let testDir: string;
@@ -643,58 +642,53 @@ describe("lintCommand — git-ignored files are not linted", () => {
643
642
  });
644
643
 
645
644
  /**
646
- * chant#2527's warning release for lint: a child project is linted today and
647
- * stops the converged walk next release; a scan root that is itself ignored
648
- * lints nothing today and is linted next release. Both still lint exactly
649
- * what they did, with a warning naming the files and the glob.
645
+ * chant#2527: lint walks through the one discovery walk. A child project
646
+ * inside a project is a boundary, `include` re-admits it, and a scan root
647
+ * that is itself git-ignored is linted.
650
648
  */
651
- describe("lintCommand — files the converged walker reads differently (#2527)", () => {
649
+ describe("lintCommand — the converged discovery walk (#2527)", () => {
652
650
  let testDir: string;
653
- let stderr: string[];
654
651
  const EVL003 = `const t = { a: 1 };\nexport function g(k: string) { return t[k]; }\n`;
655
652
 
656
653
  beforeEach(async () => {
657
654
  testDir = join(tmpdir(), `chant-lint-2527-${Date.now()}-${Math.random()}`);
658
655
  await mkdir(testDir, { recursive: true });
659
656
  process.env.NO_COLOR = "1";
660
- resetDiscoveryWarnings();
661
- stderr = [];
662
- vi.spyOn(console, "error").mockImplementation((...args: unknown[]) => void stderr.push(args.join(" ")));
663
657
  });
664
658
 
665
659
  afterEach(async () => {
666
- vi.restoreAllMocks();
667
660
  await rm(testDir, { recursive: true, force: true });
668
661
  delete process.env.NO_COLOR;
669
662
  });
670
663
 
671
- test("a nested child project is still linted, and named with its include glob", async () => {
672
- await writeFile(join(testDir, "chant.config.json"), JSON.stringify({ knowledge: { dir: "knowledge" } }));
664
+ async function childProject(config: Record<string, unknown>): Promise<void> {
665
+ await writeFile(join(testDir, "chant.config.json"), JSON.stringify({ knowledge: { dir: "knowledge" }, ...config }));
673
666
  await writeFile(join(testDir, "index.ts"), `export const x = 1;\n`);
674
667
  await mkdir(join(testDir, "stacks", "east"), { recursive: true });
675
668
  await writeFile(join(testDir, "stacks", "east", "chant.config.ts"), `export default {};\n`);
676
669
  await writeFile(join(testDir, "stacks", "east", "east.ts"), EVL003);
670
+ }
671
+
672
+ test("a nested child project is not linted from the parent", async () => {
673
+ await childProject({});
674
+ const result = await lintCommand({ path: testDir, format: "stylish" });
675
+ expect(result.diagnostics.some((d) => d.file.includes("east.ts"))).toBe(false);
676
+ });
677
677
 
678
+ test("include re-admits the child project", async () => {
679
+ await childProject({ include: ["stacks/east"] });
678
680
  const result = await lintCommand({ path: testDir, format: "stylish" });
679
681
  expect(result.diagnostics.some((d) => d.file.includes("east.ts") && d.ruleId === "EVL003")).toBe(true);
680
- const warning = stderr.find((l) => l.startsWith("warning: chant lint under"));
681
- expect(warning).toContain(" stacks/east/chant.config.ts, stacks/east/east.ts: stacks/east/ is a child project");
682
- expect(warning).toContain('To keep reading them after the change, add "stacks/east" to include in');
683
682
  });
684
683
 
685
- test("an ignored scan root lints nothing today, and says it will be linted", async () => {
684
+ test("an ignored scan root is linted: ignore rules apply below the root only", async () => {
686
685
  execFileSync("git", ["init", "-q"], { cwd: testDir });
687
686
  await writeFile(join(testDir, ".gitignore"), "/carveout/\n");
688
687
  await mkdir(join(testDir, "carveout", "src"), { recursive: true });
689
688
  await writeFile(join(testDir, "carveout", "src", "main.ts"), EVL003);
690
689
 
691
690
  const result = await lintCommand({ path: join(testDir, "carveout", "src"), format: "stylish" });
692
- expect(result.diagnostics.filter((d) => d.file.includes("main.ts"))).toHaveLength(0);
693
- const warning = stderr.find((l) => l.startsWith("warning: chant lint under"));
694
- expect(warning).toContain(" main.ts: ");
695
- expect(warning).toContain("is itself git-ignored, so today nothing under it is linted. ");
696
- expect(warning).toContain("so the files under it are linted.");
697
- expect(warning).toContain('To keep skipping it, add "**" to exclude in');
691
+ expect(result.diagnostics.some((d) => d.file.includes("main.ts") && d.ruleId === "EVL003")).toBe(true);
698
692
  });
699
693
  });
700
694
 
@@ -1,7 +1,6 @@
1
1
  import { resolve, join, relative } from "path";
2
2
  import type { BuildParamProvenance } from "../../provenance";
3
- import { readFileSync, writeFileSync, readdirSync, statSync } from "fs";
4
- import { execFileSync } from "child_process";
3
+ import { readFileSync, writeFileSync, readdirSync } from "fs";
5
4
  import { runLint, parseDisableComments } from "../../lint/engine";
6
5
  import type { LintRule, LintDiagnostic, LintFix } from "../../lint/rule";
7
6
  import type { IntrinsicDef, LexiconPlugin } from "../../lexicon";
@@ -19,8 +18,8 @@ import { runPostSynthChecks } from "../../lint/post-synth";
19
18
  import { rule } from "../../lint/declarative";
20
19
  import { watchDirectory, formatTimestamp, formatChangedFiles } from "../watch";
21
20
  import { formatError, formatInfo } from "../format";
22
- import { GENERATED_MARKER, hasSkipMarker, compileDiscoveryFilter } from "../../discovery/files";
23
- import { dirProbe, warnDiscoveryChanges } from "../../discovery/convergence";
21
+ import { hasDiscoveryMarkerSync, isSourceFileName } from "../../discovery/files";
22
+ import { walkDiscovery, workspaceMemberDirs } from "../../discovery/walk";
24
23
  import { buildParamValues, resolveBuildParams } from "../../build-params";
25
24
  import { setBuildParams } from "../../params";
26
25
  import { isNoLexiconDetected } from "../../detectLexicon";
@@ -270,80 +269,26 @@ export interface LintResult {
270
269
  }
271
270
 
272
271
  /**
273
- * Drop paths git ignores. Vendored trees (`vendor/`), build output (`dist/`),
274
- * and anything else in `.gitignore` are not authored chant source — linting
275
- * them surfaces EVL/COR errors in code the project never wrote, which then
276
- * gates `chant graph --format ir` on files outside the graph. Delegates to
277
- * `git check-ignore` for exact gitignore semantics (nesting, negation) in one
278
- * batched call; a non-git tree (or absent git) filters nothing.
272
+ * Every TypeScript file `chant lint` reads under `dir`, through the one
273
+ * discovery walk (`../../discovery/walk.ts`, #2527): git-ignored paths
274
+ * (vendored trees and the like, which the project never wrote), dot-directories,
275
+ * `dist` and, inside a project, child projects are skipped, and so is what the
276
+ * project's `exclude` globs (#2519) name, unless `include` re-admits it. Test
277
+ * and spec files, chant-generated files and skip-marked files are left out.
278
+ * Symlinks are followed, as lint always did.
279
279
  */
280
- function filterGitIgnored(files: string[], cwd: string, ignoredOut?: Set<string>): string[] {
281
- if (files.length === 0) return files;
282
- try {
283
- const out = execFileSync("git", ["check-ignore", "--stdin"], {
284
- cwd,
285
- // The scan root rides along as one extra probe for #2527's warning
286
- // release: whether it is itself ignored. It is never a file in `files`.
287
- input: [...files, dirProbe(cwd)].join("\n"),
288
- encoding: "utf-8",
289
- // stdin piped (input), stdout captured, stderr silenced so a non-git tree's
290
- // "fatal: not a git repository" never leaks to the lint output.
291
- stdio: ["pipe", "pipe", "ignore"],
292
- // Exit 1 means "nothing ignored" — execFileSync throws on non-zero, so the
293
- // catch handles it; exit 128 (not a repo) lands there too and filters none.
294
- });
295
- const ignored = new Set(out.split(/\r?\n/).filter(Boolean));
296
- for (const p of ignored) ignoredOut?.add(p);
297
- return ignored.size === 0 ? files : files.filter((f) => !ignored.has(f));
298
- } catch {
299
- // No git, not a repo, or nothing ignored (exit 1) — keep every file.
300
- return files;
301
- }
302
- }
303
-
304
- /** What {@link getTypeScriptFiles} saw before its git-ignore filter, for #2527's warning. */
305
- interface TypeScriptScan {
306
- raw: string[];
307
- ignored: Set<string>;
308
- }
309
-
310
- /**
311
- * Get all TypeScript files recursively, skipping git-ignored paths and the
312
- * files `skip` (the project's `exclude` globs, #2519) names. `scanOut`, when
313
- * given, receives the files found before the ignore filter and the paths git
314
- * reported ignored.
315
- */
316
- function getTypeScriptFiles(dir: string, skip?: (file: string) => boolean, scanOut?: TypeScriptScan): string[] {
317
- const files: string[] = [];
318
-
319
- function scan(currentDir: string): void {
320
- const entries = readdirSync(currentDir);
321
-
322
- for (const entry of entries) {
323
- const fullPath = join(currentDir, entry);
324
- const stat = statSync(fullPath);
325
-
326
- if (stat.isDirectory()) {
327
- if (entry !== "node_modules" && !entry.startsWith(".")) {
328
- scan(fullPath);
329
- }
330
- } else if (entry.endsWith(".ts") && !entry.endsWith(".test.ts") && !entry.endsWith(".spec.ts")) {
331
- if (skip?.(fullPath)) continue;
332
- // Skip chant-generated files (worker/workflow/activities bootstrap): they
333
- // hold no authored source and use runtime patterns the EVL* rules forbid.
334
- // A hand-written file opts out with the skip marker (#2519).
335
- const head = readFileSync(fullPath, "utf-8").slice(0, 1024);
336
- if (head.slice(0, 256).includes(GENERATED_MARKER) || hasSkipMarker(head)) {
337
- continue;
338
- }
339
- files.push(fullPath);
340
- }
341
- }
342
- }
343
-
344
- scan(dir);
345
- scanOut?.raw.push(...files);
346
- return filterGitIgnored(files, dir, scanOut?.ignored);
280
+ async function getTypeScriptFiles(dir: string): Promise<string[]> {
281
+ // A missing or non-directory path fails here, as lint's own walk always did;
282
+ // the shared walk skips an unreadable directory in silence.
283
+ readdirSync(dir);
284
+ return walkDiscovery({
285
+ walker: "lint",
286
+ root: dir,
287
+ globs: await resolveDiscoveryGlobs(dir),
288
+ excludeDirs: await workspaceMemberDirs(dir),
289
+ followSymlinks: true,
290
+ accept: (name, full) => isSourceFileName(name) && !hasDiscoveryMarkerSync(full),
291
+ });
347
292
  }
348
293
 
349
294
  /**
@@ -723,13 +668,8 @@ export async function lintCommand(options: LintOptions): Promise<LintResult> {
723
668
  allRules = new Map([...allRules, ...pluginRules]);
724
669
  }
725
670
 
726
- // Get all TypeScript files (scan scoped to the lint arg, git-ignored trees
727
- // and the project's `exclude` globs dropped)
728
- const scanned: TypeScriptScan = { raw: [], ignored: new Set() };
729
- const files = getTypeScriptFiles(infraPath, compileDiscoveryFilter(await resolveDiscoveryGlobs(infraPath)), scanned);
730
- // #2527's warning release: which of these the converged walker reads
731
- // differently. `files` is today's list, unchanged.
732
- await warnDiscoveryChanges({ walker: "lint", root: infraPath, files, lintIgnored: scanned });
671
+ // Get all TypeScript files, through the one discovery walk (#2527)
672
+ const files = await getTypeScriptFiles(infraPath);
733
673
 
734
674
  // Run lint — use per-file rules when overrides are present
735
675
  let diagnostics: LintDiagnostic[];
@@ -1,6 +1,6 @@
1
1
  import { lexiconModulePath, lexiconNames } from "../../lexicon-module";
2
2
  import { existsSync, mkdirSync, writeFileSync, cpSync, readdirSync, statSync, readFileSync } from "fs";
3
- import { join, resolve } from "path";
3
+ import { dirname, join, resolve } from "path";
4
4
  import { createRequire } from "module";
5
5
  import { formatSuccess, formatWarning, formatError } from "../format";
6
6
  import { loadChantConfig } from "../../config";
@@ -29,6 +29,15 @@ export interface UpdateResult {
29
29
  error?: string;
30
30
  }
31
31
 
32
+ /**
33
+ * Where `chant update` writes a lexicon skill, relative to the project. The
34
+ * workspace drift check (#2541) compares these files with what the skills
35
+ * render to, so both read the path from here.
36
+ */
37
+ export function skillFilePath(skillName: string): string {
38
+ return `skills/${skillName}/SKILL.md`;
39
+ }
40
+
32
41
  /**
33
42
  * Copy directory contents recursively, filtering to .ts and .d.ts files
34
43
  */
@@ -170,9 +179,9 @@ export async function updateCommand(options: UpdateOptions): Promise<UpdateResul
170
179
  const skills = plugin.skills();
171
180
  if (skills.length > 0) {
172
181
  for (const skill of skills) {
173
- const skillDir = join(projectDir, "skills", skill.name);
174
- mkdirSync(skillDir, { recursive: true });
175
- writeFileSync(join(skillDir, "SKILL.md"), skill.content);
182
+ const file = join(projectDir, skillFilePath(skill.name));
183
+ mkdirSync(dirname(file), { recursive: true });
184
+ writeFileSync(file, skill.content);
176
185
  }
177
186
  synced.push(`${plugin.name} skills (${skills.length})`);
178
187
  }
@@ -76,7 +76,7 @@ export function contentHash(files: Map<string, Buffer>): string {
76
76
  // ── Source resolution → a { relpath → bytes } file set ──────────────────────
77
77
 
78
78
  /** Walk a local directory into a relpath→bytes map (or a single file). */
79
- function readLocal(absPath: string): Map<string, Buffer> {
79
+ export function readLocal(absPath: string): Map<string, Buffer> {
80
80
  const files = new Map<string, Buffer>();
81
81
  const stat = statSync(absPath);
82
82
  if (stat.isFile()) {
@@ -135,12 +135,17 @@ export function scopeArchiveFiles(
135
135
  }
136
136
 
137
137
  async function resolveSource(entry: VendorEntry, manifestDir: string): Promise<Map<string, Buffer>> {
138
- if (entry.source.type === "local") {
139
- const abs = resolve(manifestDir, entry.source.path);
140
- if (!existsSync(abs)) throw new Error(`local source not found: ${entry.source.path}`);
138
+ return resolveVendorSource(entry.source, manifestDir);
139
+ }
140
+
141
+ /** Resolve a vendor source (a local path, or an archive URL) to its file set. */
142
+ export async function resolveVendorSource(source: VendorEntry["source"], manifestDir: string): Promise<Map<string, Buffer>> {
143
+ if (source.type === "local") {
144
+ const abs = resolve(manifestDir, source.path);
145
+ if (!existsSync(abs)) throw new Error(`local source not found: ${source.path}`);
141
146
  return readLocal(abs);
142
147
  }
143
- return readArchive(entry.source.url, entry.source.subpath);
148
+ return readArchive(source.url, source.subpath);
144
149
  }
145
150
 
146
151
  // ── File I/O ────────────────────────────────────────────────────────────────
@@ -154,7 +159,7 @@ function writeFiles(targetDir: string, files: Map<string, Buffer>): void {
154
159
  }
155
160
  }
156
161
 
157
- function readTarget(targetDir: string): Map<string, Buffer> {
162
+ export function readTarget(targetDir: string): Map<string, Buffer> {
158
163
  if (!existsSync(targetDir)) return new Map();
159
164
  return readLocal(targetDir);
160
165
  }
@@ -0,0 +1,38 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`a project outside any workspace (level 0) > the pipeline is the generator's output, unchanged 1`] = `
4
+ "name: chant-components-staging
5
+
6
+ on:
7
+ workflow_dispatch: {}
8
+
9
+ env:
10
+ CHANT_ENV: staging
11
+
12
+ jobs:
13
+ shared-alb:
14
+ runs-on: ubuntu-latest
15
+ container: node:22-slim
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - run: chant run --components shared-alb --env staging --dump-outputs shared-alb.outputs.json
19
+ - name: Upload shared-alb outputs
20
+ uses: actions/upload-artifact@v4
21
+ with:
22
+ name: shared-alb-outputs
23
+ path: shared-alb.outputs.json
24
+ api:
25
+ runs-on: ubuntu-latest
26
+ needs:
27
+ - shared-alb
28
+ container: node:22-slim
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+ - name: Download shared-alb outputs
32
+ uses: actions/download-artifact@v4
33
+ with:
34
+ name: shared-alb-outputs
35
+ path: .
36
+ - run: chant run --components api --env staging --seed-outputs shared-alb.outputs.json
37
+ "
38
+ `;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * `chant build --components --generate` inside a workspace member (#2542).
3
+ * Component discovery is stubbed with a fixed graph; the github generator is
4
+ * the real one, so the files checked here are what a user gets. A project
5
+ * outside any workspace is held to its old output byte for byte, and to a
6
+ * snapshot, since level 0 must not move (#2525).
7
+ */
8
+
9
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
10
+ import { execFileSync } from "node:child_process";
11
+ import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+ import type { DriverComponent } from "../../components/driver";
15
+ import type { ComponentPipelineOptions } from "../../lexicon";
16
+ import type { ParsedArgs } from "../registry";
17
+ import { generateGithubPipeline } from "@intentius/chant-lexicon-github/components/generate-pipeline";
18
+ import { twoMemberWorkspace } from "../../workspace/__fixtures__/two-member-workspace";
19
+
20
+ const components: DriverComponent[] = [
21
+ { name: "shared-alb", dependsOn: [], deploy: [] },
22
+ { name: "api", dependsOn: ["shared-alb"], deploy: [] },
23
+ ];
24
+
25
+ const received: (ComponentPipelineOptions | undefined)[] = [];
26
+ vi.mock("../../components/cli-support", () => ({
27
+ generateComponentsPipeline: async (_path: string, _lexicon: string, options?: ComponentPipelineOptions) => {
28
+ received.push(options);
29
+ const { yaml, stages, jobs, env } = generateGithubPipeline(components, options);
30
+ return { success: true, yaml, stages, jobs, env };
31
+ },
32
+ }));
33
+ vi.mock("../../config", async () => {
34
+ const actual = await vi.importActual<typeof import("../../config")>("../../config");
35
+ return { ...actual, loadChantConfigUpward: async () => ({ config: {} }) };
36
+ });
37
+
38
+ const { runBuild } = await import("./build");
39
+
40
+ function args(overrides: Partial<ParsedArgs>): ParsedArgs {
41
+ return {
42
+ command: "build",
43
+ path: ".",
44
+ format: "",
45
+ fix: false,
46
+ watch: false,
47
+ verbose: false,
48
+ help: false,
49
+ live: false,
50
+ components: true,
51
+ generate: "github",
52
+ ...overrides,
53
+ };
54
+ }
55
+
56
+ let repo: string;
57
+ let logs: string[];
58
+ beforeEach(() => {
59
+ received.length = 0;
60
+ logs = [];
61
+ vi.spyOn(console, "error").mockImplementation(() => {});
62
+ vi.spyOn(console, "log").mockImplementation((s: string) => {
63
+ logs.push(s);
64
+ });
65
+ });
66
+ afterEach(() => {
67
+ vi.restoreAllMocks();
68
+ rmSync(repo, { recursive: true, force: true });
69
+ });
70
+
71
+ describe("a project inside a workspace member", () => {
72
+ beforeEach(() => {
73
+ repo = twoMemberWorkspace();
74
+ });
75
+
76
+ test("the pipeline is written to the member's file at the repository root, filtered and recorded", async () => {
77
+ expect(await runBuild({ args: args({ path: join(repo, "services/api"), env: "staging" }), plugins: [], serializers: [] })).toBe(0);
78
+
79
+ expect(received[0]?.member).toEqual({ name: "api", dir: "services/api", file: ".github/workflows/chant-api-staging.yml" });
80
+ const file = join(repo, ".github/workflows/chant-api-staging.yml");
81
+ const text = readFileSync(file, "utf-8");
82
+ const [header, ...rest] = text.split("\n");
83
+ expect(header).toContain("Regenerate with: chant build --components --generate github --env staging");
84
+ expect(rest.join("\n")).toBe(generateGithubPipeline(components, { env: "staging", member: received[0]!.member }).yaml);
85
+ expect(text).toContain("working-directory: services/api");
86
+
87
+ const record = JSON.parse(readFileSync(join(repo, "services/api/.chant/generated.json"), "utf-8"));
88
+ expect(record.files).toEqual([{ path: ".github/workflows/chant-api-staging.yml", command: "chant build --components --generate github --env staging", env: "staging" }]);
89
+ });
90
+
91
+ test("two members write two files and never collide", async () => {
92
+ await runBuild({ args: args({ path: join(repo, "services/api"), env: "prod" }), plugins: [], serializers: [] });
93
+ await runBuild({ args: args({ path: join(repo, "apps/web"), env: "prod" }), plugins: [], serializers: [] });
94
+ expect(existsSync(join(repo, ".github/workflows/chant-api-prod.yml"))).toBe(true);
95
+ expect(existsSync(join(repo, ".github/workflows/chant-web-prod.yml"))).toBe(true);
96
+ });
97
+
98
+ test("--format json names the member and the file and writes nothing", async () => {
99
+ expect(await runBuild({ args: args({ path: join(repo, "apps/web"), env: "prod", format: "json" }), plugins: [], serializers: [] })).toBe(0);
100
+ const out = JSON.parse(logs.join("\n"));
101
+ expect(out).toMatchObject({ member: "web", file: ".github/workflows/chant-web-prod.yml", env: "prod" });
102
+ expect(existsSync(join(repo, ".github/workflows/chant-web-prod.yml"))).toBe(false);
103
+ });
104
+
105
+ test("an example project in a group is not a member, and gets the plain pipeline", async () => {
106
+ expect(await runBuild({ args: args({ path: join(repo, "examples/demo") }), plugins: [], serializers: [] })).toBe(0);
107
+ expect(received[0]).toEqual({ env: undefined });
108
+ expect(logs.join("\n")).toBe(generateGithubPipeline(components, {}).yaml);
109
+ });
110
+ });
111
+
112
+ describe("a project outside any workspace (level 0)", () => {
113
+ beforeEach(() => {
114
+ repo = realpathSync(mkdtempSync(join(tmpdir(), "chant-build-level0-")));
115
+ execFileSync("git", ["init", "-q"], { cwd: repo });
116
+ });
117
+
118
+ test("the pipeline is the generator's output, unchanged", async () => {
119
+ const output = join(repo, "pipeline.yml");
120
+ expect(await runBuild({ args: args({ path: repo, env: "staging", output }), plugins: [], serializers: [] })).toBe(0);
121
+ expect(received[0]).toEqual({ env: "staging" });
122
+ const text = readFileSync(output, "utf-8");
123
+ expect(text).toBe(generateGithubPipeline(components, { env: "staging" }).yaml);
124
+ expect(text).toMatchSnapshot();
125
+ expect(existsSync(join(repo, ".chant"))).toBe(false);
126
+ });
127
+ });
@@ -6,6 +6,7 @@ import type { CommandContext } from "../registry";
6
6
  import { generateComponentsPipeline } from "../../components/cli-support";
7
7
  import { loadChantConfigUpward, type ChantConfig } from "../../config";
8
8
  import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli";
9
+ import { findWorkspaceRoot } from "../../project-root";
9
10
 
10
11
  /**
11
12
  * `chant build --components --generate <lexicon>` — generate mode (#563,
@@ -44,6 +45,33 @@ async function runGenerateComponents(ctx: CommandContext): Promise<number> {
44
45
  return 1;
45
46
  }
46
47
 
48
+ // A project inside a workspace member gets that member's pipeline (#2542):
49
+ // path-filtered, run in the member's directory, written to a file named
50
+ // after the member and recorded as the member's generated file. The
51
+ // workspace module loads only when a declaration was found, so a project
52
+ // outside any workspace generates exactly what it did before.
53
+ const found = findWorkspaceRoot(resolve(args.path));
54
+ let plan: import("../../workspace/member-pipeline").MemberComponentPlan | undefined;
55
+ let memberPipeline: typeof import("../../workspace/member-pipeline") | undefined;
56
+ if (found) {
57
+ memberPipeline = await import("../../workspace/member-pipeline");
58
+ try {
59
+ plan = memberPipeline.planMemberComponentPipeline({
60
+ projectDir: resolve(args.path),
61
+ found,
62
+ lexicon,
63
+ env: args.env,
64
+ promoteTo: args.promoteTo,
65
+ output: args.output,
66
+ params: args.param,
67
+ paramsFile: args.paramsFile,
68
+ });
69
+ } catch (err) {
70
+ console.error(formatError({ message: memberPipeline.describeError(err) }));
71
+ return 1;
72
+ }
73
+ }
74
+
47
75
  // Which lexicons support generate mode is a property of the loaded lexicon
48
76
  // plugins (those implementing `generateComponentPipeline`, #688), not a
49
77
  // hard-coded core list — `generateComponentsPipeline` returns a descriptive
@@ -51,7 +79,7 @@ async function runGenerateComponents(ctx: CommandContext): Promise<number> {
51
79
  const result = await generateComponentsPipeline(
52
80
  args.path,
53
81
  lexicon,
54
- { env: args.env, ...(args.promoteTo ? { promoteTo: args.promoteTo } : {}) },
82
+ { env: args.env, ...(args.promoteTo ? { promoteTo: args.promoteTo } : {}), ...(plan ? { member: plan.member } : {}) },
55
83
  args.sandbox,
56
84
  paramsResolution.provenance,
57
85
  );
@@ -73,11 +101,17 @@ async function runGenerateComponents(ctx: CommandContext): Promise<number> {
73
101
  // generator's own resolution, forwarded rather than left for a
74
102
  // consumer to re-derive by parsing the YAML back (#2060).
75
103
  ...(result.env ? { env: result.env } : {}),
104
+ // A workspace member's pipeline (#2542): the member and the file
105
+ // the pipeline belongs in, relative to the repository root.
106
+ ...(plan ? { member: plan.member.name, file: plan.file } : {}),
76
107
  },
77
108
  null,
78
109
  2,
79
110
  ),
80
111
  );
112
+ } else if (plan && memberPipeline) {
113
+ memberPipeline.writeMemberComponentPipeline(plan, yaml);
114
+ console.error(formatSuccess(`wrote ${formatBold(plan.file)} for member ${formatBold(plan.member.name)}`));
81
115
  } else if (args.output) {
82
116
  const outputPath = resolve(args.output);
83
117
  mkdirSync(dirname(outputPath), { recursive: true });