@intentius/chant 0.124.31 → 0.124.33

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 (509) hide show
  1. package/bin/chant +7 -2
  2. package/dist/ci-jobs.d.ts +4 -0
  3. package/dist/ci-jobs.d.ts.map +1 -1
  4. package/dist/ci-jobs.js +9 -2
  5. package/dist/cli/commands/check-lexicon-plugin.d.ts +7 -4
  6. package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
  7. package/dist/cli/commands/check-lexicon-plugin.js +10 -7
  8. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  9. package/dist/cli/commands/check-lexicon.js +18 -12
  10. package/dist/cli/commands/import.d.ts +10 -0
  11. package/dist/cli/commands/import.d.ts.map +1 -1
  12. package/dist/cli/commands/import.js +37 -0
  13. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
  14. package/dist/cli/commands/init-lexicon/templates/project.js +4 -3
  15. package/dist/cli/commands/onboard.d.ts.map +1 -1
  16. package/dist/cli/commands/onboard.js +8 -4
  17. package/dist/cli/commands/update.d.ts.map +1 -1
  18. package/dist/cli/commands/update.js +2 -1
  19. package/dist/cli/dependencies.d.ts +18 -0
  20. package/dist/cli/dependencies.d.ts.map +1 -0
  21. package/dist/cli/dependencies.js +120 -0
  22. package/dist/cli/handlers/build.d.ts.map +1 -1
  23. package/dist/cli/handlers/build.js +38 -4
  24. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  25. package/dist/cli/handlers/fan-out.js +12 -4
  26. package/dist/cli/handlers/graph.d.ts.map +1 -1
  27. package/dist/cli/handlers/graph.js +16 -6
  28. package/dist/cli/handlers/lifecycle-health.d.ts +16 -0
  29. package/dist/cli/handlers/lifecycle-health.d.ts.map +1 -0
  30. package/dist/cli/handlers/lifecycle-health.js +150 -0
  31. package/dist/cli/handlers/lifecycle.d.ts +26 -0
  32. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  33. package/dist/cli/handlers/lifecycle.js +1 -1
  34. package/dist/cli/handlers/lint.d.ts.map +1 -1
  35. package/dist/cli/handlers/lint.js +6 -0
  36. package/dist/cli/handlers/operator.d.ts +2 -2
  37. package/dist/cli/handlers/operator.js +3 -3
  38. package/dist/cli/handlers/pr.d.ts +16 -0
  39. package/dist/cli/handlers/pr.d.ts.map +1 -1
  40. package/dist/cli/handlers/pr.js +610 -32
  41. package/dist/cli/handlers/promote.d.ts.map +1 -1
  42. package/dist/cli/handlers/promote.js +19 -2
  43. package/dist/cli/handlers/release.d.ts +16 -1
  44. package/dist/cli/handlers/release.d.ts.map +1 -1
  45. package/dist/cli/handlers/release.js +83 -11
  46. package/dist/cli/handlers/run.d.ts +13 -1
  47. package/dist/cli/handlers/run.d.ts.map +1 -1
  48. package/dist/cli/handlers/run.js +38 -15
  49. package/dist/cli/main.d.ts.map +1 -1
  50. package/dist/cli/main.js +121 -33
  51. package/dist/cli/parse-args.d.ts.map +1 -1
  52. package/dist/cli/parse-args.js +37 -0
  53. package/dist/cli/registry.d.ts +29 -6
  54. package/dist/cli/registry.d.ts.map +1 -1
  55. package/dist/cli/registry.js +6 -2
  56. package/dist/codegen/generate-post-synth-barrel.js +2 -2
  57. package/dist/codegen/lexicon-regen.d.ts +4 -0
  58. package/dist/codegen/lexicon-regen.d.ts.map +1 -1
  59. package/dist/codegen/lexicon-regen.js +1 -1
  60. package/dist/codegen/pinned-upgrade.js +2 -2
  61. package/dist/components/capability.d.ts +44 -3
  62. package/dist/components/capability.d.ts.map +1 -1
  63. package/dist/components/capability.js +21 -0
  64. package/dist/components/changed-files.d.ts +7 -1
  65. package/dist/components/changed-files.d.ts.map +1 -1
  66. package/dist/components/changed-files.js +9 -1
  67. package/dist/components/deploy-inputs.d.ts +91 -0
  68. package/dist/components/deploy-inputs.d.ts.map +1 -0
  69. package/dist/components/deploy-inputs.js +184 -0
  70. package/dist/components/driver.d.ts +38 -0
  71. package/dist/components/driver.d.ts.map +1 -1
  72. package/dist/components/driver.js +56 -6
  73. package/dist/components/fan-out-record.d.ts +6 -1
  74. package/dist/components/fan-out-record.d.ts.map +1 -1
  75. package/dist/components/fan-out-record.js +24 -0
  76. package/dist/components/fan-out-run.d.ts +20 -0
  77. package/dist/components/fan-out-run.d.ts.map +1 -1
  78. package/dist/components/fan-out-run.js +21 -1
  79. package/dist/components/gate-plan.d.ts +8 -0
  80. package/dist/components/gate-plan.d.ts.map +1 -1
  81. package/dist/components/gate-plan.js +6 -0
  82. package/dist/components/pr-pipeline.d.ts +98 -12
  83. package/dist/components/pr-pipeline.d.ts.map +1 -1
  84. package/dist/components/pr-pipeline.js +146 -23
  85. package/dist/components/pr-run.d.ts +128 -30
  86. package/dist/components/pr-run.d.ts.map +1 -1
  87. package/dist/components/pr-run.js +290 -70
  88. package/dist/components/release-compile.d.ts +37 -2
  89. package/dist/components/release-compile.d.ts.map +1 -1
  90. package/dist/components/release-compile.js +64 -8
  91. package/dist/components/release-promote.d.ts +17 -3
  92. package/dist/components/release-promote.d.ts.map +1 -1
  93. package/dist/components/release-promote.js +79 -18
  94. package/dist/components/verbs/bom-writer.d.ts +1 -1
  95. package/dist/components/verbs/bom-writer.d.ts.map +1 -1
  96. package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
  97. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
  98. package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
  99. package/dist/components/wave-plan.d.ts +15 -2
  100. package/dist/components/wave-plan.d.ts.map +1 -1
  101. package/dist/components/wave-plan.js +44 -5
  102. package/dist/discovery/fold-import.d.ts.map +1 -1
  103. package/dist/discovery/fold-import.js +16 -6
  104. package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
  105. package/dist/discovery/sandbox/bundle.js +6 -0
  106. package/dist/discovery/specifier-resolve.d.ts.map +1 -1
  107. package/dist/discovery/specifier-resolve.js +8 -2
  108. package/dist/fold/fold.d.ts.map +1 -1
  109. package/dist/fold/fold.js +68 -1
  110. package/dist/fold/subset.d.ts +37 -0
  111. package/dist/fold/subset.d.ts.map +1 -1
  112. package/dist/fold/subset.js +88 -0
  113. package/dist/graph-detail.d.ts.map +1 -1
  114. package/dist/graph-detail.js +32 -7
  115. package/dist/health.d.ts +110 -0
  116. package/dist/health.d.ts.map +1 -0
  117. package/dist/health.js +105 -0
  118. package/dist/health.schema.json +191 -0
  119. package/dist/lexicon-module.d.ts +10 -0
  120. package/dist/lexicon-module.d.ts.map +1 -1
  121. package/dist/lexicon-module.js +53 -8
  122. package/dist/lexicon-plugin-helpers.d.ts.map +1 -1
  123. package/dist/lexicon-plugin-helpers.js +6 -4
  124. package/dist/lexicon.d.ts +29 -0
  125. package/dist/lexicon.d.ts.map +1 -1
  126. package/dist/lifecycle/affected.d.ts.map +1 -1
  127. package/dist/lifecycle/affected.js +7 -1
  128. package/dist/lifecycle/merge-record.d.ts +104 -0
  129. package/dist/lifecycle/merge-record.d.ts.map +1 -0
  130. package/dist/lifecycle/merge-record.js +102 -0
  131. package/dist/lifecycle/publish-attachments.d.ts +8 -5
  132. package/dist/lifecycle/publish-attachments.d.ts.map +1 -1
  133. package/dist/lifecycle/publish-attachments.js +27 -13
  134. package/dist/lifecycle/publish-npm.d.ts +3 -2
  135. package/dist/lifecycle/publish-npm.d.ts.map +1 -1
  136. package/dist/lifecycle/publish-npm.js +4 -3
  137. package/dist/lifecycle/release-at.d.ts +40 -0
  138. package/dist/lifecycle/release-at.d.ts.map +1 -0
  139. package/dist/lifecycle/release-at.js +83 -0
  140. package/dist/lifecycle/release-cut.d.ts.map +1 -1
  141. package/dist/lifecycle/release-cut.js +23 -12
  142. package/dist/lifecycle/release-ledger.d.ts +21 -0
  143. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  144. package/dist/lifecycle/release-ledger.js +24 -0
  145. package/dist/lifecycle/release-publish.d.ts +28 -12
  146. package/dist/lifecycle/release-publish.d.ts.map +1 -1
  147. package/dist/lifecycle/release-publish.js +29 -17
  148. package/dist/lifecycle/release-store.d.ts +25 -2
  149. package/dist/lifecycle/release-store.d.ts.map +1 -1
  150. package/dist/lifecycle/release-store.js +40 -13
  151. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  152. package/dist/lifecycle/run-ledger.js +1 -0
  153. package/dist/lifecycle/work-dir.d.ts +45 -0
  154. package/dist/lifecycle/work-dir.d.ts.map +1 -0
  155. package/dist/lifecycle/work-dir.js +78 -0
  156. package/dist/lint/discover.d.ts +17 -9
  157. package/dist/lint/discover.d.ts.map +1 -1
  158. package/dist/lint/discover.js +62 -54
  159. package/dist/lint/rules/op/index.d.ts +2 -1
  160. package/dist/lint/rules/op/index.d.ts.map +1 -1
  161. package/dist/lint/rules/op/index.js +4 -2
  162. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts +10 -0
  163. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts.map +1 -0
  164. package/dist/lint/rules/op/ops018-shell-image-digest.js +38 -0
  165. package/dist/module-source.d.ts +57 -0
  166. package/dist/module-source.d.ts.map +1 -0
  167. package/dist/module-source.js +104 -0
  168. package/dist/op/activities/activity-contracts.d.ts +5 -0
  169. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  170. package/dist/op/activities/activity-contracts.js +3 -0
  171. package/dist/op/activities/factory.js +1 -1
  172. package/dist/op/activities/shell.d.ts +38 -0
  173. package/dist/op/activities/shell.d.ts.map +1 -1
  174. package/dist/op/activities/shell.js +89 -3
  175. package/dist/op/builders.d.ts +7 -0
  176. package/dist/op/builders.d.ts.map +1 -1
  177. package/dist/op/builders.js +7 -0
  178. package/dist/op/discover.d.ts.map +1 -1
  179. package/dist/op/discover.js +13 -3
  180. package/dist/op/gate.d.ts.map +1 -1
  181. package/dist/op/gate.js +3 -0
  182. package/dist/op/op-waves-run.d.ts.map +1 -1
  183. package/dist/op/op-waves-run.js +20 -5
  184. package/dist/op/operator.d.ts +2 -2
  185. package/dist/op/operator.js +2 -2
  186. package/dist/op/runtime.d.ts +2 -0
  187. package/dist/op/runtime.d.ts.map +1 -1
  188. package/dist/op/steward.d.ts +6 -4
  189. package/dist/op/steward.d.ts.map +1 -1
  190. package/dist/op/steward.js +5 -3
  191. package/dist/op/work-lease-run.d.ts +20 -7
  192. package/dist/op/work-lease-run.d.ts.map +1 -1
  193. package/dist/op/work-lease-run.js +56 -12
  194. package/dist/pr-forge.d.ts +10 -0
  195. package/dist/pr-forge.d.ts.map +1 -1
  196. package/dist/pr-forge.js +39 -0
  197. package/dist/pr-loop.d.ts +155 -7
  198. package/dist/pr-loop.d.ts.map +1 -1
  199. package/dist/pr-loop.js +137 -11
  200. package/dist/pr-report-url.d.ts +4 -4
  201. package/dist/pr-report-url.d.ts.map +1 -1
  202. package/dist/pr-report-url.js +5 -5
  203. package/dist/runner-start.d.ts +179 -0
  204. package/dist/runner-start.d.ts.map +1 -0
  205. package/dist/runner-start.js +277 -0
  206. package/dist/ts-loader.d.ts +38 -4
  207. package/dist/ts-loader.d.ts.map +1 -1
  208. package/dist/ts-loader.js +190 -16
  209. package/dist/wave-runtime.mjs +238 -231
  210. package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts +34 -0
  211. package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts.map +1 -0
  212. package/dist/workspace/chant-migrations/fountain-retired-kinds.js +176 -0
  213. package/dist/workspace/chant-migrations.d.ts.map +1 -1
  214. package/dist/workspace/chant-migrations.js +2 -0
  215. package/dist/workspace/check.schema.json +70 -2
  216. package/dist/workspace/checks/boxes.d.ts +6 -6
  217. package/dist/workspace/checks/boxes.js +9 -9
  218. package/dist/workspace/checks/security.d.ts +35 -0
  219. package/dist/workspace/checks/security.d.ts.map +1 -0
  220. package/dist/workspace/checks/security.js +305 -0
  221. package/dist/workspace/checks.d.ts +2 -0
  222. package/dist/workspace/checks.d.ts.map +1 -1
  223. package/dist/workspace/checks.js +5 -0
  224. package/dist/workspace/ci-builds.d.ts +163 -0
  225. package/dist/workspace/ci-builds.d.ts.map +1 -0
  226. package/dist/workspace/ci-builds.js +315 -0
  227. package/dist/workspace/ci-green-cli.d.ts +2 -1
  228. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  229. package/dist/workspace/ci-green-cli.js +25 -5
  230. package/dist/workspace/ci-green-forge.d.ts +18 -4
  231. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  232. package/dist/workspace/ci-green-forge.js +12 -0
  233. package/dist/workspace/ci-green-workflow.d.ts +55 -1
  234. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  235. package/dist/workspace/ci-green-workflow.js +128 -1
  236. package/dist/workspace/ci-green.d.ts +117 -16
  237. package/dist/workspace/ci-green.d.ts.map +1 -1
  238. package/dist/workspace/ci-green.js +358 -43
  239. package/dist/workspace/ci-last-green.schema.json +49 -0
  240. package/dist/workspace/ci-list.schema.json +47 -7
  241. package/dist/workspace/ci-report-url.d.ts +18 -0
  242. package/dist/workspace/ci-report-url.d.ts.map +1 -0
  243. package/dist/workspace/ci-report-url.js +33 -0
  244. package/dist/workspace/ci-run-index.d.ts +13 -1
  245. package/dist/workspace/ci-run-index.d.ts.map +1 -1
  246. package/dist/workspace/ci-run-index.js +6 -4
  247. package/dist/workspace/ci-run-index.schema.json +6 -4
  248. package/dist/workspace/ci-run.d.ts +85 -6
  249. package/dist/workspace/ci-run.d.ts.map +1 -1
  250. package/dist/workspace/ci-run.js +262 -64
  251. package/dist/workspace/ci-runner-key.d.ts +16 -3
  252. package/dist/workspace/ci-runner-key.d.ts.map +1 -1
  253. package/dist/workspace/ci-runner-key.js +30 -10
  254. package/dist/workspace/ci-runner-start.d.ts +77 -0
  255. package/dist/workspace/ci-runner-start.d.ts.map +1 -0
  256. package/dist/workspace/ci-runner-start.js +185 -0
  257. package/dist/workspace/ci-serve.d.ts +130 -11
  258. package/dist/workspace/ci-serve.d.ts.map +1 -1
  259. package/dist/workspace/ci-serve.js +323 -40
  260. package/dist/workspace/declaration.d.ts +25 -0
  261. package/dist/workspace/declaration.d.ts.map +1 -1
  262. package/dist/workspace/declaration.js +14 -2
  263. package/dist/workspace/declaration.schema.json +30 -2
  264. package/dist/workspace/export.d.ts +1 -1
  265. package/dist/workspace/export.d.ts.map +1 -1
  266. package/dist/workspace/export.js +7 -3
  267. package/dist/workspace/ls.d.ts +8 -1
  268. package/dist/workspace/ls.d.ts.map +1 -1
  269. package/dist/workspace/ls.js +2 -1
  270. package/dist/workspace/ls.schema.json +9 -2
  271. package/dist/workspace/member-pipeline.d.ts +1 -0
  272. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  273. package/dist/workspace/member-pipeline.js +2 -0
  274. package/dist/workspace/merge-record.schema.json +49 -0
  275. package/dist/workspace/pin-integrity.d.ts.map +1 -1
  276. package/dist/workspace/pin-integrity.js +3 -2
  277. package/dist/workspace/pr-report.schema.json +75 -8
  278. package/dist/workspace/reason-codes.d.ts +18 -1
  279. package/dist/workspace/reason-codes.d.ts.map +1 -1
  280. package/dist/workspace/reason-codes.js +20 -2
  281. package/dist/workspace/release-candidates.d.ts +129 -0
  282. package/dist/workspace/release-candidates.d.ts.map +1 -0
  283. package/dist/workspace/release-candidates.js +233 -0
  284. package/dist/workspace/release-candidates.schema.json +353 -0
  285. package/dist/workspace/run-event.schema.json +3 -1
  286. package/dist/workspace/security.d.ts +40 -0
  287. package/dist/workspace/security.d.ts.map +1 -0
  288. package/dist/workspace/security.js +99 -0
  289. package/dist/workspace/status.schema.json +3 -1
  290. package/dist/workspace/write-scope.d.ts +9 -0
  291. package/dist/workspace/write-scope.d.ts.map +1 -1
  292. package/dist/workspace/write-scope.js +11 -1
  293. package/package.json +8 -1
  294. package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
  295. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +1 -1
  296. package/src/ci-jobs.ts +11 -2
  297. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +3 -3
  298. package/src/cli/commands/audit.test.ts +2 -2
  299. package/src/cli/commands/check-lexicon-plugin.test.ts +9 -0
  300. package/src/cli/commands/check-lexicon-plugin.ts +11 -8
  301. package/src/cli/commands/check-lexicon.test.ts +1 -1
  302. package/src/cli/commands/check-lexicon.ts +16 -11
  303. package/src/cli/commands/import.ts +40 -0
  304. package/src/cli/commands/init-lexicon/templates/project.ts +4 -3
  305. package/src/cli/commands/init-lexicon.test.ts +8 -5
  306. package/src/cli/commands/onboard.test.ts +45 -0
  307. package/src/cli/commands/onboard.ts +8 -4
  308. package/src/cli/commands/update.test.ts +6 -0
  309. package/src/cli/commands/update.ts +4 -1
  310. package/src/cli/compiled-cli.e2e.test.ts +76 -11
  311. package/src/cli/dependencies.e2e.test.ts +101 -0
  312. package/src/cli/dependencies.test.ts +93 -0
  313. package/src/cli/dependencies.ts +122 -0
  314. package/src/cli/handlers/build.test.ts +105 -0
  315. package/src/cli/handlers/build.ts +35 -4
  316. package/src/cli/handlers/fan-out.test.ts +73 -0
  317. package/src/cli/handlers/fan-out.ts +11 -4
  318. package/src/cli/handlers/graph.test.ts +76 -0
  319. package/src/cli/handlers/graph.ts +19 -12
  320. package/src/cli/handlers/lifecycle-health.test.ts +123 -0
  321. package/src/cli/handlers/lifecycle-health.ts +157 -0
  322. package/src/cli/handlers/lifecycle.ts +1 -1
  323. package/src/cli/handlers/lint.test.ts +42 -0
  324. package/src/cli/handlers/lint.ts +6 -0
  325. package/src/cli/handlers/operator.ts +3 -3
  326. package/src/cli/handlers/pr-deploy-inputs.test.ts +209 -0
  327. package/src/cli/handlers/pr-record.test.ts +201 -0
  328. package/src/cli/handlers/pr-waves.test.ts +449 -0
  329. package/src/cli/handlers/pr.test.ts +156 -0
  330. package/src/cli/handlers/pr.ts +648 -33
  331. package/src/cli/handlers/promote.test.ts +43 -1
  332. package/src/cli/handlers/promote.ts +19 -3
  333. package/src/cli/handlers/release-cut-at.e2e.test.ts +115 -0
  334. package/src/cli/handlers/release-publish-targets.e2e.test.ts +2 -2
  335. package/src/cli/handlers/release-verify.e2e.test.ts +12 -10
  336. package/src/cli/handlers/release.e2e.test.ts +19 -28
  337. package/src/cli/handlers/release.ts +81 -11
  338. package/src/cli/handlers/run-components-exit.test.ts +99 -0
  339. package/src/cli/handlers/run-signer.test.ts +79 -0
  340. package/src/cli/handlers/run.ts +34 -16
  341. package/src/cli/main.test.ts +18 -0
  342. package/src/cli/main.ts +120 -33
  343. package/src/cli/parse-args.ts +28 -0
  344. package/src/cli/registry.ts +35 -8
  345. package/src/codegen/generate-post-synth-barrel.ts +2 -2
  346. package/src/codegen/lexicon-regen.ts +1 -1
  347. package/src/codegen/pinned-upgrade.ts +2 -2
  348. package/src/codegen/post-synth-barrel-guards.test.ts +6 -11
  349. package/src/components/capability.ts +51 -3
  350. package/src/components/changed-files.test.ts +16 -1
  351. package/src/components/changed-files.ts +9 -1
  352. package/src/components/deploy-inputs.test.ts +108 -0
  353. package/src/components/deploy-inputs.ts +225 -0
  354. package/src/components/driver.test.ts +36 -1
  355. package/src/components/driver.ts +87 -4
  356. package/src/components/fan-out-record.ts +29 -1
  357. package/src/components/fan-out-run.test.ts +35 -0
  358. package/src/components/fan-out-run.ts +40 -1
  359. package/src/components/gate-plan.ts +9 -0
  360. package/src/components/pr-pipeline.test.ts +100 -0
  361. package/src/components/pr-pipeline.ts +184 -28
  362. package/src/components/pr-run.test.ts +323 -18
  363. package/src/components/pr-run.ts +355 -76
  364. package/src/components/release-compile.test.ts +34 -0
  365. package/src/components/release-compile.ts +89 -8
  366. package/src/components/release-promote.test.ts +55 -2
  367. package/src/components/release-promote.ts +93 -23
  368. package/src/components/verbs/bom-writer.ts +1 -1
  369. package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
  370. package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
  371. package/src/components/wave-plan.ts +48 -8
  372. package/src/discovery/fold-host-method.test.ts +87 -0
  373. package/src/discovery/fold-import.ts +15 -5
  374. package/src/discovery/fold-package-composite.test.ts +27 -0
  375. package/src/discovery/fold-specifiers.test.ts +8 -0
  376. package/src/discovery/sandbox/bundle.ts +6 -0
  377. package/src/discovery/specifier-resolve.ts +8 -2
  378. package/src/fold/fold.test.ts +72 -0
  379. package/src/fold/fold.ts +71 -0
  380. package/src/fold/subset.test.ts +31 -0
  381. package/src/fold/subset.ts +99 -0
  382. package/src/graph-detail.test.ts +57 -0
  383. package/src/graph-detail.ts +33 -7
  384. package/src/health.schema.json +191 -0
  385. package/src/health.test.ts +62 -0
  386. package/src/health.ts +178 -0
  387. package/src/lexicon-dist.e2e.test.ts +254 -0
  388. package/src/lexicon-module.test.ts +47 -0
  389. package/src/lexicon-module.ts +49 -7
  390. package/src/lexicon-plugin-helpers.ts +6 -4
  391. package/src/lexicon.ts +43 -1
  392. package/src/lifecycle/affected.test.ts +7 -0
  393. package/src/lifecycle/affected.ts +6 -1
  394. package/src/lifecycle/merge-record.test.ts +111 -0
  395. package/src/lifecycle/merge-record.ts +154 -0
  396. package/src/lifecycle/publish-attachments.ts +25 -13
  397. package/src/lifecycle/publish-npm.ts +4 -3
  398. package/src/lifecycle/publish-targets.test.ts +34 -4
  399. package/src/lifecycle/release-at.test.ts +48 -0
  400. package/src/lifecycle/release-at.ts +100 -0
  401. package/src/lifecycle/release-cut.test.ts +41 -0
  402. package/src/lifecycle/release-cut.ts +22 -11
  403. package/src/lifecycle/release-ledger.ts +34 -0
  404. package/src/lifecycle/release-publish.test.ts +43 -13
  405. package/src/lifecycle/release-publish.ts +38 -18
  406. package/src/lifecycle/release-store.ts +57 -13
  407. package/src/lifecycle/run-ledger.test.ts +13 -0
  408. package/src/lifecycle/run-ledger.ts +1 -0
  409. package/src/lifecycle/work-dir.test.ts +77 -0
  410. package/src/lifecycle/work-dir.ts +82 -0
  411. package/src/lint/discover.test.ts +46 -2
  412. package/src/lint/discover.ts +55 -49
  413. package/src/lint/rules/op/index.ts +4 -2
  414. package/src/lint/rules/op/ops018-shell-image-digest.test.ts +60 -0
  415. package/src/lint/rules/op/ops018-shell-image-digest.ts +48 -0
  416. package/src/module-source.test.ts +85 -0
  417. package/src/module-source.ts +112 -0
  418. package/src/op/activities/activity-contracts.ts +3 -0
  419. package/src/op/activities/factory.ts +1 -1
  420. package/src/op/activities/shell-container.e2e.test.ts +67 -0
  421. package/src/op/activities/shell.test.ts +44 -1
  422. package/src/op/activities/shell.ts +110 -3
  423. package/src/op/builders.ts +7 -0
  424. package/src/op/discover-root.test.ts +31 -2
  425. package/src/op/discover.ts +14 -4
  426. package/src/op/gate.test.ts +30 -0
  427. package/src/op/gate.ts +3 -0
  428. package/src/op/op-waves-run.ts +20 -5
  429. package/src/op/op-waves.test.ts +49 -0
  430. package/src/op/operator.ts +2 -2
  431. package/src/op/runtime.ts +2 -0
  432. package/src/op/step-console.test.ts +4 -2
  433. package/src/op/steward.ts +6 -4
  434. package/src/op/work-lease-run.test.ts +88 -2
  435. package/src/op/work-lease-run.ts +52 -12
  436. package/src/pr-forge-bundle.test.ts +2 -2
  437. package/src/pr-forge.test.ts +42 -0
  438. package/src/pr-forge.ts +40 -0
  439. package/src/pr-loop-bundle.test.ts +2 -1
  440. package/src/pr-loop.test.ts +146 -1
  441. package/src/pr-loop.ts +278 -17
  442. package/src/pr-report-url.ts +7 -7
  443. package/src/runner-start-bundle.test.ts +29 -0
  444. package/src/runner-start.test.ts +179 -0
  445. package/src/runner-start.ts +329 -0
  446. package/src/terraform/__fixtures__/advise.test.ts +21 -2
  447. package/src/ts-loader.test.ts +89 -1
  448. package/src/ts-loader.ts +180 -13
  449. package/src/workspace/changes.test.ts +1 -1
  450. package/src/workspace/chant-migrations/fountain-retired-kinds.test.ts +33 -0
  451. package/src/workspace/chant-migrations/fountain-retired-kinds.ts +190 -0
  452. package/src/workspace/chant-migrations.e2e.test.ts +5 -0
  453. package/src/workspace/chant-migrations.ts +2 -0
  454. package/src/workspace/check-contract.test.ts +3 -2
  455. package/src/workspace/check.schema.json +70 -2
  456. package/src/workspace/checks/boxes.ts +9 -9
  457. package/src/workspace/checks/security.test.ts +138 -0
  458. package/src/workspace/checks/security.ts +305 -0
  459. package/src/workspace/checks.ts +5 -0
  460. package/src/workspace/ci-builds.e2e.test.ts +208 -0
  461. package/src/workspace/ci-builds.test.ts +174 -0
  462. package/src/workspace/ci-builds.ts +385 -0
  463. package/src/workspace/ci-green-builds.test.ts +180 -0
  464. package/src/workspace/ci-green-cli.test.ts +54 -0
  465. package/src/workspace/ci-green-cli.ts +26 -5
  466. package/src/workspace/ci-green-forge.ts +30 -4
  467. package/src/workspace/ci-green-runs.test.ts +53 -11
  468. package/src/workspace/ci-green-workflow.test.ts +84 -1
  469. package/src/workspace/ci-green-workflow.ts +139 -2
  470. package/src/workspace/ci-green.test.ts +6 -3
  471. package/src/workspace/ci-green.ts +408 -48
  472. package/src/workspace/ci-last-green.schema.json +49 -0
  473. package/src/workspace/ci-list.schema.json +47 -7
  474. package/src/workspace/ci-report-url.test.ts +220 -0
  475. package/src/workspace/ci-report-url.ts +33 -0
  476. package/src/workspace/ci-run-index.schema.json +6 -4
  477. package/src/workspace/ci-run-index.test.ts +42 -3
  478. package/src/workspace/ci-run-index.ts +19 -5
  479. package/src/workspace/ci-run.test.ts +203 -8
  480. package/src/workspace/ci-run.ts +295 -63
  481. package/src/workspace/ci-runner-key.test.ts +55 -17
  482. package/src/workspace/ci-runner-key.ts +36 -11
  483. package/src/workspace/ci-runner-start.test.ts +151 -0
  484. package/src/workspace/ci-runner-start.ts +193 -0
  485. package/src/workspace/ci-serve-builds.test.ts +86 -0
  486. package/src/workspace/ci-serve.test.ts +209 -6
  487. package/src/workspace/ci-serve.ts +341 -42
  488. package/src/workspace/declaration.schema.json +30 -2
  489. package/src/workspace/declaration.test.ts +40 -0
  490. package/src/workspace/declaration.ts +40 -3
  491. package/src/workspace/export.ts +6 -3
  492. package/src/workspace/ls.schema.json +9 -2
  493. package/src/workspace/ls.ts +10 -4
  494. package/src/workspace/member-pipeline.ts +2 -1
  495. package/src/workspace/merge-record.schema.json +49 -0
  496. package/src/workspace/pin-integrity.test.ts +5 -0
  497. package/src/workspace/pin-integrity.ts +3 -2
  498. package/src/workspace/pr-report.schema.json +75 -8
  499. package/src/workspace/reason-codes.test.ts +2 -0
  500. package/src/workspace/reason-codes.ts +20 -2
  501. package/src/workspace/release-candidates.schema.json +353 -0
  502. package/src/workspace/release-candidates.test.ts +106 -0
  503. package/src/workspace/release-candidates.ts +315 -0
  504. package/src/workspace/run-event.schema.json +3 -1
  505. package/src/workspace/security.test.ts +70 -0
  506. package/src/workspace/security.ts +118 -0
  507. package/src/workspace/status.schema.json +3 -1
  508. package/src/workspace/write-scope.test.ts +29 -1
  509. package/src/workspace/write-scope.ts +12 -1
@@ -0,0 +1,254 @@
1
+ /**
2
+ * #3981 — lexicons loaded from compiled JS, with plain node.
3
+ *
4
+ * Lexicon packages publish compiled `dist/*.js` as each export's `default`.
5
+ * This compiles core and a set of real lexicons (aws and helm, which find
6
+ * their lint rules by directory scan, and the lexicons they import) with tsc
7
+ * into a scratch `node_modules`, with each lexicon's own package.json. A
8
+ * plain node process with no
9
+ * loader then loads each plugin through the compiled core, and must find the
10
+ * same lint rules, post-synth checks, skills and generated data as the same
11
+ * plugins loaded from source, without loading tsx or any TypeScript module.
12
+ * A `chant build` and `chant lint` of a project against the compiled aws
13
+ * lexicon must work and load none of the lexicon's TypeScript.
14
+ *
15
+ * End to end because it runs tsc and spawns node.
16
+ */
17
+
18
+ import { execFileSync, spawnSync } from "node:child_process";
19
+ import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
20
+ import { tmpdir } from "node:os";
21
+ import { join, resolve } from "node:path";
22
+ import { pathToFileURL } from "node:url";
23
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
24
+
25
+ const coreRoot = resolve(import.meta.dirname, "..");
26
+ const repo = resolve(coreRoot, "..", "..");
27
+ const repoModules = join(repo, "node_modules");
28
+
29
+ /** Compiled and counted. aws and helm discover their lint rules from a directory; the rest are what they import. */
30
+ const LEXICONS = ["aws", "helm", "k8s", "grafana", "otel", "prometheus"];
31
+
32
+ let scratch: string;
33
+ let modules: string;
34
+ let coreDist: string;
35
+ let project: string;
36
+
37
+ /**
38
+ * Loads each named plugin through core and prints what it found. argv[2] is a
39
+ * JSON object: `core` (the URL of core's ts-loader and cli/plugins modules,
40
+ * without extension), `ext` (`.js` or `.ts`), `names`, and `taggable` (the
41
+ * URL of the aws lexicon's taggable module).
42
+ */
43
+ const COUNT = `
44
+ import { registerHooks } from "node:module";
45
+ const seen = { tsx: 0, typeScript: [] };
46
+ registerHooks({
47
+ load(url, context, next) {
48
+ const path = url.replace(/[?#].*$/, "");
49
+ if (path.includes("/node_modules/tsx/")) seen.tsx++;
50
+ else if (/\\.[cm]?tsx?$/.test(path)) seen.typeScript.push(path);
51
+ return next(url, context);
52
+ },
53
+ });
54
+ const args = JSON.parse(process.argv[2]);
55
+ const { installCompiledCli } = await import(args.core + "/ts-loader" + args.ext);
56
+ installCompiledCli();
57
+ const { loadPlugin } = await import(args.core + "/cli/plugins" + args.ext);
58
+ const plugins = {};
59
+ for (const name of args.names) {
60
+ const plugin = await loadPlugin(name);
61
+ const catalogs = {};
62
+ for (const resource of plugin.mcpResources?.() ?? []) {
63
+ if (!resource.uri.endsWith(":resource-catalog")) continue;
64
+ try { catalogs[resource.uri] = JSON.parse(await resource.handler()).length ?? 0; } catch (err) { catalogs[resource.uri] = String(err); }
65
+ }
66
+ plugins[name] = {
67
+ catalogs,
68
+ lintRules: (plugin.lintRules?.() ?? []).map((r) => r.id).sort(),
69
+ postSynthChecks: (plugin.postSynthChecks?.() ?? []).map((c) => c.id).sort(),
70
+ skills: (plugin.skills?.() ?? []).filter((s) => s.content.length > 0).map((s) => s.name).sort(),
71
+ };
72
+ }
73
+ const { loadTaggableResources } = await import(args.taggable);
74
+ const compiled = globalThis[Symbol.for("chant.compiledCli")];
75
+ process.stdout.write(JSON.stringify({
76
+ plugins,
77
+ taggable: loadTaggableResources().size,
78
+ tsxRegistered: compiled ? compiled.tsx : null,
79
+ seen,
80
+ }));
81
+ `;
82
+
83
+ interface Counted {
84
+ plugins: Record<string, { catalogs: Record<string, number | string>; lintRules: string[]; postSynthChecks: string[]; skills: string[] }>;
85
+ taggable: number;
86
+ tsxRegistered: boolean | null;
87
+ seen: { tsx: number; typeScript: string[] };
88
+ }
89
+
90
+ function tsc(project: string, outDir: string): void {
91
+ execFileSync(join(repo, "scripts", "tsc7.sh"), ["-p", project, "--outDir", outDir, "--sourceMap", "false", "--declaration", "false", "--declarationMap", "false"], {
92
+ cwd: repo,
93
+ stdio: ["ignore", "pipe", "pipe"],
94
+ });
95
+ execFileSync(process.execPath, [join(repoModules, "tsc-alias", "dist", "bin", "index.js"), "-p", project, "--outDir", outDir], {
96
+ cwd: repo,
97
+ stdio: ["ignore", "pipe", "pipe"],
98
+ });
99
+ }
100
+
101
+ function count(script: string, args: object, nodeArgs: string[] = []): Counted {
102
+ const r = spawnSync(process.execPath, [...nodeArgs, script, JSON.stringify(args)], { cwd: scratch, encoding: "utf-8", maxBuffer: 16 * 1024 * 1024 });
103
+ if (r.status !== 0) throw new Error(`count script failed (${r.status}):\n${r.stderr}`);
104
+ return JSON.parse(r.stdout) as Counted;
105
+ }
106
+
107
+ beforeAll(() => {
108
+ scratch = mkdtempSync(join(tmpdir(), "chant-lexicon-dist-"));
109
+ modules = join(scratch, "node_modules");
110
+ mkdirSync(join(modules, "@intentius"), { recursive: true });
111
+ for (const name of readdirSync(repoModules)) {
112
+ if (name === "@intentius" || name === ".bin") continue;
113
+ symlinkSync(join(repoModules, name), join(modules, name));
114
+ }
115
+
116
+ // Core as an install has it: its source (the package exports name it, and
117
+ // the compiled CLI redirects each import of it to dist), and dist.
118
+ const core = join(modules, "@intentius", "chant");
119
+ mkdirSync(core);
120
+ cpSync(join(coreRoot, "package.json"), join(core, "package.json"));
121
+ cpSync(join(coreRoot, "src"), join(core, "src"), { recursive: true, filter: (p) => !/\.test\.ts$/.test(p) });
122
+ coreDist = join(core, "dist");
123
+ tsc(join(coreRoot, "tsconfig.build.json"), coreDist);
124
+ mkdirSync(join(core, "scripts"));
125
+ cpSync(join(coreRoot, "scripts", "copy-assets.mjs"), join(core, "scripts", "copy-assets.mjs"));
126
+ execFileSync(process.execPath, [join(core, "scripts", "copy-assets.mjs")], { stdio: "ignore" });
127
+
128
+ for (const name of LEXICONS) {
129
+ const root = join(repo, "lexicons", name);
130
+ const pkg = join(modules, "@intentius", `chant-lexicon-${name}`);
131
+ mkdirSync(pkg);
132
+ cpSync(join(root, "package.json"), join(pkg, "package.json"));
133
+ // src/ is published beside dist/, and holds what a module reads beside itself.
134
+ symlinkSync(join(root, "src"), join(pkg, "src"));
135
+ if (existsSync(join(root, "node_modules"))) symlinkSync(join(root, "node_modules"), join(pkg, "node_modules"));
136
+ // The bundle's dist/ (meta.json, manifest.json), then the compiled modules.
137
+ if (existsSync(join(root, "dist"))) cpSync(join(root, "dist"), join(pkg, "dist"), { recursive: true });
138
+ tsc(join(root, "tsconfig.build.json"), join(pkg, "dist"));
139
+ }
140
+
141
+ writeFileSync(join(scratch, "count.mjs"), COUNT);
142
+
143
+ project = join(scratch, "project");
144
+ mkdirSync(join(project, "src"), { recursive: true });
145
+ writeFileSync(join(project, "package.json"), JSON.stringify({ name: "lexicon-dist-e2e", private: true, type: "module" }));
146
+ writeFileSync(join(project, "chant.config.ts"), `export default { lexicons: ["aws"] };\n`);
147
+ writeFileSync(
148
+ join(project, "src", "infra.ts"),
149
+ `import { Queue } from "@intentius/chant-lexicon-aws";\n` +
150
+ `export const taskQueue = new Queue({ QueueName: "lexicon-dist-e2e", Tags: [{ Key: "suite", Value: "lexicon-dist" }], SqsManagedSseEnabled: true });\n`,
151
+ );
152
+ }, 600_000);
153
+
154
+ afterAll(() => {
155
+ if (scratch) rmSync(scratch, { recursive: true, force: true });
156
+ });
157
+
158
+ describe("lexicons loaded from compiled JS (#3981)", () => {
159
+ let fromSource: Counted;
160
+ let fromDist: Counted;
161
+
162
+ beforeAll(() => {
163
+ const tsxLoader = pathToFileURL(join(repoModules, "tsx", "dist", "loader.mjs")).href;
164
+ fromSource = count(
165
+ join(scratch, "count.mjs"),
166
+ {
167
+ core: pathToFileURL(join(coreRoot, "src")).href,
168
+ ext: ".ts",
169
+ names: LEXICONS,
170
+ taggable: pathToFileURL(join(repo, "lexicons", "aws", "src", "taggable.ts")).href,
171
+ },
172
+ // The repository's lexicons through their `development` export, src/*.ts, as bin/chant runs from source.
173
+ ["--conditions=development", "--import", tsxLoader],
174
+ );
175
+ fromDist = count(join(scratch, "count.mjs"), {
176
+ core: pathToFileURL(coreDist).href,
177
+ ext: ".js",
178
+ names: LEXICONS,
179
+ taggable: "@intentius/chant-lexicon-aws/taggable",
180
+ });
181
+ }, 300_000);
182
+
183
+ test("plain node loads every plugin with no tsx and no TypeScript module", () => {
184
+ expect(fromDist.tsxRegistered).toBe(false);
185
+ expect(fromDist.seen.tsx).toBe(0);
186
+ expect(fromDist.seen.typeScript).toEqual([]);
187
+ // The comparison run really loads the lexicons' source.
188
+ expect(fromSource.seen.typeScript.some((path) => path.includes("/lexicons/aws/src/"))).toBe(true);
189
+ });
190
+
191
+ test("the compiled plugins find the same lint rules as the source", () => {
192
+ for (const name of LEXICONS) {
193
+ expect(fromDist.plugins[name]!.lintRules, name).toEqual(fromSource.plugins[name]!.lintRules);
194
+ }
195
+ // The two that scan a directory for their rules found some.
196
+ expect(fromDist.plugins.aws!.lintRules.length).toBeGreaterThan(0);
197
+ expect(fromDist.plugins.helm!.lintRules.length).toBeGreaterThan(0);
198
+ });
199
+
200
+ test("the compiled plugins find the same post-synth checks as the source", () => {
201
+ for (const name of LEXICONS) {
202
+ expect(fromDist.plugins[name]!.postSynthChecks, name).toEqual(fromSource.plugins[name]!.postSynthChecks);
203
+ }
204
+ expect(fromDist.plugins.aws!.postSynthChecks.length).toBeGreaterThan(0);
205
+ });
206
+
207
+ test("the compiled plugins read their skills from src/", () => {
208
+ for (const name of LEXICONS) {
209
+ expect(fromDist.plugins[name]!.skills, name).toEqual(fromSource.plugins[name]!.skills);
210
+ }
211
+ expect(fromDist.plugins.aws!.skills.length).toBeGreaterThan(0);
212
+ });
213
+
214
+ test("a compiled module reads the generated JSON under src/", () => {
215
+ expect(fromSource.taggable).toBeGreaterThan(0);
216
+ expect(fromDist.taggable).toBe(fromSource.taggable);
217
+ for (const name of LEXICONS) {
218
+ expect(fromDist.plugins[name]!.catalogs, name).toEqual(fromSource.plugins[name]!.catalogs);
219
+ for (const size of Object.values(fromDist.plugins[name]!.catalogs)) expect(typeof size, `${name}: ${size}`).toBe("number");
220
+ }
221
+ expect(fromDist.plugins.aws!.catalogs["aws:resource-catalog"]).toBeGreaterThan(0);
222
+ });
223
+
224
+ test("build and lint run against the compiled aws lexicon and load none of its TypeScript", () => {
225
+ const tracer = join(scratch, "trace.mjs");
226
+ writeFileSync(
227
+ tracer,
228
+ `import { registerHooks } from "node:module";\nimport { appendFileSync } from "node:fs";\n` +
229
+ `const seen = [];\n` +
230
+ `registerHooks({ load(url, context, next) { if (/\\/(lexicons|chant-lexicon-[a-z0-9]+)\\/src\\/.*\\.ts$/.test(url)) seen.push(url); return next(url, context); } });\n` +
231
+ `process.on("exit", () => appendFileSync(process.env.TRACE_OUT, JSON.stringify(seen) + "\\n"));\n`,
232
+ );
233
+ const main = join(coreDist, "cli", "main.js");
234
+ const run = (args: string[]) => {
235
+ const out = join(scratch, `trace-${args[0]}.jsonl`);
236
+ const env: NodeJS.ProcessEnv = { ...process.env, TRACE_OUT: out };
237
+ delete env.CHANT_ENV;
238
+ const r = spawnSync(process.execPath, ["--import", pathToFileURL(tracer).href, main, ...args], { cwd: project, encoding: "utf-8", env });
239
+ return { ...r, lexiconTypeScript: JSON.parse(readFileSync(out, "utf-8").trim().split("\n")[0]!) as string[] };
240
+ };
241
+
242
+ const built = run(["build", "src", "-o", "out/template.json"]);
243
+ expect(built.status, built.stderr).toBe(0);
244
+ const template = JSON.parse(readFileSync(join(project, "out", "template.json"), "utf-8")) as {
245
+ Resources: Record<string, { Type: string; Properties: { QueueName?: string } }>;
246
+ };
247
+ expect(Object.values(template.Resources).map((r) => [r.Type, r.Properties.QueueName])).toEqual([["AWS::SQS::Queue", "lexicon-dist-e2e"]]);
248
+ expect(built.lexiconTypeScript).toEqual([]);
249
+
250
+ const linted = run(["lint", "src"]);
251
+ expect(linted.status, linted.stdout + linted.stderr).toBe(0);
252
+ expect(linted.lexiconTypeScript).toEqual([]);
253
+ });
254
+ });
@@ -201,3 +201,50 @@ describe("lexicons declared by module path (#2520)", () => {
201
201
  expect(typeof evaluator.evaluateGatePolicy).toBe("function");
202
202
  });
203
203
  });
204
+
205
+ describe("tsx only for a lexicon that resolves to TypeScript (#3981)", () => {
206
+ const STATE = Symbol.for("chant.compiledCli");
207
+ const global = globalThis as { [STATE]?: { tsx: boolean } };
208
+ let dir: string;
209
+
210
+ beforeEach(async () => {
211
+ dir = await mkdtemp(join(tmpdir(), "chant-lexicon-js-"));
212
+ // Stand in for the compiled CLI before it has registered tsx.
213
+ global[STATE] = { tsx: false };
214
+ });
215
+
216
+ afterEach(async () => {
217
+ delete global[STATE];
218
+ await rm(dir, { recursive: true, force: true });
219
+ });
220
+
221
+ async function jsPackage(root: string, name: string): Promise<void> {
222
+ const pkg = join(root, "node_modules", "@intentius", `chant-lexicon-${name}`);
223
+ await mkdir(join(pkg, "dist"), { recursive: true });
224
+ await writeFile(join(pkg, "package.json"), JSON.stringify({ name: `@intentius/chant-lexicon-${name}`, type: "module", exports: { ".": "./dist/index.js" } }));
225
+ await writeFile(join(pkg, "dist", "index.js"), `export const marker = ${JSON.stringify(name)};\n`);
226
+ }
227
+
228
+ test("a compiled package the project installs imports with plain node", async () => {
229
+ await jsPackage(dir, "jsonly");
230
+ const { importLexiconPackage } = await import("./lexicon-module");
231
+ const mod = await importLexiconPackage("@intentius/chant-lexicon-jsonly", dir);
232
+ expect(mod.marker).toBe("jsonly");
233
+ expect(global[STATE]).toEqual({ tsx: false });
234
+ });
235
+
236
+ test("a compiled module named by absolute path imports with plain node", async () => {
237
+ const path = join(dir, "lexicon.mjs");
238
+ await writeFile(path, `export const marker = "by-path";\n`);
239
+ const { importLexiconPackage } = await import("./lexicon-module");
240
+ const mod = await importLexiconPackage(path, dir);
241
+ expect(mod.marker).toBe("by-path");
242
+ expect(global[STATE]).toEqual({ tsx: false });
243
+ });
244
+
245
+ test("a package missing from the project too rethrows the first error and registers nothing", async () => {
246
+ const { importLexiconPackage } = await import("./lexicon-module");
247
+ await expect(importLexiconPackage("@intentius/chant-lexicon-not-installed-3981", dir)).rejects.toThrow(/chant-lexicon-not-installed-3981/);
248
+ expect(global[STATE]).toEqual({ tsx: false });
249
+ });
250
+ });
@@ -27,8 +27,9 @@
27
27
  import { createRequire } from "node:module";
28
28
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
29
29
  import { pathToFileURL } from "node:url";
30
- import { ensureTypeScriptLoader } from "./ts-loader";
30
+ import { ensureTypeScriptLoader, ensureTypeScriptLoaderFor, typeScriptLoaderPending } from "./ts-loader";
31
31
  import { esModuleNamespace } from "./module-interop";
32
+ import { isJavaScriptModule } from "./module-source";
32
33
 
33
34
  /**
34
35
  * One `lexicons` entry: a package-backed name, or a name plus the module that
@@ -180,6 +181,9 @@ export function resetLexiconModules(): void {
180
181
  * `typeScript: false` imports it with plain node, without registering tsx
181
182
  * (#3276): tsx's ESM hooks run on a thread of their own, which costs the
182
183
  * compiled CLI about 200 MB, so a caller whose module is JavaScript says so.
184
+ * Left out, the module's extension decides (#3981): a `.js` or `.mjs` module
185
+ * imports with plain node, and anything else (`.ts`, or no extension, which
186
+ * only tsx's resolver completes) registers tsx first.
183
187
  */
184
188
  export async function importLexiconModule(
185
189
  name: string,
@@ -188,7 +192,8 @@ export async function importLexiconModule(
188
192
  const path = modulePaths.get(name);
189
193
  if (path === undefined) return undefined;
190
194
  try {
191
- if (options.typeScript !== false) ensureTypeScriptLoader();
195
+ const typeScript = options.typeScript ?? !isJavaScriptModule(path);
196
+ if (typeScript) ensureTypeScriptLoader();
192
197
  return esModuleNamespace((await import(path)) as Record<string, unknown>);
193
198
  } catch (err) {
194
199
  throw new Error(
@@ -258,6 +263,18 @@ export function resolveFromProject(spec: string, fromDir: string = process.cwd()
258
263
  }
259
264
  }
260
265
 
266
+ /**
267
+ * #3981 — where `spec` resolves from this module, as `import(spec)` here
268
+ * would resolve it. Throws as the import would when it cannot be resolved.
269
+ * An absolute path is its own file URL.
270
+ */
271
+ function resolveImport(spec: string): string {
272
+ if (isAbsolute(spec)) return pathToFileURL(spec).href;
273
+ const resolveMeta = (import.meta as { resolve?: (specifier: string) => string }).resolve;
274
+ if (typeof resolveMeta === "function") return resolveMeta(spec);
275
+ return pathToFileURL(createRequire(import.meta.url).resolve(spec)).href;
276
+ }
277
+
261
278
  /**
262
279
  * chant#2845 — import a lexicon package, or one of its subpaths, by its bare
263
280
  * specifier. chant's own install is tried first, as a bare `import()` always
@@ -266,16 +283,41 @@ export function resolveFromProject(spec: string, fromDir: string = process.cwd()
266
283
  * that installs its lexicons in its own node_modules, it is resolved from
267
284
  * `fromDir` (the project) and imported from there. Any other failure, and a
268
285
  * package the project doesn't have either, rethrows the first error.
286
+ *
287
+ * #3981 — tsx is registered only when the module resolves to TypeScript. A
288
+ * lexicon that ships compiled JS imports with plain node. The module is
289
+ * resolved before it is imported, because a failed import is cached: one
290
+ * tried without tsx cannot be tried again with it. A resolution that fails
291
+ * for any reason but a missing package registers tsx, whose resolver may
292
+ * still complete it.
269
293
  */
270
294
  export async function importLexiconPackage(spec: string, fromDir: string = process.cwd()): Promise<Record<string, unknown>> {
295
+ const pkg = packageNameOf(spec);
296
+ if (typeScriptLoaderPending()) {
297
+ let resolved: string | undefined;
298
+ try {
299
+ resolved = resolveImport(spec);
300
+ } catch (err) {
301
+ if (pkg === undefined || !isPackageNotFound(err, pkg)) {
302
+ ensureTypeScriptLoader();
303
+ } else {
304
+ return importFromProject(spec, fromDir, err);
305
+ }
306
+ }
307
+ if (resolved !== undefined) ensureTypeScriptLoaderFor(resolved);
308
+ }
271
309
  try {
272
- ensureTypeScriptLoader();
273
310
  return esModuleNamespace((await import(spec)) as Record<string, unknown>);
274
311
  } catch (err) {
275
- const pkg = packageNameOf(spec);
276
312
  if (pkg === undefined || !isPackageNotFound(err, pkg)) throw err;
277
- const resolved = resolveFromProject(spec, fromDir);
278
- if (resolved === undefined) throw err;
279
- return esModuleNamespace((await import(pathToFileURL(resolved).href)) as Record<string, unknown>);
313
+ return importFromProject(spec, fromDir, err);
280
314
  }
281
315
  }
316
+
317
+ /** The project's copy of `spec`, for {@link importLexiconPackage}. Rethrows `err` when the project has none. */
318
+ async function importFromProject(spec: string, fromDir: string, err: unknown): Promise<Record<string, unknown>> {
319
+ const resolved = resolveFromProject(spec, fromDir);
320
+ if (resolved === undefined) throw err;
321
+ ensureTypeScriptLoaderFor(resolved);
322
+ return esModuleNamespace((await import(pathToFileURL(resolved).href)) as Record<string, unknown>);
323
+ }
@@ -7,8 +7,8 @@
7
7
  */
8
8
 
9
9
  import { readFileSync } from "fs";
10
- import { join, dirname } from "path";
11
- import { fileURLToPath } from "url";
10
+ import { join } from "path";
11
+ import { moduleSourceDir } from "./module-source";
12
12
  import type { SkillDefinition } from "./lexicon";
13
13
  import type { McpToolContribution, McpResourceContribution } from "./mcp/types";
14
14
  import type { Serializer } from "./serializer";
@@ -44,7 +44,8 @@ export function createSkillsLoader(
44
44
  specs: SkillFileSpec[],
45
45
  ): () => SkillDefinition[] {
46
46
  return () => {
47
- const skillsDir = join(dirname(fileURLToPath(importMetaUrl)), "skills");
47
+ // Skills are markdown beside the source; a compiled plugin finds them under src/ (#3981).
48
+ const skillsDir = join(moduleSourceDir(importMetaUrl), "skills");
48
49
  return specs.map(({ file, ...meta }) => {
49
50
  try {
50
51
  const content = readFileSync(join(skillsDir, file), "utf-8");
@@ -123,7 +124,8 @@ export function createCatalogResource(
123
124
  description,
124
125
  mimeType: "application/json",
125
126
  async handler(): Promise<string> {
126
- const dir = dirname(fileURLToPath(importMetaUrl));
127
+ // Generated JSON sits beside the source; a compiled plugin finds it under src/ (#3981).
128
+ const dir = moduleSourceDir(importMetaUrl);
127
129
  const lexicon = JSON.parse(
128
130
  readFileSync(join(dir, "generated", lexiconJsonFile), "utf-8"),
129
131
  ) as Record<string, { resourceType: string; kind: string }>;
package/src/lexicon.ts CHANGED
@@ -78,6 +78,15 @@ export type {
78
78
  UnobservedReason,
79
79
  } from "./observation";
80
80
 
81
+ // The runtime-health contract (#4177), re-exported so a lexicon's
82
+ // `observeHealth` types itself from the same entry.
83
+ export type {
84
+ HealthEntry,
85
+ HealthReadOptions,
86
+ HealthReadResult,
87
+ HealthState,
88
+ } from "./health";
89
+
81
90
  // The identity contract (#1982), re-exported from the same entry so a
82
91
  // lexicon's `describeIdentity` types itself without a second import path.
83
92
  // Runtime helpers live in `@intentius/chant/identity`.
@@ -554,6 +563,8 @@ export interface ComponentPipelineOptions {
554
563
  * `wave-<k>` runs `chant components fan-out --wave-gate <gate> --wave <k>`
555
564
  * and hands the attempt record to the next job. See
556
565
  * `components/gated-wave-pipeline.ts`. Unset, the pipeline is unchanged.
566
+ * Beside `prLoop` (#4003) it gives the pull request's apply a gate per
567
+ * wave instead: one apply job per wave (`prLoopWithWaves`).
557
568
  */
558
569
  gatedWaves?: GatedWavePipelineOptions;
559
570
  /**
@@ -689,7 +700,19 @@ export type OpFindingMode = "report" | "issue" | "comment" | "pull-request" | "m
689
700
  export type OpTrigger =
690
701
  | { kind: "cron"; schedule: string }
691
702
  | { kind: "pull_request"; branches?: string[] }
692
- | { kind: "push"; branches?: string[] };
703
+ | {
704
+ kind: "push";
705
+ branches?: string[];
706
+ /**
707
+ * Which pushes run the Op (#4169), for an Op a `ci.green` phase names
708
+ * with `build` and the tick reads from chant runs. `every`: each
709
+ * commit, in a run of its own that no later push cancels. `newest`:
710
+ * one run at a time, the newest push waiting and the ones between
711
+ * dropped, plus a run at each `ci/build/<sha>` tag the tick or `ci run
712
+ * --at` pushes. Absent: one run at a time per Op, as before.
713
+ */
714
+ build?: "every" | "newest";
715
+ };
693
716
 
694
717
  /**
695
718
  * One step a generated Op job runs between the checkout and the
@@ -1797,6 +1820,25 @@ export interface LexiconPlugin {
1797
1820
  owned?: boolean;
1798
1821
  }): Promise<DeepObservationResult>;
1799
1822
 
1823
+ /**
1824
+ * Entity types {@link observeHealth} reads (#4177). Data, not a method, so
1825
+ * core picks the entities a health read applies to without calling it.
1826
+ */
1827
+ healthTypes?: readonly string[];
1828
+
1829
+ /**
1830
+ * Whether each declared entity of a {@link healthTypes} type is up (#4177),
1831
+ * read separately from drift: a service's running count, a health check's
1832
+ * checkers, a revision's running state, a Ready condition.
1833
+ *
1834
+ * Every entity in `options.entities` lands in exactly one of `entities`,
1835
+ * `unobserved` or `absent`. A read that failed is `unobserved` with a reason,
1836
+ * never `unhealthy`: `unhealthy` is something the provider said. Throwing is
1837
+ * the whole-lexicon failure, and core reports every entity `read-failed`.
1838
+ * Endpoint overrides apply as on the other reads, so an emulator answers.
1839
+ */
1840
+ observeHealth?(options: import("./health").HealthReadOptions): Promise<import("./health").HealthReadResult>;
1841
+
1800
1842
  /**
1801
1843
  * This lexicon's noise rules for deep observation (#1014): which fields are
1802
1844
  * read-only / server-populated / controller-managed / provider-defaulted, and
@@ -14,6 +14,7 @@ import {
14
14
  import type { StackGraph } from "../build";
15
15
  import type { Serializer } from "../serializer";
16
16
  import type { Declarable } from "../declarable";
17
+ import { EMPTY_TREE_SHA } from "../components/changed-files";
17
18
 
18
19
  const execFileAsync = promisify(execFile);
19
20
 
@@ -136,6 +137,12 @@ describe("affectedStacks — baseDir (caller-supplied)", () => {
136
137
  const r = await affectedStacks({ projectPath: head, baseDir: base, serializers: [fakeSerializer] });
137
138
  expect(r.changed).toEqual([]);
138
139
  });
140
+
141
+ test("the empty tree as the base (a root commit, #4004) builds nothing: every head stack is changed", async () => {
142
+ const head = srcDir("head", widget("Widget"));
143
+ const r = await affectedStacks({ projectPath: head, baseRef: EMPTY_TREE_SHA, serializers: [fakeSerializer] });
144
+ expect(r.changed).toEqual(["fake"]);
145
+ });
139
146
  });
140
147
 
141
148
  // A second lexicon, so a stack that serializes through two of them can be shown
@@ -37,6 +37,7 @@ import { tmpdir } from "node:os";
37
37
  import { build, type StackGraph } from "../build";
38
38
  import { getPrimaryOutput } from "../lint/post-synth";
39
39
  import type { Serializer } from "../serializer";
40
+ import { EMPTY_TREE_SHA } from "../components/changed-files";
40
41
  import type { Declarable } from "../declarable";
41
42
 
42
43
  const execFileAsync = promisify(execFile);
@@ -258,7 +259,9 @@ export interface AffectedStacksOptions {
258
259
  */
259
260
  export async function affectedStacks(opts: AffectedStacksOptions): Promise<AffectedResult> {
260
261
  const projectPath = resolve(opts.projectPath);
261
- const needsWorktree = Boolean(opts.headRef) || Boolean(opts.baseRef && !opts.baseDir);
262
+ // The empty tree (#4004) builds nothing, so it needs no worktree.
263
+ const emptyBase = !opts.baseDir && opts.baseRef === EMPTY_TREE_SHA;
264
+ const needsWorktree = Boolean(opts.headRef) || Boolean(opts.baseRef && !opts.baseDir && !emptyBase);
262
265
  const repoRoot = needsWorktree ? await gitTopLevel(projectPath) : projectPath;
263
266
  // Canonicalize via realpath so the worktree-relative path is correct even when
264
267
  // the temp dir is a symlink (macOS /var → /private/var) and `git
@@ -293,6 +296,8 @@ export async function affectedStacks(opts: AffectedStacksOptions): Promise<Affec
293
296
  let baseMap: Map<string, string>;
294
297
  if (opts.baseDir) {
295
298
  baseMap = (await readArtifacts(resolve(opts.baseDir), false)).artifacts;
299
+ } else if (emptyBase) {
300
+ baseMap = new Map();
296
301
  } else if (opts.baseRef) {
297
302
  baseMap = await withWorktree(repoRoot, opts.baseRef, async (dir) => (await readArtifacts(join(dir, relProject), false)).artifacts);
298
303
  } else {
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Merge records (#4168 step 1) against temp git repos: written under the
3
+ * commit on chant/lifecycle, rewritten only when the record changed, read
4
+ * back, and validated against the schema; and where the change came from.
5
+ */
6
+
7
+ import { describe, expect, test } from "vitest";
8
+ import { withTestDir } from "@intentius/chant-test-utils";
9
+ import { spawnSync } from "node:child_process";
10
+ import { writeFileSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { MERGE_RECORD_CONTRACT, MERGE_RECORD_SCHEMA_ID, MERGES_DIR, mergeRecordFile, mergeSourceOf, readMergeRecord, writeMergeRecord, type MergeRecord } from "./merge-record";
13
+ import { contract, validSchema } from "../workspace/__fixtures__/contract-repo";
14
+ import schema from "../workspace/merge-record.schema.json";
15
+
16
+ const { expectValid } = contract(schema);
17
+
18
+ function git(dir: string, ...args: string[]): string {
19
+ const r = spawnSync("git", args, { cwd: dir, encoding: "utf-8" });
20
+ if (r.status !== 0) throw new Error(`git ${args.join(" ")}: ${r.stderr}`);
21
+ return r.stdout.trim();
22
+ }
23
+
24
+ function initRepo(dir: string): string {
25
+ git(dir, "init", "-q", "-b", "main");
26
+ git(dir, "config", "user.email", "test@chant.dev");
27
+ git(dir, "config", "user.name", "Test");
28
+ writeFileSync(join(dir, "README.md"), "fixture\n");
29
+ git(dir, "add", ".");
30
+ git(dir, "commit", "-q", "-m", "init");
31
+ return git(dir, "rev-parse", "HEAD");
32
+ }
33
+
34
+ const record = (commit: string, over: Partial<MergeRecord> = {}): MergeRecord => ({
35
+ $schema: MERGE_RECORD_SCHEMA_ID,
36
+ contract: MERGE_RECORD_CONTRACT,
37
+ commit,
38
+ base: "b".repeat(40),
39
+ env: "prod",
40
+ source: { kind: "pull-request", pr: 12 },
41
+ op: "pr-12",
42
+ gate: "pr-apply",
43
+ digest: `jcs1-sha256:${"1".repeat(64)}`,
44
+ plan: "approved",
45
+ approvedBy: ["github:rev"],
46
+ components: ["net", "app"],
47
+ recordedAt: "2026-10-20T00:00:00.000Z",
48
+ ...over,
49
+ });
50
+
51
+ describe("the merge record schema", () => {
52
+ test("is a valid schema and names this module's id", () => {
53
+ expect(validSchema(schema)).toBe(true);
54
+ expect(schema.$id).toBe(MERGE_RECORD_SCHEMA_ID);
55
+ });
56
+
57
+ test("each source and plan status validates", () => {
58
+ const sha = "a".repeat(40);
59
+ expectValid(record(sha));
60
+ expectValid(record(sha, { source: { kind: "work-item", records: [{ kind: "work", id: "w-7" }] }, op: null, plan: "unapproved", approvedBy: [] }));
61
+ expectValid(record(sha, { source: { kind: "direct-push" }, op: null, digest: null, plan: "nothing", approvedBy: [], components: [] }));
62
+ expectValid(record(sha, { plan: "changed", approvedDigest: `jcs1-sha256:${"9".repeat(64)}`, approvedBy: [], authorApproved: ["github:carol"], member: "net" }));
63
+ });
64
+ });
65
+
66
+ describe("writeMergeRecord and readMergeRecord", () => {
67
+ test("writes the record under its commit, leaves the same record alone, and replaces a changed one", async () => {
68
+ await withTestDir(async (dir) => {
69
+ const sha = initRepo(dir);
70
+ expect(await readMergeRecord(sha, { cwd: dir })).toBeNull();
71
+ expect((await writeMergeRecord(record(sha), { cwd: dir })).written).toBe(true);
72
+ expect(git(dir, "ls-tree", "--name-only", `chant/lifecycle:${MERGES_DIR}`)).toBe(mergeRecordFile(sha));
73
+ expect(await readMergeRecord(sha, { cwd: dir })).toEqual(record(sha));
74
+
75
+ // The record job ran again: only the time differs, so nothing is written.
76
+ const tip = git(dir, "rev-parse", "chant/lifecycle");
77
+ expect((await writeMergeRecord(record(sha, { recordedAt: "2026-10-21T00:00:00.000Z" }), { cwd: dir })).written).toBe(false);
78
+ expect(git(dir, "rev-parse", "chant/lifecycle")).toBe(tip);
79
+
80
+ // A plan that moved since is a new record for the same commit.
81
+ const moved = record(sha, { plan: "changed", approvedDigest: `jcs1-sha256:${"9".repeat(64)}`, approvedBy: [] });
82
+ expect((await writeMergeRecord(moved, { cwd: dir })).written).toBe(true);
83
+ expect(await readMergeRecord(sha, { cwd: dir })).toEqual(moved);
84
+ });
85
+ });
86
+
87
+ test("a member's record sits under its own prefix", async () => {
88
+ await withTestDir(async (dir) => {
89
+ const sha = initRepo(dir);
90
+ await writeMergeRecord(record(sha, { member: "net" }), { cwd: dir, member: "net" });
91
+ expect(git(dir, "ls-tree", "-r", "--name-only", "chant/lifecycle")).toBe(`_members/net/${MERGES_DIR}/${mergeRecordFile(sha)}`);
92
+ expect(await readMergeRecord(sha, { cwd: dir, member: "net" })).toMatchObject({ member: "net" });
93
+ expect(await readMergeRecord(sha, { cwd: dir })).toBeNull();
94
+ });
95
+ });
96
+ });
97
+
98
+ describe("mergeSourceOf", () => {
99
+ test("a pull request first, then the work items a commit's trailers name, then a direct push", async () => {
100
+ await withTestDir(async (dir) => {
101
+ const plain = initRepo(dir);
102
+ expect(mergeSourceOf(dir, plain, 12)).toEqual({ kind: "pull-request", pr: 12 });
103
+ expect(mergeSourceOf(dir, plain, null)).toEqual({ kind: "direct-push" });
104
+ writeFileSync(join(dir, "a.txt"), "a\n");
105
+ git(dir, "add", ".");
106
+ git(dir, "commit", "-q", "-m", "carry out w-7\n\nChant-Record: work:w-7\nChant-Record: decision:d-1\nChant-Record: work:w-8\nChant-Record: work:w-7");
107
+ expect(mergeSourceOf(dir, git(dir, "rev-parse", "HEAD"), null)).toEqual({ kind: "work-item", records: [{ kind: "work", id: "w-7" }, { kind: "work", id: "w-8" }] });
108
+ expect(mergeSourceOf(dir, "f".repeat(40), null)).toEqual({ kind: "direct-push" });
109
+ });
110
+ });
111
+ });