@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
@@ -1,4 +1,4 @@
1
- import { describe, test, expect, beforeEach, afterEach } from "vitest";
1
+ import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
2
2
  import { lintCommand, isLintRule, loadPluginRules, LEXICON_RESOLUTION_RULE_ID, type LintOptions } from "./lint";
3
3
  import { loadPlugins, resolveProjectLexicons } from "../plugins";
4
4
  import { mkdir, rm, writeFile } from "node:fs/promises";
@@ -641,6 +641,57 @@ describe("lintCommand — git-ignored files are not linted", () => {
641
641
  });
642
642
  });
643
643
 
644
+ /**
645
+ * chant#2527: lint walks through the one discovery walk. A child project
646
+ * inside a project is a boundary, `include` re-admits it, and a scan root
647
+ * that is itself git-ignored is linted.
648
+ */
649
+ describe("lintCommand — the converged discovery walk (#2527)", () => {
650
+ let testDir: string;
651
+ const EVL003 = `const t = { a: 1 };\nexport function g(k: string) { return t[k]; }\n`;
652
+
653
+ beforeEach(async () => {
654
+ testDir = join(tmpdir(), `chant-lint-2527-${Date.now()}-${Math.random()}`);
655
+ await mkdir(testDir, { recursive: true });
656
+ process.env.NO_COLOR = "1";
657
+ });
658
+
659
+ afterEach(async () => {
660
+ await rm(testDir, { recursive: true, force: true });
661
+ delete process.env.NO_COLOR;
662
+ });
663
+
664
+ async function childProject(config: Record<string, unknown>): Promise<void> {
665
+ await writeFile(join(testDir, "chant.config.json"), JSON.stringify({ knowledge: { dir: "knowledge" }, ...config }));
666
+ await writeFile(join(testDir, "index.ts"), `export const x = 1;\n`);
667
+ await mkdir(join(testDir, "stacks", "east"), { recursive: true });
668
+ await writeFile(join(testDir, "stacks", "east", "chant.config.ts"), `export default {};\n`);
669
+ await writeFile(join(testDir, "stacks", "east", "east.ts"), EVL003);
670
+ }
671
+
672
+ test("a nested child project is not linted from the parent", async () => {
673
+ await childProject({});
674
+ const result = await lintCommand({ path: testDir, format: "stylish" });
675
+ expect(result.diagnostics.some((d) => d.file.includes("east.ts"))).toBe(false);
676
+ });
677
+
678
+ test("include re-admits the child project", async () => {
679
+ await childProject({ include: ["stacks/east"] });
680
+ const result = await lintCommand({ path: testDir, format: "stylish" });
681
+ expect(result.diagnostics.some((d) => d.file.includes("east.ts") && d.ruleId === "EVL003")).toBe(true);
682
+ });
683
+
684
+ test("an ignored scan root is linted: ignore rules apply below the root only", async () => {
685
+ execFileSync("git", ["init", "-q"], { cwd: testDir });
686
+ await writeFile(join(testDir, ".gitignore"), "/carveout/\n");
687
+ await mkdir(join(testDir, "carveout", "src"), { recursive: true });
688
+ await writeFile(join(testDir, "carveout", "src", "main.ts"), EVL003);
689
+
690
+ const result = await lintCommand({ path: join(testDir, "carveout", "src"), format: "stylish" });
691
+ expect(result.diagnostics.some((d) => d.file.includes("main.ts") && d.ruleId === "EVL003")).toBe(true);
692
+ });
693
+ });
694
+
644
695
  /**
645
696
  * chant #2222 (epic #2215 sub-issue 7) — `chant lint` used to print "No
646
697
  * problems found" and exit 0 on a project whose declared lexicon could not be
@@ -1,7 +1,6 @@
1
1
  import { resolve, join, relative } from "path";
2
2
  import type { BuildParamProvenance } from "../../provenance";
3
- import { readFileSync, writeFileSync, readdirSync, statSync } from "fs";
4
- import { execFileSync } from "child_process";
3
+ import { readFileSync, writeFileSync, readdirSync } from "fs";
5
4
  import { runLint, parseDisableComments } from "../../lint/engine";
6
5
  import type { LintRule, LintDiagnostic, LintFix } from "../../lint/rule";
7
6
  import type { IntrinsicDef, LexiconPlugin } from "../../lexicon";
@@ -19,14 +18,15 @@ import { runPostSynthChecks } from "../../lint/post-synth";
19
18
  import { rule } from "../../lint/declarative";
20
19
  import { watchDirectory, formatTimestamp, formatChangedFiles } from "../watch";
21
20
  import { formatError, formatInfo } from "../format";
22
- import { GENERATED_MARKER } from "../../discovery/files";
21
+ import { hasDiscoveryMarkerSync, isSourceFileName } from "../../discovery/files";
22
+ import { walkDiscovery, workspaceMemberDirs } from "../../discovery/walk";
23
23
  import { buildParamValues, resolveBuildParams } from "../../build-params";
24
24
  import { setBuildParams } from "../../params";
25
25
  import { isNoLexiconDetected } from "../../detectLexicon";
26
26
 
27
27
  // Import config loader
28
28
  import { loadConfig, resolveRulesForFile, resolveConfiguredSeverity, findProjectRoot } from "../../lint/config";
29
- import { loadChantConfig, resolveKnowledgeDir } from "../../config";
29
+ import { loadChantConfig, resolveKnowledgeDir, resolveDiscoveryGlobs } from "../../config";
30
30
  import { findProjectConfig } from "../../project-root";
31
31
  import type { LintProjectConfig } from "../../lint/rule";
32
32
  import { loadOkfBundle, type OkfBundle } from "../../okf-read";
@@ -141,7 +141,7 @@ function lexiconResolutionDiagnostic(projectRoot: string, error: Error): LintDia
141
141
  */
142
142
  async function loadAllPluginRules(
143
143
  projectPath: string,
144
- ): Promise<{ rules: Map<string, LintRule>; intrinsics: IntrinsicDef[]; lexiconError?: Error }> {
144
+ ): Promise<{ rules: Map<string, LintRule>; intrinsics: IntrinsicDef[]; plugins: LexiconPlugin[]; lexiconError?: Error }> {
145
145
  const rules = new Map<string, LintRule>();
146
146
 
147
147
  // Load core COR/EVL rules directly
@@ -219,7 +219,7 @@ async function loadAllPluginRules(
219
219
  rules.set(r.id, r);
220
220
  }
221
221
 
222
- return { rules, intrinsics, ...(lexiconError ? { lexiconError } : {}) };
222
+ return { rules, intrinsics, plugins, ...(lexiconError ? { lexiconError } : {}) };
223
223
  }
224
224
 
225
225
  /**
@@ -269,64 +269,26 @@ export interface LintResult {
269
269
  }
270
270
 
271
271
  /**
272
- * Drop paths git ignores. Vendored trees (`vendor/`), build output (`dist/`),
273
- * and anything else in `.gitignore` are not authored chant source — linting
274
- * them surfaces EVL/COR errors in code the project never wrote, which then
275
- * gates `chant graph --format ir` on files outside the graph. Delegates to
276
- * `git check-ignore` for exact gitignore semantics (nesting, negation) in one
277
- * batched call; a non-git tree (or absent git) filters nothing.
272
+ * Every TypeScript file `chant lint` reads under `dir`, through the one
273
+ * discovery walk (`../../discovery/walk.ts`, #2527): git-ignored paths
274
+ * (vendored trees and the like, which the project never wrote), dot-directories,
275
+ * `dist` and, inside a project, child projects are skipped, and so is what the
276
+ * project's `exclude` globs (#2519) name, unless `include` re-admits it. Test
277
+ * and spec files, chant-generated files and skip-marked files are left out.
278
+ * Symlinks are followed, as lint always did.
278
279
  */
279
- function filterGitIgnored(files: string[], cwd: string): string[] {
280
- if (files.length === 0) return files;
281
- try {
282
- const out = execFileSync("git", ["check-ignore", "--stdin"], {
283
- cwd,
284
- input: files.join("\n"),
285
- encoding: "utf-8",
286
- // stdin piped (input), stdout captured, stderr silenced so a non-git tree's
287
- // "fatal: not a git repository" never leaks to the lint output.
288
- stdio: ["pipe", "pipe", "ignore"],
289
- // Exit 1 means "nothing ignored" — execFileSync throws on non-zero, so the
290
- // catch handles it; exit 128 (not a repo) lands there too and filters none.
291
- });
292
- const ignored = new Set(out.split(/\r?\n/).filter(Boolean));
293
- return ignored.size === 0 ? files : files.filter((f) => !ignored.has(f));
294
- } catch {
295
- // No git, not a repo, or nothing ignored (exit 1) — keep every file.
296
- return files;
297
- }
298
- }
299
-
300
- /**
301
- * Get all TypeScript files recursively, skipping git-ignored paths.
302
- */
303
- function getTypeScriptFiles(dir: string): string[] {
304
- const files: string[] = [];
305
-
306
- function scan(currentDir: string): void {
307
- const entries = readdirSync(currentDir);
308
-
309
- for (const entry of entries) {
310
- const fullPath = join(currentDir, entry);
311
- const stat = statSync(fullPath);
312
-
313
- if (stat.isDirectory()) {
314
- if (entry !== "node_modules" && !entry.startsWith(".")) {
315
- scan(fullPath);
316
- }
317
- } else if (entry.endsWith(".ts") && !entry.endsWith(".test.ts") && !entry.endsWith(".spec.ts")) {
318
- // Skip chant-generated files (worker/workflow/activities bootstrap): they
319
- // hold no authored source and use runtime patterns the EVL* rules forbid.
320
- if (readFileSync(fullPath, "utf-8").slice(0, 256).includes(GENERATED_MARKER)) {
321
- continue;
322
- }
323
- files.push(fullPath);
324
- }
325
- }
326
- }
327
-
328
- scan(dir);
329
- return filterGitIgnored(files, dir);
280
+ async function getTypeScriptFiles(dir: string): Promise<string[]> {
281
+ // A missing or non-directory path fails here, as lint's own walk always did;
282
+ // the shared walk skips an unreadable directory in silence.
283
+ readdirSync(dir);
284
+ return walkDiscovery({
285
+ walker: "lint",
286
+ root: dir,
287
+ globs: await resolveDiscoveryGlobs(dir),
288
+ excludeDirs: await workspaceMemberDirs(dir),
289
+ followSymlinks: true,
290
+ accept: (name, full) => isSourceFileName(name) && !hasDiscoveryMarkerSync(full),
291
+ });
330
292
  }
331
293
 
332
294
  /**
@@ -522,8 +484,10 @@ async function runComponentCheckDiagnostics(
522
484
  *
523
485
  * The project's configured lexicon NAMES are read, though (chant #2101), to
524
486
  * resolve the activity contracts each one declares at
525
- * `@intentius/chant-lexicon-<name>/op/activity-contracts` — one dynamic
526
- * import per lexicon, no plugin load. Without them a step calling a
487
+ * `@intentius/chant-lexicon-<name>/op/activity-contracts`. The plugins
488
+ * `lintCommand` already loaded are passed in (chant #2520), so a plugin's
489
+ * `activityContracts()` member counts here as it does in `chant build`, and
490
+ * a lexicon declared by module path is covered. Without them a step calling a
527
491
  * lexicon's activity is flagged for the absence of a contract that lexicon
528
492
  * does declare, which is the whole point of OPS013 firing outside a build.
529
493
  *
@@ -538,6 +502,7 @@ async function runOpCheckDiagnostics(
538
502
  infraPath: string,
539
503
  files: string[],
540
504
  buildParams?: BuildParamProvenance[],
505
+ plugins: readonly LexiconPlugin[] = [],
541
506
  ): Promise<{ diagnostics: LintDiagnostic[]; suppressed: Array<LintDiagnostic & { reason?: string }> }> {
542
507
  const config = loadConfig(findProjectRoot(infraPath));
543
508
  const opFiles = files.filter((f) => f.endsWith(".op.ts"));
@@ -573,8 +538,13 @@ async function runOpCheckDiagnostics(
573
538
 
574
539
  if (entities.size === 0) return { diagnostics: [], suppressed: [] };
575
540
 
576
- const lexiconNames = await resolveProjectLexicons(infraPath).catch(() => [] as string[]);
577
- const activityContracts = await loadActivityContracts(lexiconNames);
541
+ // chant #2520 — hand over the loaded plugin objects when there are any, as
542
+ // `chant build` does, so a plugin's `activityContracts()` member is read
543
+ // here too. Names alone are the fallback when no plugin loaded.
544
+ const activityContracts =
545
+ plugins.length > 0
546
+ ? await loadActivityContracts(plugins)
547
+ : await loadActivityContracts(await resolveProjectLexicons(infraPath).catch(() => [] as string[]));
578
548
 
579
549
  const raw = runPostSynthChecks(
580
550
  coreOpChecks(),
@@ -698,8 +668,8 @@ export async function lintCommand(options: LintOptions): Promise<LintResult> {
698
668
  allRules = new Map([...allRules, ...pluginRules]);
699
669
  }
700
670
 
701
- // Get all TypeScript files (scan scoped to the lint arg, git-ignored trees dropped)
702
- const files = getTypeScriptFiles(infraPath);
671
+ // Get all TypeScript files, through the one discovery walk (#2527)
672
+ const files = await getTypeScriptFiles(infraPath);
703
673
 
704
674
  // Run lint — use per-file rules when overrides are present
705
675
  let diagnostics: LintDiagnostic[];
@@ -736,7 +706,7 @@ export async function lintCommand(options: LintOptions): Promise<LintResult> {
736
706
  // Run the OPS* Op-model post-synth checks (#2122) over every `*.op.ts`
737
707
  // file under the lint target — see runOpCheckDiagnostics's doc for why
738
708
  // this needs no lexicon or build to fire.
739
- const opResult = await runOpCheckDiagnostics(infraPath, files, buildParams);
709
+ const opResult = await runOpCheckDiagnostics(infraPath, files, buildParams, loaded.plugins);
740
710
  diagnostics.push(...opResult.diagnostics);
741
711
  suppressed.push(...opResult.suppressed);
742
712
 
@@ -790,7 +760,7 @@ export async function lintCommand(options: LintOptions): Promise<LintResult> {
790
760
 
791
761
  // OPS* checks have no `.fix` either; re-run for the same consistency
792
762
  // reason as the COMP* re-run just above.
793
- const postOpResult = await runOpCheckDiagnostics(infraPath, files, buildParams);
763
+ const postOpResult = await runOpCheckDiagnostics(infraPath, files, buildParams, loaded.plugins);
794
764
  diagnostics.push(...postOpResult.diagnostics);
795
765
  suppressed.push(...postOpResult.suppressed);
796
766
  }
@@ -2,6 +2,7 @@ import { readFileSync, writeFileSync, existsSync } from "fs";
2
2
  import { join, dirname } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { formatSuccess, formatError, formatWarning } from "../format";
5
+ import { lexiconModulePath, lexiconSourceLabel } from "../../lexicon-module";
5
6
 
6
7
  export interface OnboardOptions {
7
8
  name: string;
@@ -331,6 +332,20 @@ export function patchDockerfile(filePath: string, name: string): PatchResult {
331
332
  * Execute the onboard command — patches monorepo infrastructure for a new lexicon.
332
333
  */
333
334
  export function onboardCommand(options: OnboardOptions): OnboardResult {
335
+ // chant#2578 — onboard wires a lexicon package into the chant monorepo. A
336
+ // lexicon declared by path has no package, so there is nothing to wire and
337
+ // no install step to print; say where it loads from instead.
338
+ if (lexiconModulePath(options.name) !== undefined) {
339
+ return {
340
+ success: false,
341
+ patched: [],
342
+ skipped: [],
343
+ error:
344
+ `lexicon "${options.name}" is declared by path in chant.config.ts and loads from ${lexiconSourceLabel(options.name)}. ` +
345
+ "onboard adds a lexicon package to the chant monorepo, and a lexicon loaded by path has no package to add.",
346
+ };
347
+ }
348
+
334
349
  const root = options.root ?? findRepoRoot();
335
350
  const patched: string[] = [];
336
351
  const skipped: string[] = [];
@@ -1,5 +1,6 @@
1
+ import { lexiconModulePath, lexiconNames } from "../../lexicon-module";
1
2
  import { existsSync, mkdirSync, writeFileSync, cpSync, readdirSync, statSync, readFileSync } from "fs";
2
- import { join, resolve } from "path";
3
+ import { dirname, join, resolve } from "path";
3
4
  import { createRequire } from "module";
4
5
  import { formatSuccess, formatWarning, formatError } from "../format";
5
6
  import { loadChantConfig } from "../../config";
@@ -28,6 +29,15 @@ export interface UpdateResult {
28
29
  error?: string;
29
30
  }
30
31
 
32
+ /**
33
+ * Where `chant update` writes a lexicon skill, relative to the project. The
34
+ * workspace drift check (#2541) compares these files with what the skills
35
+ * render to, so both read the path from here.
36
+ */
37
+ export function skillFilePath(skillName: string): string {
38
+ return `skills/${skillName}/SKILL.md`;
39
+ }
40
+
31
41
  /**
32
42
  * Copy directory contents recursively, filtering to .ts and .d.ts files
33
43
  */
@@ -105,7 +115,7 @@ export async function updateCommand(options: UpdateOptions): Promise<UpdateResul
105
115
 
106
116
  // Load config to get lexicons
107
117
  const { config } = await loadChantConfig(projectDir);
108
- const lexicons = config.lexicons ?? [];
118
+ const lexicons = lexiconNames(config.lexicons ?? []);
109
119
 
110
120
  if (lexicons.length === 0) {
111
121
  return {
@@ -137,6 +147,9 @@ export async function updateCommand(options: UpdateOptions): Promise<UpdateResul
137
147
 
138
148
  // Sync each lexicon
139
149
  for (const lexicon of lexicons) {
150
+ // chant #2520 — a lexicon declared by module path is project source, with
151
+ // no package to copy types from.
152
+ if (lexiconModulePath(lexicon) !== undefined) continue;
140
153
  const pkgName = `@intentius/chant-lexicon-${lexicon}`;
141
154
  const pkgPath = resolvePackagePath(pkgName, projectDir);
142
155
 
@@ -166,9 +179,9 @@ export async function updateCommand(options: UpdateOptions): Promise<UpdateResul
166
179
  const skills = plugin.skills();
167
180
  if (skills.length > 0) {
168
181
  for (const skill of skills) {
169
- const skillDir = join(projectDir, "skills", skill.name);
170
- mkdirSync(skillDir, { recursive: true });
171
- writeFileSync(join(skillDir, "SKILL.md"), skill.content);
182
+ const file = join(projectDir, skillFilePath(skill.name));
183
+ mkdirSync(dirname(file), { recursive: true });
184
+ writeFileSync(file, skill.content);
172
185
  }
173
186
  synced.push(`${plugin.name} skills (${skills.length})`);
174
187
  }
@@ -76,7 +76,7 @@ export function contentHash(files: Map<string, Buffer>): string {
76
76
  // ── Source resolution → a { relpath → bytes } file set ──────────────────────
77
77
 
78
78
  /** Walk a local directory into a relpath→bytes map (or a single file). */
79
- function readLocal(absPath: string): Map<string, Buffer> {
79
+ export function readLocal(absPath: string): Map<string, Buffer> {
80
80
  const files = new Map<string, Buffer>();
81
81
  const stat = statSync(absPath);
82
82
  if (stat.isFile()) {
@@ -135,12 +135,17 @@ export function scopeArchiveFiles(
135
135
  }
136
136
 
137
137
  async function resolveSource(entry: VendorEntry, manifestDir: string): Promise<Map<string, Buffer>> {
138
- if (entry.source.type === "local") {
139
- const abs = resolve(manifestDir, entry.source.path);
140
- if (!existsSync(abs)) throw new Error(`local source not found: ${entry.source.path}`);
138
+ return resolveVendorSource(entry.source, manifestDir);
139
+ }
140
+
141
+ /** Resolve a vendor source (a local path, or an archive URL) to its file set. */
142
+ export async function resolveVendorSource(source: VendorEntry["source"], manifestDir: string): Promise<Map<string, Buffer>> {
143
+ if (source.type === "local") {
144
+ const abs = resolve(manifestDir, source.path);
145
+ if (!existsSync(abs)) throw new Error(`local source not found: ${source.path}`);
141
146
  return readLocal(abs);
142
147
  }
143
- return readArchive(entry.source.url, entry.source.subpath);
148
+ return readArchive(source.url, source.subpath);
144
149
  }
145
150
 
146
151
  // ── File I/O ────────────────────────────────────────────────────────────────
@@ -154,7 +159,7 @@ function writeFiles(targetDir: string, files: Map<string, Buffer>): void {
154
159
  }
155
160
  }
156
161
 
157
- function readTarget(targetDir: string): Map<string, Buffer> {
162
+ export function readTarget(targetDir: string): Map<string, Buffer> {
158
163
  if (!existsSync(targetDir)) return new Map();
159
164
  return readLocal(targetDir);
160
165
  }
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect } from "vitest";
2
- import { checkConflicts } from "./conflict-check";
2
+ import { checkConflicts, describeConflict } from "./conflict-check";
3
3
  import type { LexiconPlugin, CommandGroup } from "../lexicon";
4
4
 
5
5
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -228,6 +228,23 @@ describe("checkConflicts", () => {
228
228
  expect(report.conflicts).toEqual([{ type: "command-group-name", key: "build", plugins: ["rogue"] }]);
229
229
  });
230
230
 
231
+ // #2529 — `workspace` is reserved ahead of core's own command, and the
232
+ // error says it is reserved rather than naming a second lexicon.
233
+ test("a command group named workspace is a hard conflict with a clear message", () => {
234
+ const plugins = [makePlugin("rogue", { commandGroup: { name: "workspace", description: "d", commands: [] } })];
235
+ const report = checkConflicts(plugins);
236
+ expect(report.conflicts).toEqual([{ type: "command-group-name", key: "workspace", plugins: ["rogue"] }]);
237
+ expect(describeConflict(report.conflicts[0])).toMatch(
238
+ /"workspace" is a reserved chant command name, so no lexicon can register a command group with it/,
239
+ );
240
+ });
241
+
242
+ test("a conflict between two lexicons is described without the reserved-name note", () => {
243
+ expect(describeConflict({ type: "command-group-name", key: "kube", plugins: ["k8s", "fly"] })).toBe(
244
+ ' command-group-name "kube" from: k8s, fly',
245
+ );
246
+ });
247
+
231
248
  test("no conflict for a single lexicon's own, non-reserved command-group name", () => {
232
249
  const plugins = [
233
250
  makePlugin("k8s", { commandGroup: { name: "kube", description: "d", commands: [] } }),
@@ -108,3 +108,16 @@ export function checkConflicts(plugins: LexiconPlugin[]): ConflictReport {
108
108
 
109
109
  return { conflicts, warnings };
110
110
  }
111
+
112
+ /**
113
+ * One line of the load-time error for a conflict. A command-group name that
114
+ * core reserves says so and says what to do, since the lexicon author reading
115
+ * it may not know the word is core's (#2529).
116
+ */
117
+ export function describeConflict(c: ConflictEntry): string {
118
+ const line = ` ${c.type} "${c.key}" from: ${c.plugins.join(", ")}`;
119
+ if (c.type === "command-group-name" && RESERVED_COMMAND_NAMES.has(c.key)) {
120
+ return `${line} — "${c.key}" is a reserved chant command name, so no lexicon can register a command group with it. Rename the group.`;
121
+ }
122
+ return line;
123
+ }
@@ -0,0 +1,168 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from "vitest";
2
+ import { spawnSync } from "node:child_process";
3
+ import { mkdir, writeFile, rm, symlink, realpath } from "node:fs/promises";
4
+ import { existsSync } from "node:fs";
5
+ import { join, dirname, resolve } from "node:path";
6
+ import { tmpdir } from "node:os";
7
+ import { fileURLToPath } from "node:url";
8
+ import { SKIP_MARKER } from "../discovery/files";
9
+
10
+ /**
11
+ * chant #2519 — a project keeps a runnable `.ts` script with top-level side
12
+ * effects next to its declarations, and whole-project commands neither import
13
+ * nor lint it once the project says so, with an `exclude` glob or the skip
14
+ * marker.
15
+ *
16
+ * The script is the one from the issue: it reads `process.argv` as an Op name
17
+ * and exits the process with code 2. Imported by discovery, it reads chant's
18
+ * own argv and ends the command, which is what the first test shows. It also
19
+ * writes a file, so "imported" is visible apart from the exit code, and it
20
+ * indexes `process.env` dynamically, which EVL003 flags, so "linted" is
21
+ * visible in lint output.
22
+ *
23
+ * End-to-end through the real CLI (`main.ts`, spawned as `bin/chant` spawns
24
+ * it): an in-process `build()` would be killed by the script's `process.exit`,
25
+ * and the point is that every command's own path honours the setting.
26
+ */
27
+
28
+ const thisDir = dirname(fileURLToPath(import.meta.url));
29
+ const mainTsPath = resolve(thisDir, "main.ts");
30
+ const repoRoot = resolve(thisDir, "../../../..");
31
+ /** One CLI run can take a minute on a loaded CI runner, and a test makes up to three. */
32
+ const SPAWN_TIMEOUT = 150_000;
33
+ const TIMEOUT = 3 * SPAWN_TIMEOUT + 30_000;
34
+
35
+ describe("CLI end-to-end — discovery skips a side-effecting script (#2519)", () => {
36
+ let testDir: string;
37
+ let ranPath: string;
38
+
39
+ /** Write the project config; `extra` is spliced into the default export. */
40
+ async function writeConfig(extra: string): Promise<void> {
41
+ await writeFile(join(testDir, "chant.config.ts"), `export default { lexicons: ["k8s"]${extra} };\n`);
42
+ }
43
+
44
+ /** The runner from the issue, optionally headed by `header`. */
45
+ async function writeRunner(header = ""): Promise<void> {
46
+ await writeFile(
47
+ join(testDir, "ops", "run.ts"),
48
+ [
49
+ header,
50
+ 'import { writeFileSync } from "node:fs";',
51
+ "const op = process.argv[2];",
52
+ "const target = process.env[`OP_${op}`];",
53
+ `writeFileSync(${JSON.stringify(ranPath)}, String(op));`,
54
+ 'console.error(`no op named "${op}"`, target ?? "");',
55
+ "process.exit(2);",
56
+ "",
57
+ ].join("\n"),
58
+ );
59
+ }
60
+
61
+ beforeEach(async () => {
62
+ testDir = await realpath(
63
+ await (async () => {
64
+ const dir = join(tmpdir(), `chant-discovery-skip-test-${Date.now()}-${Math.random()}`);
65
+ await mkdir(join(dir, "src"), { recursive: true });
66
+ await mkdir(join(dir, "ops"), { recursive: true });
67
+ return dir;
68
+ })(),
69
+ );
70
+ ranPath = join(testDir, "runner-ran.txt");
71
+
72
+ await mkdir(join(testDir, "node_modules", "@intentius"), { recursive: true });
73
+ await symlink(
74
+ join(repoRoot, "lexicons", "k8s"),
75
+ join(testDir, "node_modules", "@intentius", "chant-lexicon-k8s"),
76
+ "dir",
77
+ );
78
+ await writeFile(
79
+ join(testDir, "src", "infra.ts"),
80
+ [
81
+ 'import { Namespace } from "@intentius/chant-lexicon-k8s";',
82
+ 'export const ns = new Namespace({ metadata: { name: "discovery-skip" } });',
83
+ "",
84
+ ].join("\n"),
85
+ );
86
+ });
87
+
88
+ afterEach(async () => {
89
+ await rm(testDir, { recursive: true, force: true });
90
+ });
91
+
92
+ function runCli(args: string[]): { status: number | null; stdout: string; stderr: string } {
93
+ const result = spawnSync("npx", ["tsx", mainTsPath, ...args], {
94
+ cwd: repoRoot,
95
+ encoding: "utf-8",
96
+ timeout: SPAWN_TIMEOUT,
97
+ });
98
+ return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
99
+ }
100
+
101
+ test("without exclude or the marker, the script runs under `chant build` and ends it", async () => {
102
+ await writeConfig("");
103
+ await writeRunner();
104
+
105
+ const { status, stderr } = runCli(["build", testDir]);
106
+
107
+ expect(status).toBe(2);
108
+ expect(stderr).toContain('no op named "build"');
109
+ expect(existsSync(ranPath)).toBe(true);
110
+ }, TIMEOUT);
111
+
112
+ test("an `exclude` glob keeps build and lint out of the script", async () => {
113
+ await writeConfig(', exclude: ["ops/**"]');
114
+ await writeRunner();
115
+
116
+ const build = runCli(["build", testDir]);
117
+ expect(build.stderr).not.toContain("no op named");
118
+ expect(build.status).toBe(0);
119
+ expect(build.stdout).toContain("name: discovery-skip");
120
+
121
+ const lint = runCli(["lint", testDir, "--format", "json"]);
122
+ expect(lint.status).toBe(0);
123
+ const diagnostics = JSON.parse(lint.stdout) as Array<{ file: string }>;
124
+ expect(diagnostics.some((d) => d.file.endsWith("infra.ts"))).toBe(true);
125
+ expect(diagnostics.filter((d) => d.file.includes("ops"))).toEqual([]);
126
+
127
+ expect(existsSync(ranPath), "the script was imported").toBe(false);
128
+ }, TIMEOUT);
129
+
130
+ test("an `exclude` glob keeps list and explain out of the script", async () => {
131
+ await writeConfig(', exclude: ["ops/**"]');
132
+ await writeRunner();
133
+
134
+ for (const command of ["list", "explain"]) {
135
+ const { status, stdout, stderr } = runCli([command, testDir]);
136
+ expect(stderr, command).not.toContain("no op named");
137
+ expect(status, command).toBe(0);
138
+ expect(stdout, command).toContain("ns");
139
+ }
140
+
141
+ expect(existsSync(ranPath), "the script was imported").toBe(false);
142
+ }, TIMEOUT);
143
+
144
+ test("the skip marker keeps build and lint out of the script with no config change", async () => {
145
+ await writeConfig("");
146
+ await writeRunner(`// ${SKIP_MARKER}: a runner, not a declaration`);
147
+
148
+ const build = runCli(["build", testDir]);
149
+ expect(build.stderr).not.toContain("no op named");
150
+ expect(build.status).toBe(0);
151
+
152
+ const lint = runCli(["lint", testDir, "--format", "json"]);
153
+ expect(lint.status).toBe(0);
154
+ const diagnostics = JSON.parse(lint.stdout) as Array<{ file: string }>;
155
+ expect(diagnostics.filter((d) => d.file.includes("ops"))).toEqual([]);
156
+
157
+ expect(existsSync(ranPath), "the script was imported").toBe(false);
158
+ }, TIMEOUT);
159
+
160
+ test("the control: without exclude, `chant lint` lints the script", async () => {
161
+ await writeConfig("");
162
+ await writeRunner();
163
+
164
+ const lint = runCli(["lint", testDir, "--format", "json"]);
165
+ const diagnostics = JSON.parse(lint.stdout) as Array<{ file: string; ruleId: string }>;
166
+ expect(diagnostics.some((d) => d.file.endsWith(join("ops", "run.ts")) && d.ruleId === "EVL003")).toBe(true);
167
+ }, TIMEOUT);
168
+ });
@@ -0,0 +1,38 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`a project outside any workspace (level 0) > the pipeline is the generator's output, unchanged 1`] = `
4
+ "name: chant-components-staging
5
+
6
+ on:
7
+ workflow_dispatch: {}
8
+
9
+ env:
10
+ CHANT_ENV: staging
11
+
12
+ jobs:
13
+ shared-alb:
14
+ runs-on: ubuntu-latest
15
+ container: node:22-slim
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - run: chant run --components shared-alb --env staging --dump-outputs shared-alb.outputs.json
19
+ - name: Upload shared-alb outputs
20
+ uses: actions/upload-artifact@v4
21
+ with:
22
+ name: shared-alb-outputs
23
+ path: shared-alb.outputs.json
24
+ api:
25
+ runs-on: ubuntu-latest
26
+ needs:
27
+ - shared-alb
28
+ container: node:22-slim
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+ - name: Download shared-alb outputs
32
+ uses: actions/download-artifact@v4
33
+ with:
34
+ name: shared-alb-outputs
35
+ path: .
36
+ - run: chant run --components api --env staging --seed-outputs shared-alb.outputs.json
37
+ "
38
+ `;