@intentius/chant 0.80.0 → 0.81.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (414) hide show
  1. package/dist/audit/discover.d.ts +8 -16
  2. package/dist/audit/discover.d.ts.map +1 -1
  3. package/dist/audit/report-html.d.ts +5 -0
  4. package/dist/audit/report-html.d.ts.map +1 -1
  5. package/dist/audit/report-model.d.ts +13 -0
  6. package/dist/audit/report-model.d.ts.map +1 -1
  7. package/dist/audit/terraform-state.d.ts +4 -5
  8. package/dist/audit/terraform-state.d.ts.map +1 -1
  9. package/dist/cli/commands/audit.d.ts +25 -9
  10. package/dist/cli/commands/audit.d.ts.map +1 -1
  11. package/dist/cli/commands/check-lexicon.d.ts +8 -0
  12. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  13. package/dist/cli/commands/doctor.d.ts.map +1 -1
  14. package/dist/cli/commands/import-agents.d.ts.map +1 -1
  15. package/dist/cli/commands/init.d.ts.map +1 -1
  16. package/dist/cli/commands/lint.d.ts.map +1 -1
  17. package/dist/cli/commands/update.d.ts +6 -0
  18. package/dist/cli/commands/update.d.ts.map +1 -1
  19. package/dist/cli/commands/vendor.d.ts +5 -0
  20. package/dist/cli/commands/vendor.d.ts.map +1 -1
  21. package/dist/cli/handlers/build.d.ts.map +1 -1
  22. package/dist/cli/handlers/components.d.ts.map +1 -1
  23. package/dist/cli/handlers/dev.d.ts.map +1 -1
  24. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  25. package/dist/cli/handlers/init.d.ts.map +1 -1
  26. package/dist/cli/handlers/operator.d.ts +7 -0
  27. package/dist/cli/handlers/operator.d.ts.map +1 -1
  28. package/dist/cli/handlers/promote.d.ts +7 -4
  29. package/dist/cli/handlers/promote.d.ts.map +1 -1
  30. package/dist/cli/handlers/run-generate.d.ts.map +1 -1
  31. package/dist/cli/handlers/run.d.ts +4 -1
  32. package/dist/cli/handlers/run.d.ts.map +1 -1
  33. package/dist/cli/handlers/vendor.d.ts +8 -3
  34. package/dist/cli/handlers/vendor.d.ts.map +1 -1
  35. package/dist/cli/main.d.ts +12 -0
  36. package/dist/cli/main.d.ts.map +1 -1
  37. package/dist/cli/plugins.d.ts +12 -7
  38. package/dist/cli/plugins.d.ts.map +1 -1
  39. package/dist/cli/registry.d.ts +32 -0
  40. package/dist/cli/registry.d.ts.map +1 -1
  41. package/dist/codegen/fetch.d.ts +10 -0
  42. package/dist/codegen/fetch.d.ts.map +1 -1
  43. package/dist/components/cli-support.d.ts +3 -1
  44. package/dist/components/cli-support.d.ts.map +1 -1
  45. package/dist/components/discover.d.ts.map +1 -1
  46. package/dist/components/driver.d.ts +16 -2
  47. package/dist/components/driver.d.ts.map +1 -1
  48. package/dist/components/gate-plan.d.ts +40 -0
  49. package/dist/components/gate-plan.d.ts.map +1 -0
  50. package/dist/components/promote.d.ts +60 -7
  51. package/dist/components/promote.d.ts.map +1 -1
  52. package/dist/components/sandbox/driver.d.ts.map +1 -1
  53. package/dist/components/sandbox/run.d.ts.map +1 -1
  54. package/dist/components/verbs/build-archive.d.ts +22 -11
  55. package/dist/components/verbs/build-archive.d.ts.map +1 -1
  56. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  57. package/dist/config-static.d.ts +59 -0
  58. package/dist/config-static.d.ts.map +1 -0
  59. package/dist/config.d.ts +32 -10
  60. package/dist/config.d.ts.map +1 -1
  61. package/dist/content-digest.d.ts +17 -0
  62. package/dist/content-digest.d.ts.map +1 -0
  63. package/dist/discovery/files.d.ts +10 -5
  64. package/dist/discovery/files.d.ts.map +1 -1
  65. package/dist/discovery/fold-import.d.ts +20 -4
  66. package/dist/discovery/fold-import.d.ts.map +1 -1
  67. package/dist/discovery/index.d.ts.map +1 -1
  68. package/dist/discovery/sandbox/driver.d.ts +20 -0
  69. package/dist/discovery/sandbox/driver.d.ts.map +1 -1
  70. package/dist/discovery/sandbox/fork.d.ts +20 -0
  71. package/dist/discovery/sandbox/fork.d.ts.map +1 -1
  72. package/dist/discovery/walk.d.ts +119 -0
  73. package/dist/discovery/walk.d.ts.map +1 -0
  74. package/dist/graph-ir.d.ts +4 -1
  75. package/dist/graph-ir.d.ts.map +1 -1
  76. package/dist/join-key.d.ts +31 -0
  77. package/dist/join-key.d.ts.map +1 -0
  78. package/dist/lexicon-module.d.ts +51 -6
  79. package/dist/lexicon-module.d.ts.map +1 -1
  80. package/dist/lexicon.d.ts +58 -1
  81. package/dist/lexicon.d.ts.map +1 -1
  82. package/dist/lifecycle/build-ledger-store.d.ts.map +1 -1
  83. package/dist/lifecycle/digest.d.ts +5 -1
  84. package/dist/lifecycle/digest.d.ts.map +1 -1
  85. package/dist/lifecycle/gate-ledger.d.ts +20 -5
  86. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  87. package/dist/lifecycle/git.d.ts +64 -0
  88. package/dist/lifecycle/git.d.ts.map +1 -1
  89. package/dist/lifecycle/lease.d.ts +8 -1
  90. package/dist/lifecycle/lease.d.ts.map +1 -1
  91. package/dist/lifecycle/legacy-digest.d.ts +41 -8
  92. package/dist/lifecycle/legacy-digest.d.ts.map +1 -1
  93. package/dist/lifecycle/member-ledger.d.ts +67 -0
  94. package/dist/lifecycle/member-ledger.d.ts.map +1 -0
  95. package/dist/lifecycle/plan-digest.d.ts +4 -3
  96. package/dist/lifecycle/plan-digest.d.ts.map +1 -1
  97. package/dist/lifecycle/release-ledger.d.ts +18 -2
  98. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  99. package/dist/lifecycle/status.d.ts +90 -5
  100. package/dist/lifecycle/status.d.ts.map +1 -1
  101. package/dist/op/activities/activity-contracts.d.ts +32 -0
  102. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  103. package/dist/op/activities/index.d.ts +2 -0
  104. package/dist/op/activities/index.d.ts.map +1 -1
  105. package/dist/op/activities/propose-upgrade.d.ts +75 -0
  106. package/dist/op/activities/propose-upgrade.d.ts.map +1 -0
  107. package/dist/op/discover.d.ts.map +1 -1
  108. package/dist/op/gate-name.d.ts +7 -0
  109. package/dist/op/gate-name.d.ts.map +1 -1
  110. package/dist/op/gate-summary.d.ts +16 -0
  111. package/dist/op/gate-summary.d.ts.map +1 -1
  112. package/dist/op/gate.d.ts +16 -2
  113. package/dist/op/gate.d.ts.map +1 -1
  114. package/dist/project-root.d.ts +26 -0
  115. package/dist/project-root.d.ts.map +1 -1
  116. package/dist/workspace/__fixtures__/contract-repo.d.ts +37 -0
  117. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -0
  118. package/dist/workspace/__fixtures__/two-member-workspace.d.ts +11 -0
  119. package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -0
  120. package/dist/workspace/checks/generated.d.ts +156 -0
  121. package/dist/workspace/checks/generated.d.ts.map +1 -0
  122. package/dist/workspace/checks/ledgers.d.ts +78 -0
  123. package/dist/workspace/checks/ledgers.d.ts.map +1 -0
  124. package/dist/workspace/checks/links.d.ts +25 -0
  125. package/dist/workspace/checks/links.d.ts.map +1 -0
  126. package/dist/workspace/checks/pipelines.d.ts +76 -0
  127. package/dist/workspace/checks/pipelines.d.ts.map +1 -0
  128. package/dist/workspace/checks.d.ts +161 -0
  129. package/dist/workspace/checks.d.ts.map +1 -0
  130. package/dist/workspace/compose-graph.d.ts +115 -0
  131. package/dist/workspace/compose-graph.d.ts.map +1 -0
  132. package/dist/workspace/compose-reports.d.ts +68 -0
  133. package/dist/workspace/compose-reports.d.ts.map +1 -0
  134. package/dist/workspace/declaration.d.ts +220 -0
  135. package/dist/workspace/declaration.d.ts.map +1 -0
  136. package/dist/workspace/declaration.schema.json +434 -0
  137. package/dist/workspace/generated-files.d.ts +65 -0
  138. package/dist/workspace/generated-files.d.ts.map +1 -0
  139. package/dist/workspace/glob.d.ts +13 -0
  140. package/dist/workspace/glob.d.ts.map +1 -0
  141. package/dist/workspace/graph-cli.d.ts +75 -0
  142. package/dist/workspace/graph-cli.d.ts.map +1 -0
  143. package/dist/workspace/init.d.ts +96 -0
  144. package/dist/workspace/init.d.ts.map +1 -0
  145. package/dist/workspace/jsonc.d.ts +38 -0
  146. package/dist/workspace/jsonc.d.ts.map +1 -0
  147. package/dist/workspace/kinds.d.ts +192 -0
  148. package/dist/workspace/kinds.d.ts.map +1 -0
  149. package/dist/workspace/lineage-check.d.ts +84 -0
  150. package/dist/workspace/lineage-check.d.ts.map +1 -0
  151. package/dist/workspace/lineage-cli.d.ts +31 -0
  152. package/dist/workspace/lineage-cli.d.ts.map +1 -0
  153. package/dist/workspace/lineage-init.d.ts +101 -0
  154. package/dist/workspace/lineage-init.d.ts.map +1 -0
  155. package/dist/workspace/lineage-lock.d.ts +258 -0
  156. package/dist/workspace/lineage-lock.d.ts.map +1 -0
  157. package/dist/workspace/lineage-merge.d.ts +14 -0
  158. package/dist/workspace/lineage-merge.d.ts.map +1 -0
  159. package/dist/workspace/lineage-migrations.d.ts +178 -0
  160. package/dist/workspace/lineage-migrations.d.ts.map +1 -0
  161. package/dist/workspace/lineage-update.d.ts +59 -0
  162. package/dist/workspace/lineage-update.d.ts.map +1 -0
  163. package/dist/workspace/lineage-upgrade-cli.d.ts +40 -0
  164. package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -0
  165. package/dist/workspace/lineage-upgrade.d.ts +146 -0
  166. package/dist/workspace/lineage-upgrade.d.ts.map +1 -0
  167. package/dist/workspace/lineage-vendor.d.ts +63 -0
  168. package/dist/workspace/lineage-vendor.d.ts.map +1 -0
  169. package/dist/workspace/lineage-version.d.ts +32 -0
  170. package/dist/workspace/lineage-version.d.ts.map +1 -0
  171. package/dist/workspace/links.d.ts +150 -0
  172. package/dist/workspace/links.d.ts.map +1 -0
  173. package/dist/workspace/ls.d.ts +99 -0
  174. package/dist/workspace/ls.d.ts.map +1 -0
  175. package/dist/workspace/member-commands.d.ts +139 -0
  176. package/dist/workspace/member-commands.d.ts.map +1 -0
  177. package/dist/workspace/member-handles.d.ts +54 -0
  178. package/dist/workspace/member-handles.d.ts.map +1 -0
  179. package/dist/workspace/member-pipeline.d.ts +143 -0
  180. package/dist/workspace/member-pipeline.d.ts.map +1 -0
  181. package/dist/workspace/member-run.d.ts +67 -0
  182. package/dist/workspace/member-run.d.ts.map +1 -0
  183. package/dist/workspace/reason-codes.d.ts +65 -0
  184. package/dist/workspace/reason-codes.d.ts.map +1 -0
  185. package/dist/workspace/records-cli.d.ts +28 -4
  186. package/dist/workspace/records-cli.d.ts.map +1 -1
  187. package/dist/workspace/records.d.ts.map +1 -1
  188. package/dist/workspace/root-refusal.d.ts +53 -0
  189. package/dist/workspace/root-refusal.d.ts.map +1 -0
  190. package/dist/workspace/status.d.ts +150 -0
  191. package/dist/workspace/status.d.ts.map +1 -0
  192. package/dist/workspace/template-manifest.d.ts +90 -0
  193. package/dist/workspace/template-manifest.d.ts.map +1 -0
  194. package/dist/workspace/tree.d.ts +36 -0
  195. package/dist/workspace/tree.d.ts.map +1 -0
  196. package/dist/workspace/trust/attestor.d.ts +67 -0
  197. package/dist/workspace/trust/attestor.d.ts.map +1 -0
  198. package/dist/workspace/trust/policy.d.ts +111 -0
  199. package/dist/workspace/trust/policy.d.ts.map +1 -0
  200. package/dist/workspace/trust/provenance.d.ts +67 -0
  201. package/dist/workspace/trust/provenance.d.ts.map +1 -0
  202. package/dist/workspace/trust/ssh-commit.d.ts +43 -0
  203. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -0
  204. package/dist/workspace/trust/test-repo.d.ts +42 -0
  205. package/dist/workspace/trust/test-repo.d.ts.map +1 -0
  206. package/dist/workspace/trust/verify-cli.d.ts +9 -0
  207. package/dist/workspace/trust/verify-cli.d.ts.map +1 -0
  208. package/dist/workspace/trust/verify.d.ts +66 -0
  209. package/dist/workspace/trust/verify.d.ts.map +1 -0
  210. package/dist/workspace/which-chant.d.ts +58 -0
  211. package/dist/workspace/which-chant.d.ts.map +1 -0
  212. package/dist/workspace/workspace-kinds.schema.json +133 -0
  213. package/package.json +2 -1
  214. package/src/audit/discover.ts +66 -78
  215. package/src/audit/report-html.ts +3 -1
  216. package/src/audit/report-model.ts +14 -1
  217. package/src/audit/terraform-state.test.ts +4 -9
  218. package/src/audit/terraform-state.ts +4 -5
  219. package/src/cli/commands/audit-truncation.test.ts +167 -0
  220. package/src/cli/commands/audit.ts +89 -60
  221. package/src/cli/commands/check-lexicon.ts +24 -0
  222. package/src/cli/commands/doctor.test.ts +30 -0
  223. package/src/cli/commands/doctor.ts +18 -0
  224. package/src/cli/commands/import-agents.ts +6 -2
  225. package/src/cli/commands/init.ts +22 -3
  226. package/src/cli/commands/lint.test.ts +17 -23
  227. package/src/cli/commands/lint.ts +24 -84
  228. package/src/cli/commands/update.ts +13 -4
  229. package/src/cli/commands/vendor.ts +11 -6
  230. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +38 -0
  231. package/src/cli/handlers/build-member.test.ts +127 -0
  232. package/src/cli/handlers/build.ts +35 -1
  233. package/src/cli/handlers/components.test.ts +127 -0
  234. package/src/cli/handlers/components.ts +70 -8
  235. package/src/cli/handlers/dev.ts +5 -3
  236. package/src/cli/handlers/fan-out.ts +9 -2
  237. package/src/cli/handlers/init.ts +54 -1
  238. package/src/cli/handlers/operator.test.ts +68 -0
  239. package/src/cli/handlers/operator.ts +37 -9
  240. package/src/cli/handlers/promote.test.ts +118 -2
  241. package/src/cli/handlers/promote.ts +82 -16
  242. package/src/cli/handlers/run-generate-member.test.ts +83 -0
  243. package/src/cli/handlers/run-generate.test.ts +6 -8
  244. package/src/cli/handlers/run-generate.ts +36 -1
  245. package/src/cli/handlers/run.test.ts +47 -0
  246. package/src/cli/handlers/run.ts +28 -5
  247. package/src/cli/handlers/vendor.ts +147 -43
  248. package/src/cli/main.test.ts +105 -1
  249. package/src/cli/main.ts +230 -17
  250. package/src/cli/plugins.ts +17 -17
  251. package/src/cli/registry.ts +32 -0
  252. package/src/cli/static-config-read.test.ts +208 -0
  253. package/src/codegen/fetch.test.ts +63 -1
  254. package/src/codegen/fetch.ts +30 -1
  255. package/src/components/cli-support.ts +6 -1
  256. package/src/components/discover.test.ts +27 -57
  257. package/src/components/discover.ts +13 -68
  258. package/src/components/driver.test.ts +78 -1
  259. package/src/components/driver.ts +43 -7
  260. package/src/components/gate-plan.ts +53 -0
  261. package/src/components/promote.test.ts +276 -2
  262. package/src/components/promote.ts +119 -15
  263. package/src/components/sandbox/driver.ts +8 -8
  264. package/src/components/sandbox/run.ts +4 -1
  265. package/src/components/verbs/build-archive.ts +27 -21
  266. package/src/components/verbs/run-agent.ts +3 -5
  267. package/src/config-static.test.ts +136 -0
  268. package/src/config-static.ts +232 -0
  269. package/src/config.test.ts +2 -2
  270. package/src/config.ts +48 -15
  271. package/src/content-digest.test.ts +120 -0
  272. package/src/content-digest.ts +21 -0
  273. package/src/discovery/files.test.ts +10 -10
  274. package/src/discovery/files.ts +25 -81
  275. package/src/discovery/fold-import.ts +49 -5
  276. package/src/discovery/index.ts +1 -0
  277. package/src/discovery/sandbox/driver.ts +73 -35
  278. package/src/discovery/sandbox/fork-diagnostic.test.ts +136 -13
  279. package/src/discovery/sandbox/fork.ts +83 -43
  280. package/src/discovery/sandbox/path-lexicon-multifile.test.ts +285 -0
  281. package/src/discovery/walk.test.ts +263 -0
  282. package/src/discovery/walk.ts +334 -0
  283. package/src/effect-receipt.ts +2 -2
  284. package/src/graph-ir.ts +4 -1
  285. package/src/join-key.test.ts +25 -0
  286. package/src/join-key.ts +39 -0
  287. package/src/lexicon-module.ts +108 -10
  288. package/src/lexicon.ts +79 -1
  289. package/src/lifecycle/build-ledger-store.ts +11 -10
  290. package/src/lifecycle/digest.ts +9 -5
  291. package/src/lifecycle/gate-ledger.test.ts +20 -0
  292. package/src/lifecycle/gate-ledger.ts +28 -6
  293. package/src/lifecycle/git.test.ts +13 -1
  294. package/src/lifecycle/git.ts +221 -176
  295. package/src/lifecycle/lease.ts +25 -10
  296. package/src/lifecycle/legacy-digest.test.ts +115 -23
  297. package/src/lifecycle/legacy-digest.ts +60 -19
  298. package/src/lifecycle/member-ledger.test.ts +319 -0
  299. package/src/lifecycle/member-ledger.ts +145 -0
  300. package/src/lifecycle/plan-digest.ts +7 -7
  301. package/src/lifecycle/release-ledger.test.ts +20 -0
  302. package/src/lifecycle/release-ledger.ts +23 -7
  303. package/src/lifecycle/status.test.ts +151 -1
  304. package/src/lifecycle/status.ts +228 -7
  305. package/src/op/activities/activity-contracts.ts +37 -0
  306. package/src/op/activities/index.ts +10 -0
  307. package/src/op/activities/propose-upgrade.ts +206 -0
  308. package/src/op/discover.test.ts +14 -4
  309. package/src/op/discover.ts +19 -27
  310. package/src/op/gate-name.ts +8 -0
  311. package/src/op/gate-summary.test.ts +14 -0
  312. package/src/op/gate-summary.ts +25 -2
  313. package/src/op/gate.ts +36 -7
  314. package/src/op/generate-pipeline.test.ts +14 -5
  315. package/src/project-root.test.ts +66 -1
  316. package/src/project-root.ts +48 -0
  317. package/src/workspace/__fixtures__/contract-repo.ts +100 -0
  318. package/src/workspace/__fixtures__/two-member-workspace.ts +35 -0
  319. package/src/workspace/behold-kinds.test.ts +162 -0
  320. package/src/workspace/check-contract.test.ts +127 -0
  321. package/src/workspace/check.schema.json +347 -0
  322. package/src/workspace/checks/generated.test.ts +287 -0
  323. package/src/workspace/checks/generated.ts +475 -0
  324. package/src/workspace/checks/ledgers.test.ts +139 -0
  325. package/src/workspace/checks/ledgers.ts +246 -0
  326. package/src/workspace/checks/links.ts +183 -0
  327. package/src/workspace/checks/pipelines.test.ts +133 -0
  328. package/src/workspace/checks/pipelines.ts +225 -0
  329. package/src/workspace/checks-repo.test.ts +71 -0
  330. package/src/workspace/checks.test.ts +450 -0
  331. package/src/workspace/checks.ts +461 -0
  332. package/src/workspace/compose-graph.test.ts +146 -0
  333. package/src/workspace/compose-graph.ts +228 -0
  334. package/src/workspace/compose-reports.test.ts +109 -0
  335. package/src/workspace/compose-reports.ts +186 -0
  336. package/src/workspace/declaration.schema.json +434 -0
  337. package/src/workspace/declaration.test.ts +347 -0
  338. package/src/workspace/declaration.ts +623 -0
  339. package/src/workspace/generated-files.test.ts +114 -0
  340. package/src/workspace/generated-files.ts +94 -0
  341. package/src/workspace/glob.ts +49 -0
  342. package/src/workspace/graph-cli.ts +220 -0
  343. package/src/workspace/graph-contract.test.ts +204 -0
  344. package/src/workspace/graph.schema.json +662 -0
  345. package/src/workspace/init.test.ts +223 -0
  346. package/src/workspace/init.ts +585 -0
  347. package/src/workspace/jsonc.test.ts +65 -0
  348. package/src/workspace/jsonc.ts +231 -0
  349. package/src/workspace/kinds.test.ts +288 -0
  350. package/src/workspace/kinds.ts +506 -0
  351. package/src/workspace/lineage-check.ts +264 -0
  352. package/src/workspace/lineage-cli.ts +114 -0
  353. package/src/workspace/lineage-init.test.ts +231 -0
  354. package/src/workspace/lineage-init.ts +365 -0
  355. package/src/workspace/lineage-lock.test.ts +224 -0
  356. package/src/workspace/lineage-lock.ts +357 -0
  357. package/src/workspace/lineage-merge.ts +44 -0
  358. package/src/workspace/lineage-migrations.test.ts +178 -0
  359. package/src/workspace/lineage-migrations.ts +343 -0
  360. package/src/workspace/lineage-update.ts +160 -0
  361. package/src/workspace/lineage-upgrade-cli.ts +151 -0
  362. package/src/workspace/lineage-upgrade.test.ts +474 -0
  363. package/src/workspace/lineage-upgrade.ts +624 -0
  364. package/src/workspace/lineage-vendor.test.ts +126 -0
  365. package/src/workspace/lineage-vendor.ts +215 -0
  366. package/src/workspace/lineage-version.ts +127 -0
  367. package/src/workspace/links.test.ts +344 -0
  368. package/src/workspace/links.ts +331 -0
  369. package/src/workspace/ls-contract.test.ts +200 -0
  370. package/src/workspace/ls.schema.json +190 -0
  371. package/src/workspace/ls.ts +254 -0
  372. package/src/workspace/member-commands.e2e.test.ts +244 -0
  373. package/src/workspace/member-commands.test.ts +217 -0
  374. package/src/workspace/member-commands.ts +540 -0
  375. package/src/workspace/member-handles.test.ts +76 -0
  376. package/src/workspace/member-handles.ts +228 -0
  377. package/src/workspace/member-pipeline.test.ts +135 -0
  378. package/src/workspace/member-pipeline.ts +317 -0
  379. package/src/workspace/member-run.ts +146 -0
  380. package/src/workspace/read-contract.test.ts +129 -0
  381. package/src/workspace/reason-codes.test.ts +113 -0
  382. package/src/workspace/reason-codes.ts +81 -0
  383. package/src/workspace/records-cli.ts +86 -14
  384. package/src/workspace/records-contract.test.ts +5 -0
  385. package/src/workspace/records.schema.json +32 -2
  386. package/src/workspace/records.ts +3 -2
  387. package/src/workspace/root-refusal.ts +97 -0
  388. package/src/workspace/status-contract.test.ts +308 -0
  389. package/src/workspace/status.schema.json +210 -0
  390. package/src/workspace/status.ts +412 -0
  391. package/src/workspace/template-manifest.test.ts +79 -0
  392. package/src/workspace/template-manifest.ts +221 -0
  393. package/src/workspace/tree.ts +132 -0
  394. package/src/workspace/trust/attestor.ts +93 -0
  395. package/src/workspace/trust/policy.ts +306 -0
  396. package/src/workspace/trust/provenance.ts +181 -0
  397. package/src/workspace/trust/ssh-commit.ts +138 -0
  398. package/src/workspace/trust/test-repo.ts +138 -0
  399. package/src/workspace/trust/trust.test.ts +397 -0
  400. package/src/workspace/trust/verify-cli.ts +56 -0
  401. package/src/workspace/trust/verify.ts +194 -0
  402. package/src/workspace/which-chant.test.ts +117 -0
  403. package/src/workspace/which-chant.ts +151 -0
  404. package/src/workspace/workspace-kinds.schema.json +133 -0
  405. package/dist/components/unbound-gate-approval.d.ts +0 -24
  406. package/dist/components/unbound-gate-approval.d.ts.map +0 -1
  407. package/dist/discovery/convergence.d.ts +0 -97
  408. package/dist/discovery/convergence.d.ts.map +0 -1
  409. package/src/cli/commands/audit-walk-warnings.test.ts +0 -156
  410. package/src/components/unbound-gate-approval.test.ts +0 -82
  411. package/src/components/unbound-gate-approval.ts +0 -51
  412. package/src/discovery/convergence.test.ts +0 -231
  413. package/src/discovery/convergence.ts +0 -399
  414. package/src/op/discover-convergence.test.ts +0 -70
@@ -0,0 +1,264 @@
1
+ /**
2
+ * `chant workspace check [--at <rev>] [--json] [--format stylish|json|sarif] [--generated]`:
3
+ * the lineage checks (#2550, D9), and the declaration checks (#2535, D16)
4
+ * when a declaration sits between the current directory and the git root.
5
+ *
6
+ * D9 says open manual steps fail `check`. So far, `check` checks the
7
+ * lineage lock only: the lock must be readable, and no scope may have an open
8
+ * manual step. It needs no `chant.workspace.json`, the way `chant workspace
9
+ * lineage` needs none, and a directory without a lock passes with nothing to
10
+ * check. The declaration checks report `WSP` findings through lint's
11
+ * reporters (`./checks.ts`), and cover member ledgers (#2538), recorded
12
+ * pipelines (#2542) and generated files (#2541) as well as the declaration
13
+ * itself (#2641). `--generated` runs each declared generator too.
14
+ *
15
+ * `chant workspace upgrade` runs the same checks in its staging worktree
16
+ * before it reaches its gate.
17
+ */
18
+
19
+ import { realpathSync } from "node:fs";
20
+ import { join, relative, resolve, sep } from "node:path";
21
+ import { formatError, formatSuccess } from "../cli/format";
22
+ import type { CommandContext } from "../cli/registry";
23
+ import type { LintDiagnostic, LintRule } from "../lint/rule";
24
+ import { findWorkspaceRoot } from "../project-root";
25
+ import type { DeclarationCheckReport } from "./checks";
26
+ import { LOCK_FILE, LockError, parseLock, readLock } from "./lineage-lock";
27
+ import type { ReasonCode } from "./reason-codes";
28
+ import type { WorkspaceTree } from "./tree";
29
+
30
+ /** Closed: a reader may switch on it. */
31
+ export const CHECK_CODES = ["lock-invalid", "manual-step-open"] as const satisfies readonly ReasonCode[];
32
+ export type CheckCode = (typeof CHECK_CODES)[number];
33
+
34
+ export interface CheckFinding {
35
+ code: CheckCode;
36
+ /** The scope the finding is in, or null for the lock as a whole. */
37
+ scope: string | null;
38
+ /** The file, relative to the lock's directory, when the finding is about one. */
39
+ path?: string;
40
+ message: string;
41
+ }
42
+
43
+ export interface CheckReport {
44
+ lock: string | null;
45
+ ok: boolean;
46
+ findings: CheckFinding[];
47
+ }
48
+
49
+ /**
50
+ * Run the lineage checks over the lock at `root`. Never throws a
51
+ * {@link LockError}. `lockText` is the lock's text when it comes from
52
+ * somewhere other than the disk, such as a revision (`--at`), with null
53
+ * for no lock.
54
+ */
55
+ export function checkLineage(root: string, lockText?: string | null): CheckReport {
56
+ let lock;
57
+ try {
58
+ lock = lockText === undefined ? readLock(root) : lockText === null ? null : parseLock(lockText);
59
+ } catch (err) {
60
+ if (!(err instanceof LockError)) throw err;
61
+ return { lock: LOCK_FILE, ok: false, findings: [{ code: "lock-invalid", scope: null, path: LOCK_FILE, message: err.message }] };
62
+ }
63
+ if (!lock) return { lock: null, ok: true, findings: [] };
64
+ const findings: CheckFinding[] = [];
65
+ for (const [scope, lineage] of Object.entries(lock.scopes)) {
66
+ for (const step of lineage.manualSteps) {
67
+ const path = scope === "." ? step.path : `${scope}/${step.path}`;
68
+ findings.push({
69
+ code: "manual-step-open",
70
+ scope,
71
+ path,
72
+ message: `manual step open (${step.reason}): merge by hand, then \`chant workspace lineage resolve ${path}\``,
73
+ });
74
+ }
75
+ }
76
+ return { lock: LOCK_FILE, ok: findings.length === 0, findings };
77
+ }
78
+
79
+ /** A finding's identity, for comparing two reports. */
80
+ export function findingKey(f: CheckFinding): string {
81
+ return `${f.code}\0${f.scope ?? ""}\0${f.path ?? ""}`;
82
+ }
83
+
84
+ const USAGE = "chant workspace check [--at <rev>] [--json] [--format stylish|json|sarif] [--generated]";
85
+ const FORMATS = ["stylish", "json", "sarif"] as const;
86
+
87
+ /** A lock finding as a lint diagnostic, for `--format json` and `--format sarif`. */
88
+ function lockDiagnostic(f: CheckFinding): LintDiagnostic {
89
+ return { file: f.path ?? LOCK_FILE, line: 1, column: 1, ruleId: f.code, severity: "error", message: f.message };
90
+ }
91
+
92
+ const LOCK_RULES: LintRule[] = [
93
+ { id: "lock-invalid", severity: "error", category: "correctness", description: "The lineage lock can be read.", helpUri: "https://intentius.io/chant/cli/workspace-check/", check: () => [] },
94
+ {
95
+ id: "manual-step-open",
96
+ severity: "error",
97
+ category: "correctness",
98
+ description: "No scope in the lineage lock has an open manual step.",
99
+ helpUri: "https://intentius.io/chant/cli/workspace-check/",
100
+ check: () => [],
101
+ },
102
+ ];
103
+
104
+ /** SARIF says `inSource` for a comment in the code; a declaration's `suppress` is `external`. */
105
+ function markExternalSuppressions(sarif: string): string {
106
+ const doc = JSON.parse(sarif) as { runs: { results: { suppressions?: { kind: string }[] }[] }[] };
107
+ for (const run of doc.runs) for (const r of run.results) for (const s of r.suppressions ?? []) s.kind = "external";
108
+ return JSON.stringify(doc, null, 2);
109
+ }
110
+
111
+ /** The path from tree directory `from` to tree directory `to`, both relative to the git root. */
112
+ function posixRelative(from: string, to: string): string {
113
+ return relative(`/${from}`, `/${to}`) || ".";
114
+ }
115
+
116
+ /** The version of the `check` document this chant writes. */
117
+ export const CHECK_CONTRACT_VERSION = 1;
118
+
119
+ /** `$id` of the JSON Schema for the `--format json` output, shipped beside this file. */
120
+ export const CHECK_OUTPUT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/check/v1/check.schema.json";
121
+
122
+ /** Why `check` could not run at all: only the `--at` codes. A declaration that can't be read is a WSP001 finding instead. */
123
+ export const CHECK_ERROR_CODES = ["not-a-git-repository", "revision-unknown"] as const satisfies readonly ReasonCode[];
124
+ export type CheckErrorCode = (typeof CHECK_ERROR_CODES)[number];
125
+
126
+ /** What `check --format json` prints (#2536). `check.schema.json` describes it. */
127
+ export type CheckDocument =
128
+ | (CheckReport & {
129
+ $schema: string;
130
+ contract: number;
131
+ chant: string;
132
+ at: string | null;
133
+ /** The declaration's workspace, or null when there is none; `name` is null when it can't be read. */
134
+ workspace: { name: string | null; root: string } | null;
135
+ declaration?: DeclarationCheckReport;
136
+ })
137
+ | { $schema: string; contract: number; chant: string; error: { code: CheckErrorCode; message: string; location: null } };
138
+
139
+ /**
140
+ * Run every check from `cwd`, in the working tree or at revision `at`, and
141
+ * build the document the JSON formats print. Paths in findings are relative
142
+ * to `cwd`. `runGenerators` is `--generated`; with `at` it does nothing,
143
+ * since the member facts describe the working tree, not the revision.
144
+ */
145
+ export async function runChecks(cwd: string, at?: string, options: { runGenerators?: boolean } = {}): Promise<CheckDocument> {
146
+ // Loaded here, not at the top: `workspace upgrade` imports this module for checkLineage alone.
147
+ const [{ findDeclarationDir, readDeclaration, readerVersion }, { gitTop, gitTree, resolveCommit, workingTree }] = await Promise.all([
148
+ import("./declaration"),
149
+ import("./tree"),
150
+ ]);
151
+ const head = { $schema: CHECK_OUTPUT_SCHEMA_ID, contract: CHECK_CONTRACT_VERSION, chant: readerVersion() };
152
+ let report: CheckReport;
153
+ /** The declaration's directory on disk, its root relative to the git root, and the files to check. */
154
+ let found: { dir: string; root: string; tree: WorkspaceTree } | undefined;
155
+ let commit: string | null = null;
156
+ if (at === undefined) {
157
+ report = checkLineage(cwd);
158
+ // Declaration checks run when a declaration sits between here and the git
159
+ // root (#2535). Without one the command stays a lock check.
160
+ const f = findWorkspaceRoot(cwd);
161
+ if (f) {
162
+ const top = gitTop(f.dir);
163
+ const rel = top ? relative(top, realpathSync(f.dir)).split(sep).join("/") : f.dir;
164
+ found = { dir: f.dir, root: rel === "" ? "." : rel, tree: workingTree(f.dir) };
165
+ }
166
+ } else {
167
+ const top = gitTop(cwd);
168
+ if (!top) return { ...head, error: { code: "not-a-git-repository", message: "--at reads git objects, and this directory is not in a git repository", location: null } };
169
+ const c = resolveCommit(top, at);
170
+ if (!c) return { ...head, error: { code: "revision-unknown", message: `--at ${at} names no commit in this repository`, location: null } };
171
+ commit = c;
172
+ const rel = relative(top, realpathSync(resolve(cwd))).split(sep).join("/");
173
+ const start = rel.startsWith("..") ? "" : rel;
174
+ const whole = gitTree(top, c);
175
+ const lockAt = start ? `${start}/${LOCK_FILE}` : LOCK_FILE;
176
+ report = checkLineage(cwd, whole.stat(lockAt) === "file" ? whole.read(lockAt) : null);
177
+ const dir = findDeclarationDir(whole, start);
178
+ // The root on disk is reached from cwd, so paths print relative to cwd as it was given.
179
+ if (dir !== undefined) found = { dir: resolve(cwd, posixRelative(start, dir)), root: dir === "" ? "." : dir, tree: dir === "" ? whole : gitTree(top, c, dir) };
180
+ }
181
+
182
+ let declaration: DeclarationCheckReport | undefined;
183
+ let workspace: { name: string | null; root: string } | null = null;
184
+ if (found) {
185
+ const { runDeclarationChecks } = await import("./checks");
186
+ const { dir, tree } = found;
187
+ declaration = await runDeclarationChecks(dir, (file) => relative(cwd, join(dir, file)).split(sep).join("/"), {
188
+ runGenerators: options.runGenerators === true,
189
+ ...(commit !== null ? { tree } : {}),
190
+ });
191
+ let name: string | null = null;
192
+ try {
193
+ name = readDeclaration(tree, "", { rootChant: true }).name;
194
+ } catch {
195
+ // The WSP001 finding already says why.
196
+ }
197
+ workspace = { name, root: found.root };
198
+ }
199
+ const ok = report.ok && (declaration?.ok ?? true);
200
+ return { ...head, at: commit, workspace, ...report, ok, ...(declaration ? { declaration } : {}) };
201
+ }
202
+
203
+ export async function runWorkspaceCheck(ctx: CommandContext): Promise<number> {
204
+ const root = process.cwd();
205
+ if (ctx.args.extraPositional) {
206
+ console.error(formatError({ message: `chant workspace check takes no argument (got ${ctx.args.extraPositional})`, hint: USAGE }));
207
+ return 1;
208
+ }
209
+ const format = (ctx.args.format || "stylish") as (typeof FORMATS)[number];
210
+ if (!FORMATS.includes(format)) {
211
+ console.error(formatError({ message: `--format ${format} is not a check format; use stylish, json or sarif`, hint: USAGE }));
212
+ return 1;
213
+ }
214
+ // The root's chant reads the declaration (ws-021). Only a workspace with a
215
+ // declaration loads this.
216
+ if (ctx.args.at !== undefined || findWorkspaceRoot(root)) {
217
+ const { handToRootChant } = await import("./which-chant");
218
+ const handed = await handToRootChant(root, ctx.args.at);
219
+ if (handed !== undefined) return handed;
220
+ }
221
+ const doc = await runChecks(root, ctx.args.at, { runGenerators: ctx.args.generated === true });
222
+ if ("error" in doc) {
223
+ if (ctx.args.json || format === "json") console.log(JSON.stringify(doc, null, 2));
224
+ else console.error(formatError({ message: `${doc.error.code}: ${doc.error.message}`, hint: USAGE }));
225
+ return 1;
226
+ }
227
+ const { declaration, ok } = doc;
228
+ const report = { lock: doc.lock, findings: doc.findings };
229
+
230
+ // --format json is the read contract's document. --json keeps the shape it
231
+ // had before the contract, the lineage report with `declaration` beside it.
232
+ if (format === "json") {
233
+ console.log(JSON.stringify(doc, null, 2));
234
+ return ok ? 0 : 1;
235
+ }
236
+ if (ctx.args.json) {
237
+ console.log(JSON.stringify({ lock: doc.lock, ok, findings: doc.findings, ...(declaration ? { declaration } : {}) }, null, 2));
238
+ return ok ? 0 : 1;
239
+ }
240
+ // The reporters load only when there is something to report through them,
241
+ // so a plain lock check loads what it did before.
242
+ if (format === "sarif") {
243
+ const { formatSarif } = await import("../cli/reporters/stylish");
244
+ const diagnostics = [...report.findings.map(lockDiagnostic), ...(declaration?.diagnostics ?? [])];
245
+ const suppressed = declaration?.suppressed ?? [];
246
+ const { workspaceCheckRules } = await import("./checks");
247
+ console.log(markExternalSuppressions(formatSarif(diagnostics, [...LOCK_RULES, ...workspaceCheckRules()], suppressed, doc.chant)));
248
+ return ok ? 0 : 1;
249
+ }
250
+
251
+ if (!report.lock) {
252
+ if (!declaration) console.error(formatSuccess(`no ${LOCK_FILE}: nothing to check`));
253
+ } else if (report.findings.length === 0) {
254
+ console.error(formatSuccess(`${LOCK_FILE}: no open manual steps`));
255
+ } else {
256
+ for (const f of report.findings) console.error(` ${f.path ?? f.scope ?? ""}: ${f.message}`);
257
+ console.error(formatError({ message: `${report.findings.length} check(s) failed` }));
258
+ }
259
+ if (declaration) {
260
+ const { formatStylish } = await import("../cli/reporters/stylish");
261
+ console.log(formatStylish(declaration.diagnostics, declaration.suppressed));
262
+ }
263
+ return ok ? 0 : 1;
264
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * `chant workspace lineage [--json]` and
3
+ * `chant workspace lineage resolve <path>` (#2540).
4
+ *
5
+ * The first shows each scope in `.chant/workspace.lock.json`: where it came
6
+ * from, which files the project has edited since, and which manual steps are
7
+ * open. The second closes a manual step once the file has been merged by
8
+ * hand. Neither needs a `chant.workspace.json`: lineage works for a plain
9
+ * project (#2525 rule 4).
10
+ */
11
+
12
+ import { formatError, formatSuccess } from "../cli/format";
13
+ import type { CommandContext } from "../cli/registry";
14
+ import { LOCK_FILE, LockError, readLock, resolveManualStep, scopeStatus, writeLock, type ManualStep } from "./lineage-lock";
15
+
16
+ const USAGE = "chant workspace lineage [--json] | chant workspace lineage resolve <path>";
17
+
18
+ export interface LineageScopeView {
19
+ scope: string;
20
+ kind: "template" | "vendor";
21
+ name?: string;
22
+ template: string;
23
+ ref?: string;
24
+ address: Record<string, unknown> | null;
25
+ files: number;
26
+ edited: string[];
27
+ missing: string[];
28
+ manualSteps: ManualStep[];
29
+ }
30
+
31
+ /** The lock as `--json` prints it: one view per scope, with its state in the tree. */
32
+ export function lineageView(root: string): { lock: string; scopes: LineageScopeView[] } | null {
33
+ const lock = readLock(root);
34
+ if (!lock) return null;
35
+ const scopes = Object.entries(lock.scopes).map(([scope, lineage]) => {
36
+ const st = scopeStatus(root, scope, lineage);
37
+ return {
38
+ scope,
39
+ kind: lineage.kind,
40
+ ...(lineage.name !== undefined ? { name: lineage.name } : {}),
41
+ template: lineage.template,
42
+ ...(lineage.ref !== undefined ? { ref: lineage.ref } : {}),
43
+ address: lineage.address,
44
+ files: Object.keys(lineage.files).length,
45
+ edited: st.customised,
46
+ missing: st.missing,
47
+ manualSteps: lineage.manualSteps,
48
+ };
49
+ });
50
+ return { lock: LOCK_FILE, scopes };
51
+ }
52
+
53
+ export async function runWorkspaceLineage(ctx: CommandContext): Promise<number> {
54
+ const { args } = ctx;
55
+ const root = process.cwd();
56
+ try {
57
+ if (args.extraPositional === "resolve") return resolve(root, args.extraPositional2);
58
+ if (args.extraPositional) {
59
+ console.error(formatError({ message: `Unknown lineage subcommand: ${args.extraPositional}`, hint: USAGE }));
60
+ return 1;
61
+ }
62
+ const view = lineageView(root);
63
+ if (!view) {
64
+ console.error(formatError({ message: `no ${LOCK_FILE} in ${root}`, hint: "chant init --from or --template writes one; so does chant vendor migrate" }));
65
+ return 1;
66
+ }
67
+ if (args.json) {
68
+ console.log(JSON.stringify(view, null, 2));
69
+ return 0;
70
+ }
71
+ const lines: string[] = [];
72
+ for (const s of view.scopes) {
73
+ const pin = s.ref ? `@${s.ref}` : "";
74
+ const at = typeof s.address?.commit === "string" ? ` (${s.address.commit.slice(0, 12)})` : "";
75
+ lines.push(`${s.scope} ${s.kind}${s.name ? ` ${s.name}` : ""} ${s.template}${pin}${at}`);
76
+ lines.push(` ${s.files} file(s), ${s.edited.length} edited, ${s.missing.length} deleted`);
77
+ for (const m of s.manualSteps) lines.push(` manual step: ${s.scope === "." ? "" : `${s.scope}/`}${m.path} (${m.reason})`);
78
+ }
79
+ const open = view.scopes.reduce((n, s) => n + s.manualSteps.length, 0);
80
+ lines.push(`${view.scopes.length} scope(s), ${open} manual step(s) open`);
81
+ console.log(lines.join("\n"));
82
+ return 0;
83
+ } catch (err) {
84
+ if (!(err instanceof LockError)) throw err;
85
+ console.error(formatError({ message: err.message }));
86
+ return 1;
87
+ }
88
+ }
89
+
90
+ function resolve(root: string, path: string | undefined): number {
91
+ if (!path) {
92
+ console.error(formatError({ message: "resolve needs the path of a file with an open manual step", hint: USAGE }));
93
+ return 1;
94
+ }
95
+ const lock = readLock(root);
96
+ if (!lock) {
97
+ console.error(formatError({ message: `no ${LOCK_FILE} in ${root}` }));
98
+ return 1;
99
+ }
100
+ const closed = resolveManualStep(lock, path);
101
+ if (!closed) {
102
+ console.error(formatError({ message: `no open manual step for ${path}`, hint: "chant workspace lineage lists them" }));
103
+ return 1;
104
+ }
105
+ writeLock(root, lock);
106
+ console.error(
107
+ formatSuccess(
108
+ closed.step.upstream === null
109
+ ? `${path}: the source removed this file; it is now the project's own`
110
+ : `${path}: resolved; the file as it stands is kept, and the source's version is its new merge base`,
111
+ ),
112
+ );
113
+ return 0;
114
+ }
@@ -0,0 +1,231 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
2
+ import { execFileSync } from "node:child_process";
3
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { dirname, join } from "node:path";
6
+ import { initFromCommand, parseTemplateSpec } from "./lineage-init";
7
+ import { LOCK_FILE, fileHash, readLock } from "./lineage-lock";
8
+ import { initCommand } from "../cli/commands/init";
9
+
10
+ let root: string;
11
+ let tpl: string;
12
+
13
+ function git(cwd: string, args: string[]): string {
14
+ return execFileSync("git", args, {
15
+ cwd,
16
+ encoding: "utf-8",
17
+ env: { ...process.env, GIT_AUTHOR_NAME: "t", GIT_AUTHOR_EMAIL: "t@t", GIT_COMMITTER_NAME: "t", GIT_COMMITTER_EMAIL: "t@t" },
18
+ }).trim();
19
+ }
20
+ function put(base: string, rel: string, content: string): void {
21
+ mkdirSync(dirname(join(base, rel)), { recursive: true });
22
+ writeFileSync(join(base, rel), content);
23
+ }
24
+
25
+ beforeEach(() => {
26
+ root = realpathSync(mkdtempSync(join(tmpdir(), "chant-init-from-test-")));
27
+ tpl = join(root, "tpl");
28
+ mkdirSync(tpl);
29
+ git(tpl, ["init", "-q", "-b", "main"]);
30
+ put(tpl, "README.md", "template\n");
31
+ put(tpl, "svc/src/main.ts", "export const a = 1;\n");
32
+ put(tpl, "svc/run.sh", "#!/bin/sh\n");
33
+ execFileSync("chmod", ["+x", join(tpl, "svc/run.sh")]);
34
+ put(tpl, "svc/.mcp.json", "{}\n");
35
+ put(tpl, `svc/${LOCK_FILE}`, JSON.stringify({ lockVersion: 1, scopes: {} }));
36
+ git(tpl, ["add", "-A"]);
37
+ git(tpl, ["commit", "-q", "-m", "v1"]);
38
+ git(tpl, ["tag", "-a", "v1", "-m", "v1"]);
39
+ put(tpl, "svc/src/main.ts", "export const a = 2;\n");
40
+ git(tpl, ["commit", "-q", "-am", "v2"]);
41
+ });
42
+ afterEach(() => {
43
+ rmSync(root, { recursive: true, force: true });
44
+ });
45
+
46
+ describe("parseTemplateSpec", () => {
47
+ test("owner/name is a GitHub repository", () => {
48
+ expect(parseTemplateSpec("acme/starter@v1.2.0#service", root)).toEqual({
49
+ repo: "acme/starter",
50
+ url: "https://github.com/acme/starter.git",
51
+ ref: "v1.2.0",
52
+ member: "service",
53
+ id: "github.com/acme/starter#service",
54
+ });
55
+ });
56
+ test("URLs and scp-style remotes keep their @ and :", () => {
57
+ expect(parseTemplateSpec("https://git.example.com/acme/starter.git@main", root)).toMatchObject({
58
+ url: "https://git.example.com/acme/starter.git",
59
+ ref: "main",
60
+ id: "git.example.com/acme/starter",
61
+ });
62
+ expect(parseTemplateSpec("git@github.com:acme/starter.git@v2", root)).toMatchObject({
63
+ url: "git@github.com:acme/starter.git",
64
+ ref: "v2",
65
+ id: "github.com/acme/starter",
66
+ });
67
+ expect(parseTemplateSpec("gitlab.com/acme/starter@v3", root)).toMatchObject({ url: "https://gitlab.com/acme/starter.git", id: "gitlab.com/acme/starter" });
68
+ });
69
+ test("a local path is used as is", () => {
70
+ expect(parseTemplateSpec("./tpl@main", root)).toMatchObject({ url: tpl, repo: "./tpl", id: "./tpl" });
71
+ });
72
+ test("a missing ref or an escaping member is refused", () => {
73
+ expect(() => parseTemplateSpec("acme/starter", root)).toThrow(/<repo>@<ref>/);
74
+ expect(() => parseTemplateSpec("git@github.com:acme/starter", root)).toThrow(/<repo>@<ref>/);
75
+ expect(() => parseTemplateSpec("acme/starter@", root)).toThrow(/<repo>@<ref>/);
76
+ expect(() => parseTemplateSpec("acme/starter@v1#../x", root)).toThrow(/not a directory/);
77
+ });
78
+ });
79
+
80
+ describe("chant init --from", () => {
81
+ test("copies one directory at a tag and records its lineage", async () => {
82
+ const target = join(root, "proj");
83
+ const result = await initFromCommand({ from: `${tpl}@v1#svc`, path: target });
84
+ expect(result.error).toBeUndefined();
85
+ expect(result.createdFiles).toEqual([".mcp.json", "run.sh", "src/main.ts", LOCK_FILE]);
86
+ expect(readFileSync(join(target, "src/main.ts"), "utf-8")).toBe("export const a = 1;\n");
87
+ expect(statSync(join(target, "run.sh")).mode & 0o111).not.toBe(0);
88
+ expect(existsSync(join(target, "README.md"))).toBe(false);
89
+
90
+ const lock = readLock(target)!;
91
+ const scope = lock.scopes["."];
92
+ const commit = git(tpl, ["rev-parse", "v1^{commit}"]);
93
+ expect(scope).toMatchObject({
94
+ kind: "template",
95
+ source: { type: "git", repo: tpl, url: "../tpl", path: "svc" },
96
+ ref: "v1",
97
+ parameters: {},
98
+ migrations: [],
99
+ manualSteps: [],
100
+ });
101
+ expect(scope.address).toMatchObject({ commit, tree: git(tpl, ["rev-parse", `${commit}:svc`]) });
102
+ expect(scope.files["src/main.ts"]).toEqual({ class: "owned", sha256: fileHash("export const a = 1;\n") });
103
+ expect(scope.files[".mcp.json"].class).toBe("seed");
104
+ // The template's own lock described the template, not this project.
105
+ expect(scope.files[LOCK_FILE]).toBeUndefined();
106
+ });
107
+
108
+ test("a branch resolves to its head commit, and the whole repository is the default", async () => {
109
+ const target = join(root, "proj");
110
+ const result = await initFromCommand({ from: `${tpl}@main`, path: target });
111
+ expect(result.success).toBe(true);
112
+ expect(readLock(target)!.scopes["."].address?.commit).toBe(git(tpl, ["rev-parse", "main"]));
113
+ expect(readFileSync(join(target, "svc/src/main.ts"), "utf-8")).toBe("export const a = 2;\n");
114
+ });
115
+
116
+ test("the template's migrations are not copied (#2550)", async () => {
117
+ put(tpl, "svc/.chant/migrations/m.json", "{}");
118
+ git(tpl, ["add", "-A"]);
119
+ git(tpl, ["commit", "-q", "-m", "a migration"]);
120
+ const target = join(root, "proj");
121
+ const result = await initFromCommand({ from: `${tpl}@main#svc`, path: target });
122
+ expect(result.success).toBe(true);
123
+ expect(existsSync(join(target, ".chant/migrations"))).toBe(false);
124
+ expect(Object.keys(readLock(target)!.scopes["."].files).some((f) => f.startsWith(".chant/"))).toBe(false);
125
+ });
126
+
127
+ test("refuses a non-empty directory, an unknown ref and a missing directory", async () => {
128
+ const target = join(root, "proj");
129
+ put(target, "keep.txt", "x");
130
+ expect((await initFromCommand({ from: `${tpl}@v1`, path: target })).error).toMatch(/not empty/);
131
+ expect((await initFromCommand({ from: `${tpl}@nope`, path: join(root, "p2") })).error).toMatch(/could not fetch/);
132
+ expect((await initFromCommand({ from: `${tpl}@v1#nope`, path: join(root, "p3") })).error).toMatch(/no directory nope/);
133
+ expect(existsSync(join(root, "p2"))).toBe(false);
134
+ });
135
+
136
+ test("--force keeps existing files and leaves them out of the lineage", async () => {
137
+ const target = join(root, "proj");
138
+ put(target, "src/main.ts", "mine\n");
139
+ const result = await initFromCommand({ from: `${tpl}@v1#svc`, path: target, force: true });
140
+ expect(result.warnings).toContain("src/main.ts already exists, skipping");
141
+ expect(readFileSync(join(target, "src/main.ts"), "utf-8")).toBe("mine\n");
142
+ expect(readLock(target)!.scopes["."].files["src/main.ts"]).toBeUndefined();
143
+ });
144
+ });
145
+
146
+ describe("chant init --from with parameters (#2627)", () => {
147
+ function declare(manifest: unknown, files: Record<string, string>): void {
148
+ put(tpl, "svc/chant.template.json", JSON.stringify(manifest));
149
+ for (const [rel, content] of Object.entries(files)) put(tpl, `svc/${rel}`, content);
150
+ git(tpl, ["add", "-A"]);
151
+ git(tpl, ["commit", "-q", "-m", "parameters"]);
152
+ }
153
+ const MANIFEST = {
154
+ parameters: { name: { type: "string", default: "Starter", description: "The app's name" }, url: { type: "string", default: "http://localhost", hostBound: true } },
155
+ files: ["package.json", "src/main.ts"],
156
+ };
157
+
158
+ beforeEach(() => {
159
+ declare(MANIFEST, {
160
+ "package.json": '{ "name": "svc", "description": "{{chant:name}}" }\n',
161
+ "src/main.ts": 'export const title = "{{chant:name}}";\nexport const url = `${"{{chant:url}}"}/`;\n',
162
+ "notes.md": "{{chant:name}} stays: this file is not listed\n",
163
+ });
164
+ });
165
+
166
+ test("--param values land in every listed file and in the lock; defaults fill the rest", async () => {
167
+ const target = join(root, "proj");
168
+ const result = await initFromCommand({ from: `${tpl}@main#svc`, path: target, params: { name: "Untitled app" } });
169
+ expect(result.error).toBeUndefined();
170
+ expect(result.parameters).toEqual({ name: "Untitled app", url: "http://localhost" });
171
+ expect(readFileSync(join(target, "package.json"), "utf-8")).toBe('{ "name": "svc", "description": "Untitled app" }\n');
172
+ expect(readFileSync(join(target, "src/main.ts"), "utf-8")).toBe('export const title = "Untitled app";\nexport const url = `${"http://localhost"}/`;\n');
173
+ expect(readFileSync(join(target, "notes.md"), "utf-8")).toBe("{{chant:name}} stays: this file is not listed\n");
174
+ // The manifest is template metadata, like its migrations.
175
+ expect(existsSync(join(target, "chant.template.json"))).toBe(false);
176
+ expect(result.createdFiles).not.toContain("chant.template.json");
177
+
178
+ const scope = readLock(target)!.scopes["."];
179
+ expect(scope.parameters).toEqual({ name: "Untitled app", url: "http://localhost" });
180
+ expect(scope.files["chant.template.json"]).toBeUndefined();
181
+ // The merge base is the file as written, with the value in it.
182
+ expect(scope.files["src/main.ts"].sha256).toBe(fileHash(readFileSync(join(target, "src/main.ts"))));
183
+ });
184
+
185
+ test("an undeclared --param is refused with the declared names listed, and nothing is written", async () => {
186
+ const target = join(root, "proj");
187
+ const result = await initFromCommand({ from: `${tpl}@main#svc`, path: target, params: { title: "x" } });
188
+ expect(result.success).toBe(false);
189
+ expect(result.error).toBe("unknown parameter title (declared: name, url)");
190
+ expect(existsSync(target)).toBe(false);
191
+ });
192
+
193
+ test("a template without a manifest refuses any --param", async () => {
194
+ const result = await initFromCommand({ from: `${tpl}@v1#svc`, path: join(root, "proj"), params: { name: "x" } });
195
+ expect(result.error).toBe("unknown parameter name (the template declares none)");
196
+ });
197
+
198
+ test("a placeholder with no declaration is an error in the template", async () => {
199
+ declare(MANIFEST, { "src/main.ts": "{{chant:owner}}\n" });
200
+ const result = await initFromCommand({ from: `${tpl}@main#svc`, path: join(root, "proj") });
201
+ expect(result.error).toMatch(/src\/main.ts has \{\{chant:owner\}\}, but chant.template.json declares no parameter owner/);
202
+ });
203
+ });
204
+
205
+ describe("chant init --template", () => {
206
+ test("writes the lock; plain init does not", async () => {
207
+ const withTemplate = join(root, "a");
208
+ const r = await initCommand({ path: withTemplate, lexicon: "github", template: "node-pipeline", skipInstall: true });
209
+ expect(r.success).toBe(true);
210
+ expect(r.createdFiles).toContain(LOCK_FILE);
211
+ const scope = readLock(withTemplate)!.scopes["."];
212
+ expect(scope).toMatchObject({
213
+ kind: "template",
214
+ template: "lexicon:github/node-pipeline",
215
+ source: { type: "lexicon", lexicon: "github", template: "node-pipeline" },
216
+ address: { package: "@intentius/chant-lexicon-github" },
217
+ });
218
+ expect(Object.keys(scope.files)).toContain("src/pipeline.ts");
219
+ expect(Object.keys(scope.files).some((f) => f.startsWith(".chant/"))).toBe(false);
220
+ expect(scope.files[".mcp.json"].class).toBe("seed");
221
+ for (const f of r.createdFiles.filter((f) => !f.startsWith(".chant/"))) {
222
+ expect(scope.files[f]?.sha256, f).toBe(fileHash(readFileSync(join(withTemplate, f))));
223
+ }
224
+
225
+ const plain = join(root, "b");
226
+ const p = await initCommand({ path: plain, lexicon: "github", skipInstall: true });
227
+ expect(p.success).toBe(true);
228
+ expect(p.createdFiles).not.toContain(LOCK_FILE);
229
+ expect(existsSync(join(plain, LOCK_FILE))).toBe(false);
230
+ });
231
+ });