@intentius/chant 0.79.0 → 0.81.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (462) hide show
  1. package/dist/audit/core.d.ts.map +1 -1
  2. package/dist/audit/discover.d.ts +30 -0
  3. package/dist/audit/discover.d.ts.map +1 -1
  4. package/dist/audit/report-html.d.ts +5 -0
  5. package/dist/audit/report-html.d.ts.map +1 -1
  6. package/dist/audit/report-model.d.ts +13 -0
  7. package/dist/audit/report-model.d.ts.map +1 -1
  8. package/dist/audit/terraform-state.d.ts +13 -0
  9. package/dist/audit/terraform-state.d.ts.map +1 -1
  10. package/dist/cli/command-group.d.ts +6 -0
  11. package/dist/cli/command-group.d.ts.map +1 -1
  12. package/dist/cli/commands/audit.d.ts +44 -3
  13. package/dist/cli/commands/audit.d.ts.map +1 -1
  14. package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
  15. package/dist/cli/commands/check-lexicon.d.ts +8 -0
  16. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  17. package/dist/cli/commands/doctor.d.ts.map +1 -1
  18. package/dist/cli/commands/import-agents.d.ts.map +1 -1
  19. package/dist/cli/commands/init.d.ts +6 -0
  20. package/dist/cli/commands/init.d.ts.map +1 -1
  21. package/dist/cli/commands/lint.d.ts.map +1 -1
  22. package/dist/cli/commands/onboard.d.ts.map +1 -1
  23. package/dist/cli/commands/update.d.ts +6 -0
  24. package/dist/cli/commands/update.d.ts.map +1 -1
  25. package/dist/cli/commands/vendor.d.ts +5 -0
  26. package/dist/cli/commands/vendor.d.ts.map +1 -1
  27. package/dist/cli/conflict-check.d.ts +6 -0
  28. package/dist/cli/conflict-check.d.ts.map +1 -1
  29. package/dist/cli/handlers/build.d.ts.map +1 -1
  30. package/dist/cli/handlers/components.d.ts.map +1 -1
  31. package/dist/cli/handlers/dev.d.ts.map +1 -1
  32. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  33. package/dist/cli/handlers/init.d.ts.map +1 -1
  34. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  35. package/dist/cli/handlers/misc.d.ts.map +1 -1
  36. package/dist/cli/handlers/operator.d.ts +7 -0
  37. package/dist/cli/handlers/operator.d.ts.map +1 -1
  38. package/dist/cli/handlers/promote.d.ts +20 -0
  39. package/dist/cli/handlers/promote.d.ts.map +1 -0
  40. package/dist/cli/handlers/run-generate.d.ts +39 -0
  41. package/dist/cli/handlers/run-generate.d.ts.map +1 -0
  42. package/dist/cli/handlers/run.d.ts +4 -1
  43. package/dist/cli/handlers/run.d.ts.map +1 -1
  44. package/dist/cli/handlers/vendor.d.ts +8 -3
  45. package/dist/cli/handlers/vendor.d.ts.map +1 -1
  46. package/dist/cli/main.d.ts +12 -0
  47. package/dist/cli/main.d.ts.map +1 -1
  48. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  49. package/dist/cli/plugins.d.ts +14 -0
  50. package/dist/cli/plugins.d.ts.map +1 -1
  51. package/dist/cli/registry.d.ts +46 -1
  52. package/dist/cli/registry.d.ts.map +1 -1
  53. package/dist/codegen/fetch.d.ts +10 -0
  54. package/dist/codegen/fetch.d.ts.map +1 -1
  55. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  56. package/dist/components/cli-support.d.ts +3 -1
  57. package/dist/components/cli-support.d.ts.map +1 -1
  58. package/dist/components/discover.d.ts.map +1 -1
  59. package/dist/components/driver.d.ts +16 -2
  60. package/dist/components/driver.d.ts.map +1 -1
  61. package/dist/components/fan-out-support.d.ts.map +1 -1
  62. package/dist/components/gate-plan.d.ts +40 -0
  63. package/dist/components/gate-plan.d.ts.map +1 -0
  64. package/dist/components/pilots/alb-ecs.pilot.d.ts +7 -1
  65. package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -1
  66. package/dist/components/presets/ecs-fargate.d.ts +10 -4
  67. package/dist/components/presets/ecs-fargate.d.ts.map +1 -1
  68. package/dist/components/promote.d.ts +264 -0
  69. package/dist/components/promote.d.ts.map +1 -0
  70. package/dist/components/sandbox/driver.d.ts.map +1 -1
  71. package/dist/components/sandbox/run.d.ts.map +1 -1
  72. package/dist/components/verbs/build-archive.d.ts +22 -11
  73. package/dist/components/verbs/build-archive.d.ts.map +1 -1
  74. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  75. package/dist/config-static.d.ts +59 -0
  76. package/dist/config-static.d.ts.map +1 -0
  77. package/dist/config.d.ts +104 -5
  78. package/dist/config.d.ts.map +1 -1
  79. package/dist/content-digest.d.ts +17 -0
  80. package/dist/content-digest.d.ts.map +1 -0
  81. package/dist/discovery/files.d.ts +48 -4
  82. package/dist/discovery/files.d.ts.map +1 -1
  83. package/dist/discovery/fold-import.d.ts +32 -1
  84. package/dist/discovery/fold-import.d.ts.map +1 -1
  85. package/dist/discovery/index.d.ts.map +1 -1
  86. package/dist/discovery/sandbox/driver.d.ts +20 -0
  87. package/dist/discovery/sandbox/driver.d.ts.map +1 -1
  88. package/dist/discovery/sandbox/fork.d.ts +20 -0
  89. package/dist/discovery/sandbox/fork.d.ts.map +1 -1
  90. package/dist/discovery/walk.d.ts +119 -0
  91. package/dist/discovery/walk.d.ts.map +1 -0
  92. package/dist/graph-ir.d.ts +17 -1
  93. package/dist/graph-ir.d.ts.map +1 -1
  94. package/dist/join-key.d.ts +31 -0
  95. package/dist/join-key.d.ts.map +1 -0
  96. package/dist/lexicon-module.d.ts +110 -0
  97. package/dist/lexicon-module.d.ts.map +1 -0
  98. package/dist/lexicon.d.ts +82 -1
  99. package/dist/lexicon.d.ts.map +1 -1
  100. package/dist/lifecycle/build-ledger-store.d.ts.map +1 -1
  101. package/dist/lifecycle/digest.d.ts +5 -1
  102. package/dist/lifecycle/digest.d.ts.map +1 -1
  103. package/dist/lifecycle/gate-ledger.d.ts +20 -5
  104. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  105. package/dist/lifecycle/git.d.ts +64 -0
  106. package/dist/lifecycle/git.d.ts.map +1 -1
  107. package/dist/lifecycle/lease.d.ts +8 -1
  108. package/dist/lifecycle/lease.d.ts.map +1 -1
  109. package/dist/lifecycle/legacy-digest.d.ts +59 -0
  110. package/dist/lifecycle/legacy-digest.d.ts.map +1 -0
  111. package/dist/lifecycle/member-ledger.d.ts +67 -0
  112. package/dist/lifecycle/member-ledger.d.ts.map +1 -0
  113. package/dist/lifecycle/plan-digest.d.ts +4 -3
  114. package/dist/lifecycle/plan-digest.d.ts.map +1 -1
  115. package/dist/lifecycle/release-ledger.d.ts +42 -1
  116. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  117. package/dist/lifecycle/status.d.ts +90 -5
  118. package/dist/lifecycle/status.d.ts.map +1 -1
  119. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +2 -3
  120. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -1
  121. package/dist/op/activities/activity-contracts.d.ts +32 -0
  122. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  123. package/dist/op/activities/index.d.ts +2 -0
  124. package/dist/op/activities/index.d.ts.map +1 -1
  125. package/dist/op/activities/propose-upgrade.d.ts +75 -0
  126. package/dist/op/activities/propose-upgrade.d.ts.map +1 -0
  127. package/dist/op/activity-contract-registry.d.ts.map +1 -1
  128. package/dist/op/activity-registry.d.ts.map +1 -1
  129. package/dist/op/discover.d.ts.map +1 -1
  130. package/dist/op/gate-approval.d.ts.map +1 -1
  131. package/dist/op/gate-name.d.ts +7 -0
  132. package/dist/op/gate-name.d.ts.map +1 -1
  133. package/dist/op/gate-summary.d.ts +16 -0
  134. package/dist/op/gate-summary.d.ts.map +1 -1
  135. package/dist/op/gate.d.ts +16 -2
  136. package/dist/op/gate.d.ts.map +1 -1
  137. package/dist/op/generate-pipeline.d.ts.map +1 -1
  138. package/dist/op/runtimes/local.d.ts.map +1 -1
  139. package/dist/project-root.d.ts +26 -0
  140. package/dist/project-root.d.ts.map +1 -1
  141. package/dist/workspace/__fixtures__/contract-repo.d.ts +37 -0
  142. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -0
  143. package/dist/workspace/__fixtures__/two-member-workspace.d.ts +11 -0
  144. package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -0
  145. package/dist/workspace/checks/generated.d.ts +156 -0
  146. package/dist/workspace/checks/generated.d.ts.map +1 -0
  147. package/dist/workspace/checks/ledgers.d.ts +78 -0
  148. package/dist/workspace/checks/ledgers.d.ts.map +1 -0
  149. package/dist/workspace/checks/links.d.ts +25 -0
  150. package/dist/workspace/checks/links.d.ts.map +1 -0
  151. package/dist/workspace/checks/pipelines.d.ts +76 -0
  152. package/dist/workspace/checks/pipelines.d.ts.map +1 -0
  153. package/dist/workspace/checks.d.ts +161 -0
  154. package/dist/workspace/checks.d.ts.map +1 -0
  155. package/dist/workspace/compose-graph.d.ts +115 -0
  156. package/dist/workspace/compose-graph.d.ts.map +1 -0
  157. package/dist/workspace/compose-reports.d.ts +68 -0
  158. package/dist/workspace/compose-reports.d.ts.map +1 -0
  159. package/dist/workspace/declaration.d.ts +220 -0
  160. package/dist/workspace/declaration.d.ts.map +1 -0
  161. package/dist/workspace/declaration.schema.json +434 -0
  162. package/dist/workspace/generated-files.d.ts +65 -0
  163. package/dist/workspace/generated-files.d.ts.map +1 -0
  164. package/dist/workspace/glob.d.ts +13 -0
  165. package/dist/workspace/glob.d.ts.map +1 -0
  166. package/dist/workspace/graph-cli.d.ts +75 -0
  167. package/dist/workspace/graph-cli.d.ts.map +1 -0
  168. package/dist/workspace/init.d.ts +96 -0
  169. package/dist/workspace/init.d.ts.map +1 -0
  170. package/dist/workspace/jsonc.d.ts +38 -0
  171. package/dist/workspace/jsonc.d.ts.map +1 -0
  172. package/dist/workspace/kinds.d.ts +192 -0
  173. package/dist/workspace/kinds.d.ts.map +1 -0
  174. package/dist/workspace/lineage-check.d.ts +84 -0
  175. package/dist/workspace/lineage-check.d.ts.map +1 -0
  176. package/dist/workspace/lineage-cli.d.ts +31 -0
  177. package/dist/workspace/lineage-cli.d.ts.map +1 -0
  178. package/dist/workspace/lineage-init.d.ts +101 -0
  179. package/dist/workspace/lineage-init.d.ts.map +1 -0
  180. package/dist/workspace/lineage-lock.d.ts +258 -0
  181. package/dist/workspace/lineage-lock.d.ts.map +1 -0
  182. package/dist/workspace/lineage-merge.d.ts +14 -0
  183. package/dist/workspace/lineage-merge.d.ts.map +1 -0
  184. package/dist/workspace/lineage-migrations.d.ts +178 -0
  185. package/dist/workspace/lineage-migrations.d.ts.map +1 -0
  186. package/dist/workspace/lineage-update.d.ts +59 -0
  187. package/dist/workspace/lineage-update.d.ts.map +1 -0
  188. package/dist/workspace/lineage-upgrade-cli.d.ts +40 -0
  189. package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -0
  190. package/dist/workspace/lineage-upgrade.d.ts +146 -0
  191. package/dist/workspace/lineage-upgrade.d.ts.map +1 -0
  192. package/dist/workspace/lineage-vendor.d.ts +63 -0
  193. package/dist/workspace/lineage-vendor.d.ts.map +1 -0
  194. package/dist/workspace/lineage-version.d.ts +32 -0
  195. package/dist/workspace/lineage-version.d.ts.map +1 -0
  196. package/dist/workspace/links.d.ts +150 -0
  197. package/dist/workspace/links.d.ts.map +1 -0
  198. package/dist/workspace/ls.d.ts +99 -0
  199. package/dist/workspace/ls.d.ts.map +1 -0
  200. package/dist/workspace/member-commands.d.ts +139 -0
  201. package/dist/workspace/member-commands.d.ts.map +1 -0
  202. package/dist/workspace/member-handles.d.ts +54 -0
  203. package/dist/workspace/member-handles.d.ts.map +1 -0
  204. package/dist/workspace/member-pipeline.d.ts +143 -0
  205. package/dist/workspace/member-pipeline.d.ts.map +1 -0
  206. package/dist/workspace/member-run.d.ts +67 -0
  207. package/dist/workspace/member-run.d.ts.map +1 -0
  208. package/dist/workspace/reason-codes.d.ts +65 -0
  209. package/dist/workspace/reason-codes.d.ts.map +1 -0
  210. package/dist/workspace/record-source.d.ts +22 -0
  211. package/dist/workspace/record-source.d.ts.map +1 -0
  212. package/dist/workspace/records-cli.d.ts +81 -0
  213. package/dist/workspace/records-cli.d.ts.map +1 -0
  214. package/dist/workspace/records.d.ts +118 -0
  215. package/dist/workspace/records.d.ts.map +1 -0
  216. package/dist/workspace/root-refusal.d.ts +53 -0
  217. package/dist/workspace/root-refusal.d.ts.map +1 -0
  218. package/dist/workspace/status.d.ts +150 -0
  219. package/dist/workspace/status.d.ts.map +1 -0
  220. package/dist/workspace/template-manifest.d.ts +90 -0
  221. package/dist/workspace/template-manifest.d.ts.map +1 -0
  222. package/dist/workspace/tree.d.ts +36 -0
  223. package/dist/workspace/tree.d.ts.map +1 -0
  224. package/dist/workspace/trust/attestor.d.ts +67 -0
  225. package/dist/workspace/trust/attestor.d.ts.map +1 -0
  226. package/dist/workspace/trust/policy.d.ts +111 -0
  227. package/dist/workspace/trust/policy.d.ts.map +1 -0
  228. package/dist/workspace/trust/provenance.d.ts +67 -0
  229. package/dist/workspace/trust/provenance.d.ts.map +1 -0
  230. package/dist/workspace/trust/ssh-commit.d.ts +43 -0
  231. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -0
  232. package/dist/workspace/trust/test-repo.d.ts +42 -0
  233. package/dist/workspace/trust/test-repo.d.ts.map +1 -0
  234. package/dist/workspace/trust/verify-cli.d.ts +9 -0
  235. package/dist/workspace/trust/verify-cli.d.ts.map +1 -0
  236. package/dist/workspace/trust/verify.d.ts +66 -0
  237. package/dist/workspace/trust/verify.d.ts.map +1 -0
  238. package/dist/workspace/which-chant.d.ts +58 -0
  239. package/dist/workspace/which-chant.d.ts.map +1 -0
  240. package/dist/workspace/workspace-kinds.schema.json +133 -0
  241. package/package.json +4 -3
  242. package/src/audit/core.ts +13 -2
  243. package/src/audit/discover.ts +102 -36
  244. package/src/audit/report-html.ts +3 -1
  245. package/src/audit/report-model.ts +14 -1
  246. package/src/audit/terraform-state.test.ts +81 -1
  247. package/src/audit/terraform-state.ts +25 -0
  248. package/src/cli/command-group.ts +7 -0
  249. package/src/cli/commands/audit-truncation.test.ts +167 -0
  250. package/src/cli/commands/audit.ts +132 -29
  251. package/src/cli/commands/check-lexicon-examples.ts +2 -1
  252. package/src/cli/commands/check-lexicon.ts +24 -0
  253. package/src/cli/commands/doctor.test.ts +30 -0
  254. package/src/cli/commands/doctor.ts +21 -0
  255. package/src/cli/commands/import-agents.ts +15 -4
  256. package/src/cli/commands/init.ts +76 -25
  257. package/src/cli/commands/lint.test.ts +52 -1
  258. package/src/cli/commands/lint.ts +41 -71
  259. package/src/cli/commands/onboard.ts +15 -0
  260. package/src/cli/commands/update.ts +18 -5
  261. package/src/cli/commands/vendor.ts +11 -6
  262. package/src/cli/conflict-check.test.ts +18 -1
  263. package/src/cli/conflict-check.ts +13 -0
  264. package/src/cli/discovery-skip.test.ts +168 -0
  265. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +38 -0
  266. package/src/cli/handlers/build-member.test.ts +127 -0
  267. package/src/cli/handlers/build.test.ts +11 -0
  268. package/src/cli/handlers/build.ts +35 -1
  269. package/src/cli/handlers/components.test.ts +127 -0
  270. package/src/cli/handlers/components.ts +70 -8
  271. package/src/cli/handlers/dev.ts +6 -1
  272. package/src/cli/handlers/fan-out.ts +9 -2
  273. package/src/cli/handlers/graph.test.ts +20 -0
  274. package/src/cli/handlers/graph.ts +4 -2
  275. package/src/cli/handlers/init.ts +54 -1
  276. package/src/cli/handlers/lifecycle.ts +2 -1
  277. package/src/cli/handlers/misc.ts +16 -0
  278. package/src/cli/handlers/operator.test.ts +68 -0
  279. package/src/cli/handlers/operator.ts +39 -10
  280. package/src/cli/handlers/promote.test.ts +377 -0
  281. package/src/cli/handlers/promote.ts +391 -0
  282. package/src/cli/handlers/run-generate-member.test.ts +83 -0
  283. package/src/cli/handlers/run-generate.test.ts +158 -0
  284. package/src/cli/handlers/run-generate.ts +168 -0
  285. package/src/cli/handlers/run.test.ts +47 -0
  286. package/src/cli/handlers/run.ts +41 -6
  287. package/src/cli/handlers/vendor.ts +147 -43
  288. package/src/cli/main.test.ts +137 -1
  289. package/src/cli/main.ts +290 -24
  290. package/src/cli/mcp/op-tools.ts +2 -1
  291. package/src/cli/path-lexicon-messages.test.ts +152 -0
  292. package/src/cli/plugins.ts +43 -5
  293. package/src/cli/registry.ts +46 -1
  294. package/src/cli/static-config-read.test.ts +208 -0
  295. package/src/codegen/fetch.test.ts +63 -1
  296. package/src/codegen/fetch.ts +30 -1
  297. package/src/components/__fixtures__/alb-ecs-service.json +0 -8
  298. package/src/components/capability-plugin-loader.ts +3 -1
  299. package/src/components/cli-support.ts +9 -3
  300. package/src/components/discover.test.ts +27 -57
  301. package/src/components/discover.ts +20 -51
  302. package/src/components/driver.test.ts +90 -12
  303. package/src/components/driver.ts +43 -5
  304. package/src/components/fan-out-support.ts +2 -1
  305. package/src/components/gate-plan.ts +53 -0
  306. package/src/components/pilots/README.md +1 -1
  307. package/src/components/pilots/alb-ecs.pilot.ts +7 -7
  308. package/src/components/presets/ecs-fargate.ts +10 -5
  309. package/src/components/promote.test.ts +645 -0
  310. package/src/components/promote.ts +641 -0
  311. package/src/components/sandbox/driver.ts +8 -8
  312. package/src/components/sandbox/run.ts +4 -1
  313. package/src/components/verbs/build-archive.ts +27 -21
  314. package/src/components/verbs/run-agent.ts +3 -5
  315. package/src/config-static.test.ts +136 -0
  316. package/src/config-static.ts +232 -0
  317. package/src/config.test.ts +40 -0
  318. package/src/config.ts +161 -11
  319. package/src/content-digest.test.ts +120 -0
  320. package/src/content-digest.ts +21 -0
  321. package/src/discovery/files.test.ts +127 -1
  322. package/src/discovery/files.ts +91 -59
  323. package/src/discovery/fold-import.ts +127 -9
  324. package/src/discovery/index.ts +1 -0
  325. package/src/discovery/sandbox/driver.ts +73 -35
  326. package/src/discovery/sandbox/fork-diagnostic.test.ts +136 -13
  327. package/src/discovery/sandbox/fork.ts +83 -43
  328. package/src/discovery/sandbox/path-lexicon-multifile.test.ts +285 -0
  329. package/src/discovery/sandbox/path-lexicon-parity.test.ts +162 -0
  330. package/src/discovery/walk.test.ts +263 -0
  331. package/src/discovery/walk.ts +334 -0
  332. package/src/effect-receipt.ts +2 -2
  333. package/src/graph-ir.ts +18 -1
  334. package/src/join-key.test.ts +25 -0
  335. package/src/join-key.ts +39 -0
  336. package/src/lexicon-module.test.ts +203 -0
  337. package/src/lexicon-module.ts +215 -0
  338. package/src/lexicon.ts +105 -1
  339. package/src/lifecycle/build-ledger-store.ts +13 -2
  340. package/src/lifecycle/digest.ts +9 -5
  341. package/src/lifecycle/gate-ledger.test.ts +20 -0
  342. package/src/lifecycle/gate-ledger.ts +28 -6
  343. package/src/lifecycle/git.test.ts +13 -1
  344. package/src/lifecycle/git.ts +221 -176
  345. package/src/lifecycle/lease.ts +25 -10
  346. package/src/lifecycle/legacy-digest.test.ts +206 -0
  347. package/src/lifecycle/legacy-digest.ts +103 -0
  348. package/src/lifecycle/member-ledger.test.ts +319 -0
  349. package/src/lifecycle/member-ledger.ts +145 -0
  350. package/src/lifecycle/plan-digest.ts +7 -7
  351. package/src/lifecycle/release-ledger.test.ts +20 -0
  352. package/src/lifecycle/release-ledger.ts +53 -3
  353. package/src/lifecycle/status.test.ts +151 -1
  354. package/src/lifecycle/status.ts +228 -7
  355. package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +2 -3
  356. package/src/op/activities/activity-contracts.ts +37 -0
  357. package/src/op/activities/index.ts +10 -0
  358. package/src/op/activities/propose-upgrade.ts +206 -0
  359. package/src/op/activity-contract-registry.ts +18 -4
  360. package/src/op/activity-registry.ts +26 -0
  361. package/src/op/discover.test.ts +14 -4
  362. package/src/op/discover.ts +19 -23
  363. package/src/op/gate-approval.ts +3 -1
  364. package/src/op/gate-name.ts +8 -0
  365. package/src/op/gate-summary.test.ts +14 -0
  366. package/src/op/gate-summary.ts +25 -2
  367. package/src/op/gate.ts +36 -7
  368. package/src/op/generate-pipeline.test.ts +14 -5
  369. package/src/op/generate-pipeline.ts +2 -1
  370. package/src/op/runtimes/local.ts +2 -1
  371. package/src/project-root.test.ts +66 -1
  372. package/src/project-root.ts +48 -0
  373. package/src/workspace/__fixtures__/contract-repo.ts +100 -0
  374. package/src/workspace/__fixtures__/two-member-workspace.ts +35 -0
  375. package/src/workspace/behold-kinds.test.ts +162 -0
  376. package/src/workspace/check-contract.test.ts +127 -0
  377. package/src/workspace/check.schema.json +347 -0
  378. package/src/workspace/checks/generated.test.ts +287 -0
  379. package/src/workspace/checks/generated.ts +475 -0
  380. package/src/workspace/checks/ledgers.test.ts +139 -0
  381. package/src/workspace/checks/ledgers.ts +246 -0
  382. package/src/workspace/checks/links.ts +183 -0
  383. package/src/workspace/checks/pipelines.test.ts +133 -0
  384. package/src/workspace/checks/pipelines.ts +225 -0
  385. package/src/workspace/checks-repo.test.ts +71 -0
  386. package/src/workspace/checks.test.ts +450 -0
  387. package/src/workspace/checks.ts +461 -0
  388. package/src/workspace/compose-graph.test.ts +146 -0
  389. package/src/workspace/compose-graph.ts +228 -0
  390. package/src/workspace/compose-reports.test.ts +109 -0
  391. package/src/workspace/compose-reports.ts +186 -0
  392. package/src/workspace/declaration.schema.json +434 -0
  393. package/src/workspace/declaration.test.ts +347 -0
  394. package/src/workspace/declaration.ts +623 -0
  395. package/src/workspace/generated-files.test.ts +114 -0
  396. package/src/workspace/generated-files.ts +94 -0
  397. package/src/workspace/glob.ts +49 -0
  398. package/src/workspace/graph-cli.ts +220 -0
  399. package/src/workspace/graph-contract.test.ts +204 -0
  400. package/src/workspace/graph.schema.json +662 -0
  401. package/src/workspace/init.test.ts +223 -0
  402. package/src/workspace/init.ts +585 -0
  403. package/src/workspace/jsonc.test.ts +65 -0
  404. package/src/workspace/jsonc.ts +231 -0
  405. package/src/workspace/kinds.test.ts +288 -0
  406. package/src/workspace/kinds.ts +506 -0
  407. package/src/workspace/lineage-check.ts +264 -0
  408. package/src/workspace/lineage-cli.ts +114 -0
  409. package/src/workspace/lineage-init.test.ts +231 -0
  410. package/src/workspace/lineage-init.ts +365 -0
  411. package/src/workspace/lineage-lock.test.ts +224 -0
  412. package/src/workspace/lineage-lock.ts +357 -0
  413. package/src/workspace/lineage-merge.ts +44 -0
  414. package/src/workspace/lineage-migrations.test.ts +178 -0
  415. package/src/workspace/lineage-migrations.ts +343 -0
  416. package/src/workspace/lineage-update.ts +160 -0
  417. package/src/workspace/lineage-upgrade-cli.ts +151 -0
  418. package/src/workspace/lineage-upgrade.test.ts +474 -0
  419. package/src/workspace/lineage-upgrade.ts +624 -0
  420. package/src/workspace/lineage-vendor.test.ts +126 -0
  421. package/src/workspace/lineage-vendor.ts +215 -0
  422. package/src/workspace/lineage-version.ts +127 -0
  423. package/src/workspace/links.test.ts +344 -0
  424. package/src/workspace/links.ts +331 -0
  425. package/src/workspace/ls-contract.test.ts +200 -0
  426. package/src/workspace/ls.schema.json +190 -0
  427. package/src/workspace/ls.ts +254 -0
  428. package/src/workspace/member-commands.e2e.test.ts +244 -0
  429. package/src/workspace/member-commands.test.ts +217 -0
  430. package/src/workspace/member-commands.ts +540 -0
  431. package/src/workspace/member-handles.test.ts +76 -0
  432. package/src/workspace/member-handles.ts +228 -0
  433. package/src/workspace/member-pipeline.test.ts +135 -0
  434. package/src/workspace/member-pipeline.ts +317 -0
  435. package/src/workspace/member-run.ts +146 -0
  436. package/src/workspace/read-contract.test.ts +129 -0
  437. package/src/workspace/reason-codes.test.ts +113 -0
  438. package/src/workspace/reason-codes.ts +81 -0
  439. package/src/workspace/record-source.ts +117 -0
  440. package/src/workspace/records-cli.ts +198 -0
  441. package/src/workspace/records-contract.test.ts +93 -0
  442. package/src/workspace/records.schema.json +150 -0
  443. package/src/workspace/records.test.ts +243 -0
  444. package/src/workspace/records.ts +384 -0
  445. package/src/workspace/root-refusal.ts +97 -0
  446. package/src/workspace/status-contract.test.ts +308 -0
  447. package/src/workspace/status.schema.json +210 -0
  448. package/src/workspace/status.ts +412 -0
  449. package/src/workspace/template-manifest.test.ts +79 -0
  450. package/src/workspace/template-manifest.ts +221 -0
  451. package/src/workspace/tree.ts +132 -0
  452. package/src/workspace/trust/attestor.ts +93 -0
  453. package/src/workspace/trust/policy.ts +306 -0
  454. package/src/workspace/trust/provenance.ts +181 -0
  455. package/src/workspace/trust/ssh-commit.ts +138 -0
  456. package/src/workspace/trust/test-repo.ts +138 -0
  457. package/src/workspace/trust/trust.test.ts +397 -0
  458. package/src/workspace/trust/verify-cli.ts +56 -0
  459. package/src/workspace/trust/verify.ts +194 -0
  460. package/src/workspace/which-chant.test.ts +117 -0
  461. package/src/workspace/which-chant.ts +151 -0
  462. package/src/workspace/workspace-kinds.schema.json +133 -0
@@ -6,14 +6,15 @@
6
6
  */
7
7
 
8
8
  import { existsSync, readFileSync, statSync, writeFileSync } from "fs";
9
- import { join } from "path";
9
+ import { dirname, join, resolve } from "path";
10
10
  import { auditFiles, type AuditInput, type AuditFinding, type ChecksProvider, type SuppressionStats } from "../../audit/core";
11
- import { AUDIT_LEXICONS, classifyFiles, collectCandidates, loadAuditPlugins, unclaimedFiles, type DetectPlugin, type RepoFile, type UnclaimedFile } from "../../audit/discover";
11
+ import { AUDIT_LEXICONS, classifyFiles, loadAuditPlugins, unclaimedFiles, walkCandidates, type CandidateWalk, type DetectPlugin, type RepoFile, type UnclaimedFile } from "../../audit/discover";
12
12
  import { RULE_CATALOG, resolveAuditCatalog, type RuleMeta } from "../../audit/catalog";
13
13
  import { scanForSecrets, parseSecretsConfig, type SecretsScanOptions } from "../../audit/secrets";
14
14
  import { auditWranglerConfigs } from "../../audit/wrangler";
15
15
  import { auditNginxConfigs } from "../../audit/nginx";
16
16
  import { auditTerraformState } from "../../audit/terraform-state";
17
+ import { workspaceMemberDirs } from "../../discovery/walk";
17
18
  import { renderMarkdown } from "../../audit/report";
18
19
  import { renderHtml, type ReportTheme } from "../../audit/report-html";
19
20
  import { buildReportJson, REPORT_SCHEMA_VERSION, type AuditSnapshot } from "../../audit/report-model";
@@ -21,6 +22,9 @@ import { fetchRepoFiles, resolveActionSha, resolveImageDigest, resolveRepoCommit
21
22
  import { extractUnpinnedActions, extractUnpinnedImages } from "../../audit/proof";
22
23
  import type { ProveOptions } from "../../audit/proof";
23
24
  import type { Severity } from "../../lint/rule";
25
+ import { formatWarning } from "../format";
26
+ import { lexiconPackagesToInstall, lexiconSourceLabel, pathLexiconMap, recordedLexiconModules } from "../../lexicon-module";
27
+ import { readLexiconDeclarationsStatically, unknownPathLexiconsNotice } from "../../config-static";
24
28
 
25
29
  export type AuditFormat = "stylish" | "json" | "sarif" | "markdown" | "html";
26
30
  export type AuditTier = "merge-worthy" | "all";
@@ -68,6 +72,23 @@ export interface AuditCommandOptions {
68
72
  * present (URL targets have no local config file to read).
69
73
  */
70
74
  secretsScan?: SecretsScanOptions;
75
+ /**
76
+ * Files the local walk takes before it stops, counting every file
77
+ * (`--max-files`, #2528). Defaults to 1000. Local paths only.
78
+ */
79
+ maxFiles?: number;
80
+ }
81
+
82
+ /**
83
+ * A diagnostic about the run rather than the repository. Printed on stderr by
84
+ * `printAuditResult`, never part of `output`, so the report on stdout is the
85
+ * same with or without one.
86
+ */
87
+ export interface AuditWarning {
88
+ /** The path the warning is about, when there is one. */
89
+ file?: string;
90
+ message: string;
91
+ hint?: string;
71
92
  }
72
93
 
73
94
  export interface AuditCommandResult {
@@ -96,14 +117,45 @@ export interface AuditCommandResult {
96
117
  * summary line.
97
118
  */
98
119
  suppressedCount?: number;
120
+ /** Run diagnostics for stderr: a truncated scan in a format that cannot carry it (#2528). */
121
+ warnings?: AuditWarning[];
99
122
  }
100
123
 
101
124
  /** Exit code when the audit had no lexicons to look with. Distinct from 1 (findings / failure). */
102
125
  export const NO_LEXICONS_EXIT_CODE = 2;
103
126
 
104
- /** The npm package that provides an audit lexicon's detection and checks. */
105
- function lexiconPackage(name: string): string {
106
- return `@intentius/chant-lexicon-${name}`;
127
+ /**
128
+ * What to tell the user about lexicons that did not load: an `npm i` line for
129
+ * the package-backed ones, and the declared path for any lexicon
130
+ * `chant.config.ts` names by path (#2520), which has no package to install
131
+ * (chant#2578).
132
+ */
133
+ function missingLexiconRemedy(names: readonly string[], paths: ReadonlyMap<string, string>): string {
134
+ const parts: string[] = [];
135
+ const pkgs = lexiconPackagesToInstall(names, paths);
136
+ if (pkgs.length > 0) parts.push(`npm i ${pkgs.join(" ")}`);
137
+ for (const name of names) {
138
+ if (paths.has(name)) parts.push(`${name} is declared by path: ${lexiconSourceLabel(name, process.cwd(), paths)}`);
139
+ }
140
+ return parts.join("; ");
141
+ }
142
+
143
+ /**
144
+ * chant#2589 — the lexicons a local target's chant.config declares by path,
145
+ * with any recorded earlier in this process. The config is read statically
146
+ * (../../config-static.ts) and never evaluated, and the paths are not
147
+ * recorded for the loaders: audit names them in its messages, but never
148
+ * imports a project's lexicon module. `notice` is set when the config's
149
+ * `lexicons` could not be read that way.
150
+ */
151
+ export function auditPathLexicons(target: string, isUrl: boolean): { paths: Map<string, string>; notice?: string } {
152
+ const paths = recordedLexiconModules();
153
+ if (isUrl) return { paths };
154
+ const read = readLexiconDeclarationsStatically(resolve(target));
155
+ if (read.status === "read") {
156
+ for (const [name, path] of pathLexiconMap(read.entries, dirname(read.configPath))) paths.set(name, path);
157
+ }
158
+ return { paths, notice: unknownPathLexiconsNotice(read) };
107
159
  }
108
160
 
109
161
  /** Missing audit lexicons the unclaimed files pointed at, in first-seen order. */
@@ -116,25 +168,31 @@ function wantedLexicons(unclaimed: UnclaimedFile[]): string[] {
116
168
  * lexicons it needs. Every wanted lexicon is a `-p` package so npx puts all of
117
169
  * them on the same resolution path.
118
170
  */
119
- export function installLine(lexicons: string[], target: string): string {
120
- const pkgs = ["@intentius/chant", ...lexicons.map(lexiconPackage)];
171
+ export function installLine(
172
+ lexicons: string[],
173
+ target: string,
174
+ paths: ReadonlyMap<string, string> = recordedLexiconModules(),
175
+ ): string {
176
+ // chant#2578 — a lexicon declared by path has no package for npx to fetch.
177
+ const pkgs = ["@intentius/chant", ...lexiconPackagesToInstall(lexicons, paths)];
121
178
  return `npx ${pkgs.map((p) => `-p ${p}`).join(" ")} chant audit ${target}`;
122
179
  }
123
180
 
124
181
  /** One-line coverage hint for the partial case: some lexicons loaded, others wanted by files on disk. */
125
- function missingLexiconHint(unclaimed: UnclaimedFile[]): string | undefined {
182
+ function missingLexiconHint(unclaimed: UnclaimedFile[], paths: ReadonlyMap<string, string>): string | undefined {
126
183
  const wanted = wantedLexicons(unclaimed);
127
184
  if (wanted.length === 0) return undefined;
128
185
  const n = unclaimed.length;
129
186
  return `${n} file${n === 1 ? " looks" : "s look"} like ${wanted.join("/")} but ${wanted.length === 1 ? "that lexicon is" : "those lexicons are"} not installed, so ${n === 1 ? "it was" : "they were"} skipped` +
130
- ` (npm i ${wanted.map(lexiconPackage).join(" ")}).`;
187
+ ` (${missingLexiconRemedy(wanted, paths)}).`;
131
188
  }
132
189
 
133
190
  /** Human-readable diagnostic for the zero-lexicon case. */
134
- function renderNoLexicons(target: string, unclaimed: UnclaimedFile[]): string {
191
+ function renderNoLexicons(target: string, unclaimed: UnclaimedFile[], paths: ReadonlyMap<string, string>, truncated?: AuditTruncation): string {
135
192
  const lines: string[] = [];
136
193
  lines.push(`chant audit had nothing to look with: no audit lexicon is installed, so nothing under ${target} was inspected.`);
137
194
  lines.push("This is not a clean result. Detection and checks live in the lexicon packages.");
195
+ if (truncated) lines.push("", `Note: ${truncationNote(truncated, target)}`);
138
196
  const wanted = wantedLexicons(unclaimed);
139
197
  if (unclaimed.length > 0) {
140
198
  lines.push("", "Files that wanted a lexicon:");
@@ -145,12 +203,17 @@ function renderNoLexicons(target: string, unclaimed: UnclaimedFile[]): string {
145
203
  lines.push("", "No file under the target looked like CI, Kubernetes, Helm, Docker, CloudFormation, ARM, Config Connector, fountain, or Terraform either.");
146
204
  }
147
205
  lines.push("", "Run it with the lexicons those files need:");
148
- lines.push(` ${installLine(wanted.length > 0 ? wanted : [...AUDIT_LEXICONS], target)}`);
206
+ const lexicons = wanted.length > 0 ? wanted : [...AUDIT_LEXICONS];
207
+ lines.push(` ${installLine(lexicons, target, paths)}`);
208
+ // chant#2589 — a lexicon declared by path is left out of the line above.
209
+ for (const name of lexicons) {
210
+ if (paths.has(name)) lines.push(` (${name} is declared by path: ${lexiconSourceLabel(name, process.cwd(), paths)})`);
211
+ }
149
212
  return lines.join("\n");
150
213
  }
151
214
 
152
215
  /** Machine-readable form of the zero-lexicon diagnostic (`status: "no-lexicons"`). */
153
- function renderNoLexiconsJson(target: string, unclaimed: UnclaimedFile[]): string {
216
+ function renderNoLexiconsJson(target: string, unclaimed: UnclaimedFile[], paths: ReadonlyMap<string, string>, truncated?: AuditTruncation): string {
154
217
  const wanted = wantedLexicons(unclaimed);
155
218
  return JSON.stringify(
156
219
  {
@@ -162,7 +225,8 @@ function renderNoLexiconsJson(target: string, unclaimed: UnclaimedFile[]): strin
162
225
  findings: [],
163
226
  unclaimed,
164
227
  missingLexicons: wanted,
165
- install: installLine(wanted.length > 0 ? wanted : [...AUDIT_LEXICONS], target),
228
+ install: installLine(wanted.length > 0 ? wanted : [...AUDIT_LEXICONS], target, paths),
229
+ truncated,
166
230
  },
167
231
  null,
168
232
  2,
@@ -372,6 +436,28 @@ async function buildSnapshot(options: AuditCommandOptions, files: string[], isUr
372
436
  };
373
437
  }
374
438
 
439
+ /** The flag that raises the local walk's file limit, named wherever a truncated scan is reported. */
440
+ export const MAX_FILES_FLAG = "--max-files";
441
+
442
+ /** How a report states that the local walk stopped at its limit (#2528). JSON carries it as `truncated`. */
443
+ export interface AuditTruncation {
444
+ limit: number;
445
+ flag: typeof MAX_FILES_FLAG;
446
+ }
447
+
448
+ /** The truncation a walk reports, or undefined for a whole scan. */
449
+ export function walkTruncation(walk: CandidateWalk): AuditTruncation | undefined {
450
+ return walk.truncated ? { limit: walk.maxFiles, flag: MAX_FILES_FLAG } : undefined;
451
+ }
452
+
453
+ /** The text-report note for a truncated scan. */
454
+ export function truncationNote(t: AuditTruncation, target: string): string {
455
+ return (
456
+ `The scan of ${target} stopped at ${t.limit} files, so part of the tree was not audited. ` +
457
+ `The limit counts every file the walk reaches, not only the ones it audits. Raise it with ${t.flag} <n>.`
458
+ );
459
+ }
460
+
375
461
  /** Run the audit and produce a rendered result. */
376
462
  export async function auditCommand(options: AuditCommandOptions): Promise<AuditCommandResult> {
377
463
  const format = options.format ?? "stylish";
@@ -379,6 +465,8 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
379
465
  const failOn = options.failOn ?? "none";
380
466
 
381
467
  const isUrl = /^https?:\/\//.test(options.path);
468
+ // chant#2589 — path lexicons the target declares, read without running its config.
469
+ const pathLexicons = auditPathLexicons(options.path, isUrl);
382
470
 
383
471
  // Detection lives in the lexicon plugins (each one's `detectTemplate`), so a
384
472
  // lexicon that isn't installed can't claim its files. Every branch below
@@ -387,6 +475,7 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
387
475
  // render the same way (#1623).
388
476
  const plugins = options.plugins ?? (await loadAuditPlugins());
389
477
  let candidates: RepoFile[];
478
+ let truncated: AuditTruncation | undefined;
390
479
  if (isUrl) {
391
480
  try {
392
481
  // Fetch the whole repo's candidate files (all lexicons, not just CI) and
@@ -406,8 +495,19 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
406
495
  // One walk, plugin-delegated detection. CI (path), Dockerfiles (name), and
407
496
  // Helm charts (bundle) are special-cased by the classifier since content
408
497
  // shape alone can't disambiguate them.
409
- candidates = collectCandidates(options.path);
498
+ // Workspace members leave the root's audit only when a declaration exists
499
+ // (#2525 rule 3); without one this is a few existsSync calls (#2527).
500
+ const excludeDirs = await workspaceMemberDirs(options.path);
501
+ const walk = walkCandidates(options.path, { maxFiles: options.maxFiles, excludeDirs });
502
+ candidates = walk.files;
503
+ truncated = walkTruncation(walk);
410
504
  }
505
+ // Every format but SARIF states a truncated scan in the report itself
506
+ // (#2528). SARIF has no slot chant fills for it, so there it goes to stderr.
507
+ const warnings: AuditWarning[] =
508
+ truncated && format === "sarif" ? [{ message: truncationNote(truncated, options.path) }] : [];
509
+ if (pathLexicons.notice !== undefined) warnings.push({ message: pathLexicons.notice });
510
+ const withWarnings = warnings.length > 0 ? { warnings } : {};
411
511
  // A local target hands its directory to the classifier, so an input whose
412
512
  // path names a directory carries the directory itself (#2217): terraform's
413
513
  // parse-to-graph follows a root module's local `module` calls from there. A
@@ -438,15 +538,19 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
438
538
  const terraformStateFindings = auditTerraformState(candidates);
439
539
 
440
540
  if (plugins.length === 0) {
441
- const output = format === "json" ? renderNoLexiconsJson(options.path, unclaimed) : renderNoLexicons(options.path, unclaimed);
442
- return { success: true, status: "no-lexicons", output, findings: [...secretsFindings, ...wranglerFindings, ...nginxFindings, ...terraformStateFindings], scanned: [], unclaimed, exitCode: NO_LEXICONS_EXIT_CODE, stream: format === "json" ? "stdout" : "stderr" };
541
+ const output =
542
+ format === "json"
543
+ ? renderNoLexiconsJson(options.path, unclaimed, pathLexicons.paths, truncated)
544
+ : renderNoLexicons(options.path, unclaimed, pathLexicons.paths, truncated);
545
+ return { success: true, status: "no-lexicons", output, findings: [...secretsFindings, ...wranglerFindings, ...nginxFindings, ...terraformStateFindings], scanned: [], unclaimed, exitCode: NO_LEXICONS_EXIT_CODE, stream: format === "json" ? "stdout" : "stderr", ...withWarnings };
443
546
  }
444
547
 
445
- const missingLexiconNote = missingLexiconHint(unclaimed);
548
+ const missingLexiconNote = missingLexiconHint(unclaimed, pathLexicons.paths);
446
549
 
447
550
  if (inputs.length === 0 && secretsFindings.length === 0 && wranglerFindings.length === 0 && nginxFindings.length === 0 && terraformStateFindings.length === 0) {
448
- const output = `No auditable files found under ${options.path}.${missingLexiconNote ? ` ${missingLexiconNote}` : ""}`;
449
- return { success: true, status: "ok", output, findings: [], scanned: [], unclaimed, exitCode: 0 };
551
+ let output = `No auditable files found under ${options.path}.${missingLexiconNote ? ` ${missingLexiconNote}` : ""}`;
552
+ if (truncated) output += `\n\nNote: ${truncationNote(truncated, options.path)}`;
553
+ return { success: true, status: "ok", output, findings: [], scanned: [], unclaimed, exitCode: 0, ...withWarnings };
450
554
  }
451
555
 
452
556
  let findings: AuditFinding[] = [];
@@ -472,6 +576,7 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
472
576
 
473
577
  if (tier === "merge-worthy") findings = findings.filter((f) => isMergeWorthy(f, catalog));
474
578
  const notes = coverageNotes(inputs);
579
+ if (truncated) notes.unshift(truncationNote(truncated, options.path));
475
580
  if (missingLexiconNote) notes.push(missingLexiconNote);
476
581
  if (suppressionStats.count > 0) {
477
582
  notes.push(
@@ -520,7 +625,7 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
520
625
  switch (format) {
521
626
  case "json": {
522
627
  const snapshot = await buildSnapshot(options, scanned, isUrl);
523
- output = JSON.stringify(buildReportJson(findings, { snapshot, toolVersion: options.toolVersion, catalog, unclaimed }), null, 2);
628
+ output = JSON.stringify(buildReportJson(findings, { snapshot, toolVersion: options.toolVersion, catalog, unclaimed, truncated }), null, 2);
524
629
  break;
525
630
  }
526
631
  case "sarif":
@@ -531,7 +636,7 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
531
636
  break;
532
637
  case "html": {
533
638
  const snapshot = await buildSnapshot(options, scanned, isUrl);
534
- output = renderHtml(findings, { files, resolveSha, resolveDigest, notes, snapshot, theme: options.theme, template: options.template, catalog });
639
+ output = renderHtml(findings, { files, resolveSha, resolveDigest, notes, snapshot, theme: options.theme, template: options.template, catalog, truncated });
535
640
  break;
536
641
  }
537
642
  default:
@@ -545,22 +650,20 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
545
650
  } catch (err) {
546
651
  return { success: false, output, findings, scanned, exitCode: 1, error: `Failed to write ${options.output}: ${err instanceof Error ? err.message : String(err)}` };
547
652
  }
548
- return { success: true, status: "ok", output, findings, scanned, unclaimed, exitCode, wroteTo: options.output, suppressedCount: suppressionStats.count };
653
+ return { success: true, status: "ok", output, findings, scanned, unclaimed, exitCode, wroteTo: options.output, suppressedCount: suppressionStats.count, ...withWarnings };
549
654
  }
550
655
 
551
- return { success: true, status: "ok", output, findings, scanned, unclaimed, exitCode, suppressedCount: suppressionStats.count };
656
+ return { success: true, status: "ok", output, findings, scanned, unclaimed, exitCode, suppressedCount: suppressionStats.count, ...withWarnings };
552
657
  }
553
658
 
554
- /** Print an audit result to stdout. */
659
+ /** Print an audit result to stdout, and its warnings to stderr. */
555
660
  export function printAuditResult(result: AuditCommandResult): void {
556
661
  if (!result.success) {
557
662
  console.error(result.error ?? "Audit failed");
558
663
  return;
559
664
  }
560
- if (result.wroteTo) {
561
- console.error(`Wrote report to ${result.wroteTo}`);
562
- return;
563
- }
564
- if (result.stream === "stderr") console.error(result.output);
665
+ if (result.wroteTo) console.error(`Wrote report to ${result.wroteTo}`);
666
+ else if (result.stream === "stderr") console.error(result.output);
565
667
  else console.log(result.output);
668
+ for (const w of result.warnings ?? []) console.error(formatWarning(w));
566
669
  }
@@ -33,6 +33,7 @@
33
33
  * validates against its own schemas.
34
34
  */
35
35
 
36
+ import { lexiconNames as declaredLexiconNames } from "../../lexicon-module";
36
37
  import { existsSync, readdirSync } from "fs";
37
38
  import { join } from "path";
38
39
  import { build, type BuildResult } from "../../build";
@@ -141,7 +142,7 @@ export async function checkExamplesBuild(lexiconDir: string): Promise<ExampleBui
141
142
  const { config: exampleConfig } = await loadChantConfig(join(examplesDir, entry.name));
142
143
  const lexiconNames =
143
144
  exampleConfig.lexicons && exampleConfig.lexicons.length > 0
144
- ? exampleConfig.lexicons
145
+ ? declaredLexiconNames(exampleConfig.lexicons)
145
146
  : await detectLexicons(files);
146
147
  if (lexiconNames.length === 0) {
147
148
  results.push({
@@ -106,6 +106,28 @@ export async function coverageReportCheck(plugin: LexiconPlugin | undefined): Pr
106
106
  };
107
107
  }
108
108
 
109
+ /**
110
+ * #2535 — a lexicon that supplies member kinds publishes them as data at a
111
+ * `./workspace-kinds` subpath (ws-031). The file must be JSON, validate as
112
+ * kind data, and ship in the package. A lexicon without the subpath passes
113
+ * vacuously, like `coverageReport()` above. The workspace module loads only
114
+ * here, when the check runs.
115
+ */
116
+ export async function workspaceKindsCheck(dir: string): Promise<CheckItem> {
117
+ const { readPackageKinds, kindsFileShips } = await import("../../workspace/kinds");
118
+ const read = readPackageKinds(dir);
119
+ const problems = [...read.problems];
120
+ if (read.file && problems.length === 0 && !kindsFileShips(dir, read.file)) {
121
+ problems.push(`${basename(read.file)} is not covered by package.json "files", so the published package would not carry it`);
122
+ }
123
+ return {
124
+ name: "Any ./workspace-kinds subpath holds valid kind data",
125
+ tier: 1,
126
+ pass: problems.length === 0,
127
+ detail: problems.length > 0 ? problems.join("; ") : read.file ? `${read.kinds.length} kind(s): ${read.kinds.map((k) => k.name).join(", ")}` : undefined,
128
+ };
129
+ }
130
+
109
131
  // ── Check runner ─────────────────────────────────────────────────────
110
132
 
111
133
  /**
@@ -434,6 +456,8 @@ export async function checkLexicon(dir: string): Promise<CheckResult> {
434
456
  // now states the fact directly via `coverageReport()`.
435
457
  items.push(await coverageReportCheck(plugin));
436
458
 
459
+ items.push(await workspaceKindsCheck(dir));
460
+
437
461
  // ── Tier 2: Recommended ────────────────────────────────────────
438
462
 
439
463
  const pluginContent = readOr(join(dir, "src/plugin.ts"));
@@ -396,3 +396,33 @@ describe("package-type-module (#1421)", () => {
396
396
  });
397
397
  });
398
398
  });
399
+
400
+ describe("doctorCommand in a declared workspace (#2535)", () => {
401
+ test("shows a member of kind other as a warning, and adds nothing outside a workspace", async () => {
402
+ await withTestDir(async (testDir) => {
403
+ mkdirSync(join(testDir, ".git"));
404
+ mkdirSync(join(testDir, "app"));
405
+ mkdirSync(join(testDir, "docs"));
406
+ writeFileSync(join(testDir, "app", "chant.config.json"), "{}");
407
+ const before = await doctorCommand(join(testDir, "app"));
408
+ expect(before.checks.filter((c) => c.name.startsWith("workspace-"))).toEqual([]);
409
+
410
+ writeFileSync(
411
+ join(testDir, "chant.workspace.json"),
412
+ JSON.stringify({
413
+ name: "acme",
414
+ schema: 1,
415
+ members: [
416
+ { name: "app", dir: "app", kind: "chant" },
417
+ { name: "docs", dir: "docs", kind: "other", because: "a static site" },
418
+ ],
419
+ }),
420
+ );
421
+ const report = await doctorCommand(join(testDir, "app"));
422
+ const workspace = report.checks.filter((c) => c.name.startsWith("workspace-"));
423
+ expect(workspace).toEqual([
424
+ { name: "workspace-WSP009", status: "warn", message: expect.stringMatching(/^member docs \(docs\) is kind other, which chant does not read: a static site \(.*chant\.workspace\.json:1\)$/) },
425
+ ]);
426
+ });
427
+ });
428
+ });
@@ -8,6 +8,7 @@ import { loadPlugins, resolveProjectLexicons } from "../plugins";
8
8
  import { MCP_CONFIG_FILENAME, MCP_SETUP_COMMAND, mcpConfigPath } from "../mcp-config";
9
9
  import { loadCapabilityPlugin } from "../../components/capability-plugin-loader";
10
10
  import { isCapabilityPlugin } from "../../components/capability-plugin";
11
+ import { findWorkspaceRoot } from "../../project-root";
11
12
 
12
13
  export interface DoctorCheck {
13
14
  name: string;
@@ -95,6 +96,9 @@ export async function doctorCommand(path: string): Promise<DoctorReport> {
95
96
  const lexicons = config?.lexicons as string[] | undefined;
96
97
  if (lexicons && Array.isArray(lexicons)) {
97
98
  for (const lex of lexicons) {
99
+ // chant #2520 — a lexicon declared by module path is project source,
100
+ // not a package `chant update` copies types from.
101
+ if (typeof lex !== "string") continue;
98
102
  const lexDir = join(projectPath, ".chant", "types", `lexicon-${lex}`);
99
103
  if (!existsSync(lexDir)) {
100
104
  checks.push({ name: `lexicon-${lex}-types`, status: "fail", message: `.chant/types/lexicon-${lex}/ not found — run chant update` });
@@ -319,6 +323,23 @@ export async function doctorCommand(path: string): Promise<DoctorReport> {
319
323
  }
320
324
  }
321
325
 
326
+ // #2535: in a declared workspace, doctor shows the declaration checks, so
327
+ // a member of kind other shows as a warning. The walk up only tests
328
+ // whether files exist; nothing under workspace/ loads without a
329
+ // declaration, so level 0 is unchanged (#2525).
330
+ const workspace = findWorkspaceRoot(projectPath);
331
+ if (workspace) {
332
+ const { runDeclarationChecks } = await import("../../workspace/checks");
333
+ // The declaration and kind checks only: `chant workspace check` runs the
334
+ // member checks, which read every member's files.
335
+ const report = await runDeclarationChecks(workspace.dir, (file) => join(workspace.dir, file), { gather: false });
336
+ const shown = report.diagnostics.filter((d) => d.severity !== "info");
337
+ for (const d of shown) {
338
+ checks.push({ name: `workspace-${d.ruleId}`, status: d.severity === "error" ? "fail" : "warn", message: `${d.message} (${d.file}:${d.line})` });
339
+ }
340
+ if (shown.length === 0) checks.push({ name: "workspace-declaration", status: "pass", message: report.file });
341
+ }
342
+
322
343
  return {
323
344
  checks,
324
345
  success: checks.every(c => c.status !== "fail"),
@@ -23,7 +23,8 @@ import { scanAgentConfigs } from "../../agents/discover";
23
23
  import type { AgentRuntime, AgentScope } from "../../agents/types";
24
24
  import type { AgentImportOutcome } from "../../agents/importer";
25
25
  import type { LexiconPlugin } from "../../lexicon";
26
- import { loadPlugin } from "../plugins";
26
+ import { loadPlugin, recordProjectLexicons, unknownPathLexiconsNotice } from "../plugins";
27
+ import { lexiconModulePath, lexiconSourceLabel } from "../../lexicon-module";
27
28
 
28
29
  /** The lexicon used when `--lexicon` isn't given: the one that models agent workloads. */
29
30
  export const DEFAULT_AGENT_LEXICON = "fountain";
@@ -91,18 +92,28 @@ export async function importAgentsCommand(opts: ImportAgentsOptions = {}): Promi
91
92
  };
92
93
  }
93
94
 
95
+ // chant#2578 — a lexicon the project's chant.config declares by path loads
96
+ // from that module, so read the declarations before loading the plugin.
97
+ // chant#2591 — read without running chant.config.ts.
98
+ if (!opts.pluginLoader) {
99
+ const notice = unknownPathLexiconsNotice(recordProjectLexicons(process.cwd()));
100
+ if (notice !== undefined) warnings.push(notice);
101
+ }
102
+
94
103
  let plugin: LexiconPlugin;
95
104
  try {
96
105
  plugin = await (opts.pluginLoader ?? loadPlugin)(lexiconName);
97
106
  } catch (err) {
107
+ const remedy =
108
+ lexiconModulePath(lexiconName) !== undefined
109
+ ? `The ${lexiconName} lexicon is declared by path in chant.config.ts: ${lexiconSourceLabel(lexiconName)}`
110
+ : `Install it with: npm i @intentius/chant-lexicon-${lexiconName}`;
98
111
  return {
99
112
  success: false,
100
113
  generatedFiles: [],
101
114
  summary: { discovered: scan.sites.length, mapped: 0 },
102
115
  warnings,
103
- error:
104
- `Could not load the ${lexiconName} lexicon: ${err instanceof Error ? err.message : String(err)}\n` +
105
- `Install it with: npm i @intentius/chant-lexicon-${lexiconName}`,
116
+ error: `Could not load the ${lexiconName} lexicon: ${err instanceof Error ? err.message : String(err)}\n${remedy}`,
106
117
  };
107
118
  }
108
119
 
@@ -3,7 +3,8 @@ import { join, resolve, dirname } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { createInterface } from "readline";
5
5
  import { formatSuccess, formatWarning } from "../format";
6
- import { loadPlugin } from "../plugins";
6
+ import { loadPlugin, recordProjectLexicons, unknownPathLexiconsNotice } from "../plugins";
7
+ import { lexiconModulePath, lexiconSourceLabel } from "../../lexicon-module";
7
8
  import { MCP_CONFIG_FILENAME, detectPackageManager, generateMcpConfig, mcpConfigPath } from "../mcp-config";
8
9
 
9
10
  /** Read the current chant package version from our own package.json. */
@@ -53,16 +54,27 @@ export interface InitResult {
53
54
  warnings: string[];
54
55
  /** Error message if failed */
55
56
  error?: string;
57
+ /**
58
+ * chant#2578 — where the lexicon loads from when the directory's existing
59
+ * chant.config declares it by path. There is no package to depend on or
60
+ * install for it.
61
+ */
62
+ lexiconModule?: string;
56
63
  }
57
64
 
58
65
  /**
59
66
  * Generate package.json content
60
67
  */
61
- function generatePackageJson(lexicon: string, extraScripts?: Record<string, string>): string {
68
+ function generatePackageJson(
69
+ lexicon: string,
70
+ extraScripts?: Record<string, string>,
71
+ lexiconIsPackage = true,
72
+ ): string {
62
73
  const ver = getChantVersion();
63
74
  const dependencies: Record<string, string> = {
64
75
  "@intentius/chant": `^${ver}`,
65
- [`@intentius/chant-lexicon-${lexicon}`]: `^${ver}`,
76
+ // chant#2578 — a lexicon declared by path is not a package to depend on.
77
+ ...(lexiconIsPackage ? { [`@intentius/chant-lexicon-${lexicon}`]: `^${ver}` } : {}),
66
78
  };
67
79
 
68
80
  const pkg = {
@@ -252,6 +264,20 @@ export async function initCommand(options: InitOptions): Promise<InitResult> {
252
264
  mkdirSync(targetDir, { recursive: true });
253
265
  }
254
266
 
267
+ // chant#2578 — `--force` into an existing project keeps its chant.config.
268
+ // When that config declares the lexicon by path, it loads from the module
269
+ // and there is no package to depend on or install. chant#2591 — the config
270
+ // is read without running it; when that fails, init says so and treats the
271
+ // lexicon as a package.
272
+ let lexiconModule: string | undefined;
273
+ if (existsSync(join(targetDir, "chant.config.ts")) || existsSync(join(targetDir, "chant.config.json"))) {
274
+ const notice = unknownPathLexiconsNotice(recordProjectLexicons(targetDir));
275
+ if (notice !== undefined) warnings.push(notice);
276
+ if (lexiconModulePath(options.lexicon) !== undefined) {
277
+ lexiconModule = lexiconSourceLabel(options.lexicon, targetDir);
278
+ }
279
+ }
280
+
255
281
  // Load plugin early to get template set (used for scripts + source files)
256
282
  let templateSet: import("../../lexicon").InitTemplateSet | undefined;
257
283
  try {
@@ -272,7 +298,7 @@ export async function initCommand(options: InitOptions): Promise<InitResult> {
272
298
  // Generate package.json
273
299
  writeIfNotExists(
274
300
  join(targetDir, "package.json"),
275
- generatePackageJson(options.lexicon, templateSet?.scripts),
301
+ generatePackageJson(options.lexicon, templateSet?.scripts, lexiconModule === undefined),
276
302
  "package.json",
277
303
  createdFiles,
278
304
  warnings,
@@ -361,29 +387,32 @@ export async function initCommand(options: InitOptions): Promise<InitResult> {
361
387
  warnings,
362
388
  );
363
389
 
364
- // Scaffold .chant/types/lexicon-{lexicon}/ stub
365
- const lexiconTypesDir = join(targetDir, ".chant", "types", `lexicon-${options.lexicon}`);
366
- mkdirSync(lexiconTypesDir, { recursive: true });
390
+ // Scaffold .chant/types/lexicon-{lexicon}/ stub. A lexicon declared by path
391
+ // is imported by its path, so it has no package name to stub (chant#2578).
392
+ if (lexiconModule === undefined) {
393
+ const lexiconTypesDir = join(targetDir, ".chant", "types", `lexicon-${options.lexicon}`);
394
+ mkdirSync(lexiconTypesDir, { recursive: true });
367
395
 
368
- writeIfNotExists(
369
- join(lexiconTypesDir, "package.json"),
370
- JSON.stringify(
371
- { name: `@intentius/chant-lexicon-${options.lexicon}`, version: "0.0.0", types: "./index.d.ts" },
372
- null,
373
- 2,
374
- ),
375
- `.chant/types/lexicon-${options.lexicon}/package.json`,
376
- createdFiles,
377
- warnings,
378
- );
396
+ writeIfNotExists(
397
+ join(lexiconTypesDir, "package.json"),
398
+ JSON.stringify(
399
+ { name: `@intentius/chant-lexicon-${options.lexicon}`, version: "0.0.0", types: "./index.d.ts" },
400
+ null,
401
+ 2,
402
+ ),
403
+ `.chant/types/lexicon-${options.lexicon}/package.json`,
404
+ createdFiles,
405
+ warnings,
406
+ );
379
407
 
380
- writeIfNotExists(
381
- join(lexiconTypesDir, "index.d.ts"),
382
- `// Lexicon type stubs — run "chant update" to sync full types\nexport {};\n`,
383
- `.chant/types/lexicon-${options.lexicon}/index.d.ts`,
384
- createdFiles,
385
- warnings,
386
- );
408
+ writeIfNotExists(
409
+ join(lexiconTypesDir, "index.d.ts"),
410
+ `// Lexicon type stubs — run "chant update" to sync full types\nexport {};\n`,
411
+ `.chant/types/lexicon-${options.lexicon}/index.d.ts`,
412
+ createdFiles,
413
+ warnings,
414
+ );
415
+ }
387
416
 
388
417
  // Generate the project's MCP config. It lands in the directory init was
389
418
  // pointed at, like everything else init produces, and at the path
@@ -421,10 +450,27 @@ export async function initCommand(options: InitOptions): Promise<InitResult> {
421
450
  // Skills are optional — don't fail init if plugin isn't installed yet
422
451
  }
423
452
 
453
+ // `--template` records where the files came from (#2540, ws-047). Plain
454
+ // init writes no lock and never loads the lineage module.
455
+ if (options.template && templateSet) {
456
+ const { writeTemplateLock } = await import("../../workspace/lineage-init");
457
+ const lock = writeTemplateLock({
458
+ targetDir,
459
+ lexicon: options.lexicon,
460
+ template: options.template,
461
+ createdFiles,
462
+ chantVersion: getChantVersion(),
463
+ lexiconModule,
464
+ });
465
+ if (lock) createdFiles.push(lock);
466
+ else warnings.push(".chant/workspace.lock.json already exists, skipping");
467
+ }
468
+
424
469
  return {
425
470
  success: true,
426
471
  createdFiles,
427
472
  warnings,
473
+ ...(lexiconModule !== undefined ? { lexiconModule } : {}),
428
474
  };
429
475
  }
430
476
 
@@ -453,6 +499,11 @@ export async function printInitResult(
453
499
 
454
500
  console.log("");
455
501
 
502
+ if (result.lexiconModule !== undefined) {
503
+ console.log(`The lexicon loads from ${result.lexiconModule}, as chant.config.ts declares. It has no package to install.`);
504
+ console.log("");
505
+ }
506
+
456
507
  const pm = detectPackageManager(options?.cwd);
457
508
 
458
509
  // Interactive install prompt