@intentius/chant 0.46.0 → 0.50.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 (377) hide show
  1. package/dist/audit/catalog.d.ts +13 -3
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/core.d.ts +30 -3
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts +9 -2
  6. package/dist/audit/discover.d.ts.map +1 -1
  7. package/dist/audit/fetch.d.ts.map +1 -1
  8. package/dist/audit/report-html.d.ts.map +1 -1
  9. package/dist/audit/report-model.d.ts +6 -0
  10. package/dist/audit/report-model.d.ts.map +1 -1
  11. package/dist/audit/report.d.ts.map +1 -1
  12. package/dist/audit/rules-doc.d.ts.map +1 -1
  13. package/dist/audit/secrets.d.ts +95 -0
  14. package/dist/audit/secrets.d.ts.map +1 -0
  15. package/dist/audit/wrangler.d.ts +33 -0
  16. package/dist/audit/wrangler.d.ts.map +1 -0
  17. package/dist/build.d.ts +3 -3
  18. package/dist/build.d.ts.map +1 -1
  19. package/dist/cli/commands/audit.d.ts +7 -0
  20. package/dist/cli/commands/audit.d.ts.map +1 -1
  21. package/dist/cli/commands/build.d.ts +23 -0
  22. package/dist/cli/commands/build.d.ts.map +1 -1
  23. package/dist/cli/commands/lint.d.ts.map +1 -1
  24. package/dist/cli/handlers/build.d.ts.map +1 -1
  25. package/dist/cli/handlers/components.d.ts +31 -0
  26. package/dist/cli/handlers/components.d.ts.map +1 -1
  27. package/dist/cli/handlers/lifecycle.d.ts +12 -1
  28. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  29. package/dist/cli/handlers/operator.d.ts +32 -0
  30. package/dist/cli/handlers/operator.d.ts.map +1 -0
  31. package/dist/cli/handlers/scenario.d.ts +39 -0
  32. package/dist/cli/handlers/scenario.d.ts.map +1 -0
  33. package/dist/cli/main.d.ts.map +1 -1
  34. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  35. package/dist/cli/mcp/server.d.ts +35 -2
  36. package/dist/cli/mcp/server.d.ts.map +1 -1
  37. package/dist/cli/mcp/tools/explain.d.ts +6 -0
  38. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  39. package/dist/cli/mcp/types.d.ts +29 -1
  40. package/dist/cli/mcp/types.d.ts.map +1 -1
  41. package/dist/cli/plugins.d.ts +1 -1
  42. package/dist/cli/plugins.d.ts.map +1 -1
  43. package/dist/cli/registry.d.ts +14 -2
  44. package/dist/cli/registry.d.ts.map +1 -1
  45. package/dist/cli/reporters/stylish.d.ts +15 -1
  46. package/dist/cli/reporters/stylish.d.ts.map +1 -1
  47. package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
  48. package/dist/components/auto-release.d.ts +4 -0
  49. package/dist/components/auto-release.d.ts.map +1 -1
  50. package/dist/components/capability.d.ts +17 -2
  51. package/dist/components/capability.d.ts.map +1 -1
  52. package/dist/components/cli-support.d.ts +7 -0
  53. package/dist/components/cli-support.d.ts.map +1 -1
  54. package/dist/components/component.d.ts +15 -0
  55. package/dist/components/component.d.ts.map +1 -1
  56. package/dist/components/driver.d.ts.map +1 -1
  57. package/dist/components/starter-plugin.d.ts +2 -0
  58. package/dist/components/starter-plugin.d.ts.map +1 -1
  59. package/dist/components/verbs/ensure-secret.d.ts +50 -0
  60. package/dist/components/verbs/ensure-secret.d.ts.map +1 -0
  61. package/dist/components/verbs/index.d.ts +13 -0
  62. package/dist/components/verbs/index.d.ts.map +1 -1
  63. package/dist/components/verbs/r2-sync.d.ts +76 -0
  64. package/dist/components/verbs/r2-sync.d.ts.map +1 -0
  65. package/dist/components/verbs/run-agent.d.ts +499 -0
  66. package/dist/components/verbs/run-agent.d.ts.map +1 -0
  67. package/dist/components/verbs/sign.d.ts +30 -0
  68. package/dist/components/verbs/sign.d.ts.map +1 -1
  69. package/dist/components/verbs/wrangler.d.ts +108 -0
  70. package/dist/components/verbs/wrangler.d.ts.map +1 -0
  71. package/dist/composite.d.ts +6 -1
  72. package/dist/composite.d.ts.map +1 -1
  73. package/dist/config.d.ts +26 -0
  74. package/dist/config.d.ts.map +1 -1
  75. package/dist/deep-observation.d.ts +14 -0
  76. package/dist/deep-observation.d.ts.map +1 -1
  77. package/dist/discovery/collect.d.ts.map +1 -1
  78. package/dist/discovery/fold-import.d.ts +15 -1
  79. package/dist/discovery/fold-import.d.ts.map +1 -1
  80. package/dist/discovery/fold-rank.d.ts +66 -0
  81. package/dist/discovery/fold-rank.d.ts.map +1 -0
  82. package/dist/discovery/index.d.ts +15 -0
  83. package/dist/discovery/index.d.ts.map +1 -1
  84. package/dist/discovery/param-deps.d.ts +17 -0
  85. package/dist/discovery/param-deps.d.ts.map +1 -0
  86. package/dist/effect-receipt.d.ts +177 -0
  87. package/dist/effect-receipt.d.ts.map +1 -0
  88. package/dist/fold/fold.d.ts +55 -2
  89. package/dist/fold/fold.d.ts.map +1 -1
  90. package/dist/fold/subset.d.ts +20 -0
  91. package/dist/fold/subset.d.ts.map +1 -1
  92. package/dist/index.d.ts +4 -0
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/lexicon-schema.d.ts +2 -0
  95. package/dist/lexicon-schema.d.ts.map +1 -1
  96. package/dist/lexicon.d.ts +137 -3
  97. package/dist/lexicon.d.ts.map +1 -1
  98. package/dist/lifecycle/change-set.d.ts +33 -5
  99. package/dist/lifecycle/change-set.d.ts.map +1 -1
  100. package/dist/lifecycle/converge-ledger.d.ts +90 -0
  101. package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
  102. package/dist/lifecycle/deep-diff.d.ts +18 -0
  103. package/dist/lifecycle/deep-diff.d.ts.map +1 -1
  104. package/dist/lifecycle/deep-observe.d.ts +9 -1
  105. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  106. package/dist/lifecycle/gate-ledger.d.ts +33 -0
  107. package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
  108. package/dist/lifecycle/git.d.ts +145 -21
  109. package/dist/lifecycle/git.d.ts.map +1 -1
  110. package/dist/lifecycle/index.d.ts +6 -0
  111. package/dist/lifecycle/index.d.ts.map +1 -1
  112. package/dist/lifecycle/lease.d.ts +113 -0
  113. package/dist/lifecycle/lease.d.ts.map +1 -0
  114. package/dist/lifecycle/observation-baseline.d.ts +21 -3
  115. package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
  116. package/dist/lifecycle/receipt-plan.d.ts +62 -0
  117. package/dist/lifecycle/receipt-plan.d.ts.map +1 -0
  118. package/dist/lifecycle/release-ledger.d.ts +20 -0
  119. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  120. package/dist/lifecycle/scenario-eval.d.ts +42 -0
  121. package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
  122. package/dist/lifecycle/scenario.d.ts +163 -0
  123. package/dist/lifecycle/scenario.d.ts.map +1 -0
  124. package/dist/lifecycle/symptoms.d.ts +63 -0
  125. package/dist/lifecycle/symptoms.d.ts.map +1 -0
  126. package/dist/lifecycle/teardown.d.ts +6 -4
  127. package/dist/lifecycle/teardown.d.ts.map +1 -1
  128. package/dist/lifecycle/unobserved-gate.d.ts +67 -0
  129. package/dist/lifecycle/unobserved-gate.d.ts.map +1 -0
  130. package/dist/lint/knowledge-checks.d.ts +48 -0
  131. package/dist/lint/knowledge-checks.d.ts.map +1 -0
  132. package/dist/lint/output-checks.d.ts +5 -0
  133. package/dist/lint/output-checks.d.ts.map +1 -0
  134. package/dist/lint/output-docs.d.ts +94 -0
  135. package/dist/lint/output-docs.d.ts.map +1 -0
  136. package/dist/lint/pipeline-change-gate.d.ts +101 -0
  137. package/dist/lint/pipeline-change-gate.d.ts.map +1 -0
  138. package/dist/lint/post-synth.d.ts +41 -0
  139. package/dist/lint/post-synth.d.ts.map +1 -1
  140. package/dist/lint/receipt-checks.d.ts +9 -0
  141. package/dist/lint/receipt-checks.d.ts.map +1 -0
  142. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
  143. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
  144. package/dist/lint/rules/cor022-receipt-leaf.d.ts +13 -0
  145. package/dist/lint/rules/cor022-receipt-leaf.d.ts.map +1 -0
  146. package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts +3 -0
  147. package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts.map +1 -0
  148. package/dist/lint/rules/evl001-non-literal-expression.d.ts.map +1 -1
  149. package/dist/lint/rules/index.d.ts +3 -1
  150. package/dist/lint/rules/index.d.ts.map +1 -1
  151. package/dist/lsp/lexicon-providers.d.ts +7 -0
  152. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  153. package/dist/okf-read.d.ts +78 -0
  154. package/dist/okf-read.d.ts.map +1 -0
  155. package/dist/op/activity-contract.d.ts +139 -0
  156. package/dist/op/activity-contract.d.ts.map +1 -0
  157. package/dist/op/builders.d.ts +140 -3
  158. package/dist/op/builders.d.ts.map +1 -1
  159. package/dist/op/converge-rule.d.ts +161 -0
  160. package/dist/op/converge-rule.d.ts.map +1 -0
  161. package/dist/op/generate-pipeline.d.ts +39 -0
  162. package/dist/op/generate-pipeline.d.ts.map +1 -0
  163. package/dist/op/index.d.ts +18 -2
  164. package/dist/op/index.d.ts.map +1 -1
  165. package/dist/op/local-executor.d.ts +2 -1
  166. package/dist/op/local-executor.d.ts.map +1 -1
  167. package/dist/op/op-verb-class.d.ts +42 -0
  168. package/dist/op/op-verb-class.d.ts.map +1 -0
  169. package/dist/op/operator.d.ts +128 -0
  170. package/dist/op/operator.d.ts.map +1 -0
  171. package/dist/op/receipt-store.d.ts +138 -0
  172. package/dist/op/receipt-store.d.ts.map +1 -0
  173. package/dist/op/step-output-ref.d.ts +187 -0
  174. package/dist/op/step-output-ref.d.ts.map +1 -0
  175. package/dist/op/types.d.ts +49 -2
  176. package/dist/op/types.d.ts.map +1 -1
  177. package/dist/provenance.d.ts +73 -3
  178. package/dist/provenance.d.ts.map +1 -1
  179. package/dist/runtime-adapter.d.ts +7 -1
  180. package/dist/runtime-adapter.d.ts.map +1 -1
  181. package/dist/secret-materialization.d.ts +138 -0
  182. package/dist/secret-materialization.d.ts.map +1 -0
  183. package/dist/secret-provenance.d.ts +218 -0
  184. package/dist/secret-provenance.d.ts.map +1 -0
  185. package/dist/serializer.d.ts +29 -0
  186. package/dist/serializer.d.ts.map +1 -1
  187. package/dist/toml.d.ts +40 -5
  188. package/dist/toml.d.ts.map +1 -1
  189. package/dist/yaml.d.ts.map +1 -1
  190. package/package.json +4 -1
  191. package/src/audit/catalog.test.ts +1 -1
  192. package/src/audit/catalog.ts +75 -3
  193. package/src/audit/core.test.ts +57 -0
  194. package/src/audit/core.ts +0 -0
  195. package/src/audit/detect-bundle.test.ts +1 -1
  196. package/src/audit/discover.test.ts +24 -0
  197. package/src/audit/discover.ts +40 -4
  198. package/src/audit/fetch.test.ts +216 -3
  199. package/src/audit/fetch.ts +270 -59
  200. package/src/audit/report-html.ts +5 -2
  201. package/src/audit/report-model.ts +9 -0
  202. package/src/audit/report.test.ts +22 -0
  203. package/src/audit/report.ts +3 -2
  204. package/src/audit/rules-doc.ts +13 -1
  205. package/src/audit/secrets.test.ts +303 -0
  206. package/src/audit/secrets.ts +406 -0
  207. package/src/audit/wrangler.test.ts +230 -0
  208. package/src/audit/wrangler.ts +290 -0
  209. package/src/build.test.ts +41 -0
  210. package/src/build.ts +39 -6
  211. package/src/cli/command-group.ts +1 -1
  212. package/src/cli/commands/__fixtures__/audit-fountain/agents/fleet.yaml +27 -0
  213. package/src/cli/commands/__fixtures__/audit-fountain/k8s/deploy.yaml +16 -0
  214. package/src/cli/commands/__fixtures__/audit-fountain-clean/fleet.yaml +20 -0
  215. package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
  216. package/src/cli/commands/audit.test.ts +268 -1
  217. package/src/cli/commands/audit.ts +87 -18
  218. package/src/cli/commands/build.test.ts +245 -0
  219. package/src/cli/commands/build.ts +210 -21
  220. package/src/cli/commands/lint.ts +15 -3
  221. package/src/cli/handlers/build.ts +2 -0
  222. package/src/cli/handlers/components.test.ts +199 -1
  223. package/src/cli/handlers/components.ts +160 -3
  224. package/src/cli/handlers/explain.test.ts +70 -1
  225. package/src/cli/handlers/graph.test.ts +20 -0
  226. package/src/cli/handlers/graph.ts +12 -3
  227. package/src/cli/handlers/lifecycle.test.ts +115 -1
  228. package/src/cli/handlers/lifecycle.ts +96 -15
  229. package/src/cli/handlers/operator.test.ts +255 -0
  230. package/src/cli/handlers/operator.ts +240 -0
  231. package/src/cli/handlers/scenario.test.ts +456 -0
  232. package/src/cli/handlers/scenario.ts +330 -0
  233. package/src/cli/main.test.ts +23 -0
  234. package/src/cli/main.ts +72 -1
  235. package/src/cli/mcp/resource-handlers.ts +38 -1
  236. package/src/cli/mcp/server.test.ts +323 -3
  237. package/src/cli/mcp/server.ts +84 -7
  238. package/src/cli/mcp/tools/explain.ts +51 -2
  239. package/src/cli/mcp/types.ts +27 -1
  240. package/src/cli/plugins.ts +4 -2
  241. package/src/cli/registry.ts +14 -2
  242. package/src/cli/reporters/stylish.test.ts +154 -0
  243. package/src/cli/reporters/stylish.ts +154 -33
  244. package/src/codegen/docs-rule-scanning.test.ts +42 -0
  245. package/src/codegen/docs-rule-scanning.ts +25 -2
  246. package/src/components/README.md +7 -0
  247. package/src/components/auto-release.ts +6 -0
  248. package/src/components/capability.ts +17 -2
  249. package/src/components/cli-support.test.ts +17 -0
  250. package/src/components/cli-support.ts +13 -1
  251. package/src/components/component-schema.test.ts +32 -0
  252. package/src/components/component.schema.json +6 -0
  253. package/src/components/component.test.ts +21 -0
  254. package/src/components/component.ts +15 -0
  255. package/src/components/driver.ts +12 -4
  256. package/src/components/registry.test.ts +7 -2
  257. package/src/components/starter-plugin.ts +17 -0
  258. package/src/components/verbs/ensure-secret.test.ts +130 -0
  259. package/src/components/verbs/ensure-secret.ts +79 -0
  260. package/src/components/verbs/index.ts +13 -0
  261. package/src/components/verbs/r2-sync.test.ts +107 -0
  262. package/src/components/verbs/r2-sync.ts +124 -0
  263. package/src/components/verbs/run-agent.test.ts +683 -0
  264. package/src/components/verbs/run-agent.ts +786 -0
  265. package/src/components/verbs/sign.test.ts +19 -0
  266. package/src/components/verbs/sign.ts +34 -2
  267. package/src/components/verbs/wrangler.test.ts +170 -0
  268. package/src/components/verbs/wrangler.ts +241 -0
  269. package/src/composite.ts +31 -2
  270. package/src/config.test.ts +15 -0
  271. package/src/config.ts +30 -0
  272. package/src/deep-observation.test.ts +19 -0
  273. package/src/deep-observation.ts +17 -0
  274. package/src/discovery/collect.ts +11 -2
  275. package/src/discovery/fold-import.test.ts +54 -0
  276. package/src/discovery/fold-import.ts +178 -38
  277. package/src/discovery/fold-rank.test.ts +197 -0
  278. package/src/discovery/fold-rank.ts +346 -0
  279. package/src/discovery/index.ts +16 -1
  280. package/src/discovery/param-deps.test.ts +118 -0
  281. package/src/discovery/param-deps.ts +170 -0
  282. package/src/effect-receipt-exclusion.test.ts +190 -0
  283. package/src/effect-receipt.test.ts +419 -0
  284. package/src/effect-receipt.ts +412 -0
  285. package/src/fold/fold.test.ts +6 -2
  286. package/src/fold/fold.ts +184 -3
  287. package/src/fold/subset.test.ts +95 -6
  288. package/src/fold/subset.ts +66 -2
  289. package/src/index.ts +4 -0
  290. package/src/lexicon-schema.ts +3 -0
  291. package/src/lexicon.ts +151 -5
  292. package/src/lifecycle/change-set.ts +46 -7
  293. package/src/lifecycle/converge-ledger.test.ts +199 -0
  294. package/src/lifecycle/converge-ledger.ts +179 -0
  295. package/src/lifecycle/deep-diff.test.ts +79 -1
  296. package/src/lifecycle/deep-diff.ts +23 -0
  297. package/src/lifecycle/deep-observe.ts +13 -2
  298. package/src/lifecycle/gate-ledger.test.ts +103 -0
  299. package/src/lifecycle/gate-ledger.ts +140 -0
  300. package/src/lifecycle/git.test.ts +430 -0
  301. package/src/lifecycle/git.ts +446 -84
  302. package/src/lifecycle/index.ts +6 -0
  303. package/src/lifecycle/lease.test.ts +343 -0
  304. package/src/lifecycle/lease.ts +270 -0
  305. package/src/lifecycle/observation-baseline.test.ts +46 -0
  306. package/src/lifecycle/observation-baseline.ts +33 -1
  307. package/src/lifecycle/receipt-plan.test.ts +250 -0
  308. package/src/lifecycle/receipt-plan.ts +249 -0
  309. package/src/lifecycle/release-ledger.ts +20 -0
  310. package/src/lifecycle/scenario-eval.test.ts +199 -0
  311. package/src/lifecycle/scenario-eval.ts +158 -0
  312. package/src/lifecycle/scenario.test.ts +195 -0
  313. package/src/lifecycle/scenario.ts +321 -0
  314. package/src/lifecycle/symptoms.test.ts +116 -0
  315. package/src/lifecycle/symptoms.ts +126 -0
  316. package/src/lifecycle/teardown.test.ts +31 -0
  317. package/src/lifecycle/teardown.ts +6 -4
  318. package/src/lifecycle/unobserved-gate.test.ts +109 -0
  319. package/src/lifecycle/unobserved-gate.ts +102 -0
  320. package/src/lint/knowledge-checks.test.ts +80 -0
  321. package/src/lint/knowledge-checks.ts +74 -0
  322. package/src/lint/output-checks.test.ts +85 -0
  323. package/src/lint/output-checks.ts +99 -0
  324. package/src/lint/output-docs.test.ts +220 -0
  325. package/src/lint/output-docs.ts +204 -0
  326. package/src/lint/pipeline-change-gate.test.ts +144 -0
  327. package/src/lint/pipeline-change-gate.ts +153 -0
  328. package/src/lint/post-synth.test.ts +97 -0
  329. package/src/lint/post-synth.ts +60 -0
  330. package/src/lint/receipt-checks.test.ts +101 -0
  331. package/src/lint/receipt-checks.ts +93 -0
  332. package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
  333. package/src/lint/rules/comp/comp.test.ts +49 -1
  334. package/src/lint/rules/cor022-receipt-leaf.test.ts +116 -0
  335. package/src/lint/rules/cor022-receipt-leaf.ts +130 -0
  336. package/src/lint/rules/cor024-receipt-secret-pointer.test.ts +121 -0
  337. package/src/lint/rules/cor024-receipt-secret-pointer.ts +218 -0
  338. package/src/lint/rules/evl001-non-literal-expression.test.ts +35 -3
  339. package/src/lint/rules/evl001-non-literal-expression.ts +7 -0
  340. package/src/lint/rules/index.ts +7 -1
  341. package/src/lsp/lexicon-providers.test.ts +44 -0
  342. package/src/lsp/lexicon-providers.ts +11 -1
  343. package/src/okf-read.test.ts +149 -0
  344. package/src/okf-read.ts +197 -0
  345. package/src/op/activity-contract.test.ts +180 -0
  346. package/src/op/activity-contract.ts +278 -0
  347. package/src/op/builders-exports.test.ts +17 -1
  348. package/src/op/builders.ts +198 -6
  349. package/src/op/converge-rule.test.ts +179 -0
  350. package/src/op/converge-rule.ts +311 -0
  351. package/src/op/effect-step.test.ts +311 -0
  352. package/src/op/generate-pipeline.test.ts +53 -0
  353. package/src/op/generate-pipeline.ts +99 -0
  354. package/src/op/index.ts +40 -3
  355. package/src/op/local-executor.test.ts +92 -0
  356. package/src/op/local-executor.ts +212 -29
  357. package/src/op/op-verb-class.test.ts +126 -0
  358. package/src/op/op-verb-class.ts +115 -0
  359. package/src/op/op.test.ts +25 -2
  360. package/src/op/operator.test.ts +346 -0
  361. package/src/op/operator.ts +213 -0
  362. package/src/op/receipt-store.ts +211 -0
  363. package/src/op/step-output-ref.test.ts +334 -0
  364. package/src/op/step-output-ref.ts +453 -0
  365. package/src/op/types.ts +51 -2
  366. package/src/provenance.test.ts +151 -4
  367. package/src/provenance.ts +118 -4
  368. package/src/runtime-adapter.ts +31 -10
  369. package/src/secret-materialization.test.ts +199 -0
  370. package/src/secret-materialization.ts +235 -0
  371. package/src/secret-provenance.test.ts +388 -0
  372. package/src/secret-provenance.ts +475 -0
  373. package/src/serializer.ts +30 -0
  374. package/src/toml.test.ts +157 -384
  375. package/src/toml.ts +371 -5
  376. package/src/yaml.test.ts +88 -0
  377. package/src/yaml.ts +76 -6
@@ -1,5 +1,6 @@
1
1
  import { describe, test, expect } from "vitest";
2
2
  import { fileURLToPath } from "url";
3
+ import Ajv from "ajv";
3
4
  import { auditCommand, tokenForHost, coverageNotes, installLine, NO_LEXICONS_EXIT_CODE } from "./audit";
4
5
  import { discoverByDetection, loadAuditPlugins } from "../../audit/discover";
5
6
  import { MissingLexiconError, type AuditInput, type AuditLexicon } from "../../audit/core";
@@ -8,12 +9,19 @@ import { MissingLexiconError, type AuditInput, type AuditLexicon } from "../../a
8
9
  async function discoverLexicon(repo: string, lexicon: AuditLexicon): Promise<AuditInput[]> {
9
10
  return discoverByDetection(repo, await loadAuditPlugins()).filter((i) => i.lexicon === lexicon);
10
11
  }
11
- import { readFileSync, existsSync, rmSync } from "fs";
12
+ import { readFileSync, existsSync, rmSync, mkdirSync, writeFileSync } from "fs";
12
13
  import { tmpdir } from "os";
13
14
  import { join } from "path";
15
+ import { fingerprintSecret } from "../../audit/secrets";
14
16
 
15
17
  const REPO = fileURLToPath(new URL("./__fixtures__/audit-repo", import.meta.url));
16
18
 
19
+ /** The real, vendored SARIF 2.1.0 JSON Schema — same structural-validation convention as component-bom.test.ts. */
20
+ function loadSarifSchema(): unknown {
21
+ const path = fileURLToPath(new URL("./__fixtures__/schemas/sarif-2.1.0.schema.json", import.meta.url));
22
+ return JSON.parse(readFileSync(path, "utf-8"));
23
+ }
24
+
17
25
  describe("auditCommand", () => {
18
26
  test("selects a host-specific token (no cross-host leakage)", () => {
19
27
  const env = { GITHUB_TOKEN: "gh", GITLAB_TOKEN: "gl", CODEBERG_TOKEN: "cb" } as unknown as NodeJS.ProcessEnv;
@@ -105,6 +113,59 @@ describe("auditCommand", () => {
105
113
  expect([...ids].some((id) => id.startsWith("WK8"))).toBe(false);
106
114
  });
107
115
 
116
+ test("discovers and audits fountain manifests (not misclassified as k8s) — FTN rules fire (#1566/#1567)", async () => {
117
+ const repo = fileURLToPath(new URL("./__fixtures__/audit-fountain", import.meta.url));
118
+ const fountain = await discoverLexicon(repo, "fountain");
119
+ expect(fountain.map((f) => f.path)).toEqual(["agents/fleet.yaml"]);
120
+ // The fountain manifest is NOT also picked up as k8s; the plain k8s manifest still is.
121
+ expect((await discoverLexicon(repo, "k8s")).map((f) => f.path)).toEqual(["k8s/deploy.yaml"]);
122
+
123
+ const result = await auditCommand({ path: repo, format: "stylish" });
124
+ expect(result.success).toBe(true);
125
+ const ftn = result.findings.filter((f) => f.checkId.startsWith("FTN"));
126
+ const ids = new Set(ftn.map((f) => f.checkId));
127
+ expect(ids).toContain("FTN011"); // unrestricted networking
128
+ expect(ids).toContain("FTN012"); // credential-shaped env_vars key
129
+ expect(ids).toContain("FTN014"); // vault key shadows an environment key
130
+ expect(ftn.find((f) => f.checkId === "FTN012")!.severity).toBe("error");
131
+ for (const f of ftn) {
132
+ expect(f.lexicon).toBe("fountain");
133
+ expect(f.file).toBe("agents/fleet.yaml");
134
+ }
135
+ // No k8s check ran against the fountain manifest.
136
+ for (const f of result.findings.filter((f) => f.lexicon === "k8s")) {
137
+ expect(f.file).not.toBe("agents/fleet.yaml");
138
+ }
139
+ });
140
+
141
+ test("fountain findings carry their authority citations in json, sarif, and markdown", async () => {
142
+ const repo = fileURLToPath(new URL("./__fixtures__/audit-fountain", import.meta.url));
143
+
144
+ const json = await auditCommand({ path: repo, format: "json" });
145
+ const envelope = JSON.parse(json.output) as { findings: Array<{ checkId: string; authority: Array<{ url: string }>; severity: string }> };
146
+ const ftn11 = envelope.findings.find((f) => f.checkId === "FTN011")!;
147
+ expect(ftn11.authority.length).toBeGreaterThan(0);
148
+ expect(ftn11.authority[0].url).toContain("fountain");
149
+ expect(envelope.findings.find((f) => f.checkId === "FTN012")!.severity).toBe("error");
150
+
151
+ const sarif = await auditCommand({ path: repo, format: "sarif" });
152
+ const run = (JSON.parse(sarif.output) as { runs: Array<{ tool: { driver: { rules: Array<{ id: string; helpUri?: string }> } }; results: Array<{ ruleId: string }> }> }).runs[0];
153
+ expect(run.results.some((r) => r.ruleId === "FTN011")).toBe(true);
154
+ expect(run.tool.driver.rules.find((r) => r.id === "FTN011")!.helpUri).toContain("fountain");
155
+
156
+ const md = await auditCommand({ path: repo, format: "markdown" });
157
+ expect(md.output).toContain("FTN011");
158
+ expect(md.output).toContain("FTN014");
159
+ });
160
+
161
+ test("a clean fountain manifest set audits quiet", async () => {
162
+ const repo = fileURLToPath(new URL("./__fixtures__/audit-fountain-clean", import.meta.url));
163
+ const result = await auditCommand({ path: repo, format: "stylish" });
164
+ expect(result.success).toBe(true);
165
+ expect(result.findings).toEqual([]);
166
+ expect(result.scanned).toEqual(["fleet.yaml"]);
167
+ });
168
+
108
169
  test("discovers and audits a Helm chart (as a bundle, not loose manifests)", async () => {
109
170
  const repo = fileURLToPath(new URL("./__fixtures__/audit-helm", import.meta.url));
110
171
  const charts = await discoverLexicon(repo, "helm");
@@ -346,4 +407,210 @@ describe("auditCommand", () => {
346
407
  expect(sarif.version).toBe("2.1.0");
347
408
  expect(sarif.runs[0].results.length).toBeGreaterThan(0);
348
409
  });
410
+
411
+ test("sarif carries tier on results and dimension/category + remediation help on rules (#442)", async () => {
412
+ const result = await auditCommand({ path: REPO, format: "sarif" });
413
+ const sarif = JSON.parse(result.output) as {
414
+ runs: Array<{
415
+ tool: { driver: { rules: Array<{ id: string; helpUri?: string; help?: { text: string }; properties?: { category?: string; dimension?: string } }> } };
416
+ results: Array<{ ruleId: string; properties?: { tier?: string } }>;
417
+ }>;
418
+ };
419
+ const run = sarif.runs[0];
420
+ // Every result carries the finding's tier so a consumer can triage merge-worthy vs. report-only without a second lookup.
421
+ expect(run.results.length).toBeGreaterThan(0);
422
+ for (const r of run.results) {
423
+ expect(r.properties?.tier).toMatch(/^(merge-worthy|report-only)$/);
424
+ }
425
+ // Every rule carries its dimension/category and remediation text (mapped from the catalog, #351) as help.
426
+ expect(run.tool.driver.rules.length).toBeGreaterThan(0);
427
+ for (const rule of run.tool.driver.rules) {
428
+ expect(rule.properties?.category).toMatch(/^(security|correctness|best-practice)$/);
429
+ expect(rule.properties?.dimension).toBe(rule.properties?.category);
430
+ expect(rule.help?.text.length).toBeGreaterThan(0);
431
+ }
432
+ });
433
+
434
+ test("sarif output validates against the real, vendored SARIF 2.1.0 JSON Schema", async () => {
435
+ const result = await auditCommand({ path: REPO, format: "sarif" });
436
+ const doc = JSON.parse(result.output);
437
+ const ajv = new Ajv({ strict: false, allErrors: true });
438
+ const validate = ajv.compile(loadSarifSchema() as object);
439
+ const valid = validate(doc);
440
+ if (!valid) throw new Error(ajv.errorsText(validate.errors));
441
+ expect(valid).toBe(true);
442
+ });
443
+
444
+ test("sarif schema validation also holds for the empty-result case (no findings)", async () => {
445
+ const repo = fileURLToPath(new URL("./__fixtures__/audit-fountain-clean", import.meta.url));
446
+ const result = await auditCommand({ path: repo, format: "sarif" });
447
+ const doc = JSON.parse(result.output);
448
+ expect(doc.runs[0].results).toEqual([]);
449
+ const ajv = new Ajv({ strict: false, allErrors: true });
450
+ const validate = ajv.compile(loadSarifSchema() as object);
451
+ const valid = validate(doc);
452
+ if (!valid) throw new Error(ajv.errorsText(validate.errors));
453
+ expect(valid).toBe(true);
454
+ });
455
+ });
456
+
457
+ describe("secrets detection (#443)", () => {
458
+ // A deliberately fake, non-functional AWS-access-key-ID-shaped value, built
459
+ // via concatenation (never a contiguous literal in this file's raw bytes)
460
+ // so pushing this file doesn't trip GitHub's own secret-scanning push
461
+ // protection on what is just a test fixture.
462
+ const FAKE_AWS_KEY = "AKIA" + "ABCDEFGHIJKLMNOP";
463
+ const FAKE_STRIPE_LIVE = "sk_live_" + "4eC39HqLyjWDarjtT1zdp7dc";
464
+
465
+ function tmpRepo(): string {
466
+ const dir = join(tmpdir(), `chant-audit-secrets-${process.pid}-${Math.random().toString(36).slice(2)}`);
467
+ mkdirSync(dir, { recursive: true });
468
+ return dir;
469
+ }
470
+
471
+ test("flags a hardcoded credential even when no lexicon is installed", async () => {
472
+ const dir = tmpRepo();
473
+ writeFileSync(join(dir, ".env"), `AWS_ACCESS_KEY_ID=${FAKE_AWS_KEY}\n`);
474
+ const result = await auditCommand({ path: dir, plugins: [] });
475
+ expect(result.status).toBe("no-lexicons"); // no lexicon installed — still not a clean miss
476
+ expect(result.findings.map((f) => f.checkId)).toContain("SEC001");
477
+ expect(JSON.stringify(result.findings)).not.toContain(FAKE_AWS_KEY); // redaction
478
+ rmSync(dir, { recursive: true, force: true });
479
+ });
480
+
481
+ test("sarif output never carries the raw secret value for a planted fake credential (#442)", async () => {
482
+ // `plugins: []` takes the "no-lexicons" diagnostic branch regardless of
483
+ // format, so a real lexicon (github) has to be installed for `format:
484
+ // "sarif"` to actually render SARIF here — same setup as the JSON
485
+ // round-trip test below.
486
+ const dir = tmpRepo();
487
+ mkdirSync(join(dir, ".github", "workflows"), { recursive: true });
488
+ writeFileSync(
489
+ join(dir, ".github", "workflows", "ci.yml"),
490
+ "name: CI\non:\n push:\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n",
491
+ );
492
+ writeFileSync(join(dir, ".env"), `AWS_ACCESS_KEY_ID=${FAKE_AWS_KEY}\nSTRIPE_KEY=${FAKE_STRIPE_LIVE}\n`);
493
+ const all = await loadAuditPlugins();
494
+ const result = await auditCommand({ path: dir, plugins: all.filter((p) => p.name === "github"), format: "sarif" });
495
+ expect(result.status).toBe("ok");
496
+ expect(result.output).not.toContain(FAKE_AWS_KEY);
497
+ expect(result.output).not.toContain(FAKE_STRIPE_LIVE);
498
+ const sarif = JSON.parse(result.output) as { runs: Array<{ results: Array<{ ruleId: string; message: { text: string } }> }> };
499
+ const ruleIds = sarif.runs[0].results.map((r) => r.ruleId);
500
+ expect(ruleIds).toContain("SEC001");
501
+ expect(ruleIds).toContain("SEC006");
502
+ for (const r of sarif.runs[0].results) {
503
+ expect(r.message.text).not.toContain(FAKE_AWS_KEY);
504
+ expect(r.message.text).not.toContain(FAKE_STRIPE_LIVE);
505
+ }
506
+ rmSync(dir, { recursive: true, force: true });
507
+ });
508
+
509
+ test("a secret rides alongside lexicon findings, tiered and cataloged like any other finding", async () => {
510
+ const dir = tmpRepo();
511
+ mkdirSync(join(dir, ".github", "workflows"), { recursive: true });
512
+ writeFileSync(
513
+ join(dir, ".github", "workflows", "ci.yml"),
514
+ "name: CI\non:\n push:\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n",
515
+ );
516
+ writeFileSync(join(dir, ".env"), `STRIPE_KEY=${FAKE_STRIPE_LIVE}\n`);
517
+ const all = await loadAuditPlugins();
518
+ const result = await auditCommand({ path: dir, plugins: all.filter((p) => p.name === "github"), format: "json" });
519
+ expect(result.status).toBe("ok");
520
+ const json = JSON.parse(result.output);
521
+ const sec = json.findings.find((f: { checkId: string }) => f.checkId === "SEC006");
522
+ expect(sec).toBeDefined();
523
+ expect(sec.tier).toBe("merge-worthy");
524
+ expect(sec.category).toBe("security");
525
+ expect(sec.file).toBe(".env");
526
+ expect(JSON.stringify(json)).not.toContain(FAKE_STRIPE_LIVE); // redaction, incl. the JSON report
527
+ rmSync(dir, { recursive: true, force: true });
528
+ });
529
+
530
+ test("an inline `chant-audit-ignore` marker suppresses the finding", async () => {
531
+ const dir = tmpRepo();
532
+ writeFileSync(join(dir, ".env"), `AWS_ACCESS_KEY_ID=${FAKE_AWS_KEY} # chant-audit-ignore: SEC001\n`);
533
+ const result = await auditCommand({ path: dir, plugins: [] });
534
+ expect(result.findings.map((f) => f.checkId)).not.toContain("SEC001");
535
+ rmSync(dir, { recursive: true, force: true });
536
+ });
537
+
538
+ test(".chant-audit.json's allowlist suppresses a finding by fingerprint, without storing the secret", async () => {
539
+ const dir = tmpRepo();
540
+ writeFileSync(join(dir, ".env"), `AWS_ACCESS_KEY_ID=${FAKE_AWS_KEY}\n`);
541
+ const fingerprint = fingerprintSecret(FAKE_AWS_KEY);
542
+ writeFileSync(join(dir, ".chant-audit.json"), JSON.stringify({ secrets: { allow: [{ ruleId: "SEC001", fingerprint }] } }));
543
+ const result = await auditCommand({ path: dir, plugins: [] });
544
+ expect(result.findings.map((f) => f.checkId)).not.toContain("SEC001");
545
+ rmSync(dir, { recursive: true, force: true });
546
+ });
547
+
548
+ test("an explicit secretsScan option overrides (and wins over) the local config", async () => {
549
+ const dir = tmpRepo();
550
+ writeFileSync(join(dir, "config.yaml"), "token: kQ7mZ9pL2xR8vT4nW1sD6uJ3\n");
551
+ writeFileSync(join(dir, ".chant-audit.json"), JSON.stringify({ secrets: { entropyThreshold: 1 } }));
552
+ const result = await auditCommand({ path: dir, plugins: [], secretsScan: { entropyThreshold: 6.5 } });
553
+ expect(result.findings.map((f) => f.checkId)).not.toContain("SEC010");
554
+ rmSync(dir, { recursive: true, force: true });
555
+ });
556
+ });
557
+
558
+ describe("Wrangler config audit (#446)", () => {
559
+ function tmpRepo(): string {
560
+ const dir = join(tmpdir(), `chant-audit-wrangler-${process.pid}-${Math.random().toString(36).slice(2)}`);
561
+ mkdirSync(dir, { recursive: true });
562
+ return dir;
563
+ }
564
+
565
+ test("audits wrangler.toml end to end, even when no lexicon is installed", async () => {
566
+ const dir = tmpRepo();
567
+ writeFileSync(
568
+ join(dir, "wrangler.toml"),
569
+ [
570
+ 'name = "my-worker"',
571
+ "",
572
+ "[observability]",
573
+ "enabled = false",
574
+ "",
575
+ "[env.production]",
576
+ "workers_dev = true",
577
+ ].join("\n"),
578
+ );
579
+ const result = await auditCommand({ path: dir, plugins: [], format: "json" });
580
+ expect(result.status).toBe("no-lexicons"); // no lexicon installed — still not a clean miss
581
+ const ids = result.findings.map((f) => f.checkId);
582
+ expect(ids).toContain("WRG001");
583
+ expect(ids).toContain("WRG003");
584
+ rmSync(dir, { recursive: true, force: true });
585
+ });
586
+
587
+ test("a wrangler finding is tiered and cataloged like any other finding, alongside lexicon findings", async () => {
588
+ const dir = tmpRepo();
589
+ mkdirSync(join(dir, ".github", "workflows"), { recursive: true });
590
+ writeFileSync(
591
+ join(dir, ".github", "workflows", "ci.yml"),
592
+ "name: CI\non:\n push:\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n",
593
+ );
594
+ writeFileSync(join(dir, "wrangler.toml"), 'routes = ["*/*"]\n');
595
+ const all = await loadAuditPlugins();
596
+ const result = await auditCommand({ path: dir, plugins: all.filter((p) => p.name === "github"), format: "json" });
597
+ expect(result.status).toBe("ok");
598
+ const json = JSON.parse(result.output);
599
+ const wrg = json.findings.find((f: { checkId: string }) => f.checkId === "WRG004");
600
+ expect(wrg).toBeDefined();
601
+ expect(wrg.tier).toBe("merge-worthy");
602
+ expect(wrg.category).toBe("security");
603
+ expect(wrg.file).toBe("wrangler.toml");
604
+ expect(wrg.docUrl).toBe("https://intentius.io/chant/lint-rules/audit-rules/#wrg004");
605
+ rmSync(dir, { recursive: true, force: true });
606
+ });
607
+
608
+ test("a malformed wrangler.toml contributes no findings, never fails the audit", async () => {
609
+ const dir = tmpRepo();
610
+ writeFileSync(join(dir, "wrangler.toml"), "not = valid = toml = [\n");
611
+ const result = await auditCommand({ path: dir, plugins: [] });
612
+ expect(result.success).toBe(true);
613
+ expect(result.findings.map((f) => f.checkId)).not.toEqual(expect.arrayContaining([expect.stringMatching(/^WRG/)]));
614
+ rmSync(dir, { recursive: true, force: true });
615
+ });
349
616
  });
@@ -5,10 +5,13 @@
5
5
  * directly and runs the real post-synth checks via the audit core.
6
6
  */
7
7
 
8
- import { existsSync, statSync, writeFileSync } from "fs";
8
+ import { existsSync, readFileSync, statSync, writeFileSync } from "fs";
9
+ import { join } from "path";
9
10
  import { auditFiles, type AuditInput, type AuditFinding, type ChecksProvider } from "../../audit/core";
10
11
  import { AUDIT_LEXICONS, classifyFiles, collectCandidates, loadAuditPlugins, unclaimedFiles, type DetectPlugin, type RepoFile, type UnclaimedFile } from "../../audit/discover";
11
12
  import { RULE_CATALOG, resolveAuditCatalog, type RuleMeta } from "../../audit/catalog";
13
+ import { scanForSecrets, parseSecretsConfig, type SecretsScanOptions } from "../../audit/secrets";
14
+ import { auditWranglerConfigs } from "../../audit/wrangler";
12
15
  import { renderMarkdown } from "../../audit/report";
13
16
  import { renderHtml, type ReportTheme } from "../../audit/report-html";
14
17
  import { buildReportJson, REPORT_SCHEMA_VERSION, type AuditSnapshot } from "../../audit/report-model";
@@ -47,6 +50,12 @@ export interface AuditCommandOptions {
47
50
  toolVersion?: string;
48
51
  /** Injectable detection plugins (testing); defaults to every installed audit lexicon. */
49
52
  plugins?: DetectPlugin[];
53
+ /**
54
+ * Secrets-detection options (#443) — entropy threshold/min-length and an
55
+ * allowlist. Merged over a `.chant-audit.json` at the local target root, if
56
+ * present (URL targets have no local config file to read).
57
+ */
58
+ secretsScan?: SecretsScanOptions;
50
59
  }
51
60
 
52
61
  export interface AuditCommandResult {
@@ -123,7 +132,7 @@ function renderNoLexicons(target: string, unclaimed: UnclaimedFile[]): string {
123
132
  lines.push(` ${u.path} -> ${note}`);
124
133
  }
125
134
  } else {
126
- lines.push("", "No file under the target looked like CI, Kubernetes, Helm, Docker, CloudFormation, ARM, or Config Connector either.");
135
+ lines.push("", "No file under the target looked like CI, Kubernetes, Helm, Docker, CloudFormation, ARM, Config Connector, or fountain either.");
127
136
  }
128
137
  lines.push("", "Run it with the lexicons those files need:");
129
138
  lines.push(` ${installLine(wanted.length > 0 ? wanted : [...AUDIT_LEXICONS], target)}`);
@@ -194,6 +203,26 @@ function sarifLevel(sev: Severity): string {
194
203
  return sev === "error" ? "error" : sev === "warning" ? "warning" : "note";
195
204
  }
196
205
 
206
+ /** `.chant-audit.json`'s `secrets` section at the local target root, if present (tolerant of a missing/malformed file). */
207
+ function readLocalSecretsConfig(root: string): SecretsScanOptions {
208
+ const file = join(root, ".chant-audit.json");
209
+ if (!existsSync(file)) return {};
210
+ try {
211
+ return parseSecretsConfig(readFileSync(file, "utf-8"));
212
+ } catch {
213
+ return {};
214
+ }
215
+ }
216
+
217
+ /** Merge the local `.chant-audit.json` config under any explicitly-passed options (options win). */
218
+ function resolveSecretsOptions(fileConfig: SecretsScanOptions, explicit?: SecretsScanOptions): SecretsScanOptions {
219
+ return {
220
+ entropyThreshold: explicit?.entropyThreshold ?? fileConfig.entropyThreshold,
221
+ entropyMinLength: explicit?.entropyMinLength ?? fileConfig.entropyMinLength,
222
+ allow: [...(fileConfig.allow ?? []), ...(explicit?.allow ?? [])],
223
+ };
224
+ }
225
+
197
226
  /** Coverage caveats about what the audit could and couldn't see. */
198
227
  export function coverageNotes(inputs: AuditInput[]): string[] {
199
228
  const notes: string[] = [];
@@ -221,7 +250,8 @@ function renderStylish(findings: AuditFinding[], scanned: string[], notes: strin
221
250
  if (list.length === 0) return;
222
251
  lines.push("", title);
223
252
  for (const f of list) {
224
- const where = f.entity ? `${f.file} (${f.entity})` : f.file;
253
+ const loc = f.line ? `${f.file}:${f.line}` : f.file;
254
+ const where = f.entity ? `${loc} (${f.entity})` : loc;
225
255
  const title = catalog[f.checkId]?.title ?? f.checkId;
226
256
  lines.push(` [${f.checkId}] ${f.severity} ${where} — ${title}`);
227
257
  }
@@ -231,6 +261,14 @@ function renderStylish(findings: AuditFinding[], scanned: string[], notes: strin
231
261
  return lines.join("\n");
232
262
  }
233
263
 
264
+ /**
265
+ * SARIF (2.1.0) export (#442). Rule-level metadata — category (the finding
266
+ * "dimension": security/correctness/best-practice) and remediation — is
267
+ * carried in the property bag / `help` block since SARIF's core schema has no
268
+ * dedicated slot for either; `tier` is per-result (a `RuleMeta` fact looked up
269
+ * per finding) so consumers can triage merge-worthy vs. report-only without a
270
+ * second catalog lookup.
271
+ */
234
272
  function renderSarif(findings: AuditFinding[], catalog: Record<string, RuleMeta> = RULE_CATALOG): string {
235
273
  const ruleIds = [...new Set(findings.map((f) => f.checkId))].sort();
236
274
  const rules = ruleIds.map((id) => {
@@ -239,15 +277,28 @@ function renderSarif(findings: AuditFinding[], catalog: Record<string, RuleMeta>
239
277
  id,
240
278
  name: m?.title ?? id,
241
279
  shortDescription: { text: m?.title ?? id },
280
+ ...(m?.remediation ? { help: { text: m.remediation } } : {}),
242
281
  helpUri: m?.authority?.[0]?.url,
282
+ ...(m?.category ? { properties: { category: m.category, dimension: m.category } } : {}),
283
+ };
284
+ });
285
+ const results = findings.map((f) => {
286
+ const tier = catalog[f.checkId]?.tier;
287
+ return {
288
+ ruleId: f.checkId,
289
+ level: sarifLevel(f.severity),
290
+ message: { text: f.message },
291
+ locations: [
292
+ {
293
+ physicalLocation: {
294
+ artifactLocation: { uri: f.file },
295
+ ...(f.line ? { region: { startLine: f.line } } : {}),
296
+ },
297
+ },
298
+ ],
299
+ ...(tier ? { properties: { tier } } : {}),
243
300
  };
244
301
  });
245
- const results = findings.map((f) => ({
246
- ruleId: f.checkId,
247
- level: sarifLevel(f.severity),
248
- message: { text: f.message },
249
- locations: [{ physicalLocation: { artifactLocation: { uri: f.file } } }],
250
- }));
251
302
  return JSON.stringify(
252
303
  {
253
304
  $schema: "https://json.schemastore.org/sarif-2.1.0.json",
@@ -327,28 +378,46 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
327
378
  const unclaimed = unclaimedFiles(candidates, inputs, plugins);
328
379
  const scanned = inputs.map((i) => i.path);
329
380
 
381
+ // Secrets detection (#443) is independent of lexicon: it scans every
382
+ // candidate file's raw text, not just the ones a lexicon claimed, and runs
383
+ // even when no lexicon is installed at all. `.chant-audit.json` at a local
384
+ // target's root supplies the entropy threshold / allowlist; a URL target
385
+ // has no local config file to read.
386
+ const secretsConfig = isUrl ? {} : readLocalSecretsConfig(options.path);
387
+ const secretsFindings = scanForSecrets(candidates, resolveSecretsOptions(secretsConfig, options.secretsScan));
388
+ // Wrangler config audit (#446) is likewise lexicon-independent: it scans
389
+ // every candidate file for `wrangler.toml` by its own detector, not through
390
+ // `classifyFiles`/an installed lexicon package (no such package exists —
391
+ // this format ships only a detector and checks, per the issue's scope).
392
+ const wranglerFindings = auditWranglerConfigs(candidates);
393
+
330
394
  if (plugins.length === 0) {
331
395
  const output = format === "json" ? renderNoLexiconsJson(options.path, unclaimed) : renderNoLexicons(options.path, unclaimed);
332
- return { success: true, status: "no-lexicons", output, findings: [], scanned: [], unclaimed, exitCode: NO_LEXICONS_EXIT_CODE, stream: format === "json" ? "stdout" : "stderr" };
396
+ return { success: true, status: "no-lexicons", output, findings: [...secretsFindings, ...wranglerFindings], scanned: [], unclaimed, exitCode: NO_LEXICONS_EXIT_CODE, stream: format === "json" ? "stdout" : "stderr" };
333
397
  }
334
398
 
335
399
  const missingLexiconNote = missingLexiconHint(unclaimed);
336
400
 
337
- if (inputs.length === 0) {
401
+ if (inputs.length === 0 && secretsFindings.length === 0 && wranglerFindings.length === 0) {
338
402
  const output = `No auditable files found under ${options.path}.${missingLexiconNote ? ` ${missingLexiconNote}` : ""}`;
339
403
  return { success: true, status: "ok", output, findings: [], scanned: [], unclaimed, exitCode: 0 };
340
404
  }
341
405
 
342
- let findings: AuditFinding[];
343
- try {
344
- findings = await auditFiles(inputs, { checksProvider: options.checksProvider });
345
- } catch (err) {
346
- const msg = err instanceof Error ? err.message : String(err);
347
- return { success: false, output: "", findings: [], scanned, exitCode: 1, error: msg };
406
+ let findings: AuditFinding[] = [];
407
+ if (inputs.length > 0) {
408
+ try {
409
+ findings = await auditFiles(inputs, { checksProvider: options.checksProvider });
410
+ } catch (err) {
411
+ const msg = err instanceof Error ? err.message : String(err);
412
+ return { success: false, output: "", findings: [], scanned, exitCode: 1, error: msg };
413
+ }
348
414
  }
415
+ findings = [...findings, ...secretsFindings, ...wranglerFindings];
349
416
  // Resolve the audit catalog once, aggregating the audited lexicons' own
350
417
  // metadata over core's static catalog (#687). The lexicons are already loaded
351
- // by `auditFiles` above, so this is cheap.
418
+ // by `auditFiles` above, so this is cheap. Core's static catalog (always
419
+ // included) carries the SEC* secrets rules, so they resolve even when
420
+ // `inputs` claimed nothing.
352
421
  const catalog = await resolveAuditCatalog([...new Set(inputs.map((i) => i.lexicon))]);
353
422
 
354
423
  if (tier === "merge-worthy") findings = findings.filter((f) => isMergeWorthy(f, catalog));