@intentius/chant 0.101.0 → 0.102.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (427) hide show
  1. package/dist/agents/checks.d.ts.map +1 -1
  2. package/dist/attrref.d.ts.map +1 -1
  3. package/dist/audit/discover.d.ts.map +1 -1
  4. package/dist/audit/nginx.d.ts.map +1 -1
  5. package/dist/audit/prior-art.d.ts +48 -0
  6. package/dist/audit/prior-art.d.ts.map +1 -1
  7. package/dist/behaviour-kinds.d.ts.map +1 -1
  8. package/dist/builder.d.ts.map +1 -1
  9. package/dist/cli/commands/carve-emit.d.ts.map +1 -1
  10. package/dist/cli/commands/check-lexicon.d.ts +16 -0
  11. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  12. package/dist/cli/commands/import.d.ts +8 -0
  13. package/dist/cli/commands/import.d.ts.map +1 -1
  14. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  15. package/dist/cli/commands/init.d.ts +19 -0
  16. package/dist/cli/commands/init.d.ts.map +1 -1
  17. package/dist/cli/commands/onboard.d.ts +9 -0
  18. package/dist/cli/commands/onboard.d.ts.map +1 -1
  19. package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
  20. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
  21. package/dist/cli/handlers/dev.d.ts.map +1 -1
  22. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  23. package/dist/cli/handlers/misc.d.ts.map +1 -1
  24. package/dist/cli/handlers/operator.d.ts +9 -0
  25. package/dist/cli/handlers/operator.d.ts.map +1 -1
  26. package/dist/cli/handlers/run.d.ts.map +1 -1
  27. package/dist/cli/lsp/server.d.ts.map +1 -1
  28. package/dist/cli/main.d.ts.map +1 -1
  29. package/dist/cli/mcp/server.d.ts.map +1 -1
  30. package/dist/cli/mcp/tools/build.d.ts.map +1 -1
  31. package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
  32. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  33. package/dist/cli/mcp/tools/import.d.ts.map +1 -1
  34. package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
  35. package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
  36. package/dist/cli/mcp/tools/search.d.ts.map +1 -1
  37. package/dist/cli/mcp/workspace-tools.d.ts +1 -1
  38. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  39. package/dist/cli/registry.d.ts +11 -2
  40. package/dist/cli/registry.d.ts.map +1 -1
  41. package/dist/codegen/fetch.d.ts.map +1 -1
  42. package/dist/codegen/naming.d.ts.map +1 -1
  43. package/dist/codegen/pinned-upgrade.d.ts +67 -3
  44. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  45. package/dist/codegen/registry.d.ts.map +1 -1
  46. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  47. package/dist/components/capability.d.ts.map +1 -1
  48. package/dist/components/driver.d.ts.map +1 -1
  49. package/dist/components/fan-out.d.ts.map +1 -1
  50. package/dist/components/starter-plugin.d.ts.map +1 -1
  51. package/dist/components/verbs/process-runner.d.ts.map +1 -1
  52. package/dist/components/verbs/r2-sync.d.ts.map +1 -1
  53. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  54. package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
  55. package/dist/components/verbs/sign.d.ts.map +1 -1
  56. package/dist/components/verbs/verify.d.ts.map +1 -1
  57. package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
  58. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  59. package/dist/components/verbs/wrangler.d.ts.map +1 -1
  60. package/dist/composite.d.ts.map +1 -1
  61. package/dist/config.d.ts +19 -7
  62. package/dist/config.d.ts.map +1 -1
  63. package/dist/declarable.d.ts +16 -0
  64. package/dist/declarable.d.ts.map +1 -1
  65. package/dist/discovery/cache.d.ts.map +1 -1
  66. package/dist/discovery/fold-import.d.ts +15 -5
  67. package/dist/discovery/fold-import.d.ts.map +1 -1
  68. package/dist/discovery/param-deps.d.ts +16 -0
  69. package/dist/discovery/param-deps.d.ts.map +1 -1
  70. package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
  71. package/dist/discovery/specifier-resolve.d.ts +37 -0
  72. package/dist/discovery/specifier-resolve.d.ts.map +1 -0
  73. package/dist/errors.d.ts.map +1 -1
  74. package/dist/fold/fold.d.ts +3 -1
  75. package/dist/fold/fold.d.ts.map +1 -1
  76. package/dist/graph-detail.d.ts.map +1 -1
  77. package/dist/graph-ir.d.ts.map +1 -1
  78. package/dist/graph-layout.d.ts.map +1 -1
  79. package/dist/import/base-parser.d.ts.map +1 -1
  80. package/dist/import/embedded.d.ts +7 -0
  81. package/dist/import/embedded.d.ts.map +1 -1
  82. package/dist/import/parser-options.d.ts +14 -0
  83. package/dist/import/parser-options.d.ts.map +1 -0
  84. package/dist/import/parser.d.ts +17 -0
  85. package/dist/import/parser.d.ts.map +1 -1
  86. package/dist/kubectl-context.d.ts.map +1 -1
  87. package/dist/lexicon-output.d.ts.map +1 -1
  88. package/dist/lexicon-schema.d.ts +3 -3
  89. package/dist/lexicon.d.ts +55 -3
  90. package/dist/lexicon.d.ts.map +1 -1
  91. package/dist/lifecycle/assert-live.d.ts.map +1 -1
  92. package/dist/lifecycle/gate-ledger.d.ts +15 -0
  93. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  94. package/dist/lifecycle/git.d.ts.map +1 -1
  95. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  96. package/dist/lifecycle/observe.d.ts.map +1 -1
  97. package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
  98. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  99. package/dist/lifecycle/scenario.d.ts.map +1 -1
  100. package/dist/lifecycle/status.d.ts.map +1 -1
  101. package/dist/lifecycle/work-lease.d.ts +61 -2
  102. package/dist/lifecycle/work-lease.d.ts.map +1 -1
  103. package/dist/lint/config.d.ts +8 -8
  104. package/dist/lint/engine.d.ts +6 -0
  105. package/dist/lint/engine.d.ts.map +1 -1
  106. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  107. package/dist/op/activities/activity-contracts.d.ts +11 -11
  108. package/dist/op/activities/apply.d.ts +79 -9
  109. package/dist/op/activities/apply.d.ts.map +1 -1
  110. package/dist/op/activities/decide.d.ts.map +1 -1
  111. package/dist/op/activities/index.d.ts +1 -1
  112. package/dist/op/activities/index.d.ts.map +1 -1
  113. package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
  114. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
  115. package/dist/op/activity-contract.d.ts +1 -1
  116. package/dist/op/activity-failure.d.ts.map +1 -1
  117. package/dist/op/activity-profiles.d.ts.map +1 -1
  118. package/dist/op/builders.d.ts.map +1 -1
  119. package/dist/op/composites/apply-op.d.ts +12 -5
  120. package/dist/op/composites/apply-op.d.ts.map +1 -1
  121. package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
  122. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
  123. package/dist/op/decide-backend.d.ts.map +1 -1
  124. package/dist/op/decide-read-inputs.d.ts.map +1 -1
  125. package/dist/op/emulator-lifecycle.d.ts +21 -2
  126. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  127. package/dist/op/gate-approval.d.ts +5 -1
  128. package/dist/op/gate-approval.d.ts.map +1 -1
  129. package/dist/op/gate-wait.d.ts.map +1 -1
  130. package/dist/op/gate.d.ts +19 -3
  131. package/dist/op/gate.d.ts.map +1 -1
  132. package/dist/op/index.d.ts +2 -0
  133. package/dist/op/index.d.ts.map +1 -1
  134. package/dist/op/local-executor.d.ts +6 -0
  135. package/dist/op/local-executor.d.ts.map +1 -1
  136. package/dist/op/operator.d.ts.map +1 -1
  137. package/dist/op/run-context.d.ts +39 -0
  138. package/dist/op/run-context.d.ts.map +1 -0
  139. package/dist/op/run-live.d.ts.map +1 -1
  140. package/dist/op/runtimes/local.d.ts.map +1 -1
  141. package/dist/op/source-archive.d.ts.map +1 -1
  142. package/dist/op/steward-beside.d.ts.map +1 -1
  143. package/dist/op/steward-points.d.ts.map +1 -1
  144. package/dist/op/steward.d.ts.map +1 -1
  145. package/dist/op/types.d.ts +8 -3
  146. package/dist/op/types.d.ts.map +1 -1
  147. package/dist/op/work-lease-run.d.ts +24 -4
  148. package/dist/op/work-lease-run.d.ts.map +1 -1
  149. package/dist/project-root.d.ts.map +1 -1
  150. package/dist/provenance.d.ts +21 -0
  151. package/dist/provenance.d.ts.map +1 -1
  152. package/dist/pseudo-parameter.d.ts.map +1 -1
  153. package/dist/reconcile.d.ts.map +1 -1
  154. package/dist/secret-materialization.d.ts.map +1 -1
  155. package/dist/terraform/emit-source.d.ts.map +1 -1
  156. package/dist/terraform/parse.d.ts.map +1 -1
  157. package/dist/testing.d.ts.map +1 -1
  158. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  159. package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
  160. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
  161. package/dist/workspace/agent-cli.d.ts +5 -0
  162. package/dist/workspace/agent-cli.d.ts.map +1 -1
  163. package/dist/workspace/box-factory.d.ts +73 -0
  164. package/dist/workspace/box-factory.d.ts.map +1 -0
  165. package/dist/workspace/changes.d.ts.map +1 -1
  166. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
  167. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  168. package/dist/workspace/checks.d.ts +5 -0
  169. package/dist/workspace/checks.d.ts.map +1 -1
  170. package/dist/workspace/compose-graph.d.ts +10 -0
  171. package/dist/workspace/compose-graph.d.ts.map +1 -1
  172. package/dist/workspace/conformance/index.d.ts +67 -6
  173. package/dist/workspace/conformance/index.d.ts.map +1 -1
  174. package/dist/workspace/conformance/vitest.d.ts +12 -3
  175. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  176. package/dist/workspace/conformance/writer.d.ts +368 -0
  177. package/dist/workspace/conformance/writer.d.ts.map +1 -0
  178. package/dist/workspace/decide.d.ts +1 -1
  179. package/dist/workspace/decide.d.ts.map +1 -1
  180. package/dist/workspace/declaration.d.ts +91 -4
  181. package/dist/workspace/declaration.d.ts.map +1 -1
  182. package/dist/workspace/declaration.schema.json +234 -5
  183. package/dist/workspace/generated-files.d.ts.map +1 -1
  184. package/dist/workspace/graph-cache.d.ts.map +1 -1
  185. package/dist/workspace/graph-cli.d.ts +2 -0
  186. package/dist/workspace/graph-cli.d.ts.map +1 -1
  187. package/dist/workspace/identity.d.ts +111 -0
  188. package/dist/workspace/identity.d.ts.map +1 -0
  189. package/dist/workspace/intent-cli.d.ts +7 -4
  190. package/dist/workspace/intent-cli.d.ts.map +1 -1
  191. package/dist/workspace/intent-record.d.ts +8 -1
  192. package/dist/workspace/intent-record.d.ts.map +1 -1
  193. package/dist/workspace/intent.d.ts +149 -5
  194. package/dist/workspace/intent.d.ts.map +1 -1
  195. package/dist/workspace/kinds.d.ts +18 -4
  196. package/dist/workspace/kinds.d.ts.map +1 -1
  197. package/dist/workspace/lineage-hash-index.d.ts.map +1 -1
  198. package/dist/workspace/lineage-lock.d.ts +2 -2
  199. package/dist/workspace/lineage-lock.d.ts.map +1 -1
  200. package/dist/workspace/links.d.ts.map +1 -1
  201. package/dist/workspace/points.d.ts.map +1 -1
  202. package/dist/workspace/principal-classes.d.ts +114 -0
  203. package/dist/workspace/principal-classes.d.ts.map +1 -0
  204. package/dist/workspace/reason-codes.d.ts +21 -1
  205. package/dist/workspace/reason-codes.d.ts.map +1 -1
  206. package/dist/workspace/record-assets.d.ts.map +1 -1
  207. package/dist/workspace/records-checkout.d.ts +54 -0
  208. package/dist/workspace/records-checkout.d.ts.map +1 -0
  209. package/dist/workspace/records-cli.d.ts +27 -1
  210. package/dist/workspace/records-cli.d.ts.map +1 -1
  211. package/dist/workspace/records-close.d.ts +1 -1
  212. package/dist/workspace/records-close.d.ts.map +1 -1
  213. package/dist/workspace/records-since.d.ts.map +1 -1
  214. package/dist/workspace/records-write.d.ts +5 -4
  215. package/dist/workspace/records-write.d.ts.map +1 -1
  216. package/dist/workspace/records.d.ts +15 -0
  217. package/dist/workspace/records.d.ts.map +1 -1
  218. package/dist/workspace/runs-cli.d.ts +164 -0
  219. package/dist/workspace/runs-cli.d.ts.map +1 -0
  220. package/dist/workspace/runs.d.ts +423 -0
  221. package/dist/workspace/runs.d.ts.map +1 -0
  222. package/dist/workspace/source-block.d.ts +1 -1
  223. package/dist/workspace/source-block.d.ts.map +1 -1
  224. package/dist/workspace/status-gates.d.ts +15 -3
  225. package/dist/workspace/status-gates.d.ts.map +1 -1
  226. package/dist/workspace/status.d.ts +10 -0
  227. package/dist/workspace/status.d.ts.map +1 -1
  228. package/dist/workspace/symbols.d.ts +52 -0
  229. package/dist/workspace/symbols.d.ts.map +1 -0
  230. package/dist/workspace/trailer-joins.d.ts +46 -0
  231. package/dist/workspace/trailer-joins.d.ts.map +1 -0
  232. package/dist/workspace/trailers.d.ts +89 -0
  233. package/dist/workspace/trailers.d.ts.map +1 -0
  234. package/dist/workspace/trust/attestor.d.ts.map +1 -1
  235. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  236. package/dist/workspace/trust/seal.d.ts +33 -0
  237. package/dist/workspace/trust/seal.d.ts.map +1 -1
  238. package/dist/workspace/trust/test-repo.d.ts.map +1 -1
  239. package/dist/workspace/work-cli.d.ts +39 -3
  240. package/dist/workspace/work-cli.d.ts.map +1 -1
  241. package/dist/workspace/work-evidence.d.ts +1 -1
  242. package/dist/workspace/work-evidence.d.ts.map +1 -1
  243. package/dist/workspace/work.d.ts +26 -1
  244. package/dist/workspace/work.d.ts.map +1 -1
  245. package/dist/workspace/workspace-principals.schema.json +60 -0
  246. package/dist/workspace/write-scope.d.ts +79 -19
  247. package/dist/workspace/write-scope.d.ts.map +1 -1
  248. package/dist/yaml.d.ts +21 -39
  249. package/dist/yaml.d.ts.map +1 -1
  250. package/package.json +2 -2
  251. package/src/audit/prior-art.ts +15 -0
  252. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
  253. package/src/cli/commands/carve-emit-state.test.ts +4 -0
  254. package/src/cli/commands/carve-emit.ts +5 -19
  255. package/src/cli/commands/check-lexicon.test.ts +62 -1
  256. package/src/cli/commands/check-lexicon.ts +36 -6
  257. package/src/cli/commands/import-live.test.ts +18 -0
  258. package/src/cli/commands/import-parser-options.test.ts +133 -0
  259. package/src/cli/commands/import.ts +21 -3
  260. package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
  261. package/src/cli/commands/init.test.ts +8 -1
  262. package/src/cli/commands/init.ts +23 -8
  263. package/src/cli/commands/lint.test.ts +37 -0
  264. package/src/cli/commands/onboard.test.ts +53 -3
  265. package/src/cli/commands/onboard.ts +52 -4
  266. package/src/cli/commands/pinned-upgrade.ts +73 -33
  267. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
  268. package/src/cli/handlers/components.ts +2 -2
  269. package/src/cli/handlers/dev.ts +8 -6
  270. package/src/cli/handlers/lifecycle.test.ts +67 -0
  271. package/src/cli/handlers/lifecycle.ts +6 -5
  272. package/src/cli/handlers/misc.ts +2 -0
  273. package/src/cli/handlers/operator.ts +87 -3
  274. package/src/cli/handlers/run.test.ts +111 -0
  275. package/src/cli/handlers/run.ts +85 -24
  276. package/src/cli/handlers/scenario.ts +2 -2
  277. package/src/cli/main.test.ts +1 -0
  278. package/src/cli/main.ts +65 -11
  279. package/src/cli/mcp/workspace-tools.test.ts +5 -2
  280. package/src/cli/mcp/workspace-tools.ts +29 -2
  281. package/src/cli/registry.ts +11 -2
  282. package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
  283. package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
  284. package/src/codegen/pinned-upgrade.test.ts +104 -0
  285. package/src/codegen/pinned-upgrade.ts +250 -56
  286. package/src/codegen/testdata/postgres-tags.json +242 -0
  287. package/src/config.test.ts +21 -0
  288. package/src/config.ts +15 -0
  289. package/src/declarable.test.ts +21 -0
  290. package/src/declarable.ts +19 -0
  291. package/src/discovery/fold-composite.test.ts +41 -5
  292. package/src/discovery/fold-import.test.ts +129 -0
  293. package/src/discovery/fold-import.ts +643 -131
  294. package/src/discovery/fold-package-composite.test.ts +264 -0
  295. package/src/discovery/fold-specifiers.test.ts +191 -0
  296. package/src/discovery/param-deps.test.ts +66 -1
  297. package/src/discovery/param-deps.ts +45 -0
  298. package/src/discovery/specifier-resolve.ts +344 -0
  299. package/src/fold/fold.test.ts +39 -1
  300. package/src/fold/fold.ts +83 -9
  301. package/src/fold-provenance.test.ts +140 -0
  302. package/src/graph-ir.test.ts +83 -0
  303. package/src/graph-ir.ts +29 -0
  304. package/src/import/embedded.test.ts +20 -0
  305. package/src/import/embedded.ts +13 -0
  306. package/src/import/parser-options.ts +46 -0
  307. package/src/import/parser.ts +20 -0
  308. package/src/lexicon.ts +55 -3
  309. package/src/lifecycle/gate-ledger.ts +16 -0
  310. package/src/lifecycle/observe.test.ts +18 -0
  311. package/src/lifecycle/observe.ts +6 -4
  312. package/src/lifecycle/snapshot.ts +2 -2
  313. package/src/lifecycle/work-lease.test.ts +1 -1
  314. package/src/lifecycle/work-lease.ts +93 -2
  315. package/src/lint/engine.test.ts +55 -0
  316. package/src/lint/engine.ts +30 -4
  317. package/src/lint/output-docs.test.ts +59 -56
  318. package/src/op/activities/apply.test.ts +189 -5
  319. package/src/op/activities/apply.ts +230 -8
  320. package/src/op/activities/index.ts +3 -0
  321. package/src/op/activities/lexicon-upgrade.test.ts +160 -6
  322. package/src/op/activities/lexicon-upgrade.ts +154 -51
  323. package/src/op/composites/apply-op.ts +12 -5
  324. package/src/op/composites/composites.test.ts +30 -3
  325. package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
  326. package/src/op/composites/lexicon-upgrade-op.ts +29 -8
  327. package/src/op/emulator-lifecycle.test.ts +23 -0
  328. package/src/op/emulator-lifecycle.ts +52 -14
  329. package/src/op/gate-approval.test.ts +74 -0
  330. package/src/op/gate-approval.ts +5 -1
  331. package/src/op/gate.ts +31 -5
  332. package/src/op/index.ts +2 -0
  333. package/src/op/local-executor.test.ts +15 -0
  334. package/src/op/local-executor.ts +40 -12
  335. package/src/op/local-output.ts +1 -1
  336. package/src/op/operator.ts +4 -0
  337. package/src/op/run-context.test.ts +141 -0
  338. package/src/op/run-context.ts +51 -0
  339. package/src/op/runtimes/local.test.ts +14 -0
  340. package/src/op/runtimes/local.ts +2 -0
  341. package/src/op/steward-beside.ts +2 -0
  342. package/src/op/types.ts +8 -3
  343. package/src/op/work-lease-run.test.ts +47 -0
  344. package/src/op/work-lease-run.ts +73 -11
  345. package/src/provenance.ts +35 -0
  346. package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
  347. package/src/workspace/agent-cli.ts +7 -3
  348. package/src/workspace/agent.schema.json +22 -0
  349. package/src/workspace/box-factory.test.ts +320 -0
  350. package/src/workspace/box-factory.ts +103 -0
  351. package/src/workspace/changes.schema.json +12 -20
  352. package/src/workspace/changes.ts +4 -1
  353. package/src/workspace/checks/boxes.test.ts +2 -0
  354. package/src/workspace/checks.ts +29 -5
  355. package/src/workspace/compose-graph.ts +7 -0
  356. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +27 -0
  357. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +95 -0
  358. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +132 -0
  359. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +48 -0
  360. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +108 -0
  361. package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  362. package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  363. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +578 -0
  364. package/src/workspace/conformance/conformance.e2e.test.ts +35 -3
  365. package/src/workspace/conformance/index.mjs +30 -3
  366. package/src/workspace/conformance/index.ts +103 -15
  367. package/src/workspace/conformance/vitest.ts +44 -2
  368. package/src/workspace/conformance/writer-conformance.e2e.test.ts +131 -0
  369. package/src/workspace/conformance/writer.test.ts +156 -0
  370. package/src/workspace/conformance/writer.ts +880 -0
  371. package/src/workspace/decide.ts +7 -1
  372. package/src/workspace/declaration.schema.json +234 -5
  373. package/src/workspace/declaration.ts +174 -10
  374. package/src/workspace/graph-cli.ts +5 -2
  375. package/src/workspace/graph.schema.json +247 -0
  376. package/src/workspace/identity.test.ts +196 -0
  377. package/src/workspace/identity.ts +186 -0
  378. package/src/workspace/intent-cli.ts +64 -10
  379. package/src/workspace/intent-record.schema.json +208 -0
  380. package/src/workspace/intent-record.ts +35 -1
  381. package/src/workspace/intent-why.test.ts +274 -0
  382. package/src/workspace/intent.schema.json +718 -7
  383. package/src/workspace/intent.test.ts +11 -2
  384. package/src/workspace/intent.ts +495 -32
  385. package/src/workspace/kinds.ts +44 -37
  386. package/src/workspace/points-write.schema.json +1 -0
  387. package/src/workspace/principal-classes.test.ts +175 -0
  388. package/src/workspace/principal-classes.ts +307 -0
  389. package/src/workspace/reason-codes.test.ts +11 -3
  390. package/src/workspace/reason-codes.ts +24 -1
  391. package/src/workspace/records-amend.schema.json +2 -0
  392. package/src/workspace/records-checkout.ts +106 -0
  393. package/src/workspace/records-cli.ts +103 -11
  394. package/src/workspace/records-close.schema.json +1 -0
  395. package/src/workspace/records-new.schema.json +2 -0
  396. package/src/workspace/records-review.schema.json +2 -0
  397. package/src/workspace/records-uncommitted.test.ts +171 -0
  398. package/src/workspace/records-write.ts +28 -6
  399. package/src/workspace/records.schema.json +72 -1
  400. package/src/workspace/records.ts +27 -0
  401. package/src/workspace/runs-cli.ts +381 -0
  402. package/src/workspace/runs-write.schema.json +583 -0
  403. package/src/workspace/runs.schema.json +810 -0
  404. package/src/workspace/runs.test.ts +242 -0
  405. package/src/workspace/runs.ts +709 -0
  406. package/src/workspace/status-contract.test.ts +5 -0
  407. package/src/workspace/status-gates.ts +22 -8
  408. package/src/workspace/status.schema.json +127 -2
  409. package/src/workspace/status.ts +21 -1
  410. package/src/workspace/symbols.ts +180 -0
  411. package/src/workspace/trailer-joins.test.ts +171 -0
  412. package/src/workspace/trailer-joins.ts +82 -0
  413. package/src/workspace/trailers.ts +148 -0
  414. package/src/workspace/trust/seal.ts +52 -1
  415. package/src/workspace/work-cli.e2e.test.ts +28 -1
  416. package/src/workspace/work-cli.ts +133 -8
  417. package/src/workspace/work-evidence.schema.json +2 -0
  418. package/src/workspace/work-history.schema.json +73 -1
  419. package/src/workspace/work-history.test.ts +48 -5
  420. package/src/workspace/work-kind-fields.test.ts +154 -0
  421. package/src/workspace/work-lease.schema.json +14 -1
  422. package/src/workspace/work.ts +82 -0
  423. package/src/workspace/workspace-principals.schema.json +60 -0
  424. package/src/workspace/write-scope.test.ts +128 -2
  425. package/src/workspace/write-scope.ts +170 -45
  426. package/src/yaml.test.ts +111 -1
  427. package/src/yaml.ts +141 -549
@@ -106,6 +106,36 @@ export async function coverageReportCheck(plugin: LexiconPlugin | undefined): Pr
106
106
  };
107
107
  }
108
108
 
109
+ /**
110
+ * #3104 — how many composites a lexicon ships.
111
+ *
112
+ * This counted every `.ts` file in `src/composites/` except `index.ts`, so
113
+ * `catalog.ts`, `catalog.test.ts`, `composites.test.ts` and helper modules all
114
+ * counted: otel reported "4 composite(s)" for `NodeAgent` alone. The same rule
115
+ * as #1342 applies: a capability is what the plugin exposes, and a lexicon
116
+ * with composites exposes them through `composites()`, the catalog generated
117
+ * from its real exports (#2662). Ask that when it is there. Only a lexicon
118
+ * without it falls back to the files, and then not to tests, the catalog, the
119
+ * barrel or helper modules.
120
+ */
121
+ export function countComposites(
122
+ plugin: LexiconPlugin | undefined,
123
+ dir: string,
124
+ ): { count: number; detail: string } {
125
+ if (registers(plugin, "composites")) {
126
+ const { items, error } = safeList(plugin!.composites!.bind(plugin));
127
+ if (error !== undefined) return { count: 0, detail: `composites() threw: ${error}` };
128
+ const names = new Set(items.map((e) => e.name));
129
+ return { count: names.size, detail: `${names.size} composite(s) in composites()` };
130
+ }
131
+ const files = listTsFiles(join(dir, "src/composites"), ["index.ts", "catalog.ts", "helpers.ts", "shared.ts"])
132
+ .filter((f) => !f.endsWith(".test.ts") && !f.endsWith(".d.ts") && !f.endsWith("-helpers.ts"));
133
+ return {
134
+ count: files.length,
135
+ detail: `${files.length} composite file(s) in src/composites/; the plugin has no composites()`,
136
+ };
137
+ }
138
+
109
139
  /**
110
140
  * #2535 — a lexicon that supplies member kinds publishes them as data at a
111
141
  * `./workspace-kinds` subpath (ws-031). The file must be JSON, validate as
@@ -551,12 +581,12 @@ export async function checkLexicon(dir: string): Promise<CheckResult> {
551
581
  : "reads no config namespace of its own",
552
582
  });
553
583
 
554
- const compositeFiles = listTsFiles(join(dir, "src/composites"), ["index.ts"]);
584
+ const composites = countComposites(plugin, dir);
555
585
  items.push({
556
- name: "At least 1 composite in src/composites/",
586
+ name: "At least 1 composite",
557
587
  tier: 2,
558
- pass: compositeFiles.length > 0,
559
- detail: compositeFiles.length > 0 ? `${compositeFiles.length} composite(s)` : undefined,
588
+ pass: composites.count > 0,
589
+ detail: composites.detail,
560
590
  });
561
591
 
562
592
  items.push({
@@ -678,8 +708,8 @@ export async function checkLexicon(dir: string): Promise<CheckResult> {
678
708
  items.push({
679
709
  name: "At least 5 composites",
680
710
  tier: 3,
681
- pass: compositeFiles.length >= 5,
682
- detail: `${compositeFiles.length} composite(s)`,
711
+ pass: composites.count >= 5,
712
+ detail: composites.detail,
683
713
  });
684
714
 
685
715
  const hasActions = existsSync(join(dir, "src/actions")) &&
@@ -106,6 +106,24 @@ describe("liveImportFromPlugins (#114)", () => {
106
106
  expect(result.error).toContain("live export");
107
107
  });
108
108
 
109
+ test("warnings an exporter returns reach the result (#3336)", async () => {
110
+ const withWarnings: ExportedTemplate = { ...sampleIR, warnings: ["bucket is kept by Terraform; left out"] };
111
+ const result = await liveImportFromPlugins([fakeExporter("fake", withWarnings)], {
112
+ environment: "prod",
113
+ output: outputDir,
114
+ force: true,
115
+ });
116
+ expect(result.success).toBe(true);
117
+ expect(result.warnings).toContain("bucket is kept by Terraform; left out");
118
+ });
119
+
120
+ test("warnings from an export with no resources are kept on the error result (#3336)", async () => {
121
+ const empty = fakeExporter("fake", { resources: [], parameters: [], warnings: ["everything is kept by Prisma"] });
122
+ const result = await liveImportFromPlugins([empty], { environment: "prod", output: outputDir });
123
+ expect(result.success).toBe(false);
124
+ expect(result.warnings).toContain("everything is kept by Prisma");
125
+ });
126
+
109
127
  test("--lexicon narrows to the named exporter", async () => {
110
128
  const result = await liveImportFromPlugins(
111
129
  [fakeExporter("a", sampleIR), fakeExporter("b", sampleIR)],
@@ -0,0 +1,133 @@
1
+ import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { mkdir, rm, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import type { LexiconPlugin } from "../../lexicon";
6
+ import type { ParserOptions } from "../../import/parser";
7
+ import { resolveParserOptions } from "../../import/parser-options";
8
+ import { parseArgs } from "../main";
9
+ import { importCommand, importFromContent } from "./import";
10
+
11
+ const { seen, withOptions, withoutOptions } = vi.hoisted(() => {
12
+ const seen: unknown[] = [];
13
+ const parser = {
14
+ parse: () => ({ resources: [], parameters: [], warnings: ["parsed"] }),
15
+ };
16
+ const generator = { generate: () => [{ path: "x.ts", content: "export {};\n" }] };
17
+ const withOptions = {
18
+ name: "with-options",
19
+ detectTemplate: (d: unknown) => typeof d === "object" && d !== null && "marker" in d,
20
+ parserOptions: () => [
21
+ { name: "flag", type: "boolean", description: "a switch" },
22
+ { name: "level", type: "number", description: "a number" },
23
+ { name: "label", type: "string", description: "a string" },
24
+ ],
25
+ templateParser: (options?: ParserOptions) => {
26
+ seen.push(options);
27
+ return parser;
28
+ },
29
+ templateGenerator: () => generator,
30
+ } as unknown as LexiconPlugin;
31
+ const withoutOptions = {
32
+ name: "without-options",
33
+ templateParser: () => parser,
34
+ templateGenerator: () => generator,
35
+ } as unknown as LexiconPlugin;
36
+ return { seen, withOptions, withoutOptions };
37
+ });
38
+
39
+ vi.mock("../plugins", () => ({
40
+ resolveProjectLexicons: async () => ["with-options"],
41
+ loadPlugins: async (names: string[]) => names.map((n) => (n === "without-options" ? withoutOptions : withOptions)),
42
+ }));
43
+
44
+ describe("resolveParserOptions (#2994)", () => {
45
+ const specs = withOptions as Pick<LexiconPlugin, "name" | "parserOptions">;
46
+
47
+ test("no entries is an empty record", () => {
48
+ expect(resolveParserOptions(specs, undefined)).toEqual({ options: {} });
49
+ });
50
+
51
+ test("converts to the declared types; a bare boolean is true", () => {
52
+ expect(resolveParserOptions(specs, ["flag", "level=3", "label=a=b"])).toEqual({
53
+ options: { flag: true, level: 3, label: "a=b" },
54
+ });
55
+ expect(resolveParserOptions(specs, ["flag=false"])).toEqual({ options: { flag: false } });
56
+ });
57
+
58
+ test("refuses an unknown name and lists what the lexicon accepts", () => {
59
+ const r = resolveParserOptions(specs, ["nope"]);
60
+ expect(r).toMatchObject({ error: expect.stringContaining('Unknown parser option "nope" for lexicon "with-options"') });
61
+ expect((r as { error: string }).error).toContain("flag (boolean): a switch");
62
+ });
63
+
64
+ test("a lexicon with no options says so", () => {
65
+ const r = resolveParserOptions({ name: "plain" }, ["x"]);
66
+ expect((r as { error: string }).error).toBe('Unknown parser option "x" for lexicon "plain"; lexicon "plain" declares no parser options.');
67
+ });
68
+
69
+ test("refuses a bad value", () => {
70
+ expect(resolveParserOptions(specs, ["flag=maybe"])).toHaveProperty("error");
71
+ expect(resolveParserOptions(specs, ["level=abc"])).toHaveProperty("error");
72
+ expect(resolveParserOptions(specs, ["level"])).toHaveProperty("error");
73
+ expect(resolveParserOptions(specs, ["label"])).toHaveProperty("error");
74
+ });
75
+ });
76
+
77
+ describe("chant import --parser-option (#2994)", () => {
78
+ let dir: string;
79
+ let templatePath: string;
80
+
81
+ beforeEach(async () => {
82
+ seen.length = 0;
83
+ dir = join(tmpdir(), `chant-import-parser-options-${Date.now()}-${Math.random()}`);
84
+ await mkdir(dir, { recursive: true });
85
+ templatePath = join(dir, "t.json");
86
+ await writeFile(templatePath, JSON.stringify({ marker: true }));
87
+ });
88
+
89
+ afterEach(async () => {
90
+ await rm(dir, { recursive: true, force: true });
91
+ });
92
+
93
+ test("the flag is parsed, repeatable", () => {
94
+ const args = parseArgs(["import", "t.json", "--lexicon", "x", "--parser-option", "a", "--parser-option", "b=1"]);
95
+ expect(args.parserOption).toEqual(["a", "b=1"]);
96
+ });
97
+
98
+ test("--lexicon: the parser receives the typed options", async () => {
99
+ const result = await importCommand({
100
+ templatePath, output: join(dir, "out"), lexicon: "with-options", parserOptions: ["flag", "level=2"],
101
+ });
102
+ expect(result.success).toBe(true);
103
+ expect(seen).toEqual([{ flag: true, level: 2 }]);
104
+ });
105
+
106
+ test("detection: the options go to the detected lexicon", async () => {
107
+ const result = await importCommand({ templatePath, output: join(dir, "out"), parserOptions: ["label=x"] });
108
+ expect(result.success).toBe(true);
109
+ expect(seen).toEqual([{ label: "x" }]);
110
+ });
111
+
112
+ test("no options: the parser gets an empty record", async () => {
113
+ await importCommand({ templatePath, output: join(dir, "out"), lexicon: "with-options" });
114
+ expect(seen).toEqual([{}]);
115
+ });
116
+
117
+ test("an unknown option is refused before parsing", async () => {
118
+ const result = await importCommand({
119
+ templatePath, output: join(dir, "out"), lexicon: "with-options", parserOptions: ["bogus"],
120
+ });
121
+ expect(result.success).toBe(false);
122
+ expect(result.error).toContain('Unknown parser option "bogus"');
123
+ expect(seen).toEqual([]);
124
+ });
125
+
126
+ test("content import refuses an option on a lexicon that declares none", async () => {
127
+ const result = await importFromContent({
128
+ content: "{}", lexicon: "without-options", output: join(dir, "out"), parserOptions: ["x"],
129
+ });
130
+ expect(result.success).toBe(false);
131
+ expect(result.error).toContain('lexicon "without-options" declares no parser options');
132
+ });
133
+ });
@@ -7,6 +7,7 @@ import { listInstalledLexicons, loadPlugin, loadPlugins, resolveProjectLexicons
7
7
  import type { ExportedTemplate, LexiconPlugin, ResourceSelector } from "../../lexicon";
8
8
  import { parseYAMLDocument, splitYAMLDocuments } from "../../yaml";
9
9
  import { importLexiconPackage } from "../../lexicon-module";
10
+ import { resolveParserOptions } from "../../import/parser-options";
10
11
  import { EmbeddedImports, type EmbeddedContent, type RegisteredEmbeddedImporter } from "../../import/embedded";
11
12
 
12
13
  /**
@@ -24,6 +25,12 @@ export interface ImportOptions {
24
25
  * JSON/YAML check: the raw content goes straight to that plugin's parser.
25
26
  */
26
27
  lexicon?: string;
28
+ /**
29
+ * `--parser-option` entries (`key` or `key=value`) for the lexicon's parser
30
+ * (#2994). Checked against the lexicon's `parserOptions()`; an unknown name
31
+ * fails the import before anything is parsed.
32
+ */
33
+ parserOptions?: string[];
27
34
  }
28
35
 
29
36
  /**
@@ -345,6 +352,7 @@ export async function importCommand(options: ImportOptions): Promise<ImportResul
345
352
  lexicon: options.lexicon,
346
353
  output: options.output,
347
354
  force: options.force,
355
+ parserOptions: options.parserOptions,
348
356
  });
349
357
  }
350
358
 
@@ -383,7 +391,7 @@ export async function importCommand(options: ImportOptions): Promise<ImportResul
383
391
  );
384
392
  }
385
393
 
386
- const result = await parseAndWrite(plugin, content, outputDir, options.force, warnings, generatedFiles, plugin.name, projectDir);
394
+ const result = await parseAndWrite(plugin, content, outputDir, options.force, warnings, generatedFiles, plugin.name, projectDir, options.parserOptions);
387
395
  return { ...result, detected: true };
388
396
  }
389
397
 
@@ -402,6 +410,8 @@ export interface ContentImportOptions {
402
410
  lexicon: string;
403
411
  output?: string;
404
412
  force?: boolean;
413
+ /** `--parser-option` entries for the lexicon's parser (#2994); see {@link ImportOptions.parserOptions}. */
414
+ parserOptions?: string[];
405
415
  }
406
416
 
407
417
  export async function importFromContent(options: ContentImportOptions): Promise<ImportResult> {
@@ -422,7 +432,7 @@ export async function importFromContent(options: ContentImportOptions): Promise<
422
432
  return { success: false, generatedFiles: [], warnings: [], error: `Lexicon "${options.lexicon}" not available.` };
423
433
  }
424
434
  const projectDir = resolve(options.output ? dirname(options.output) : ".");
425
- return parseAndWrite(plugin, options.content, outputDir, options.force, [], [], plugin.name, projectDir);
435
+ return parseAndWrite(plugin, options.content, outputDir, options.force, [], [], plugin.name, projectDir, options.parserOptions);
426
436
  }
427
437
 
428
438
  /**
@@ -520,6 +530,7 @@ async function parseAndWrite(
520
530
  generatedFiles: string[],
521
531
  lexicon: string,
522
532
  projectDir: string,
533
+ parserOptionEntries?: readonly string[],
523
534
  ): Promise<ImportResult> {
524
535
  // A lexicon can recognize a template (detectTemplate) without being able to
525
536
  // import it (grafana had no parser until #2945). Every path funnels through here, so
@@ -534,11 +545,16 @@ async function parseAndWrite(
534
545
  };
535
546
  }
536
547
 
548
+ const resolved = resolveParserOptions(plugin, parserOptionEntries);
549
+ if ("error" in resolved) {
550
+ return { success: false, generatedFiles: [], warnings: [], error: resolved.error, lexicon: plugin.name };
551
+ }
552
+
537
553
  // Parse template
538
554
  let ir: TemplateIR;
539
555
  let embedded: EmbeddedImports | undefined;
540
556
  try {
541
- const parser = plugin.templateParser();
557
+ const parser = plugin.templateParser(resolved.options);
542
558
  let irs: TemplateIR[];
543
559
  ({ irs, embedded } = await parseWithEmbedded([(context) => parser.parse(content, context)], [plugin], projectDir));
544
560
  ir = irs[0];
@@ -743,6 +759,8 @@ export async function liveImportFromPlugins(
743
759
  warnings.push(`${plugin.name}: live export failed — ${err instanceof Error ? err.message : String(err)}`);
744
760
  continue;
745
761
  }
762
+ // What the exporter left out or could not carry (#3336).
763
+ if (ir.warnings) warnings.push(...ir.warnings);
746
764
  if (ir.resources.length === 0) continue;
747
765
  irParts.push(ir);
748
766
  generatorLexicon ??= plugin;
@@ -37,7 +37,7 @@ export function generatePackageJson(name: string, names: { packageName: string }
37
37
  // (the `workspace:` protocol is rejected outside a workspace).
38
38
  "@intentius/chant": "*",
39
39
  "tsc-alias": "^1.8.17",
40
- typescript: "^5.9.3",
40
+ typescript: "^5.9.3 || ^7.0.0",
41
41
  },
42
42
  };
43
43
 
@@ -87,7 +87,9 @@ describe("initCommand", () => {
87
87
 
88
88
  expect(pkg.dependencies["@intentius/chant"]).toBeDefined();
89
89
  expect(pkg.dependencies["@intentius/chant-lexicon-aws"]).toBeDefined();
90
- expect(pkg.devDependencies["typescript"]).toBeDefined();
90
+ // #3089: the project's TypeScript admits 7, and @types/node backs `types: ["node"]`.
91
+ expect(pkg.devDependencies["typescript"]).toBe("^5.9.3 || ^7.0.0");
92
+ expect(pkg.devDependencies["@types/node"]).toBeDefined();
91
93
  expect(pkg.scripts.build).toBe("chant build src --lexicon aws");
92
94
  expect(pkg.scripts.dev).toBe("chant build src --lexicon aws --watch");
93
95
  expect(pkg.scripts.lint).toBe("chant lint src");
@@ -115,6 +117,11 @@ describe("initCommand", () => {
115
117
  expect(tsconfig.compilerOptions.rootDir).toBe("./src");
116
118
  expect(tsconfig.include).toContain("src");
117
119
  expect(tsconfig.compilerOptions.paths).toBeUndefined();
120
+ // #3089: bundler resolution accepts the extensionless `./config` imports
121
+ // the templates use; NodeNext rejects them (TS2835).
122
+ expect(tsconfig.compilerOptions.module).toBe("esnext");
123
+ expect(tsconfig.compilerOptions.moduleResolution).toBe("bundler");
124
+ expect(tsconfig.compilerOptions.types).toEqual(["node"]);
118
125
  });
119
126
  });
120
127
 
@@ -62,6 +62,16 @@ export interface InitResult {
62
62
  lexiconModule?: string;
63
63
  }
64
64
 
65
+ /**
66
+ * The devDependencies a scaffolded project gets, from `chant init` and from
67
+ * `chant carve` (#3233). The project's TypeScript is for its editor and `tsc`
68
+ * only; chant parses with its own copy, so any 5.9+ or 7 release works (#3089).
69
+ */
70
+ export const SCAFFOLD_DEV_DEPENDENCIES = {
71
+ "@types/node": "^24.0.0",
72
+ typescript: "^5.9.3 || ^7.0.0",
73
+ } as const;
74
+
65
75
  /**
66
76
  * Generate package.json content
67
77
  */
@@ -88,23 +98,28 @@ function generatePackageJson(
88
98
  ...extraScripts,
89
99
  },
90
100
  dependencies,
91
- devDependencies: {
92
- typescript: "^5.0.0",
93
- },
101
+ devDependencies: { ...SCAFFOLD_DEV_DEPENDENCIES },
94
102
  };
95
103
 
96
104
  return JSON.stringify(pkg, null, 2);
97
105
  }
98
106
 
99
107
  /**
100
- * Generate tsconfig.json content with path mappings for .chant/ types
108
+ * Generate tsconfig.json content.
109
+ *
110
+ * `bundler` resolution, because chant resolves a project's imports the way tsx
111
+ * and fold do: `./config` with no extension. NodeNext rejects that form
112
+ * (TS2835), and its suggested `./config.js` makes fold fall back to running the
113
+ * file (#3089). `types` names node because TypeScript 6 and later no longer load
114
+ * every `@types` package by default.
101
115
  */
102
- function generateTsConfig(lexicon: string): string {
116
+ export function generateTsConfig(): string {
103
117
  const config = {
104
118
  compilerOptions: {
105
119
  target: "ES2022",
106
- module: "NodeNext",
107
- moduleResolution: "NodeNext",
120
+ module: "esnext",
121
+ moduleResolution: "bundler",
122
+ types: ["node"],
108
123
  strict: true,
109
124
  esModuleInterop: true,
110
125
  skipLibCheck: true,
@@ -307,7 +322,7 @@ export async function initCommand(options: InitOptions): Promise<InitResult> {
307
322
  // Generate tsconfig.json
308
323
  writeIfNotExists(
309
324
  join(targetDir, "tsconfig.json"),
310
- generateTsConfig(options.lexicon),
325
+ generateTsConfig(),
311
326
  "tsconfig.json",
312
327
  createdFiles,
313
328
  warnings,
@@ -281,6 +281,43 @@ export const b = new Bucket({ config: { y: 2 } });
281
281
  expect(result.diagnostics.some(d => d.ruleId === "COR001")).toBe(true);
282
282
  expect(result.diagnostics.filter(d => d.ruleId === "COR001")).toHaveLength(2);
283
283
  });
284
+
285
+ // #2511: a project-local rule that throws is an error, never a clean pass.
286
+ test("a project-local rule that throws fails the lint and names the rule, file and message", async () => {
287
+ await mkdir(join(testDir, ".chant", "rules"), { recursive: true });
288
+ await writeFile(
289
+ join(testDir, ".chant", "rules", "local.ts"),
290
+ `
291
+ export const boom = {
292
+ id: "LOCAL_BOOM",
293
+ severity: "warning",
294
+ category: "correctness",
295
+ check() { throw new Error("cannot read sibling file"); },
296
+ };
297
+ export const fine = {
298
+ id: "LOCAL_FINE",
299
+ severity: "warning",
300
+ category: "correctness",
301
+ check(ctx) {
302
+ return [{ file: ctx.filePath, line: 1, column: 1, ruleId: "LOCAL_FINE", severity: "warning", message: "fine ran" }];
303
+ },
304
+ };
305
+ `,
306
+ );
307
+ const file = join(testDir, "clean.ts");
308
+ await writeFile(file, `// chant-disable\nexport const config = { a: 1 };\n`);
309
+
310
+ const result = await lintCommand({ path: testDir, format: "stylish" });
311
+
312
+ expect(result.success).toBe(false);
313
+ const threw = result.diagnostics.filter((d) => d.ruleId === "LOCAL_BOOM");
314
+ expect(threw).toHaveLength(1);
315
+ expect(threw[0].severity).toBe("error");
316
+ expect(threw[0].file).toBe(file);
317
+ expect(threw[0].message).toContain("LOCAL_BOOM");
318
+ expect(threw[0].message).toContain(file);
319
+ expect(threw[0].message).toContain("cannot read sibling file");
320
+ });
284
321
  });
285
322
 
286
323
  describe("isLintRule", () => {
@@ -7,6 +7,7 @@ import {
7
7
  patchDockerfile,
8
8
  patchRootTsconfigPaths,
9
9
  insertPrepackAfterEach,
10
+ patchLexiconList,
10
11
  } from "./onboard";
11
12
 
12
13
  function makeTempDir(): string {
@@ -184,6 +185,8 @@ function writeFixtureRepo(root: string): void {
184
185
  writeFileSync(join(root, ".github/workflows/publish.yml"), publishContent);
185
186
  writeFileSync(join(root, "test/Dockerfile.smoke"), dockerLoopContent);
186
187
  writeFileSync(join(root, "test/Dockerfile.smoke-npm"), dockerNpmContent);
188
+ writeFileSync(join(root, "test/smoke-npm-lexicons.txt"), "aws\ngitlab\nk8s\n");
189
+ writeFileSync(join(root, "test/smoke-e2e-lexicons.txt"), "aws\nk8s\n");
187
190
  }
188
191
 
189
192
  const TRACKED = [
@@ -193,6 +196,8 @@ const TRACKED = [
193
196
  ".github/workflows/publish.yml",
194
197
  "test/Dockerfile.smoke",
195
198
  "test/Dockerfile.smoke-npm",
199
+ "test/smoke-npm-lexicons.txt",
200
+ "test/smoke-e2e-lexicons.txt",
196
201
  ];
197
202
 
198
203
  function snapshot(root: string): Record<string, string> {
@@ -223,8 +228,12 @@ describe("onboardCommand", () => {
223
228
  "publish.yml (prepack)",
224
229
  "Dockerfile.smoke (lexicon list)",
225
230
  "Dockerfile.smoke-npm (lexicon list)",
231
+ "smoke-npm-lexicons.txt (lexicon list)",
226
232
  ]);
227
- expect(result.skipped).toEqual([]);
233
+ expect(result.skipped).toHaveLength(1);
234
+ expect(result.skipped[0]).toContain("smoke-e2e-lexicons.txt: not patched");
235
+ expect(readFileSync(join(root, "test/smoke-npm-lexicons.txt"), "utf-8")).toBe("aws\ngitlab\nk8s\nterraform\n");
236
+ expect(readFileSync(join(root, "test/smoke-e2e-lexicons.txt"), "utf-8")).toBe("aws\nk8s\n");
228
237
 
229
238
  const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf-8"));
230
239
  expect(pkg.dependencies["@intentius/chant-lexicon-terraform"]).toBe("workspace:*");
@@ -254,6 +263,8 @@ describe("onboardCommand", () => {
254
263
  "publish.yml: prepack for terraform already present",
255
264
  "Dockerfile.smoke: already covers terraform",
256
265
  "Dockerfile.smoke-npm: already covers terraform",
266
+ "smoke-npm-lexicons.txt: terraform already in test/smoke-npm-lexicons.txt",
267
+ expect.stringContaining("smoke-e2e-lexicons.txt: not patched"),
257
268
  ]);
258
269
  });
259
270
 
@@ -312,9 +323,9 @@ describe("root tsconfig.json paths (#1614)", () => {
312
323
 
313
324
  const cfg = JSON.parse(readFileSync(join(root, "tsconfig.json"), "utf-8"));
314
325
  expect(cfg.compilerOptions.paths["@intentius/chant-lexicon-terraform"]).toEqual([
315
- "lexicons/terraform/src/index.ts",
326
+ "./lexicons/terraform/src/index.ts",
316
327
  ]);
317
- expect(cfg.compilerOptions.paths["@intentius/chant-lexicon-terraform/*"]).toEqual(["lexicons/terraform/src/*"]);
328
+ expect(cfg.compilerOptions.paths["@intentius/chant-lexicon-terraform/*"]).toEqual(["./lexicons/terraform/src/*"]);
318
329
  // Existing entries untouched
319
330
  expect(cfg.compilerOptions.paths["@intentius/chant-lexicon-aws"]).toEqual(["lexicons/aws/src/index.ts"]);
320
331
  expect(cfg.compilerOptions.moduleResolution).toBe("node");
@@ -462,3 +473,42 @@ describe("insertPrepackAfterEach", () => {
462
473
  ]);
463
474
  });
464
475
  });
476
+
477
+ describe("smoke lists and publish.yml reporting (#3227)", () => {
478
+ let root: string;
479
+
480
+ beforeEach(() => {
481
+ root = makeTempDir();
482
+ writeFixtureRepo(root);
483
+ });
484
+
485
+ afterEach(() => {
486
+ rmSync(root, { recursive: true, force: true });
487
+ });
488
+
489
+ test("patchLexiconList appends once and keeps the trailing newline", () => {
490
+ const file = join(root, "test/smoke-npm-lexicons.txt");
491
+ writeFileSync(file, "aws\nk8s");
492
+ expect(patchLexiconList(file, "sql").patched).toBe(true);
493
+ expect(readFileSync(file, "utf-8")).toBe("aws\nk8s\nsql\n");
494
+ expect(patchLexiconList(file, "sql").patched).toBe(false);
495
+ expect(readFileSync(file, "utf-8")).toBe("aws\nk8s\nsql\n");
496
+ });
497
+
498
+ test("a Dockerfile that reads the list file points at it instead of claiming coverage", () => {
499
+ const file = join(root, "test/Dockerfile.smoke-npm");
500
+ writeFileSync(file, "RUN LEXICONS=$(cat /app/test/smoke-npm-lexicons.txt) && \\\n for lex in $LEXICONS; do :; done\n");
501
+ const result = patchDockerfile(file, "sql");
502
+ expect(result.patched).toBe(false);
503
+ expect(result.reason).toContain("test/smoke-npm-lexicons.txt");
504
+ expect(result.reason).not.toContain("already covers");
505
+ });
506
+
507
+ test("publish.yml with no per-lexicon prepack lines says nothing needs patching", () => {
508
+ writeFileSync(join(root, ".github/workflows/publish.yml"), "name: publish\njobs:\n test:\n steps:\n - run: bash scripts/publish-verify-ci.sh\n");
509
+ const result = onboardCommand({ name: "sql", root });
510
+ const line = result.skipped.find((s) => s.startsWith("publish.yml:"));
511
+ expect(line).toContain("nothing to patch");
512
+ expect(line).toContain("scripts/ci-lexicon-artifacts.sh");
513
+ });
514
+ });
@@ -117,6 +117,10 @@ function patchRootPackageJson(root: string, name: string): PatchResult {
117
117
  * The whole-repo typecheck runs with `moduleResolution: node`, which never
118
118
  * reads package exports maps, so every lexicon needs a bare + subpath
119
119
  * mapping here or an example importing the package fails to resolve.
120
+ *
121
+ * Entries are `./`-prefixed like every other entry (#3227): with no `baseUrl`
122
+ * (removed in #3205) a bare `lexicons/...` path is invalid and tsx's tsconfig
123
+ * loader throws on it for every run in the repo.
120
124
  */
121
125
  export function patchRootTsconfigPaths(root: string, name: string): PatchResult {
122
126
  const tsconfigPath = join(root, "tsconfig.json");
@@ -128,8 +132,8 @@ export function patchRootTsconfigPaths(root: string, name: string): PatchResult
128
132
 
129
133
  const bare = `@intentius/chant-lexicon-${name}`;
130
134
  const wanted: Record<string, string[]> = {
131
- [bare]: [`lexicons/${name}/src/index.ts`],
132
- [`${bare}/*`]: [`lexicons/${name}/src/*`],
135
+ [bare]: [`./lexicons/${name}/src/index.ts`],
136
+ [`${bare}/*`]: [`./lexicons/${name}/src/*`],
133
137
  };
134
138
 
135
139
  let changed = false;
@@ -297,7 +301,15 @@ function patchPublishWorkflow(root: string, name: string): PatchResult {
297
301
  }
298
302
 
299
303
  if (!insertPrepackAfterEach(lines, name)) {
300
- return { patched: false, reason: "no prepack line found to anchor on" };
304
+ // #3227 — not a failure and not "covered by a prepack line": publish.yml
305
+ // carries no per-lexicon lines. The run builds every lexicon through
306
+ // scripts/ci-lexicon-artifacts.sh and publishes through
307
+ // scripts/publish-packages.sh, both of which enumerate the workspace.
308
+ return {
309
+ patched: false,
310
+ reason:
311
+ "nothing to patch: it has no per-lexicon prepack lines; scripts/ci-lexicon-artifacts.sh and scripts/publish-packages.sh enumerate the workspace",
312
+ };
301
313
  }
302
314
  writeFileSync(filePath, lines.join("\n"));
303
315
  return { patched: true };
@@ -321,6 +333,12 @@ export function patchDockerfile(filePath: string, name: string): PatchResult {
321
333
 
322
334
  const changed = addToLexiconLoops(lines, name) || insertPrepackAfterEach(lines, name);
323
335
  if (!changed) {
336
+ // #3227 — the smoke-npm and smoke-e2e images read their lexicon list from
337
+ // a file. Point at it instead of reporting a missing anchor.
338
+ const listFile = lines.join("\n").match(/test\/(smoke-[\w-]+-lexicons\.txt)/)?.[1];
339
+ if (listFile) {
340
+ return { patched: false, reason: `reads its lexicon list from test/${listFile}, not from the Dockerfile` };
341
+ }
324
342
  return { patched: false, reason: "no lexicon loop or prepack line found to anchor on" };
325
343
  }
326
344
 
@@ -328,6 +346,22 @@ export function patchDockerfile(filePath: string, name: string): PatchResult {
328
346
  return { patched: true };
329
347
  }
330
348
 
349
+ /**
350
+ * Append a lexicon to a one-name-per-line list file (test/smoke-npm-lexicons.txt).
351
+ * Idempotent; keeps the file's trailing newline.
352
+ */
353
+ export function patchLexiconList(filePath: string, name: string): PatchResult {
354
+ const rel = filePath.split("/").slice(-2).join("/");
355
+ if (!existsSync(filePath)) return { patched: false, reason: `${rel} not found` };
356
+ const content = readFileSync(filePath, "utf-8");
357
+ if (content.split("\n").some((l) => l.trim() === name)) {
358
+ return { patched: false, reason: `${name} already in ${rel}` };
359
+ }
360
+ const sep = content === "" || content.endsWith("\n") ? "" : "\n";
361
+ writeFileSync(filePath, `${content}${sep}${name}\n`);
362
+ return { patched: true };
363
+ }
364
+
331
365
  /**
332
366
  * Execute the onboard command — patches monorepo infrastructure for a new lexicon.
333
367
  */
@@ -375,6 +409,20 @@ export function onboardCommand(options: OnboardOptions): OnboardResult {
375
409
  patchDockerfile(join(root, "test/Dockerfile.smoke-npm"), options.name),
376
410
  );
377
411
 
412
+ // 6. Smoke image lexicon lists (#3227). The npm smoke packs every lexicon.
413
+ // The e2e list is opt-in: it names the lexicons an example uses plus
414
+ // their dependencies, so onboard leaves it alone and says so.
415
+ record(
416
+ "smoke-npm-lexicons.txt",
417
+ "lexicon list",
418
+ patchLexiconList(join(root, "test/smoke-npm-lexicons.txt"), options.name),
419
+ );
420
+ if (existsSync(join(root, "test/smoke-e2e-lexicons.txt"))) {
421
+ skipped.push(
422
+ `smoke-e2e-lexicons.txt: not patched; it lists only lexicons an e2e example uses (and their dependencies). Add ${options.name} to test/smoke-e2e-lexicons.txt when an example needs it`,
423
+ );
424
+ }
425
+
378
426
  return { success: true, patched, skipped, verbose: options.verbose };
379
427
  }
380
428
 
@@ -396,7 +444,7 @@ export async function printOnboardResult(result: OnboardResult, name: string): P
396
444
 
397
445
  if (result.skipped.length > 0) {
398
446
  console.log("");
399
- console.log("Skipped (already covered):");
447
+ console.log("Not patched (already covered, or nothing to patch):");
400
448
  for (const s of result.skipped) {
401
449
  console.log(` ${s}`);
402
450
  }