@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
@@ -3,6 +3,7 @@ import { realpathSync, rmSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
4
  import { bundleDriver } from "../../discovery/sandbox/bundle";
5
5
  import { classifyChildError } from "../../discovery/sandbox/child-errors";
6
+ import { settleOnExitAfterChannelDrains } from "../../discovery/sandbox/fork";
6
7
  import { DiscoveryError, type DiscoveryErrorType } from "../../errors";
7
8
  import type { Component } from "../component";
8
9
  import type { DiscoveredComponent } from "../discover";
@@ -163,7 +164,9 @@ function runChildProcess(
163
164
  reject(err);
164
165
  });
165
166
 
166
- child.on("exit", (code, signal) => {
167
+ // chant#2461 — a payload can still be in the IPC socket when the exit is
168
+ // handled; see settleOnExitAfterChannelDrains.
169
+ settleOnExitAfterChannelDrains(child, (code, signal) => {
167
170
  if (settled) return;
168
171
  settled = true;
169
172
  clearTimeout(timeout);
@@ -55,6 +55,8 @@
55
55
 
56
56
  import type { ArtifactReproducibility, ProvenanceLink } from "./reproducibility";
57
57
  import { defaultReproducibility } from "./reproducibility";
58
+ import { contentDigest } from "../../content-digest";
59
+ import { canonicalJson } from "../../effect-receipt";
58
60
 
59
61
  export type BuildArchiveEntryKind = "image" | "template" | "asset" | "sbom";
60
62
 
@@ -129,6 +131,12 @@ export interface BuildArchiveManifest {
129
131
  * changed contents, not merely a re-run.
130
132
  */
131
133
  manifestDigest: string;
134
+ /**
135
+ * Set on read, never written: `["legacy-digest"]` when a manifest
136
+ * persisted on `chant/lifecycle` holds digests from the 32-bit hash chant
137
+ * used before real SHA-256 (#2514). See ../../lifecycle/legacy-digest.ts.
138
+ */
139
+ flags?: Array<"legacy-digest">;
132
140
  }
133
141
 
134
142
  /**
@@ -147,30 +155,28 @@ const DEFAULT_MEDIA_TYPES: Record<BuildArchiveEntryKind, string> = {
147
155
  };
148
156
 
149
157
  /**
150
- * Deterministic content digest over arbitrary string content. Same shape as
151
- * the FNV-ish fake digest `MockCloudExecutor` uses, so tests can assert on it
152
- * without a real `sha256` implementation. Exported so callers that need to
153
- * digest content that never goes through the `CloudExecutor` (e.g. a
154
- * synthesized template's serialized bytes, see ./build.ts's
155
- * `addArchiveTemplate`) share this one algorithm rather than each defining
156
- * their own. Production `image`/`asset` entries instead carry the real
157
- * digest their producing capability already computed (e.g. `docker-build`'s
158
- * `executor.docker.build` digest).
158
+ * The content digest every entry chant computes itself carries: real
159
+ * SHA-256 over the UTF-8 bytes of its input (../../content-digest.ts,
160
+ * chant #2514). Callers that digest content that never goes through the
161
+ * `CloudExecutor` (a synthesized template's serialized bytes, see ./build.ts's
162
+ * `addArchiveTemplate`, or an SBOM) use it. Production `image`/`asset`
163
+ * entries instead carry the digest their producing capability already
164
+ * computed (e.g. `docker-build`'s `executor.docker.build` digest).
159
165
  */
160
- export function contentDigest(input: string): string {
161
- let hash = 0;
162
- for (let i = 0; i < input.length; i++) {
163
- hash = (Math.imul(hash, 31) + input.charCodeAt(i)) | 0;
164
- }
165
- const hex = Math.abs(hash).toString(16).padStart(8, "0");
166
- return `sha256:${hex.repeat(8).slice(0, 64)}`;
167
- }
166
+ export { contentDigest };
168
167
 
169
- /** Compute the aggregate `manifestDigest` over a set of entries — sorted by path so entry order never changes the digest, only content does. */
168
+ /**
169
+ * Compute the aggregate `manifestDigest` over a set of entries. Each entry
170
+ * contributes its `kind`, `path` and `digest`; entries are sorted by path
171
+ * (UTF-16 code units, the order `canonicalJson` sorts keys in) so entry order
172
+ * never changes the digest, only content does. The list is hashed as
173
+ * `canonicalJson`, so no path or digest can be read as a field separator.
174
+ */
170
175
  export function computeManifestDigest(contents: BuildArchiveEntry[]): string {
171
- const sorted = [...contents].sort((a, b) => a.path.localeCompare(b.path));
172
- const canonical = sorted.map((e) => `${e.kind}:${e.path}:${e.digest}`).join("\n");
173
- return contentDigest(canonical);
176
+ const sorted = contents
177
+ .map((e) => ({ kind: e.kind, path: e.path, digest: e.digest }))
178
+ .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
179
+ return contentDigest(canonicalJson(sorted));
174
180
  }
175
181
 
176
182
  /** Start a new, empty manifest for `component`. Entries are added with `addArchiveEntry`. */
@@ -237,7 +237,7 @@
237
237
  * `externalParameters`, never replacing it), not a shape break.
238
238
  */
239
239
 
240
- import { createHash } from "node:crypto";
240
+ import { contentDigest } from "../../content-digest";
241
241
  import type { Capability, DeployContext } from "../capability";
242
242
  import type { ProvenanceLink } from "./reproducibility";
243
243
  import type { BuildArchiveEntry } from "./build-archive";
@@ -555,10 +555,8 @@ export function createRunAgentCapability(
555
555
  const PROMPT_PATH = "/work/prompt";
556
556
  const OUTPUT_PATH = "/work/output";
557
557
 
558
- /** `sha256:<hex>` over a string, prefixed the same way ./build-archive.ts's `contentDigest` and ./build.ts's zip/jar digests are. */
559
- function sha256Digest(content: string): string {
560
- return `sha256:${createHash("sha256").update(content, "utf8").digest("hex")}`;
561
- }
558
+ /** `sha256:<hex>` over a string: ../../content-digest.ts's `contentDigest`, the same digest ./build-archive.ts uses. */
559
+ const sha256Digest = contentDigest;
562
560
 
563
561
  const EMPTY_DIGEST = sha256Digest("");
564
562
 
@@ -0,0 +1,136 @@
1
+ /**
2
+ * chant#2591 — the static reader of a config's `lexicons`: what it reads, and
3
+ * what it reports as unreadable instead of running the config.
4
+ */
5
+ import { describe, test, expect, beforeEach, afterEach } from "vitest";
6
+ import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import { readConfigFieldsStatically, readLexiconDeclarationsStatically, unknownPathLexiconsNotice } from "./config-static";
10
+
11
+ let dir: string;
12
+
13
+ beforeEach(() => {
14
+ dir = realpathSync(mkdtempSync(join(tmpdir(), "chant-2591-static-")));
15
+ // A project boundary, so the upward walk stops here.
16
+ writeFileSync(join(dir, "package.json"), "{}");
17
+ });
18
+
19
+ afterEach(() => {
20
+ rmSync(dir, { recursive: true, force: true });
21
+ });
22
+
23
+ function readTs(source: string) {
24
+ writeFileSync(join(dir, "chant.config.ts"), source);
25
+ return readLexiconDeclarationsStatically(dir);
26
+ }
27
+
28
+ const SITE = { name: "site", module: "./lexicon/index.ts" };
29
+
30
+ describe("readable configs", () => {
31
+ test.each([
32
+ ["an object literal default export", `export default { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }] };`],
33
+ [
34
+ "satisfies and a type import",
35
+ `import type { ChantConfig } from "@intentius/chant";\nexport default { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }] } satisfies ChantConfig;`,
36
+ ],
37
+ [
38
+ "a const alias",
39
+ `const config = { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }], sourceDir: "src" };\nexport default config;`,
40
+ ],
41
+ ["a named config export", `export const config = { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }] };`],
42
+ ["named exports", `export const lexicons = ["aws", { name: "site", module: "./lexicon/index.ts" }];\nexport const sourceDir = "src";`],
43
+ [
44
+ "consts, a spread and a template",
45
+ `const dir = "lexicon";\nconst base = ["aws"];\nexport default { lexicons: [...base, { name: "site", module: \`./\${dir}/index.ts\` }] };`,
46
+ ],
47
+ [
48
+ "a spread config",
49
+ `const shared = { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }] };\nexport default { ...shared, sourceDir: "src" };`,
50
+ ],
51
+ [
52
+ "other keys that are not readable",
53
+ `import { plugin } from "./rules";\nexport default { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }], lint: { plugins: [plugin()] } };`,
54
+ ],
55
+ ])("%s", (_label, source) => {
56
+ const read = readTs(source);
57
+ expect(read).toEqual({ status: "read", configPath: join(dir, "chant.config.ts"), entries: ["aws", SITE] });
58
+ });
59
+
60
+ test("a same-file function the fold subset covers is interpreted, not run", () => {
61
+ const read = readTs(`const pick = (extra) => ["aws", extra];\nexport default { lexicons: pick("k8s") };`);
62
+ expect(read).toMatchObject({ status: "read", entries: ["aws", "k8s"] });
63
+ });
64
+
65
+ test("a config with no lexicons reads as none", () => {
66
+ expect(readTs(`export default { sourceDir: "src" };`)).toMatchObject({ status: "read", entries: [] });
67
+ });
68
+
69
+ test("chant.config.json is parsed", () => {
70
+ writeFileSync(join(dir, "chant.config.json"), JSON.stringify({ lexicons: ["aws", SITE] }));
71
+ expect(readLexiconDeclarationsStatically(dir)).toMatchObject({ status: "read", entries: ["aws", SITE] });
72
+ });
73
+
74
+ test("the walk goes up from a subdirectory", () => {
75
+ writeFileSync(join(dir, "chant.config.ts"), `export default { lexicons: [{ name: "site", module: "./lexicon/index.ts" }] };`);
76
+ mkdirSync(join(dir, "src", "deep"), { recursive: true });
77
+ expect(readLexiconDeclarationsStatically(join(dir, "src", "deep"))).toMatchObject({ status: "read", entries: [SITE] });
78
+ });
79
+
80
+ test("no config", () => {
81
+ expect(readLexiconDeclarationsStatically(dir)).toEqual({ status: "no-config" });
82
+ });
83
+ });
84
+
85
+ describe("unreadable configs are reported, never run", () => {
86
+ test.each([
87
+ ["an imported binding", `import { lexicons } from "./shared";\nexport default { lexicons };`, /lexicons/],
88
+ ["process.env", `export default { lexicons: process.env.CI ? ["aws"] : ["aws", "k8s"] };`, /process/],
89
+ ["a call as the default export", `export default defineConfig({ lexicons: ["aws"] });`, /not an object literal/],
90
+ ["a re-export", `export { default } from "./base";`, /export/],
91
+ ["an imported function in lexicons", `import { pick } from "./pick";\nexport default { lexicons: pick() };`, /line 4: .*pick/],
92
+ ["entries of the wrong shape", `export default { lexicons: [{ name: "site" }] };`, /not a list of lexicon entries/],
93
+ ])("%s", (_label, source, reason) => {
94
+ const marker = join(dir, "ran");
95
+ const read = readTs(`import { writeFileSync } from "node:fs";\nwriteFileSync(${JSON.stringify(marker)}, "x");\n${source}`);
96
+ expect(read.status).toBe("unknown");
97
+ expect(read.status === "unknown" && read.reason).toMatch(reason);
98
+ expect(unknownPathLexiconsNotice(read)).toContain("is unknown here and is treated as a package");
99
+ expect(existsSync(marker)).toBe(false);
100
+ });
101
+
102
+ test("a readable config has no notice", () => {
103
+ expect(unknownPathLexiconsNotice(readTs(`export default { lexicons: ["aws"] };`))).toBeUndefined();
104
+ });
105
+ });
106
+
107
+ describe("readConfigFieldsStatically (#2641)", () => {
108
+ test("reads the asked-for fields of a default export, a named config or named exports, and leaves unset ones out", () => {
109
+ writeFileSync(join(dir, "chant.config.ts"), `const env = "prod";\nexport default { environments: [env, { name: "staging" }], ownership: { stack: "shop", env }, lexicons: ["aws"] };`);
110
+ expect(readConfigFieldsStatically(dir, ["ownership", "environments", "sourceDir"])).toEqual({
111
+ status: "read",
112
+ configPath: join(dir, "chant.config.ts"),
113
+ fields: { ownership: { stack: "shop", env: "prod" }, environments: ["prod", { name: "staging" }] },
114
+ });
115
+ writeFileSync(join(dir, "chant.config.ts"), `export const ownership = { stack: "named" };`);
116
+ expect(readConfigFieldsStatically(dir, ["ownership"])).toMatchObject({ status: "read", fields: { ownership: { stack: "named" } } });
117
+ });
118
+
119
+ test("a value it can't fold is unknown, with the reason, and the config is not run", () => {
120
+ const marker = join(dir, "ran");
121
+ writeFileSync(
122
+ join(dir, "chant.config.ts"),
123
+ `import { writeFileSync } from "node:fs";\nwriteFileSync(${JSON.stringify(marker)}, "x");\nexport default { ownership: { stack: process.env.STACK } };`,
124
+ );
125
+ const read = readConfigFieldsStatically(dir, ["ownership"]);
126
+ expect(read.status).toBe("unknown");
127
+ expect(existsSync(marker)).toBe(false);
128
+ });
129
+
130
+ test("reads chant.config.json, and only in the directory itself", () => {
131
+ writeFileSync(join(dir, "chant.config.json"), JSON.stringify({ environments: ["prod"] }));
132
+ expect(readConfigFieldsStatically(dir, ["environments"])).toMatchObject({ status: "read", fields: { environments: ["prod"] } });
133
+ mkdirSync(join(dir, "sub"));
134
+ expect(readConfigFieldsStatically(join(dir, "sub"), ["environments"])).toEqual({ status: "no-config" });
135
+ });
136
+ });
@@ -0,0 +1,232 @@
1
+ /**
2
+ * chant#2591, chant#2589 — read the `lexicons` a project's config declares
3
+ * without running the config.
4
+ *
5
+ * `chant.config.ts` is project code. Most commands evaluate it (see
6
+ * `./config-sandbox.ts`), but a few only need to know which lexicons it
7
+ * declares by path (#2520) so their messages can name the path instead of a
8
+ * package: `init --force`, `dev onboard`, `import --agents` and `audit`.
9
+ * Those read the config here, statically, and never evaluate it.
10
+ *
11
+ * The reader parses the file and reduces the `lexicons` value with the fold
12
+ * machinery (`./fold/fold.ts`'s {@link fold}), given the file's own top-level
13
+ * `const`s and nothing else. It never imports anything, so an imported
14
+ * binding, a function call or `process.env` is not readable. When the value
15
+ * cannot be read, the result says why and callers treat path lexicons as
16
+ * unknown. They never fall back to evaluating the config.
17
+ *
18
+ * What the config's export looks like follows `selectConfigExport` in
19
+ * `./config-sandbox.ts`: `export default`, else `export const config`, else
20
+ * the module's named exports (`export const lexicons`).
21
+ *
22
+ * `chant.config.json` is data and is simply parsed.
23
+ *
24
+ * {@link readConfigFieldsStatically} reads other top-level fields the same
25
+ * way. `chant workspace check` reads a member's `ownership` and
26
+ * `environments` with it for the ledger checks (#2641).
27
+ */
28
+ import { existsSync, readFileSync } from "node:fs";
29
+ import { dirname, join } from "node:path";
30
+ import * as ts from "typescript";
31
+ import { ChantConfigSchema, findProjectConfigPastFragments } from "./config";
32
+ import { collectConsts, fold, propName, FoldError } from "./fold/fold";
33
+ import type { LexiconDeclaration } from "./lexicon-module";
34
+
35
+ /** What {@link readLexiconDeclarationsStatically} found. */
36
+ export type StaticLexiconRead =
37
+ /** No `chant.config.ts` or `chant.config.json` for the directory. */
38
+ | { status: "no-config" }
39
+ /** The declarations, in order. Empty when the config declares no `lexicons`. */
40
+ | { status: "read"; configPath: string; entries: LexiconDeclaration[] }
41
+ /** The config exists, but its `lexicons` could not be read without running it. */
42
+ | { status: "unknown"; configPath: string; reason: string };
43
+
44
+ /** Signals a config shape the reader does not follow. The message is the reason reported. */
45
+ class Unreadable extends Error {}
46
+
47
+ /**
48
+ * Find the project config for `startDir` (the walk `loadChantConfigUpward`
49
+ * makes) and read its `lexicons` without evaluating it.
50
+ */
51
+ export function readLexiconDeclarationsStatically(startDir: string): StaticLexiconRead {
52
+ const { configPath } = findProjectConfigPastFragments(startDir);
53
+ if (configPath === undefined) return { status: "no-config" };
54
+ try {
55
+ const value = readFields(configPath, ["lexicons"]).lexicons;
56
+ if (value === undefined) return { status: "read", configPath, entries: [] };
57
+ const parsed = ChantConfigSchema.shape.lexicons.safeParse(value);
58
+ if (!parsed.success) {
59
+ return { status: "unknown", configPath, reason: "`lexicons` is not a list of lexicon entries" };
60
+ }
61
+ return { status: "read", configPath, entries: (parsed.data ?? []) as LexiconDeclaration[] };
62
+ } catch (err) {
63
+ return { status: "unknown", configPath, reason: unreadableReason(err) };
64
+ }
65
+ }
66
+
67
+ function unreadableReason(err: unknown): string {
68
+ return err instanceof FoldError
69
+ ? `line ${err.line}: ${err.message}`
70
+ : err instanceof Unreadable
71
+ ? err.message
72
+ : `the file could not be read (${err instanceof Error ? err.message : String(err)})`;
73
+ }
74
+
75
+ /** What {@link readConfigFieldsStatically} found. */
76
+ export type StaticFieldsRead =
77
+ /** No `chant.config.ts` or `chant.config.json` in the directory. */
78
+ | { status: "no-config" }
79
+ /** Each asked-for field the config sets, folded to a value. A field it doesn't set is absent. */
80
+ | { status: "read"; configPath: string; fields: Record<string, unknown> }
81
+ /** The config exists, but a field could not be read without running it. */
82
+ | { status: "unknown"; configPath: string; reason: string };
83
+
84
+ /**
85
+ * Read top-level `fields` of the config in `dir` itself, as
86
+ * `loadChantConfig(dir)` finds it (`chant.config.ts`, else
87
+ * `chant.config.json`), without evaluating it. The values are not checked
88
+ * against the config schema; the caller does that for the fields it reads.
89
+ */
90
+ export function readConfigFieldsStatically(dir: string, fields: readonly string[]): StaticFieldsRead {
91
+ const configPath = ["chant.config.ts", "chant.config.json"].map((f) => join(dir, f)).find((f) => existsSync(f));
92
+ if (configPath === undefined) return { status: "no-config" };
93
+ try {
94
+ return { status: "read", configPath, fields: readFields(configPath, fields) };
95
+ } catch (err) {
96
+ return { status: "unknown", configPath, reason: unreadableReason(err) };
97
+ }
98
+ }
99
+
100
+ function readFields(configPath: string, fields: readonly string[]): Record<string, unknown> {
101
+ const values = configPath.endsWith(".json") ? readJsonFields(configPath, fields) : readTsFields(configPath, fields);
102
+ // Absent fields are left out rather than set to undefined.
103
+ return Object.fromEntries(Object.entries(values).filter(([, v]) => v !== undefined));
104
+ }
105
+
106
+ /** The directory a read config's relative module paths resolve against. */
107
+ export function staticConfigBaseDir(read: { configPath: string }): string {
108
+ return dirname(read.configPath);
109
+ }
110
+
111
+ /**
112
+ * The line a command prints when {@link readLexiconDeclarationsStatically}
113
+ * could not read the config, or `undefined` when it could.
114
+ */
115
+ export function unknownPathLexiconsNotice(read: StaticLexiconRead): string | undefined {
116
+ if (read.status !== "unknown") return undefined;
117
+ return (
118
+ `note: could not read the lexicons in ${read.configPath} without running it (${read.reason}). ` +
119
+ "chant does not run it for this command, so any lexicon it declares by path is unknown here and is treated as a package."
120
+ );
121
+ }
122
+
123
+ function readJsonFields(configPath: string, fields: readonly string[]): Record<string, unknown> {
124
+ const parsed = JSON.parse(readFileSync(configPath, "utf-8")) as unknown;
125
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
126
+ throw new Unreadable("the config is not a JSON object");
127
+ }
128
+ return Object.fromEntries(fields.map((f) => [f, (parsed as Record<string, unknown>)[f]]));
129
+ }
130
+
131
+ function readTsFields(configPath: string, fields: readonly string[]): Record<string, unknown> {
132
+ const source = ts.createSourceFile(configPath, readFileSync(configPath, "utf-8"), ts.ScriptTarget.Latest, true);
133
+ const consts = collectConsts(source);
134
+
135
+ let defaultExport: ts.Expression | undefined;
136
+ const named = new Map<string, ts.Expression>();
137
+ for (const statement of source.statements) {
138
+ if (ts.isExportAssignment(statement)) {
139
+ if (statement.isExportEquals) throw new Unreadable("`export =` is not read");
140
+ defaultExport = statement.expression;
141
+ } else if (ts.isExportDeclaration(statement)) {
142
+ // `export { x as default }`, `export * from "./other"`: the export's
143
+ // value lives elsewhere, possibly in another module.
144
+ throw new Unreadable("an `export { … }` or `export … from` declaration is not read");
145
+ } else if (ts.isVariableStatement(statement) && hasExportModifier(statement)) {
146
+ if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) {
147
+ throw new Unreadable("an exported `let` or `var` is not read");
148
+ }
149
+ for (const decl of statement.declarationList.declarations) {
150
+ if (ts.isIdentifier(decl.name) && decl.initializer) named.set(decl.name.text, decl.initializer);
151
+ else throw new Unreadable("a destructured export is not read");
152
+ }
153
+ } else if (hasExportModifier(statement) && hasDefaultModifier(statement)) {
154
+ throw new Unreadable("the default export is not an object");
155
+ }
156
+ }
157
+
158
+ const configExpr = defaultExport ?? named.get("config");
159
+ if (configExpr === undefined) {
160
+ // The module's named exports are the config.
161
+ return Object.fromEntries(
162
+ fields.map((f) => {
163
+ const expr = named.get(f);
164
+ return [f, expr === undefined ? undefined : fold(expr, consts)];
165
+ }),
166
+ );
167
+ }
168
+ const obj = configObject(configExpr, consts, new Set());
169
+ return Object.fromEntries(fields.map((f) => [f, fieldOfObject(obj, f, consts)]));
170
+ }
171
+
172
+ /** The object literal a config export reduces to, following `const` aliases. */
173
+ function configObject(
174
+ node: ts.Expression,
175
+ consts: Map<string, ts.Expression>,
176
+ seen: Set<string>,
177
+ ): ts.ObjectLiteralExpression {
178
+ const inner = unwrap(node);
179
+ if (ts.isObjectLiteralExpression(inner)) return inner;
180
+ if (ts.isIdentifier(inner)) {
181
+ const init = consts.get(inner.text);
182
+ if (init === undefined || seen.has(inner.text)) {
183
+ throw new Unreadable(`the exported config \`${inner.text}\` is not a top-level const of this file`);
184
+ }
185
+ seen.add(inner.text);
186
+ return configObject(init, consts, seen);
187
+ }
188
+ throw new Unreadable("the exported config is not an object literal");
189
+ }
190
+
191
+ /** The value `field` takes in a config object literal, or `undefined` when it sets none. */
192
+ function fieldOfObject(obj: ts.ObjectLiteralExpression, field: string, consts: Map<string, ts.Expression>): unknown {
193
+ let value: unknown = undefined;
194
+ for (const prop of obj.properties) {
195
+ if (ts.isSpreadAssignment(prop)) {
196
+ const spread = fold(prop.expression, consts);
197
+ if (spread !== null && typeof spread === "object" && Object.prototype.hasOwnProperty.call(spread, field)) {
198
+ value = (spread as Record<string, unknown>)[field];
199
+ }
200
+ continue;
201
+ }
202
+ if (prop.name === undefined) continue;
203
+ const name = propName(prop.name);
204
+ if (name !== field) continue;
205
+ if (ts.isPropertyAssignment(prop)) value = fold(prop.initializer, consts);
206
+ else if (ts.isShorthandPropertyAssignment(prop)) value = fold(prop.name, consts);
207
+ else throw new Unreadable(`\`${field}\` is a method or accessor`);
208
+ }
209
+ return value;
210
+ }
211
+
212
+ function unwrap(node: ts.Expression): ts.Expression {
213
+ let current = node;
214
+ while (
215
+ ts.isParenthesizedExpression(current) ||
216
+ ts.isAsExpression(current) ||
217
+ ts.isSatisfiesExpression(current) ||
218
+ ts.isNonNullExpression(current) ||
219
+ ts.isTypeAssertionExpression(current)
220
+ ) {
221
+ current = current.expression;
222
+ }
223
+ return current;
224
+ }
225
+
226
+ function hasExportModifier(node: ts.Node): boolean {
227
+ return (ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) ?? false;
228
+ }
229
+
230
+ function hasDefaultModifier(node: ts.Node): boolean {
231
+ return (ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword)) ?? false;
232
+ }
@@ -12,6 +12,7 @@ import {
12
12
  environmentEndpoint,
13
13
  matchesEnvironmentPattern,
14
14
  matchesDeclaredEnvironment,
15
+ resolveDiscoveryGlobs,
15
16
  } from "./config";
16
17
  import { writeFileSync, mkdirSync, rmSync } from "fs";
17
18
  import { join } from "path";
@@ -448,3 +449,42 @@ describe("resolveKnowledgeDir (#1864, design #1059)", () => {
448
449
  expect(resolveKnowledgeDir({ knowledge: {} }, "/proj")).toBe(join("/proj", "knowledge"));
449
450
  });
450
451
  });
452
+
453
+ describe("exclude / include discovery globs (#2519)", () => {
454
+ test("loads string arrays", async () => {
455
+ writeFileSync(join(TEST_DIR, "chant.config.json"), JSON.stringify({ exclude: ["ops/**"], include: ["ops/keep.ts"] }));
456
+ const { config } = await loadChantConfig(TEST_DIR);
457
+ expect(config.exclude).toEqual(["ops/**"]);
458
+ expect(config.include).toEqual(["ops/keep.ts"]);
459
+ });
460
+
461
+ test.each([
462
+ ["a non-string entry", { exclude: ["ops/**", 42] }, /exclude\.1: .*expected string/],
463
+ ["a bare string", { exclude: "ops/**" }, /exclude: .*expected array/],
464
+ ["an empty pattern", { include: [""] }, /include\.0: /],
465
+ ])("rejects %s loudly", async (_label, raw, message) => {
466
+ writeFileSync(join(TEST_DIR, "chant.config.json"), JSON.stringify(raw));
467
+ await expect(loadChantConfig(TEST_DIR)).rejects.toThrow(message);
468
+ await expect(resolveDiscoveryGlobs(TEST_DIR)).rejects.toThrow(message);
469
+ });
470
+
471
+ test("resolveDiscoveryGlobs returns include alone, since it re-admits the walk's default skips (#2527)", async () => {
472
+ expect(await resolveDiscoveryGlobs(TEST_DIR)).toBeUndefined();
473
+ writeFileSync(join(TEST_DIR, "chant.config.json"), JSON.stringify({ include: ["ops/**"] }));
474
+ expect(await resolveDiscoveryGlobs(TEST_DIR)).toEqual({ root: TEST_DIR, exclude: [], include: ["ops/**"] });
475
+ });
476
+
477
+ test("resolveDiscoveryGlobs anchors the globs at the project config's directory, past a lint fragment", async () => {
478
+ const src = join(TEST_DIR, "src");
479
+ mkdirSync(src, { recursive: true });
480
+ writeFileSync(join(src, "chant.config.json"), JSON.stringify({ rules: { COR001: "off" } }));
481
+ writeFileSync(join(TEST_DIR, "chant.config.json"), JSON.stringify({ exclude: ["ops"] }));
482
+ expect(await resolveDiscoveryGlobs(src)).toEqual({ root: TEST_DIR, exclude: ["ops"], include: [] });
483
+ });
484
+
485
+ test("resolveDiscoveryGlobs leaves an unrelated config error to the command's own load", async () => {
486
+ writeFileSync(join(TEST_DIR, "chant.config.json"), JSON.stringify({ lexicons: [7], exclude: ["ops"] }));
487
+ await expect(loadChantConfig(TEST_DIR)).rejects.toThrow(/lexicons\.0/);
488
+ expect(await resolveDiscoveryGlobs(TEST_DIR)).toBeUndefined();
489
+ });
490
+ });