@intentius/chant 0.124.30 → 0.124.32

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 (346) hide show
  1. package/bin/chant +7 -2
  2. package/dist/cli/commands/check-lexicon-plugin.d.ts +7 -4
  3. package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
  4. package/dist/cli/commands/check-lexicon-plugin.js +10 -7
  5. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  6. package/dist/cli/commands/check-lexicon.js +18 -12
  7. package/dist/cli/commands/import.d.ts +10 -0
  8. package/dist/cli/commands/import.d.ts.map +1 -1
  9. package/dist/cli/commands/import.js +37 -0
  10. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
  11. package/dist/cli/commands/init-lexicon/templates/project.js +4 -3
  12. package/dist/cli/commands/onboard.d.ts.map +1 -1
  13. package/dist/cli/commands/onboard.js +8 -4
  14. package/dist/cli/commands/update.d.ts.map +1 -1
  15. package/dist/cli/commands/update.js +2 -1
  16. package/dist/cli/dependencies.d.ts +18 -0
  17. package/dist/cli/dependencies.d.ts.map +1 -0
  18. package/dist/cli/dependencies.js +120 -0
  19. package/dist/cli/handlers/build.d.ts.map +1 -1
  20. package/dist/cli/handlers/build.js +23 -4
  21. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  22. package/dist/cli/handlers/fan-out.js +12 -4
  23. package/dist/cli/handlers/graph.d.ts.map +1 -1
  24. package/dist/cli/handlers/graph.js +16 -6
  25. package/dist/cli/handlers/lint.d.ts.map +1 -1
  26. package/dist/cli/handlers/lint.js +6 -0
  27. package/dist/cli/handlers/operator.d.ts +2 -2
  28. package/dist/cli/handlers/operator.js +3 -3
  29. package/dist/cli/handlers/pr.d.ts +14 -0
  30. package/dist/cli/handlers/pr.d.ts.map +1 -1
  31. package/dist/cli/handlers/pr.js +488 -32
  32. package/dist/cli/handlers/promote.d.ts.map +1 -1
  33. package/dist/cli/handlers/promote.js +3 -1
  34. package/dist/cli/handlers/run.d.ts +13 -1
  35. package/dist/cli/handlers/run.d.ts.map +1 -1
  36. package/dist/cli/handlers/run.js +38 -15
  37. package/dist/cli/main.d.ts.map +1 -1
  38. package/dist/cli/main.js +74 -12
  39. package/dist/cli/parse-args.d.ts.map +1 -1
  40. package/dist/cli/parse-args.js +65 -0
  41. package/dist/cli/registry.d.ts +29 -2
  42. package/dist/cli/registry.d.ts.map +1 -1
  43. package/dist/cli/registry.js +6 -2
  44. package/dist/codegen/generate-post-synth-barrel.js +2 -2
  45. package/dist/codegen/lexicon-regen.d.ts +4 -0
  46. package/dist/codegen/lexicon-regen.d.ts.map +1 -1
  47. package/dist/codegen/lexicon-regen.js +1 -1
  48. package/dist/codegen/pinned-upgrade.js +2 -2
  49. package/dist/components/capability.d.ts +44 -3
  50. package/dist/components/capability.d.ts.map +1 -1
  51. package/dist/components/capability.js +21 -0
  52. package/dist/components/changed-files.d.ts +7 -1
  53. package/dist/components/changed-files.d.ts.map +1 -1
  54. package/dist/components/changed-files.js +9 -1
  55. package/dist/components/driver.d.ts +38 -0
  56. package/dist/components/driver.d.ts.map +1 -1
  57. package/dist/components/driver.js +47 -6
  58. package/dist/components/fan-out-record.d.ts +6 -1
  59. package/dist/components/fan-out-record.d.ts.map +1 -1
  60. package/dist/components/fan-out-record.js +24 -0
  61. package/dist/components/fan-out-run.d.ts +20 -0
  62. package/dist/components/fan-out-run.d.ts.map +1 -1
  63. package/dist/components/fan-out-run.js +21 -1
  64. package/dist/components/pr-pipeline.d.ts +65 -2
  65. package/dist/components/pr-pipeline.d.ts.map +1 -1
  66. package/dist/components/pr-pipeline.js +107 -15
  67. package/dist/components/pr-run.d.ts +127 -29
  68. package/dist/components/pr-run.d.ts.map +1 -1
  69. package/dist/components/pr-run.js +290 -70
  70. package/dist/components/wave-plan.d.ts +5 -2
  71. package/dist/components/wave-plan.d.ts.map +1 -1
  72. package/dist/components/wave-plan.js +12 -1
  73. package/dist/discovery/fold-import.d.ts.map +1 -1
  74. package/dist/discovery/fold-import.js +16 -6
  75. package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
  76. package/dist/discovery/sandbox/bundle.js +6 -0
  77. package/dist/discovery/specifier-resolve.d.ts.map +1 -1
  78. package/dist/discovery/specifier-resolve.js +8 -2
  79. package/dist/fold/fold.d.ts.map +1 -1
  80. package/dist/fold/fold.js +68 -1
  81. package/dist/fold/subset.d.ts +37 -0
  82. package/dist/fold/subset.d.ts.map +1 -1
  83. package/dist/fold/subset.js +88 -0
  84. package/dist/lexicon-module.d.ts +10 -0
  85. package/dist/lexicon-module.d.ts.map +1 -1
  86. package/dist/lexicon-module.js +53 -8
  87. package/dist/lexicon-plugin-helpers.d.ts.map +1 -1
  88. package/dist/lexicon-plugin-helpers.js +6 -4
  89. package/dist/lexicon.d.ts +2 -0
  90. package/dist/lexicon.d.ts.map +1 -1
  91. package/dist/lifecycle/affected.d.ts.map +1 -1
  92. package/dist/lifecycle/affected.js +7 -1
  93. package/dist/lifecycle/release-ledger.d.ts +21 -0
  94. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  95. package/dist/lifecycle/release-ledger.js +24 -0
  96. package/dist/lifecycle/work-dir.d.ts +45 -0
  97. package/dist/lifecycle/work-dir.d.ts.map +1 -0
  98. package/dist/lifecycle/work-dir.js +78 -0
  99. package/dist/lint/discover.d.ts +17 -9
  100. package/dist/lint/discover.d.ts.map +1 -1
  101. package/dist/lint/discover.js +62 -54
  102. package/dist/module-source.d.ts +57 -0
  103. package/dist/module-source.d.ts.map +1 -0
  104. package/dist/module-source.js +104 -0
  105. package/dist/op/activities/factory.js +1 -1
  106. package/dist/op/discover.d.ts.map +1 -1
  107. package/dist/op/discover.js +13 -3
  108. package/dist/op/gate.d.ts.map +1 -1
  109. package/dist/op/gate.js +3 -0
  110. package/dist/op/local-executor.d.ts +12 -0
  111. package/dist/op/local-executor.d.ts.map +1 -1
  112. package/dist/op/local-executor.js +22 -3
  113. package/dist/op/op-waves-run.d.ts.map +1 -1
  114. package/dist/op/op-waves-run.js +20 -5
  115. package/dist/op/operator.d.ts +2 -2
  116. package/dist/op/operator.js +2 -2
  117. package/dist/op/redact.d.ts +34 -0
  118. package/dist/op/redact.d.ts.map +1 -0
  119. package/dist/op/redact.js +94 -0
  120. package/dist/op/step-console.d.ts +2 -0
  121. package/dist/op/step-console.d.ts.map +1 -1
  122. package/dist/op/step-console.js +5 -2
  123. package/dist/op/steward.d.ts +6 -4
  124. package/dist/op/steward.d.ts.map +1 -1
  125. package/dist/op/steward.js +5 -3
  126. package/dist/op/work-lease-run.d.ts +20 -7
  127. package/dist/op/work-lease-run.d.ts.map +1 -1
  128. package/dist/op/work-lease-run.js +56 -12
  129. package/dist/pr-forge.d.ts +27 -0
  130. package/dist/pr-forge.d.ts.map +1 -1
  131. package/dist/pr-forge.js +51 -6
  132. package/dist/pr-loop.d.ts +141 -4
  133. package/dist/pr-loop.d.ts.map +1 -1
  134. package/dist/pr-loop.js +122 -6
  135. package/dist/pr-report-url.d.ts +10 -2
  136. package/dist/pr-report-url.d.ts.map +1 -1
  137. package/dist/pr-report-url.js +52 -6
  138. package/dist/ts-loader.d.ts +38 -4
  139. package/dist/ts-loader.d.ts.map +1 -1
  140. package/dist/ts-loader.js +190 -16
  141. package/dist/wave-runtime.mjs +208 -208
  142. package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts +34 -0
  143. package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts.map +1 -0
  144. package/dist/workspace/chant-migrations/fountain-retired-kinds.js +176 -0
  145. package/dist/workspace/chant-migrations.d.ts.map +1 -1
  146. package/dist/workspace/chant-migrations.js +2 -0
  147. package/dist/workspace/checks/boxes.d.ts +6 -6
  148. package/dist/workspace/checks/boxes.js +9 -9
  149. package/dist/workspace/ci-green-forge.d.ts +6 -4
  150. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  151. package/dist/workspace/ci-green.d.ts +28 -12
  152. package/dist/workspace/ci-green.d.ts.map +1 -1
  153. package/dist/workspace/ci-green.js +49 -21
  154. package/dist/workspace/ci-list.schema.json +12 -6
  155. package/dist/workspace/ci-report-url.d.ts +18 -0
  156. package/dist/workspace/ci-report-url.d.ts.map +1 -0
  157. package/dist/workspace/ci-report-url.js +33 -0
  158. package/dist/workspace/ci-run-index.d.ts +108 -0
  159. package/dist/workspace/ci-run-index.d.ts.map +1 -0
  160. package/dist/workspace/ci-run-index.js +164 -0
  161. package/dist/workspace/ci-run-index.schema.json +52 -0
  162. package/dist/workspace/ci-run.d.ts +159 -9
  163. package/dist/workspace/ci-run.d.ts.map +1 -1
  164. package/dist/workspace/ci-run.js +419 -50
  165. package/dist/workspace/ci-runner-key.d.ts +133 -0
  166. package/dist/workspace/ci-runner-key.d.ts.map +1 -0
  167. package/dist/workspace/ci-runner-key.js +237 -0
  168. package/dist/workspace/ci-secrets.d.ts +65 -0
  169. package/dist/workspace/ci-secrets.d.ts.map +1 -0
  170. package/dist/workspace/ci-secrets.js +211 -0
  171. package/dist/workspace/ci-serve.d.ts +67 -4
  172. package/dist/workspace/ci-serve.d.ts.map +1 -1
  173. package/dist/workspace/ci-serve.js +240 -35
  174. package/dist/workspace/declaration.d.ts +8 -0
  175. package/dist/workspace/declaration.d.ts.map +1 -1
  176. package/dist/workspace/declaration.js +11 -2
  177. package/dist/workspace/declaration.schema.json +7 -2
  178. package/dist/workspace/export.d.ts +1 -1
  179. package/dist/workspace/export.d.ts.map +1 -1
  180. package/dist/workspace/export.js +7 -3
  181. package/dist/workspace/ls.d.ts +6 -1
  182. package/dist/workspace/ls.d.ts.map +1 -1
  183. package/dist/workspace/ls.js +1 -0
  184. package/dist/workspace/ls.schema.json +7 -1
  185. package/dist/workspace/pin-integrity.d.ts.map +1 -1
  186. package/dist/workspace/pin-integrity.js +3 -2
  187. package/dist/workspace/pr-report.schema.json +74 -7
  188. package/dist/workspace/reason-codes.d.ts +7 -3
  189. package/dist/workspace/reason-codes.d.ts.map +1 -1
  190. package/dist/workspace/reason-codes.js +9 -4
  191. package/dist/workspace/run-event.schema.json +3 -1
  192. package/dist/workspace/status.schema.json +3 -1
  193. package/dist/workspace/trust/trust-write.d.ts +109 -0
  194. package/dist/workspace/trust/trust-write.d.ts.map +1 -0
  195. package/dist/workspace/trust/trust-write.js +320 -0
  196. package/dist/workspace/trust-write.schema.json +265 -0
  197. package/dist/workspace/write-scope.d.ts +9 -0
  198. package/dist/workspace/write-scope.d.ts.map +1 -1
  199. package/dist/workspace/write-scope.js +11 -1
  200. package/package.json +1 -1
  201. package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
  202. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +3 -3
  203. package/src/cli/commands/audit.test.ts +2 -2
  204. package/src/cli/commands/check-lexicon-plugin.test.ts +9 -0
  205. package/src/cli/commands/check-lexicon-plugin.ts +11 -8
  206. package/src/cli/commands/check-lexicon.test.ts +1 -1
  207. package/src/cli/commands/check-lexicon.ts +16 -11
  208. package/src/cli/commands/import.ts +40 -0
  209. package/src/cli/commands/init-lexicon/templates/project.ts +4 -3
  210. package/src/cli/commands/init-lexicon.test.ts +8 -5
  211. package/src/cli/commands/onboard.test.ts +45 -0
  212. package/src/cli/commands/onboard.ts +8 -4
  213. package/src/cli/commands/update.test.ts +6 -0
  214. package/src/cli/commands/update.ts +4 -1
  215. package/src/cli/compiled-cli.e2e.test.ts +76 -11
  216. package/src/cli/dependencies.e2e.test.ts +101 -0
  217. package/src/cli/dependencies.test.ts +93 -0
  218. package/src/cli/dependencies.ts +122 -0
  219. package/src/cli/handlers/build.test.ts +69 -0
  220. package/src/cli/handlers/build.ts +20 -4
  221. package/src/cli/handlers/fan-out.test.ts +73 -0
  222. package/src/cli/handlers/fan-out.ts +11 -4
  223. package/src/cli/handlers/graph.test.ts +76 -0
  224. package/src/cli/handlers/graph.ts +19 -12
  225. package/src/cli/handlers/lint.test.ts +42 -0
  226. package/src/cli/handlers/lint.ts +6 -0
  227. package/src/cli/handlers/operator.ts +3 -3
  228. package/src/cli/handlers/pr-waves.test.ts +449 -0
  229. package/src/cli/handlers/pr.test.ts +134 -0
  230. package/src/cli/handlers/pr.ts +523 -31
  231. package/src/cli/handlers/promote.test.ts +26 -1
  232. package/src/cli/handlers/promote.ts +3 -2
  233. package/src/cli/handlers/run-components-exit.test.ts +99 -0
  234. package/src/cli/handlers/run-signer.test.ts +79 -0
  235. package/src/cli/handlers/run.ts +34 -16
  236. package/src/cli/main.test.ts +19 -0
  237. package/src/cli/main.ts +73 -12
  238. package/src/cli/parse-args.ts +48 -0
  239. package/src/cli/registry.ts +35 -4
  240. package/src/codegen/generate-post-synth-barrel.ts +2 -2
  241. package/src/codegen/lexicon-regen.ts +1 -1
  242. package/src/codegen/pinned-upgrade.ts +2 -2
  243. package/src/codegen/post-synth-barrel-guards.test.ts +6 -11
  244. package/src/components/capability.ts +51 -3
  245. package/src/components/changed-files.test.ts +16 -1
  246. package/src/components/changed-files.ts +9 -1
  247. package/src/components/driver.test.ts +36 -1
  248. package/src/components/driver.ts +76 -4
  249. package/src/components/fan-out-record.ts +29 -1
  250. package/src/components/fan-out-run.test.ts +35 -0
  251. package/src/components/fan-out-run.ts +40 -1
  252. package/src/components/pr-pipeline.test.ts +100 -0
  253. package/src/components/pr-pipeline.ts +132 -17
  254. package/src/components/pr-run.test.ts +323 -18
  255. package/src/components/pr-run.ts +354 -75
  256. package/src/components/wave-plan.ts +16 -4
  257. package/src/discovery/fold-host-method.test.ts +87 -0
  258. package/src/discovery/fold-import.ts +15 -5
  259. package/src/discovery/fold-package-composite.test.ts +27 -0
  260. package/src/discovery/fold-specifiers.test.ts +8 -0
  261. package/src/discovery/sandbox/bundle.ts +6 -0
  262. package/src/discovery/specifier-resolve.ts +8 -2
  263. package/src/fold/fold.test.ts +72 -0
  264. package/src/fold/fold.ts +71 -0
  265. package/src/fold/subset.test.ts +31 -0
  266. package/src/fold/subset.ts +99 -0
  267. package/src/lexicon-dist.e2e.test.ts +254 -0
  268. package/src/lexicon-module.test.ts +47 -0
  269. package/src/lexicon-module.ts +49 -7
  270. package/src/lexicon-plugin-helpers.ts +6 -4
  271. package/src/lexicon.ts +2 -0
  272. package/src/lifecycle/affected.test.ts +7 -0
  273. package/src/lifecycle/affected.ts +6 -1
  274. package/src/lifecycle/release-ledger.ts +34 -0
  275. package/src/lifecycle/work-dir.test.ts +77 -0
  276. package/src/lifecycle/work-dir.ts +82 -0
  277. package/src/lint/discover.test.ts +46 -2
  278. package/src/lint/discover.ts +55 -49
  279. package/src/module-source.test.ts +85 -0
  280. package/src/module-source.ts +112 -0
  281. package/src/op/activities/factory.ts +1 -1
  282. package/src/op/discover-root.test.ts +31 -2
  283. package/src/op/discover.ts +14 -4
  284. package/src/op/gate.test.ts +30 -0
  285. package/src/op/gate.ts +3 -0
  286. package/src/op/local-executor.ts +31 -3
  287. package/src/op/op-waves-run.ts +20 -5
  288. package/src/op/op-waves.test.ts +49 -0
  289. package/src/op/operator.ts +2 -2
  290. package/src/op/redact.ts +93 -0
  291. package/src/op/step-console.ts +7 -2
  292. package/src/op/steward.ts +6 -4
  293. package/src/op/work-lease-run.test.ts +88 -2
  294. package/src/op/work-lease-run.ts +52 -12
  295. package/src/pr-forge-bundle.test.ts +2 -2
  296. package/src/pr-forge.test.ts +71 -0
  297. package/src/pr-forge.ts +70 -6
  298. package/src/pr-loop-bundle.test.ts +2 -1
  299. package/src/pr-loop.test.ts +117 -1
  300. package/src/pr-loop.ts +252 -10
  301. package/src/pr-report-url.ts +58 -6
  302. package/src/terraform/__fixtures__/advise.test.ts +21 -2
  303. package/src/ts-loader.test.ts +89 -1
  304. package/src/ts-loader.ts +180 -13
  305. package/src/workspace/changes.test.ts +1 -1
  306. package/src/workspace/chant-migrations/fountain-retired-kinds.test.ts +33 -0
  307. package/src/workspace/chant-migrations/fountain-retired-kinds.ts +190 -0
  308. package/src/workspace/chant-migrations.e2e.test.ts +5 -0
  309. package/src/workspace/chant-migrations.ts +2 -0
  310. package/src/workspace/checks/boxes.ts +9 -9
  311. package/src/workspace/ci-green-forge.ts +6 -4
  312. package/src/workspace/ci-green-runs.test.ts +53 -11
  313. package/src/workspace/ci-green.test.ts +1 -1
  314. package/src/workspace/ci-green.ts +50 -22
  315. package/src/workspace/ci-list.schema.json +12 -6
  316. package/src/workspace/ci-report-url.test.ts +220 -0
  317. package/src/workspace/ci-report-url.ts +33 -0
  318. package/src/workspace/ci-run-index.schema.json +52 -0
  319. package/src/workspace/ci-run-index.test.ts +392 -0
  320. package/src/workspace/ci-run-index.ts +206 -0
  321. package/src/workspace/ci-run.test.ts +520 -42
  322. package/src/workspace/ci-run.ts +483 -51
  323. package/src/workspace/ci-runner-key.test.ts +204 -0
  324. package/src/workspace/ci-runner-key.ts +275 -0
  325. package/src/workspace/ci-secrets.test.ts +106 -0
  326. package/src/workspace/ci-secrets.ts +213 -0
  327. package/src/workspace/ci-serve.test.ts +282 -39
  328. package/src/workspace/ci-serve.ts +231 -34
  329. package/src/workspace/declaration.schema.json +7 -2
  330. package/src/workspace/declaration.test.ts +23 -0
  331. package/src/workspace/declaration.ts +19 -3
  332. package/src/workspace/export.ts +6 -3
  333. package/src/workspace/ls.schema.json +7 -1
  334. package/src/workspace/ls.ts +7 -2
  335. package/src/workspace/pin-integrity.test.ts +5 -0
  336. package/src/workspace/pin-integrity.ts +3 -2
  337. package/src/workspace/pr-report.schema.json +74 -7
  338. package/src/workspace/reason-codes.test.ts +2 -0
  339. package/src/workspace/reason-codes.ts +9 -4
  340. package/src/workspace/run-event.schema.json +3 -1
  341. package/src/workspace/status.schema.json +3 -1
  342. package/src/workspace/trust/trust-write.test.ts +179 -0
  343. package/src/workspace/trust/trust-write.ts +377 -0
  344. package/src/workspace/trust-write.schema.json +265 -0
  345. package/src/workspace/write-scope.test.ts +29 -1
  346. package/src/workspace/write-scope.ts +12 -1
package/src/ts-loader.ts CHANGED
@@ -18,13 +18,15 @@
18
18
  *
19
19
  * Run from source (the repository's `bin/chant` through tsx, vitest), this
20
20
  * module is itself TypeScript, the TypeScript loader is already there, and
21
- * both functions do nothing.
21
+ * both functions do nothing, except that {@link installCompiledCli} resolves
22
+ * chant's packages to their source (#3981, {@link installSourceResolution}).
22
23
  */
23
24
 
24
- import { existsSync } from "node:fs";
25
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
25
26
  import * as nodeModule from "node:module";
26
27
  import { dirname, join, sep } from "node:path";
27
28
  import { fileURLToPath, pathToFileURL } from "node:url";
29
+ import { isJavaScriptModule, moduleSourceDir } from "./module-source";
28
30
 
29
31
  // `module.registerHooks` (Node 22.15+, 24) is newer than the @types/node the
30
32
  // repository pins, so its shape is declared here.
@@ -57,11 +59,15 @@ function runsCompiled(): boolean {
57
59
 
58
60
  /**
59
61
  * Called once by the CLI's `main()`. Compiled, it records that TypeScript
60
- * loads lazily and installs the `src/*.ts` to `dist/*.js` redirect; from
61
- * source it does nothing.
62
+ * loads lazily and installs the `src/*.ts` to `dist/*.js` redirect. From
63
+ * source it installs {@link installSourceResolution}.
62
64
  */
63
65
  export function installCompiledCli(): void {
64
- if (!runsCompiled() || compiledState()) return;
66
+ if (!runsCompiled()) {
67
+ installSourceResolution();
68
+ return;
69
+ }
70
+ if (compiledState()) return;
65
71
  (globalThis as { [STATE]?: CompiledCliState })[STATE] = { tsx: false };
66
72
 
67
73
  const distDir = dirname(fileURLToPath(import.meta.url));
@@ -72,7 +78,10 @@ export function installCompiledCli(): void {
72
78
  registerHooks({
73
79
  resolve(specifier, context, nextResolve) {
74
80
  const result = nextResolve(specifier, context);
75
- if (!result.url.startsWith(srcUrl)) return result;
81
+ if (!result.url.startsWith(srcUrl)) {
82
+ const twin = lexiconCompiledTwin(result.url);
83
+ return twin === undefined ? result : { ...result, url: twin, format: "module" };
84
+ }
76
85
  const path = result.url.slice(srcUrl.length).replace(/[?#].*$/, "");
77
86
  if (!path.endsWith(".ts") || path.endsWith(".d.ts")) return result;
78
87
  let target = compiled.get(path);
@@ -86,6 +95,144 @@ export function installCompiledCli(): void {
86
95
  });
87
96
  }
88
97
 
98
+ /** Package directories by the directory a module sits in: a lexicon package's root, or null for any other package. */
99
+ const lexiconRoots = new Map<string, string | null>();
100
+
101
+ /** The lexicon package `dir` belongs to: the nearest directory up with a package.json, when it names a chant lexicon. */
102
+ function lexiconRootOf(dir: string): string | undefined {
103
+ const seen: string[] = [];
104
+ let root: string | null = null;
105
+ for (let at = dir; ; ) {
106
+ const cached = lexiconRoots.get(at);
107
+ if (cached !== undefined) {
108
+ root = cached;
109
+ break;
110
+ }
111
+ seen.push(at);
112
+ const manifest = join(at, "package.json");
113
+ if (existsSync(manifest)) {
114
+ try {
115
+ const name = (JSON.parse(readFileSync(manifest, "utf-8")) as { name?: unknown }).name;
116
+ root = typeof name === "string" && name.startsWith("@intentius/chant-lexicon-") ? at : null;
117
+ } catch {
118
+ root = null;
119
+ }
120
+ break;
121
+ }
122
+ const parent = dirname(at);
123
+ if (parent === at) break;
124
+ at = parent;
125
+ }
126
+ for (const at of seen) lexiconRoots.set(at, root);
127
+ return root ?? undefined;
128
+ }
129
+
130
+ /**
131
+ * #3981 — the compiled module for a lexicon's `src/*.ts`: `<lexicon>/dist/*.js`
132
+ * when it exists, as a URL, else `undefined`. A lexicon ships both, and its
133
+ * exports name the compiled one; fold reads a composite from the source, and
134
+ * an import that source makes must land on the module the plugin and the
135
+ * project already loaded, with no TypeScript loader.
136
+ */
137
+ export function lexiconCompiledTwin(url: string): string | undefined {
138
+ if (!url.startsWith("file:")) return undefined;
139
+ const path = fileURLToPath(url.replace(/[?#].*$/, ""));
140
+ if (!/\.[cm]?ts$/.test(path) || /\.d\.[cm]?ts$/.test(path) || !path.includes(`${sep}src${sep}`)) return undefined;
141
+ const root = lexiconRootOf(dirname(path));
142
+ if (root === undefined) return undefined;
143
+ const srcDir = join(root, "src") + sep;
144
+ if (!path.startsWith(srcDir)) return undefined;
145
+ const js = join(root, "dist", path.slice(srcDir.length)).replace(/\.([cm]?)ts$/, ".$1js");
146
+ return existsSync(js) ? pathToFileURL(js).href : undefined;
147
+ }
148
+
149
+ const SOURCE_RESOLUTION = Symbol.for("chant.sourceResolution");
150
+
151
+ /**
152
+ * #3981 — the `development` target a chant package's `exports` give
153
+ * `specifier`, found from `parentPath`'s directory up through each
154
+ * `node_modules`, or `undefined` when there is none or its file does not
155
+ * exist. Reads exact keys and single-`*` patterns, the shapes chant's
156
+ * packages use; anything else is left to Node.
157
+ */
158
+ export function chantPackageSourceTarget(specifier: string, parentPath: string): string | undefined {
159
+ const match = /^(@intentius\/chant(?:-[a-z0-9-]+)?)(\/.*)?$/.exec(specifier);
160
+ if (!match) return undefined;
161
+ const subpath = match[2] ? `.${match[2]}` : ".";
162
+ let dir = dirname(parentPath);
163
+ for (;;) {
164
+ const pkgDir = join(dir, "node_modules", match[1]!);
165
+ const manifest = join(pkgDir, "package.json");
166
+ if (existsSync(manifest)) {
167
+ let exportsField: unknown;
168
+ try {
169
+ exportsField = (JSON.parse(readFileSync(manifest, "utf-8")) as { exports?: unknown }).exports;
170
+ } catch {
171
+ return undefined;
172
+ }
173
+ if (!exportsField || typeof exportsField !== "object") return undefined;
174
+ const map = exportsField as Record<string, unknown>;
175
+ let entry: unknown = map[subpath];
176
+ let star: string | undefined;
177
+ if (entry === undefined) {
178
+ let best = "";
179
+ for (const key of Object.keys(map)) {
180
+ const at = key.indexOf("*");
181
+ if (at < 0 || key.indexOf("*", at + 1) >= 0) continue;
182
+ const prefix = key.slice(0, at);
183
+ const suffix = key.slice(at + 1);
184
+ if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix) || subpath.length < key.length || prefix.length <= best.length) continue;
185
+ best = prefix;
186
+ entry = map[key];
187
+ star = subpath.slice(prefix.length, subpath.length - suffix.length);
188
+ }
189
+ }
190
+ const development = entry && typeof entry === "object" ? (entry as { development?: unknown }).development : undefined;
191
+ if (typeof development !== "string") return undefined;
192
+ const target = join(pkgDir, star === undefined ? development : development.split("*").join(star));
193
+ // Node keys a module by its real path, so a symlinked package must too.
194
+ return existsSync(target) ? realpathSync(target) : undefined;
195
+ }
196
+ const parent = dirname(dir);
197
+ if (parent === dir) return undefined;
198
+ dir = parent;
199
+ }
200
+ }
201
+
202
+ /**
203
+ * #3981 — from source, resolve chant's own packages through their
204
+ * `development` export condition, as `node --conditions=development` (which
205
+ * the repository's `bin/chant` passes) would. A lexicon's `default` export is
206
+ * its compiled `dist/*.js`, which a checkout may not have built, or built from
207
+ * older source; `development` is its `src/*.ts`, which vitest, a project's
208
+ * tsconfig `paths` and fold's own resolution (`SOURCE_EXPORT_CONDITIONS`)
209
+ * pick. Without this, a CLI run from source by `node --import tsx` would load
210
+ * a plugin from `dist/` and fold the project against `src/`: two copies of the
211
+ * lexicon with their own classes.
212
+ *
213
+ * The hook resolves the specifier itself and short-circuits: conditions added
214
+ * to the context here do not reach tsx's resolver. Nothing is installed under
215
+ * vitest, whose resolver already adds the condition, or on a node without
216
+ * `module.registerHooks`.
217
+ */
218
+ function installSourceResolution(): void {
219
+ // In a vitest worker (not a CLI it spawns, which inherits its environment), vite resolves.
220
+ if ("__vitest_worker__" in globalThis || typeof registerHooks !== "function") return;
221
+ const flags = globalThis as { [SOURCE_RESOLUTION]?: boolean };
222
+ if (flags[SOURCE_RESOLUTION]) return;
223
+ flags[SOURCE_RESOLUTION] = true;
224
+ registerHooks({
225
+ resolve(specifier, context, nextResolve) {
226
+ const parentURL = (context as { parentURL?: string } | undefined)?.parentURL;
227
+ if (specifier.startsWith("@intentius/chant") && parentURL?.startsWith("file:")) {
228
+ const target = chantPackageSourceTarget(specifier, fileURLToPath(parentURL));
229
+ if (target !== undefined) return { url: pathToFileURL(target).href, shortCircuit: true };
230
+ }
231
+ return nextResolve(specifier, context);
232
+ },
233
+ });
234
+ }
235
+
89
236
  /**
90
237
  * Register tsx for the ESM and CommonJS loaders, once, before importing
91
238
  * anything that may be TypeScript. A no-op from source, where tsx (or vitest)
@@ -104,19 +251,39 @@ export function ensureTypeScriptLoader(): boolean {
104
251
  return true;
105
252
  }
106
253
 
254
+ /**
255
+ * #3981 — register tsx only when the module about to be imported is not
256
+ * JavaScript. `pathOrUrl` is the resolved module, a path or a file URL. A
257
+ * compiled lexicon (`dist/*.js`) imports with plain node, so a command that
258
+ * loads only compiled modules never pays for tsx's hooks thread. A `.ts`
259
+ * module, or one with no extension, registers tsx as before. Returns what
260
+ * {@link ensureTypeScriptLoader} does, or whether tsx is already registered
261
+ * when the module is JavaScript.
262
+ */
263
+ export function ensureTypeScriptLoaderFor(pathOrUrl: string): boolean {
264
+ if (!isJavaScriptModule(pathOrUrl)) return ensureTypeScriptLoader();
265
+ return compiledState()?.tsx ?? false;
266
+ }
267
+
268
+ /**
269
+ * #3981 — whether a caller about to import a module of unknown kind should
270
+ * resolve it first: true only in the compiled CLI before tsx is registered.
271
+ * From source, and once tsx is in, every module loads either way.
272
+ */
273
+ export function typeScriptLoaderPending(): boolean {
274
+ const state = compiledState();
275
+ return state !== undefined && !state.tsx;
276
+ }
277
+
107
278
  /**
108
279
  * The source directory for a module of chant's own: `dirname` of `moduleUrl`
109
280
  * from source, and the matching directory under `<package>/src` when the
110
281
  * module runs from `<package>/dist`. The sandbox drivers hand chant's own
111
282
  * modules to esbuild by path, and must name the TypeScript source the
112
283
  * project's own `@intentius/chant/*` imports resolve to. String arithmetic
113
- * only: no filesystem access, so it is safe inside a sandboxed child.
284
+ * only: no filesystem access, so it is safe inside a sandboxed child. The
285
+ * same mapping as {@link moduleSourceDir}, which lexicons use.
114
286
  */
115
287
  export function chantSourceDir(moduleUrl: string): string {
116
- const dir = dirname(fileURLToPath(moduleUrl));
117
- if (!moduleUrl.endsWith(".js")) return dir;
118
- const marker = `${sep}dist${sep}`;
119
- const at = (dir + sep).lastIndexOf(marker);
120
- if (at < 0) return dir;
121
- return dir.slice(0, at) + `${sep}src` + dir.slice(at + marker.length - 1);
288
+ return moduleSourceDir(moduleUrl);
122
289
  }
@@ -292,7 +292,7 @@ describe("the forward coverage check (#2773)", () => {
292
292
  });
293
293
 
294
294
  test("on a work branch, the range and the item come from the branch (an Op under changesCheckout)", async () => {
295
- const tree = join(root, ".git", "chant-work", "W-001");
295
+ const tree = join(root, ".chant", "work", "W-001");
296
296
  git(root, "worktree", "add", "-q", "-b", "chant/work/W-001", tree, "HEAD");
297
297
  writeFiles(tree, { "app/lib/a.mjs": "export const a = 2;\n", "docs/readme.md": "# Docs, again\n" });
298
298
  git(tree, "add", "-A");
@@ -0,0 +1,33 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { rewriteFountainFile } from "./fountain-retired-kinds";
5
+
6
+ const fixture = JSON.parse(readFileSync(join(import.meta.dirname, "../__fixtures__/studio-template-84c8b21.json"), "utf-8")) as { files: Record<string, string> };
7
+ const team = fixture.files["delivery/agents/team.ts"]!;
8
+
9
+ describe("fountain-retired-kinds (#3987)", () => {
10
+ test("the studio template's team.ts loses its Teammates, Schedules and persistent sandbox, and keeps its Agents", () => {
11
+ const r = rewriteFountainFile("delivery/agents/team.ts", team)!;
12
+ expect(r.conflict).toBeUndefined();
13
+ expect(r.text).toContain('import { Agent, Environment, Vault } from "@intentius/chant-lexicon-fountain";');
14
+ expect(r.text).not.toMatch(/new (Teammate|Schedule)\(|sandbox_mode|nightlyMeta|Teammates below/);
15
+ for (const agent of ["formalizer", "splitter", "builderSmall", "builderMedium", "builderLarge", "dispatcher"]) expect(r.text).toContain(`export const ${agent} = new Agent({`);
16
+ expect(r.text.endsWith("});\n")).toBe(true);
17
+ expect(r.notMoved.map((n) => n.what)).toEqual([
18
+ 'the Schedule nightly (cron "0 3 * * *") in delivery/agents/team.ts',
19
+ 'the Schedule factoryDispatch (cron "*/15 * * * *") in delivery/agents/team.ts',
20
+ ]);
21
+ expect(rewriteFountainFile("delivery/agents/team.ts", r.text)).toBeNull();
22
+ });
23
+
24
+ test("a removed declaration the file still uses is a conflict", () => {
25
+ const text = `${team}\nexport const crew = [factory];\n`;
26
+ expect(rewriteFountainFile("delivery/agents/team.ts", text)!.conflict).toMatch(/^factory is still used/);
27
+ });
28
+
29
+ test("a file with no retired kind plans nothing", () => {
30
+ const text = 'import { Agent } from "@intentius/chant-lexicon-fountain";\nexport const a = new Agent({\n name: "a",\n});\n';
31
+ expect(rewriteFountainFile("agents.ts", text)).toBeNull();
32
+ });
33
+ });
@@ -0,0 +1,190 @@
1
+ /**
2
+ * `fountain-retired-kinds` (#3987, managoat/managoat#2793): take a project's
3
+ * fountain declarations off the kinds Managoat v0.29.0 removed.
4
+ *
5
+ * Managoat dropped the team, its schedules, webhooks and persistent
6
+ * sandboxes. Since #3987 the fountain lexicon keeps `Teammate`, `Schedule`
7
+ * and `Webhook` only as stubs, and FTN025 fails a build that declares one, or
8
+ * an Agent with `sandbox_mode: "persistent"`. chud's template (and the studio
9
+ * kit's copy of it) declares a Teammate and a Schedule for its night shift
10
+ * and its factory, and gives the dispatcher a persistent sandbox, so a repo
11
+ * migrated off chud stopped building with chant 0.124.32.
12
+ *
13
+ * In each `chant` member, every `.ts` file that imports the fountain lexicon
14
+ * loses its `new Teammate(...)`, `new Schedule(...)` and `new Webhook(...)`
15
+ * declarations, those names from the import, any one-line constant only they
16
+ * used, and `sandbox_mode: "persistent"`. Each removed Schedule is listed as
17
+ * not moved: its clock now lives outside Managoat. A removed declaration whose
18
+ * name the file still uses elsewhere is a conflict, as is a file that still
19
+ * names a retired kind after the edits.
20
+ *
21
+ * The plan is empty once no fountain file declares a retired kind.
22
+ */
23
+
24
+ import { existsSync, readdirSync } from "node:fs";
25
+ import { join, posix } from "node:path";
26
+ import type { ChantMigration, ChantMigrationContext, ChantMigrationPlan, NotMoved, PlanConflict, PlannedChange } from "../chant-migrations";
27
+ import { applyEdits, readText, type Edit } from "./chud-lexicon-exit";
28
+
29
+ export const FOUNTAIN_RETIRED_KINDS = "fountain-retired-kinds";
30
+
31
+ const DESCRIPTION = "drop the fountain kinds Managoat removed: Teammate, Schedule, Webhook and persistent sandboxes (#3987)";
32
+
33
+ const FOUNTAIN = "@intentius/chant-lexicon-fountain";
34
+ const RETIRED = ["Teammate", "Schedule", "Webhook"] as const;
35
+
36
+ const IMPORT = /import\s*\{([^}]*)\}\s*from\s*"@intentius\/chant-lexicon-fountain";/;
37
+ /** A top-level declaration of a retired kind, with the doc comment just above it. */
38
+ const DECLARATION = /(?:\/\*\*(?:(?!\*\/)[\s\S])*\*\/\n)?export const (\w+) = new (Teammate|Schedule|Webhook)\(\{\n[\s\S]*?\n\}\);\n(?:\n)?/g;
39
+ const PERSISTENT = /^[ \t]*sandbox_mode:\s*"persistent",?[ \t]*\n/gm;
40
+
41
+ /** The template's prose about what the edits remove. */
42
+ const TEMPLATE_PROSE: Edit[] = [
43
+ {
44
+ find: " * build by this name (CHUD_FOUNTAIN_VAULT overrides it), and the two\n * Teammates below run with it.\n",
45
+ replace: " * build by this name (CHUD_FOUNTAIN_VAULT overrides it).\n",
46
+ },
47
+ {
48
+ find:
49
+ " * Steward does: a persistent sandbox (its checkout survives a turn) and no\n" +
50
+ " * permission prompts (nobody is at the keyboard; chant's own gates still need\n" +
51
+ " * a person). Its Teammate gives it one thread, so two schedule fires never\n" +
52
+ " * run two dispatchers on one checkout, and the Schedule is what triggers it.\n",
53
+ replace:
54
+ " * Steward does, with no permission prompts (nobody is at the keyboard;\n" +
55
+ " * chant's own gates still need a person). Managoat has no team or scheduler\n" +
56
+ " * any more: a clock outside it runs `chant run dispatch --on fountain`.\n",
57
+ },
58
+ ];
59
+
60
+ /** The README row `chud-lexicon-exit` wrote, which names the factory's Schedule. */
61
+ const README_EDITS: Edit[] = [
62
+ { find: " (the agents that build the app, as fountain specs; its factory Schedule still calls the kit's dispatch Op)", replace: " (the agents that build the app, as fountain specs)" },
63
+ ];
64
+
65
+ function tsFiles(dir: string, rel = "", out: string[] = []): string[] {
66
+ let entries;
67
+ try {
68
+ entries = readdirSync(join(dir, rel), { withFileTypes: true });
69
+ } catch {
70
+ return out;
71
+ }
72
+ for (const e of entries) {
73
+ const path = rel ? posix.join(rel, e.name) : e.name;
74
+ if (e.isDirectory()) {
75
+ if (e.name === "node_modules" || e.name === "dist" || e.name.startsWith(".")) continue;
76
+ tsFiles(dir, path, out);
77
+ } else if (e.name.endsWith(".ts") && !e.name.endsWith(".d.ts")) {
78
+ out.push(path);
79
+ }
80
+ }
81
+ return out;
82
+ }
83
+
84
+ /** The text with its comments and string contents blanked, so prose naming a kind is not a use of it. */
85
+ const code = (text: string) => text.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*'|`(?:\\.|[^`\\])*`/g, '""');
86
+ const uses = (text: string, name: string) => new RegExp(`(?<![\\w$.])${name}(?![\\w$])`).test(code(text));
87
+
88
+ interface Rewrite {
89
+ text: string;
90
+ notMoved: NotMoved[];
91
+ conflict?: string;
92
+ }
93
+
94
+ /** The file without its retired declarations, or null when it has none. */
95
+ export function rewriteFountainFile(path: string, original: string): Rewrite | null {
96
+ const imported = original.match(IMPORT);
97
+ if (!imported) return null;
98
+ const declared = [...original.matchAll(DECLARATION)];
99
+ const persistent = PERSISTENT.test(original);
100
+ PERSISTENT.lastIndex = 0;
101
+ if (declared.length === 0 && !persistent) return null;
102
+
103
+ const notMoved: NotMoved[] = [];
104
+ let text = original;
105
+ const removed: string[] = [];
106
+ const referenced = new Set<string>();
107
+ for (const [block, name, kind] of declared) {
108
+ text = text.replace(block, "");
109
+ removed.push(name!);
110
+ const body = block.slice(block.indexOf("new "));
111
+ for (const id of body.match(/\b[a-zA-Z_$][\w$]*\b/g) ?? []) referenced.add(id);
112
+ if (kind === "Schedule") {
113
+ const cron = body.match(/cron:\s*"([^"]+)"/)?.[1];
114
+ notMoved.push({
115
+ what: `the Schedule ${name}${cron ? ` (cron "${cron}")` : ""} in ${path}`,
116
+ where: "a clock outside Managoat, which has no scheduler: a CI schedule or a CronJob running `chant run <op> --on fountain`, or `chant operator --steward`",
117
+ });
118
+ } else if (kind === "Webhook") {
119
+ notMoved.push({ what: `the Webhook ${name} in ${path}`, where: "nothing: read a run's outcome from `chant run status` or `log`, or the conversation's event stream" });
120
+ }
121
+ }
122
+ text = text.replace(PERSISTENT, "");
123
+
124
+ // A one-line constant only the removed declarations used (the template's `nightlyMeta`).
125
+ for (const id of referenced) {
126
+ const line = new RegExp(`^export const ${id} = [^\\n]*;\\n\\n?`, "m");
127
+ const hit = text.match(line);
128
+ if (hit && !uses(text.replace(hit[0], ""), id)) text = text.replace(hit[0], "");
129
+ }
130
+
131
+ const names = imported[1]!.split(",").map((n) => n.trim()).filter(Boolean);
132
+ const kept = names.filter((n) => !(RETIRED as readonly string[]).includes(n) || uses(text.replace(IMPORT, ""), n));
133
+ if (kept.length !== names.length) text = text.replace(IMPORT, `import { ${kept.join(", ")} } from "${FOUNTAIN}";`);
134
+ text = applyEdits(text, TEMPLATE_PROSE).text.replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "\n");
135
+
136
+ const dangling = removed.filter((n) => uses(text, n));
137
+ if (dangling.length > 0) return { text, notMoved, conflict: `${dangling.join(", ")} ${dangling.length === 1 ? "is" : "are"} still used after the retired declarations go. Remove ${dangling.length === 1 ? "that use" : "those uses"} by hand, then run the upgrade again` };
138
+ const still = RETIRED.filter((k) => new RegExp(`\\bnew ${k}\\(`).test(text));
139
+ if (still.length > 0) return { text, notMoved, conflict: `a ${still.join(", ")} declaration this migration cannot read. Remove it by hand, then run the upgrade again` };
140
+ return { text, notMoved };
141
+ }
142
+
143
+ interface Declaration {
144
+ members?: Array<{ dir?: string; kind?: string }>;
145
+ }
146
+
147
+ function planRetiredKinds(ctx: ChantMigrationContext): ChantMigrationPlan | null {
148
+ const { dir } = ctx;
149
+ let decl: Declaration | null = null;
150
+ try {
151
+ decl = JSON.parse(readText(dir, "chant.workspace.json") ?? "null") as Declaration | null;
152
+ } catch {
153
+ decl = null;
154
+ }
155
+ const members = (decl?.members ?? []).filter((m) => m.kind === "chant" && m.dir).map((m) => posix.normalize(m.dir!));
156
+ if (members.length === 0 && existsSync(join(dir, "chant.config.ts"))) members.push(".");
157
+
158
+ const changes: PlannedChange[] = [];
159
+ const notMoved: NotMoved[] = [];
160
+ const conflicts: PlanConflict[] = [];
161
+ for (const member of members) {
162
+ for (const rel of tsFiles(join(dir, member))) {
163
+ const path = posix.join(member, rel);
164
+ const text = readText(dir, path);
165
+ if (text === undefined || !text.includes(FOUNTAIN)) continue;
166
+ const rewrite = rewriteFountainFile(path, text);
167
+ if (!rewrite) continue;
168
+ if (rewrite.conflict) {
169
+ conflicts.push({ path, reason: rewrite.conflict });
170
+ continue;
171
+ }
172
+ changes.push({ path, action: "write", why: "no Teammate, Schedule, Webhook or persistent sandbox, which Managoat removed and FTN025 fails", data: Buffer.from(rewrite.text) });
173
+ notMoved.push(...rewrite.notMoved);
174
+ }
175
+ }
176
+ if (changes.length === 0 && conflicts.length === 0) return null;
177
+
178
+ const readme = readText(dir, "README.md");
179
+ if (readme !== undefined && conflicts.length === 0) {
180
+ const edited = applyEdits(readme, README_EDITS).text;
181
+ if (edited !== readme) changes.push({ path: "README.md", action: "write", why: "the agents no longer declare a factory Schedule", data: Buffer.from(edited) });
182
+ }
183
+ return { id: FOUNTAIN_RETIRED_KINDS, description: DESCRIPTION, changes, notMoved, conflicts };
184
+ }
185
+
186
+ export const fountainRetiredKinds: ChantMigration = {
187
+ id: FOUNTAIN_RETIRED_KINDS,
188
+ description: DESCRIPTION,
189
+ plan: planRetiredKinds,
190
+ };
@@ -34,6 +34,7 @@ import { CHUD_LEXICON_EXIT_ROLLBACK } from "./chant-migrations/chud-lexicon-exit
34
34
  import { CHUD_LEXICON_EXIT_SHIP_POLICY } from "./chant-migrations/chud-lexicon-exit-ship-policy";
35
35
  import { CHUD_LEXICON_EXIT_FLY_SITE, chudLexiconExitFlySite } from "./chant-migrations/chud-lexicon-exit-fly-site";
36
36
  import { CHUD_LEXICON_EXIT_LIVE_NAMES } from "./chant-migrations/chud-lexicon-exit-live-names";
37
+ import { FOUNTAIN_RETIRED_KINDS } from "./chant-migrations/fountain-retired-kinds";
37
38
  import { readerVersion } from "./declaration";
38
39
  import { runChecks } from "./lineage-check";
39
40
  import { initFromCommand } from "./lineage-init";
@@ -174,6 +175,10 @@ describe.each(["dir", "git"] as const)("chud-lexicon-exit, from a %s source", (f
174
175
  expect(out).toContain(`chant migration: ${CHUD_LEXICON_EXIT_ROLLBACK} (applied)`);
175
176
  expect(out).toContain("write: delivery/ops/rollback.op.ts");
176
177
  expect(out).not.toContain("not moved: the release's steps after the ship gate");
178
+ // #3987: the template's Teammates, Schedules and persistent sandbox fail FTN025, so a migrated repo would not build.
179
+ expect(out).toContain(`chant migration: ${FOUNTAIN_RETIRED_KINDS} (applied)`);
180
+ expect(out).toContain("write: delivery/agents/team.ts");
181
+ expect(out).toMatch(/not moved: the Schedule factoryDispatch \(cron "\*\/15 \* \* \* \*"\) in delivery\/agents\/team\.ts -> a clock outside Managoat/);
177
182
  // #2805: the plan lists README.md, CLAUDE.md and design/CLAUDE.md as
178
183
  // changed, not silently left describing what the migration just deleted.
179
184
  expect(out).toContain("write: README.md");
@@ -42,6 +42,7 @@ import { chudLexiconExitShipInputs } from "./chant-migrations/chud-lexicon-exit-
42
42
  import { chudLexiconExitShipPolicy } from "./chant-migrations/chud-lexicon-exit-ship-policy";
43
43
  import { chudLexiconExitFlySite } from "./chant-migrations/chud-lexicon-exit-fly-site";
44
44
  import { chudLexiconExitLiveNames } from "./chant-migrations/chud-lexicon-exit-live-names";
45
+ import { fountainRetiredKinds } from "./chant-migrations/fountain-retired-kinds";
45
46
 
46
47
  /** One file a plan writes or deletes, relative to the scope. */
47
48
  export interface PlannedChange {
@@ -98,6 +99,7 @@ export const CHANT_MIGRATIONS: readonly ChantMigration[] = [
98
99
  chudLexiconExitShipPolicy,
99
100
  chudLexiconExitFlySite,
100
101
  chudLexiconExitLiveNames,
102
+ fountainRetiredKinds,
101
103
  ];
102
104
 
103
105
  /** The plans for a scope: each migration not yet in the lineage that finds something to move. */
@@ -28,15 +28,15 @@
28
28
  * listed here after these two.
29
29
  *
30
30
  * WSP125 (`box-fountain-callback-undeclared`, #2780) is the one credential a
31
- * box gets without asking. fountain v0.21.0 gives every persistent sandbox a
32
- * callback token scoped to its owner (`FOUNTAIN_TOKEN`, `sandbox_api_access:
33
- * owner`), and a fountain `Box` is always persistent, so its member's block
34
- * has to say so: the capability `fountain-callback`, brokered by `fountain`,
31
+ * box gets without asking. fountain gives every sandbox a callback token
32
+ * scoped to its owner (`FOUNTAIN_TOKEN`, `sandbox_api_access: owner`) unless
33
+ * its conversation asks for none, and a fountain `Box` runs with it, so its
34
+ * member's block has to say so: the capability `fountain-callback`, brokered by `fountain`,
35
35
  * with scope `owner`. It is named apart from `fountain`, the API access a
36
36
  * lobby may broker for the same box. A member builds a Box when one of its TypeScript or JavaScript files
37
37
  * imports `Box` from `@intentius/chant-lexicon-fountain` and calls it, read as
38
- * syntax and never run. Upstream, managoat/fountain#2497 asks for a way to run
39
- * a persistent agent with no callback token.
38
+ * syntax and never run. Upstream, managoat/fountain#2497 asked for a way to run
39
+ * a box's agent with no callback token.
40
40
  *
41
41
  * WSP126 (`box-intent-unknown`) and WSP127 (`box-intent-unconstrained`), #2850,
42
42
  * read the decision record a box block names as its intent (`box-intent.ts`).
@@ -75,7 +75,7 @@ export const WSP_BOX_FOUNTAIN_CALLBACK = "WSP125";
75
75
  export const WSP_BOX_INTENT_UNKNOWN = "WSP126";
76
76
  export const WSP_BOX_INTENT_UNCONSTRAINED = "WSP127";
77
77
 
78
- /** The fountain lexicon's package, whose `Box` composite runs a persistent sandbox. */
78
+ /** The fountain lexicon's package, whose `Box` composite declares a box's agent. */
79
79
  const FOUNTAIN_LEXICON = "@intentius/chant-lexicon-fountain";
80
80
 
81
81
  /**
@@ -402,7 +402,7 @@ export const BOX_CHECKS: readonly WorkspaceCheck[] = [
402
402
  id: WSP_BOX_FOUNTAIN_CALLBACK,
403
403
  name: "box-fountain-callback-undeclared",
404
404
  description:
405
- "A box member that builds a fountain Box declares the callback token fountain gives its persistent sandbox: the fountain-callback capability, brokered by fountain, with scope owner.",
405
+ "A box member that builds a fountain Box declares the callback token fountain gives its sandbox: the fountain-callback capability, brokered by fountain, with scope owner.",
406
406
  severity: "error",
407
407
  configurable: true,
408
408
  check(ctx) {
@@ -442,7 +442,7 @@ export const BOX_CHECKS: readonly WorkspaceCheck[] = [
442
442
  severity: this.severity,
443
443
  code: "box-fountain-callback-undeclared",
444
444
  message:
445
- `box-fountain-callback-undeclared: member ${m.name} builds a fountain Box (${at.path}), and fountain v0.21.0 gives a persistent box's sandbox ` +
445
+ `box-fountain-callback-undeclared: member ${m.name} builds a fountain Box (${at.path}), and fountain gives a box's sandbox ` +
446
446
  `a callback token scoped to its owner (FOUNTAIN_TOKEN); ${why}. Declare ${expected} in the box block ` +
447
447
  `(managoat/fountain#2497 tracks running without it)`,
448
448
  entity: m.name,
@@ -32,8 +32,8 @@ export interface CheckRun {
32
32
  url: string | null;
33
33
  /**
34
34
  * Set when the "check run" is a chant Op run (#4044, `ci.green.source:
35
- * "runs"`): the run's id and env, the runner whose statement it carries,
36
- * and that statement. `name` is then the Op's name and `id` the run's end
35
+ * "runs"`): the run's id and env, and the runner whose statement it
36
+ * carries with that statement, or a null signer for an unsigned run. `name` is then the Op's name and `id` the run's end
37
37
  * time in milliseconds, which orders a re-run after the run it replaced.
38
38
  */
39
39
  opRun?: OpRunRef;
@@ -45,8 +45,10 @@ export interface CheckRun {
45
45
  export interface OpRunRef {
46
46
  id: string;
47
47
  env: string;
48
- signer: string;
49
- statement: NonNullable<OpRunRecord["statement"]>;
48
+ /** The runner whose statement the run carries, or null for an unsigned run, which counts unless `ci.green.signedSince` asks for one (#4156). */
49
+ signer: string | null;
50
+ /** The run's statement, when it has one. */
51
+ statement?: NonNullable<OpRunRecord["statement"]>;
50
52
  }
51
53
 
52
54
  export interface CiForge {