@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
package/src/config.ts CHANGED
@@ -10,6 +10,7 @@ import type { BuildParamsConfig } from "./build-params";
10
10
  import type { BuildParamProvenance } from "./provenance";
11
11
  import { findProjectConfig } from "./project-root";
12
12
  import { evaluateProjectConfig } from "./config-sandbox";
13
+ import { lexiconNames, pathLexiconRoot, registerLexiconDeclarations, type LexiconDeclaration } from "./lexicon-module";
13
14
 
14
15
  /**
15
16
  * One project-declared environment (chant #1166). Historically always a bare
@@ -125,8 +126,17 @@ const EnvironmentEntrySchema = z.union([
125
126
  }),
126
127
  ]);
127
128
 
129
+ const LexiconEntrySchema = z.union([
130
+ z.string().min(1),
131
+ z.object({
132
+ name: z.string().min(1),
133
+ module: z.string().min(1),
134
+ root: z.string().min(1).optional(),
135
+ }).strict(),
136
+ ]);
137
+
128
138
  export const ChantConfigSchema = z.object({
129
- lexicons: z.array(z.string().min(1)).optional(),
139
+ lexicons: z.array(LexiconEntrySchema).optional(),
130
140
  capabilities: z.array(z.string().min(1)).optional(),
131
141
  environments: z.array(EnvironmentEntrySchema).optional(),
132
142
  sourceDir: z.string().min(1).optional(),
@@ -184,6 +194,9 @@ export const ChantConfigSchema = z.object({
184
194
  knowledge: z.object({
185
195
  dir: z.string().min(1).optional(),
186
196
  }).optional(),
197
+ exclude: z.array(z.string().min(1)).optional(),
198
+ include: z.array(z.string().min(1)).optional(),
199
+ rootOnly: z.boolean().optional(),
187
200
  }).passthrough();
188
201
 
189
202
  /**
@@ -192,8 +205,25 @@ export const ChantConfigSchema = z.object({
192
205
  * Loaded from `chant.config.ts` (preferred) or `chant.config.json`.
193
206
  */
194
207
  export interface ChantConfig {
195
- /** Lexicon package names to load (e.g. ["aws"]) */
196
- lexicons?: string[];
208
+ /**
209
+ * Lexicons to load. A bare name (`"aws"`) loads the package
210
+ * `@intentius/chant-lexicon-aws`. `{ name, module }` (#2520) loads a
211
+ * project-local lexicon from a module path, resolved against the directory
212
+ * holding this config; see `./lexicon-module.ts`. An optional `root`
213
+ * (chant#2590) names the directory holding the lexicon's other files.
214
+ *
215
+ * Once loaded, this holds names only: the loader replaces each
216
+ * `{ name, module }` entry with its name and records the path in
217
+ * {@link ChantConfig.lexiconModules}.
218
+ */
219
+ lexicons?: LexiconDeclaration[];
220
+
221
+ /**
222
+ * Set by the config loader, never written by hand: each path-declared
223
+ * lexicon's name mapped to its absolute module path. Absent when every
224
+ * `lexicons` entry is a bare name.
225
+ */
226
+ lexiconModules?: Record<string, string>;
197
227
 
198
228
  /**
199
229
  * Capability plugin package names to load in addition to the built-in
@@ -418,14 +448,57 @@ export interface ChantConfig {
418
448
  /** Bundle directory, relative to the project root. Defaults to `"knowledge"`. */
419
449
  dir?: string;
420
450
  };
451
+
452
+ /**
453
+ * Globs, relative to the directory holding this config, naming files that
454
+ * source discovery must skip (#2519): `build`, `lint`, `list`, `explain`
455
+ * and every other command that walks the project for declarations never
456
+ * import or lint a matching file. A pattern matches a file when it matches
457
+ * the file's path or the path of any directory above it, so `"ops"` and
458
+ * `"ops/**"` both skip everything under `ops/`. Matched with picomatch,
459
+ * dot files included. Op discovery and component discovery honour it too
460
+ * (#2527). See `./discovery/walk.ts`.
461
+ */
462
+ exclude?: string[];
463
+
464
+ /**
465
+ * Globs that re-admit files {@link exclude} would skip, and, since #2527,
466
+ * files the discovery walk skips by default: dot-directories, `dist`,
467
+ * git-ignored paths and child projects. `include` always wins over
468
+ * `exclude`, and it never narrows discovery on its own: a file nothing
469
+ * skips is discovered whether or not it matches an `include`. It never
470
+ * re-admits `node_modules`, `.git`, test files, workspace members, or
471
+ * files carrying the generated or skip marker.
472
+ */
473
+ include?: string[];
474
+
475
+ /**
476
+ * Lets `chant build` and `chant lint` run on this project when it is the
477
+ * root of a declared workspace (#2537, #2524 D0). Without it, or the
478
+ * `--root-only` flag, both commands refuse there with `WSP000` and point to
479
+ * `chant workspace build` and `lint`. With it they act on the root project
480
+ * alone. Member directories and example-group matches are left out of
481
+ * discovery either way, as they are for every walk under a declaration
482
+ * (#2527, `workspaceMemberDirs` in `./discovery/walk.ts`). It means nothing
483
+ * in a project with no `chant.workspace.json`.
484
+ */
485
+ rootOnly?: boolean;
421
486
  }
422
487
 
423
488
  /**
424
489
  * Resolved project configuration with metadata about how it was loaded.
425
490
  */
491
+ /**
492
+ * A configuration as the loader returns it. `lexicons` holds names only: each
493
+ * `{ name, module }` entry has been replaced by its name and its path moved
494
+ * to `lexiconModules` (chant #2520), so a reader of a loaded config never
495
+ * sees the object form.
496
+ */
497
+ export type LoadedChantConfig = Omit<ChantConfig, "lexicons"> & { lexicons?: string[] };
498
+
426
499
  export interface ResolvedConfig {
427
500
  /** The loaded configuration */
428
- config: ChantConfig;
501
+ config: LoadedChantConfig;
429
502
 
430
503
  /** Path to the config file that was loaded, or undefined if defaults */
431
504
  configPath?: string;
@@ -434,7 +507,7 @@ export interface ResolvedConfig {
434
507
  /**
435
508
  * Default configuration when no config file exists.
436
509
  */
437
- export const DEFAULT_CHANT_CONFIG: ChantConfig = {};
510
+ export const DEFAULT_CHANT_CONFIG: LoadedChantConfig = {};
438
511
 
439
512
  /**
440
513
  * Load project configuration from a directory.
@@ -499,14 +572,56 @@ export async function loadChantConfig(dir: string): Promise<ResolvedConfig> {
499
572
  * config declaring any project-level key still wins where it stands.
500
573
  */
501
574
  export async function loadChantConfigUpward(startDir: string): Promise<ResolvedConfig> {
575
+ const { dir, configPath } = findProjectConfigPastFragments(startDir);
576
+ if (configPath) warnIfFragmentShadowsProjectConfig(configPath);
577
+ return loadChantConfig(dir);
578
+ }
579
+
580
+ /** The walk {@link loadChantConfigUpward} makes, lint-only fragments skipped, without its warning. */
581
+ export function findProjectConfigPastFragments(startDir: string): { dir: string; configPath?: string } {
502
582
  let { dir, configPath } = findProjectConfig(startDir);
503
583
  while (configPath && isLintOnlyFragment(configPath)) {
504
584
  const parent = dirname(dir);
505
585
  if (parent === dir) break;
506
586
  ({ dir, configPath } = findProjectConfig(parent));
507
587
  }
508
- if (configPath) warnIfFragmentShadowsProjectConfig(configPath);
509
- return loadChantConfig(dir);
588
+ return { dir, configPath };
589
+ }
590
+
591
+ /** A project's discovery globs (#2519) and the directory they are relative to. */
592
+ export interface DiscoveryGlobs {
593
+ /** The directory holding the config that declared the globs. */
594
+ root: string;
595
+ exclude: string[];
596
+ include: string[];
597
+ }
598
+
599
+ /**
600
+ * The `exclude`/`include` globs of the project `startDir` belongs to, found by
601
+ * the same upward walk as {@link loadChantConfigUpward}. `undefined` when the
602
+ * project declares neither. `include` re-admits what `exclude` skips, and
603
+ * also what the discovery walk skips by default (#2527), so it means
604
+ * something on its own.
605
+ *
606
+ * A config that fails to load is left to the command's own config load to
607
+ * report, so discovery keeps working where it did before, unless the failure
608
+ * is in `exclude` or `include` themselves: ignoring those would discover the
609
+ * very files the project asked to skip.
610
+ */
611
+ export async function resolveDiscoveryGlobs(startDir: string): Promise<DiscoveryGlobs | undefined> {
612
+ const { dir, configPath } = findProjectConfigPastFragments(startDir);
613
+ if (!configPath) return undefined;
614
+ let config: ChantConfig;
615
+ try {
616
+ ({ config } = await loadChantConfig(dir));
617
+ } catch (err) {
618
+ if (err instanceof InvalidChantConfigError && (err.key === "exclude" || err.key === "include")) throw err;
619
+ return undefined;
620
+ }
621
+ const exclude = config.exclude ?? [];
622
+ const include = config.include ?? [];
623
+ if (exclude.length === 0 && include.length === 0) return undefined;
624
+ return { root: dir, exclude, include };
510
625
  }
511
626
 
512
627
  /**
@@ -531,7 +646,7 @@ const LINT_FRAGMENT_KEYS = new Set(["$schema", "extends", "rules", "overrides",
531
646
  */
532
647
  const isCommentKey = (k: string): boolean => k.startsWith("_");
533
648
 
534
- function isLintOnlyFragment(configPath: string): boolean {
649
+ export function isLintOnlyFragment(configPath: string): boolean {
535
650
  if (!configPath.endsWith("chant.config.json")) return false;
536
651
  try {
537
652
  const parsed = JSON.parse(readFileSync(configPath, "utf-8")) as unknown;
@@ -813,10 +928,20 @@ export function resolveKnowledgeDir(config: ChantConfig, projectPath: string): s
813
928
  return join(projectPath, config.knowledge?.dir ?? "knowledge");
814
929
  }
815
930
 
931
+ /**
932
+ * A config that failed schema validation. `key` is the top-level key of the
933
+ * first problem. `name` stays `"Error"`, so the printed error reads as before.
934
+ */
935
+ export class InvalidChantConfigError extends Error {
936
+ constructor(message: string, readonly key?: string) {
937
+ super(message);
938
+ }
939
+ }
940
+
816
941
  /**
817
942
  * Validate and normalize a raw config object into ChantConfig shape.
818
943
  */
819
- function normalizeConfig(raw: Record<string, unknown>, source?: string): ChantConfig {
944
+ function normalizeConfig(raw: Record<string, unknown>, source?: string): LoadedChantConfig {
820
945
  if (typeof raw !== "object" || raw === null) {
821
946
  return DEFAULT_CHANT_CONFIG;
822
947
  }
@@ -826,8 +951,33 @@ function normalizeConfig(raw: Record<string, unknown>, source?: string): ChantCo
826
951
  const issue = result.error.issues[0];
827
952
  const path = issue.path.length > 0 ? issue.path.join(".") : undefined;
828
953
  const loc = source ? ` in ${source}` : "";
829
- throw new Error(`Invalid chant config${loc}: ${path ? `${path}: ` : ""}${issue.message}`);
954
+ throw new InvalidChantConfigError(
955
+ `Invalid chant config${loc}: ${path ? `${path}: ` : ""}${issue.message}`,
956
+ issue.path.length > 0 ? String(issue.path[0]) : undefined,
957
+ );
958
+ }
959
+
960
+ // chant #2520 — a lexicon declared by module path. The path is recorded for
961
+ // the loaders and the entry becomes its name, so every reader of `lexicons`
962
+ // keeps seeing names. A config of plain names is returned untouched.
963
+ const lexicons = (raw as ChantConfig).lexicons;
964
+ const baseDir = source !== undefined ? dirname(source) : process.cwd();
965
+ const sourceDir = (raw as ChantConfig).sourceDir;
966
+ // chant#2590 — a declared `root` must stay inside the project and must not
967
+ // contain its source directory. Refused here rather than narrowed, since
968
+ // the project asked for it by name.
969
+ for (const [i, entry] of (lexicons ?? []).entries()) {
970
+ if (typeof entry === "string" || entry.root === undefined) continue;
971
+ const { problem } = pathLexiconRoot(entry, baseDir, sourceDir);
972
+ if (problem !== undefined) {
973
+ const loc = source ? ` in ${source}` : "";
974
+ throw new InvalidChantConfigError(`Invalid chant config${loc}: lexicons.${i}.root: ${problem}`, "lexicons");
975
+ }
976
+ }
977
+ const recorded = registerLexiconDeclarations(lexicons, baseDir, sourceDir);
978
+ if (Object.keys(recorded).length > 0) {
979
+ return { ...(raw as ChantConfig), lexicons: lexiconNames(lexicons), lexiconModules: recorded };
830
980
  }
831
981
 
832
- return raw as ChantConfig;
982
+ return raw as LoadedChantConfig;
833
983
  }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * chant #2514 — `contentDigest` is real SHA-256 over UTF-8, and the manifest
3
+ * and SBOM digests built on it separate inputs one byte apart.
4
+ */
5
+ import { describe, expect, test } from "vitest";
6
+ import { createHash } from "node:crypto";
7
+ import { contentDigest } from "./content-digest";
8
+ import { canonicalJson } from "./effect-receipt";
9
+ import {
10
+ addArchiveEntry,
11
+ computeManifestDigest,
12
+ contentDigest as archiveContentDigest,
13
+ createBuildArchiveManifest,
14
+ } from "./components/verbs/build-archive";
15
+ import { createGenerateSbomCapability } from "./components/verbs/sbom";
16
+ import type { SbomDocument, SbomGenerator } from "./components/verbs/sbom-generator";
17
+ import { computePlanDigest } from "./lifecycle/plan-digest";
18
+ import { hashProps } from "./lifecycle/digest";
19
+ import { isLegacyContentDigest } from "./lifecycle/legacy-digest";
20
+
21
+ describe("contentDigest (#2514)", () => {
22
+ test("matches the FIPS 180-2 test vectors", () => {
23
+ expect(contentDigest("abc")).toBe("sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
24
+ expect(contentDigest("")).toBe("sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
25
+ });
26
+
27
+ test("hashes the UTF-8 bytes, not UTF-16 code units", () => {
28
+ const text = "héllo, 世界 🌍";
29
+ const expected = createHash("sha256").update(Buffer.from(text, "utf8")).digest("hex");
30
+ expect(contentDigest(text)).toBe(`sha256:${expected}`);
31
+ });
32
+
33
+ test("the build archive exports the same function", () => {
34
+ expect(archiveContentDigest).toBe(contentDigest);
35
+ });
36
+
37
+ test("its output is never mistaken for the old form", () => {
38
+ for (const input of ["", "x", "hello", "{}"]) {
39
+ expect(isLegacyContentDigest(contentDigest(input))).toBe(false);
40
+ }
41
+ });
42
+
43
+ test("inputs one byte apart get different digests", () => {
44
+ expect(contentDigest("template-a")).not.toBe(contentDigest("template-b"));
45
+ expect(contentDigest("abc")).not.toBe(contentDigest("abc "));
46
+ });
47
+ });
48
+
49
+ describe("manifest digest (#2514)", () => {
50
+ const entry = (digest: string, path = "web.json") => ({ kind: "template" as const, path, digest });
51
+
52
+ test("is contentDigest over the canonical JSON of the sorted entries", () => {
53
+ const a = entry(contentDigest("{}"), "b.json");
54
+ const b = entry(contentDigest("[]"), "a.json");
55
+ const expected = contentDigest(
56
+ canonicalJson([
57
+ { kind: b.kind, path: b.path, digest: b.digest },
58
+ { kind: a.kind, path: a.path, digest: a.digest },
59
+ ]),
60
+ );
61
+ expect(computeManifestDigest([a, b])).toBe(expected);
62
+ expect(computeManifestDigest([b, a])).toBe(expected);
63
+ });
64
+
65
+ test("templates one byte apart give different entry and manifest digests", () => {
66
+ const base = createBuildArchiveManifest("web", { now: () => new Date(0) });
67
+ const one = addArchiveEntry(base, entry(contentDigest('{"Resources":{"A":1}}')));
68
+ const two = addArchiveEntry(base, entry(contentDigest('{"Resources":{"A":2}}')));
69
+ expect(one.contents[0]!.digest).not.toBe(two.contents[0]!.digest);
70
+ expect(one.manifestDigest).not.toBe(two.manifestDigest);
71
+ });
72
+
73
+ test("a path cannot be read as a separator", () => {
74
+ // Under the old `kind:path:digest` join these two could be confused.
75
+ const x = computeManifestDigest([entry("sha256:1", "a:template:b")]);
76
+ const y = computeManifestDigest([entry("template:b:sha256:1", "a")]);
77
+ expect(x).not.toBe(y);
78
+ });
79
+ });
80
+
81
+ describe("SBOM digest (#2514)", () => {
82
+ function fixedGenerator(bytes: string): SbomGenerator {
83
+ const doc = async (): Promise<SbomDocument> => ({
84
+ format: "spdx",
85
+ mediaType: "application/spdx+json",
86
+ bytes,
87
+ generator: "test",
88
+ });
89
+ return { forImage: doc, forJar: doc, forZip: doc, forDir: doc };
90
+ }
91
+
92
+ test("SBOMs one byte apart get different digests, each real SHA-256", async () => {
93
+ const ctx = { env: "dev", component: "web" };
94
+ const input = { artifactType: "image" as const, path: "archive/web.tar", digest: contentDigest("image") };
95
+ const a = await createGenerateSbomCapability(fixedGenerator('{"packages":[1]}')).run(ctx, input);
96
+ const b = await createGenerateSbomCapability(fixedGenerator('{"packages":[2]}')).run(ctx, input);
97
+ expect(a.digest).toBe(contentDigest('{"packages":[1]}'));
98
+ expect(a.digest).not.toBe(b.digest);
99
+ expect(a.manifest.manifestDigest).not.toBe(b.manifest.manifestDigest);
100
+ });
101
+ });
102
+
103
+ describe("other hashes use contentDigest over canonicalJson (#2514)", () => {
104
+ test("a plan digest", () => {
105
+ const subject = { b: 1, a: [true, null] };
106
+ expect(computePlanDigest("lifecycle-diff", subject)).toBe(
107
+ contentDigest(canonicalJson({ kind: "lifecycle-diff", subject })),
108
+ );
109
+ });
110
+
111
+ test("a plan digest orders keys by code unit, not by locale", () => {
112
+ // localeCompare puts "a" before "B"; canonicalJson puts "B" first.
113
+ expect(computePlanDigest("k", { a: 1, B: 2 })).toBe(contentDigest('{"kind":"k","subject":{"B":2,"a":1}}'));
114
+ });
115
+
116
+ test("a props hash, which drops what JSON cannot hold instead of throwing", () => {
117
+ const props = { Name: "x", fn: () => 1, nested: { z: 1, a: undefined } };
118
+ expect(hashProps(props)).toBe(contentDigest('{"Name":"x","nested":{"z":1}}').slice("sha256:".length));
119
+ });
120
+ });
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The one content digest chant computes itself (chant #2514).
3
+ *
4
+ * `contentDigest(x)` is `"sha256:"` followed by the lowercase hex SHA-256 of
5
+ * the UTF-8 bytes of `x`. Anything core hashes goes through it, and anything
6
+ * structured is first put in canonical form with `canonicalJson`
7
+ * (./effect-receipt.ts), so the same value always hashes the same way. The
8
+ * build archive re-exports it from ./components/verbs/build-archive.ts, where
9
+ * it used to live.
10
+ *
11
+ * Before #2514 this function computed a 32-bit string hash and repeated its
12
+ * eight hex digits eight times. Values recorded then are recognised and
13
+ * flagged by ./lifecycle/legacy-digest.ts.
14
+ */
15
+
16
+ import { createHash } from "node:crypto";
17
+
18
+ /** `sha256:<64 hex>` over the UTF-8 bytes of `input`. */
19
+ export function contentDigest(input: string): string {
20
+ return `sha256:${createHash("sha256").update(input, "utf8").digest("hex")}`;
21
+ }
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect } from "vitest";
2
- import { findInfraFiles } from "./files";
2
+ import { findInfraFiles, compileDiscoveryFilter, hasSkipMarker, SKIP_MARKER } from "./files";
3
3
  import { withTestDir } from "@intentius/chant-test-utils";
4
4
  import { mkdir, writeFile } from "node:fs/promises";
5
5
  import { join } from "node:path";
@@ -169,3 +169,129 @@ describe("findInfraFiles", () => {
169
169
  });
170
170
  });
171
171
  });
172
+
173
+ describe("findInfraFiles — project exclude/include globs (#2519)", () => {
174
+ /** Lay out a project: `files` maps a relative path to its contents. */
175
+ async function project(testDir: string, config: Record<string, unknown> | undefined, files: Record<string, string>) {
176
+ if (config) await writeFile(join(testDir, "chant.config.json"), JSON.stringify(config));
177
+ for (const [rel, body] of Object.entries(files)) {
178
+ await mkdir(join(testDir, rel, ".."), { recursive: true });
179
+ await writeFile(join(testDir, rel), body);
180
+ }
181
+ }
182
+ const rel = (testDir: string, files: string[]) => files.map((f) => f.slice(testDir.length + 1)).sort();
183
+
184
+ const LAYOUT = {
185
+ "src/app.ts": "export const app = {};",
186
+ "src/lib/util.ts": "export const util = {};",
187
+ "ops/run.ts": "process.exit(2);",
188
+ "ops/keep.ts": "export const keep = {};",
189
+ ".stage/copy.ts": "export const copy = {};",
190
+ };
191
+
192
+ test("with neither key configured, every file but the dot directory's is discovered", async () => {
193
+ await withTestDir(async (testDir) => {
194
+ await project(testDir, { lexicons: ["aws"] }, LAYOUT);
195
+ expect(rel(testDir, await findInfraFiles(testDir))).toEqual([
196
+ "ops/keep.ts", "ops/run.ts", "src/app.ts", "src/lib/util.ts",
197
+ ]);
198
+ });
199
+ });
200
+
201
+ test.each([["ops"], ["ops/**"], ["ops/*.ts"]])("exclude %s skips everything under ops/", async (pattern) => {
202
+ await withTestDir(async (testDir) => {
203
+ await project(testDir, { exclude: [pattern] }, LAYOUT);
204
+ expect(rel(testDir, await findInfraFiles(testDir))).toEqual(["src/app.ts", "src/lib/util.ts"]);
205
+ });
206
+ });
207
+
208
+ test("include re-admits a dot directory the walk skips by default (#2527)", async () => {
209
+ await withTestDir(async (testDir) => {
210
+ await project(testDir, { include: [".stage"] }, LAYOUT);
211
+ expect(rel(testDir, await findInfraFiles(testDir))).toContain(".stage/copy.ts");
212
+ });
213
+ });
214
+
215
+ test("include wins over exclude", async () => {
216
+ await withTestDir(async (testDir) => {
217
+ await project(testDir, { exclude: ["ops"], include: ["ops/keep.ts"] }, LAYOUT);
218
+ expect(rel(testDir, await findInfraFiles(testDir))).toEqual([
219
+ "ops/keep.ts", "src/app.ts", "src/lib/util.ts",
220
+ ]);
221
+ });
222
+ });
223
+
224
+ test("exclude everything and include a directory narrows discovery to it", async () => {
225
+ await withTestDir(async (testDir) => {
226
+ await project(testDir, { exclude: ["**"], include: ["src"] }, LAYOUT);
227
+ expect(rel(testDir, await findInfraFiles(testDir))).toEqual(["src/app.ts", "src/lib/util.ts"]);
228
+ });
229
+ });
230
+
231
+ test("include alone changes nothing", async () => {
232
+ await withTestDir(async (testDir) => {
233
+ await project(testDir, { include: ["src"] }, LAYOUT);
234
+ expect(await findInfraFiles(testDir)).toHaveLength(4);
235
+ });
236
+ });
237
+
238
+ test("patterns stay relative to the project root when discovery starts in a subdirectory", async () => {
239
+ await withTestDir(async (testDir) => {
240
+ await project(testDir, { exclude: ["src/lib"] }, LAYOUT);
241
+ expect(rel(testDir, await findInfraFiles(join(testDir, "src")))).toEqual(["src/app.ts"]);
242
+ });
243
+ });
244
+
245
+ test("include does not re-admit test files", async () => {
246
+ await withTestDir(async (testDir) => {
247
+ await project(testDir, { exclude: ["ops"], include: ["**"] }, { ...LAYOUT, "src/app.test.ts": "test();" });
248
+ expect(rel(testDir, await findInfraFiles(testDir))).not.toContain("src/app.test.ts");
249
+ });
250
+ });
251
+
252
+ test("explicit globs override the config; null applies none", async () => {
253
+ await withTestDir(async (testDir) => {
254
+ await project(testDir, { exclude: ["ops"] }, LAYOUT);
255
+ expect(await findInfraFiles(testDir, { globs: null })).toHaveLength(4);
256
+ const only = await findInfraFiles(testDir, { globs: { root: testDir, exclude: ["src"], include: [] } });
257
+ expect(rel(testDir, only)).toEqual(["ops/keep.ts", "ops/run.ts"]);
258
+ });
259
+ });
260
+ });
261
+
262
+ describe("compileDiscoveryFilter (#2519)", () => {
263
+ test("is undefined with no exclude patterns", () => {
264
+ expect(compileDiscoveryFilter(undefined)).toBeUndefined();
265
+ expect(compileDiscoveryFilter({ root: "/p", exclude: [], include: ["**"] })).toBeUndefined();
266
+ });
267
+
268
+ test("never skips a file outside the root", () => {
269
+ const skip = compileDiscoveryFilter({ root: "/p/app", exclude: ["**"], include: [] })!;
270
+ expect(skip("/p/app/x.ts")).toBe(true);
271
+ expect(skip("/p/other/x.ts")).toBe(false);
272
+ });
273
+ });
274
+
275
+ describe("findInfraFiles — skip marker (#2519)", () => {
276
+ test.each([
277
+ ["a line comment", `// ${SKIP_MARKER}\nprocess.exit(2);`],
278
+ ["a line comment after a shebang", `#!/usr/bin/env -S node --import tsx\n// ${SKIP_MARKER}: a runner, not a declaration\nprocess.exit(2);`],
279
+ ["a block comment", `/**\n * Runs an Op.\n * ${SKIP_MARKER}\n */\nprocess.exit(2);`],
280
+ ])("skips a file carrying the marker in %s", async (_label, body) => {
281
+ await withTestDir(async (testDir) => {
282
+ await writeFile(join(testDir, "app.ts"), "export const app = {};");
283
+ await writeFile(join(testDir, "run.ts"), body);
284
+ const files = await findInfraFiles(testDir);
285
+ expect(files).toHaveLength(1);
286
+ expect(files[0]).toMatch(/app\.ts$/);
287
+ });
288
+ });
289
+
290
+ test.each([
291
+ ["in a string", `export const s = "${SKIP_MARKER}";`],
292
+ ["as a longer word", `// ${SKIP_MARKER}-not\nexport const a = {};`],
293
+ ["past the head", `${"// padding\n".repeat(100)}// ${SKIP_MARKER}\nexport const a = {};`],
294
+ ])("does not treat the marker %s as a skip", (_label, body) => {
295
+ expect(hasSkipMarker(body)).toBe(false);
296
+ });
297
+ });