@intentius/chant 0.7.0 → 0.8.1

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 (424) hide show
  1. package/dist/attrref.d.ts +35 -0
  2. package/dist/attrref.d.ts.map +1 -0
  3. package/dist/audit/catalog.d.ts +65 -0
  4. package/dist/audit/catalog.d.ts.map +1 -0
  5. package/dist/audit/core.d.ts +76 -0
  6. package/dist/audit/core.d.ts.map +1 -0
  7. package/dist/audit/discover.d.ts +76 -0
  8. package/dist/audit/discover.d.ts.map +1 -0
  9. package/dist/audit/fetch.d.ts +90 -0
  10. package/dist/audit/fetch.d.ts.map +1 -0
  11. package/dist/audit/proof.d.ts +53 -0
  12. package/dist/audit/proof.d.ts.map +1 -0
  13. package/dist/audit/report-html.d.ts +32 -0
  14. package/dist/audit/report-html.d.ts.map +1 -0
  15. package/dist/audit/report-model.d.ts +119 -0
  16. package/dist/audit/report-model.d.ts.map +1 -0
  17. package/dist/audit/report.d.ts +31 -0
  18. package/dist/audit/report.d.ts.map +1 -0
  19. package/dist/audit/rules-doc.d.ts +11 -0
  20. package/dist/audit/rules-doc.d.ts.map +1 -0
  21. package/dist/build.d.ts +120 -0
  22. package/dist/build.d.ts.map +1 -0
  23. package/dist/builder.d.ts +56 -0
  24. package/dist/builder.d.ts.map +1 -0
  25. package/dist/child-project.d.ts +34 -0
  26. package/dist/child-project.d.ts.map +1 -0
  27. package/dist/cli/commands/audit.d.ts +60 -0
  28. package/dist/cli/commands/audit.d.ts.map +1 -0
  29. package/dist/cli/commands/build.d.ts +70 -0
  30. package/dist/cli/commands/build.d.ts.map +1 -0
  31. package/dist/cli/commands/check-lexicon.d.ts +19 -0
  32. package/dist/cli/commands/check-lexicon.d.ts.map +1 -0
  33. package/dist/cli/commands/describe.d.ts +39 -0
  34. package/dist/cli/commands/describe.d.ts.map +1 -0
  35. package/dist/cli/commands/diff.d.ts +32 -0
  36. package/dist/cli/commands/diff.d.ts.map +1 -0
  37. package/dist/cli/commands/doctor.d.ts +11 -0
  38. package/dist/cli/commands/doctor.d.ts.map +1 -0
  39. package/dist/cli/commands/import.d.ts +68 -0
  40. package/dist/cli/commands/import.d.ts.map +1 -0
  41. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts +9 -0
  42. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -0
  43. package/dist/cli/commands/init-lexicon/templates/docs.d.ts +9 -0
  44. package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -0
  45. package/dist/cli/commands/init-lexicon/templates/examples.d.ts +8 -0
  46. package/dist/cli/commands/init-lexicon/templates/examples.d.ts.map +1 -0
  47. package/dist/cli/commands/init-lexicon/templates/lint.d.ts +8 -0
  48. package/dist/cli/commands/init-lexicon/templates/lint.d.ts.map +1 -0
  49. package/dist/cli/commands/init-lexicon/templates/lsp.d.ts +6 -0
  50. package/dist/cli/commands/init-lexicon/templates/lsp.d.ts.map +1 -0
  51. package/dist/cli/commands/init-lexicon/templates/plugin.d.ts +12 -0
  52. package/dist/cli/commands/init-lexicon/templates/plugin.d.ts.map +1 -0
  53. package/dist/cli/commands/init-lexicon/templates/project.d.ts +19 -0
  54. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -0
  55. package/dist/cli/commands/init-lexicon/templates/spec.d.ts +6 -0
  56. package/dist/cli/commands/init-lexicon/templates/spec.d.ts.map +1 -0
  57. package/dist/cli/commands/init-lexicon/templates/tests.d.ts +12 -0
  58. package/dist/cli/commands/init-lexicon/templates/tests.d.ts.map +1 -0
  59. package/dist/cli/commands/init-lexicon.d.ts +37 -0
  60. package/dist/cli/commands/init-lexicon.d.ts.map +1 -0
  61. package/dist/cli/commands/init.d.ts +48 -0
  62. package/dist/cli/commands/init.d.ts.map +1 -0
  63. package/dist/cli/commands/licenses.d.ts +23 -0
  64. package/dist/cli/commands/licenses.d.ts.map +1 -0
  65. package/dist/cli/commands/lint.d.ts +52 -0
  66. package/dist/cli/commands/lint.d.ts.map +1 -0
  67. package/dist/cli/commands/list.d.ts +38 -0
  68. package/dist/cli/commands/list.d.ts.map +1 -0
  69. package/dist/cli/commands/migrate.d.ts +51 -0
  70. package/dist/cli/commands/migrate.d.ts.map +1 -0
  71. package/dist/cli/commands/onboard.d.ts +19 -0
  72. package/dist/cli/commands/onboard.d.ts.map +1 -0
  73. package/dist/cli/commands/update.d.ts +29 -0
  74. package/dist/cli/commands/update.d.ts.map +1 -0
  75. package/dist/cli/commands/vendor.d.ts +84 -0
  76. package/dist/cli/commands/vendor.d.ts.map +1 -0
  77. package/dist/cli/conflict-check.d.ts +18 -0
  78. package/dist/cli/conflict-check.d.ts.map +1 -0
  79. package/dist/cli/debug.d.ts +5 -0
  80. package/dist/cli/debug.d.ts.map +1 -0
  81. package/dist/cli/format.d.ts +36 -0
  82. package/dist/cli/format.d.ts.map +1 -0
  83. package/dist/cli/handlers/build.d.ts +3 -0
  84. package/dist/cli/handlers/build.d.ts.map +1 -0
  85. package/dist/cli/handlers/dev.d.ts +7 -0
  86. package/dist/cli/handlers/dev.d.ts.map +1 -0
  87. package/dist/cli/handlers/graph.d.ts +8 -0
  88. package/dist/cli/handlers/graph.d.ts.map +1 -0
  89. package/dist/cli/handlers/init.d.ts +4 -0
  90. package/dist/cli/handlers/init.d.ts.map +1 -0
  91. package/dist/cli/handlers/lifecycle.d.ts +39 -0
  92. package/dist/cli/handlers/lifecycle.d.ts.map +1 -0
  93. package/dist/cli/handlers/lint.d.ts +3 -0
  94. package/dist/cli/handlers/lint.d.ts.map +1 -0
  95. package/dist/cli/handlers/migrate.d.ts +3 -0
  96. package/dist/cli/handlers/migrate.d.ts.map +1 -0
  97. package/dist/cli/handlers/misc.d.ts +8 -0
  98. package/dist/cli/handlers/misc.d.ts.map +1 -0
  99. package/dist/cli/handlers/run-client.d.ts +107 -0
  100. package/dist/cli/handlers/run-client.d.ts.map +1 -0
  101. package/dist/cli/handlers/run-report.d.ts +5 -0
  102. package/dist/cli/handlers/run-report.d.ts.map +1 -0
  103. package/dist/cli/handlers/run.d.ts +27 -0
  104. package/dist/cli/handlers/run.d.ts.map +1 -0
  105. package/dist/cli/handlers/serve.d.ts +5 -0
  106. package/dist/cli/handlers/serve.d.ts.map +1 -0
  107. package/dist/cli/handlers/vendor.d.ts +9 -0
  108. package/dist/cli/handlers/vendor.d.ts.map +1 -0
  109. package/dist/cli/index.d.ts +4 -0
  110. package/dist/cli/index.d.ts.map +1 -0
  111. package/dist/cli/lsp/capabilities.d.ts +21 -0
  112. package/dist/cli/lsp/capabilities.d.ts.map +1 -0
  113. package/dist/cli/lsp/diagnostics.d.ts +30 -0
  114. package/dist/cli/lsp/diagnostics.d.ts.map +1 -0
  115. package/dist/cli/lsp/server.d.ts +106 -0
  116. package/dist/cli/lsp/server.d.ts.map +1 -0
  117. package/dist/cli/main.d.ts +7 -0
  118. package/dist/cli/main.d.ts.map +1 -0
  119. package/dist/cli/mcp/lifecycle-tools.d.ts +15 -0
  120. package/dist/cli/mcp/lifecycle-tools.d.ts.map +1 -0
  121. package/dist/cli/mcp/op-tools.d.ts +7 -0
  122. package/dist/cli/mcp/op-tools.d.ts.map +1 -0
  123. package/dist/cli/mcp/resource-handlers.d.ts +28 -0
  124. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -0
  125. package/dist/cli/mcp/resources/context.d.ts +6 -0
  126. package/dist/cli/mcp/resources/context.d.ts.map +1 -0
  127. package/dist/cli/mcp/server.d.ts +44 -0
  128. package/dist/cli/mcp/server.d.ts.map +1 -0
  129. package/dist/cli/mcp/tools/build.d.ts +31 -0
  130. package/dist/cli/mcp/tools/build.d.ts.map +1 -0
  131. package/dist/cli/mcp/tools/explain.d.ts +27 -0
  132. package/dist/cli/mcp/tools/explain.d.ts.map +1 -0
  133. package/dist/cli/mcp/tools/import.d.ts +26 -0
  134. package/dist/cli/mcp/tools/import.d.ts.map +1 -0
  135. package/dist/cli/mcp/tools/lint.d.ts +26 -0
  136. package/dist/cli/mcp/tools/lint.d.ts.map +1 -0
  137. package/dist/cli/mcp/tools/scaffold.d.ts +31 -0
  138. package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -0
  139. package/dist/cli/mcp/tools/search.d.ts +31 -0
  140. package/dist/cli/mcp/tools/search.d.ts.map +1 -0
  141. package/dist/cli/mcp/types.d.ts +42 -0
  142. package/dist/cli/mcp/types.d.ts.map +1 -0
  143. package/dist/cli/plugins.d.ts +21 -0
  144. package/dist/cli/plugins.d.ts.map +1 -0
  145. package/dist/cli/registry.d.ts +100 -0
  146. package/dist/cli/registry.d.ts.map +1 -0
  147. package/dist/cli/reporters/stylish.d.ts +26 -0
  148. package/dist/cli/reporters/stylish.d.ts.map +1 -0
  149. package/dist/cli/watch.d.ts +29 -0
  150. package/dist/cli/watch.d.ts.map +1 -0
  151. package/dist/codegen/bound-property-types.d.ts +38 -0
  152. package/dist/codegen/bound-property-types.d.ts.map +1 -0
  153. package/dist/codegen/coverage.d.ts +57 -0
  154. package/dist/codegen/coverage.d.ts.map +1 -0
  155. package/dist/codegen/docs-file-markers.d.ts +16 -0
  156. package/dist/codegen/docs-file-markers.d.ts.map +1 -0
  157. package/dist/codegen/docs-rule-scanning.d.ts +14 -0
  158. package/dist/codegen/docs-rule-scanning.d.ts.map +1 -0
  159. package/dist/codegen/docs-rules.d.ts +17 -0
  160. package/dist/codegen/docs-rules.d.ts.map +1 -0
  161. package/dist/codegen/docs-sections.d.ts +11 -0
  162. package/dist/codegen/docs-sections.d.ts.map +1 -0
  163. package/dist/codegen/docs-sidebar.d.ts +6 -0
  164. package/dist/codegen/docs-sidebar.d.ts.map +1 -0
  165. package/dist/codegen/docs-types.d.ts +84 -0
  166. package/dist/codegen/docs-types.d.ts.map +1 -0
  167. package/dist/codegen/docs.d.ts +28 -0
  168. package/dist/codegen/docs.d.ts.map +1 -0
  169. package/dist/codegen/fetch.d.ts +76 -0
  170. package/dist/codegen/fetch.d.ts.map +1 -0
  171. package/dist/codegen/generate-post-synth-barrel.d.ts +32 -0
  172. package/dist/codegen/generate-post-synth-barrel.d.ts.map +1 -0
  173. package/dist/codegen/generate-registry.d.ts +40 -0
  174. package/dist/codegen/generate-registry.d.ts.map +1 -0
  175. package/dist/codegen/generate-runtime-index.d.ts +40 -0
  176. package/dist/codegen/generate-runtime-index.d.ts.map +1 -0
  177. package/dist/codegen/generate-typescript.d.ts +48 -0
  178. package/dist/codegen/generate-typescript.d.ts.map +1 -0
  179. package/dist/codegen/generate.d.ts +92 -0
  180. package/dist/codegen/generate.d.ts.map +1 -0
  181. package/dist/codegen/json-patch.d.ts +12 -0
  182. package/dist/codegen/json-patch.d.ts.map +1 -0
  183. package/dist/codegen/json-schema.d.ts +82 -0
  184. package/dist/codegen/json-schema.d.ts.map +1 -0
  185. package/dist/codegen/naming.d.ts +64 -0
  186. package/dist/codegen/naming.d.ts.map +1 -0
  187. package/dist/codegen/package.d.ts +64 -0
  188. package/dist/codegen/package.d.ts.map +1 -0
  189. package/dist/codegen/topo-sort.d.ts +13 -0
  190. package/dist/codegen/topo-sort.d.ts.map +1 -0
  191. package/dist/codegen/typecheck.d.ts +10 -0
  192. package/dist/codegen/typecheck.d.ts.map +1 -0
  193. package/dist/codegen/validate.d.ts +45 -0
  194. package/dist/codegen/validate.d.ts.map +1 -0
  195. package/dist/composite.d.ts +116 -0
  196. package/dist/composite.d.ts.map +1 -0
  197. package/dist/config.d.ts +78 -0
  198. package/dist/config.d.ts.map +1 -0
  199. package/dist/declarable.d.ts +34 -0
  200. package/dist/declarable.d.ts.map +1 -0
  201. package/dist/detectLexicon.d.ts +10 -0
  202. package/dist/detectLexicon.d.ts.map +1 -0
  203. package/dist/discovery/cache.d.ts +36 -0
  204. package/dist/discovery/cache.d.ts.map +1 -0
  205. package/dist/discovery/collect.d.ts +17 -0
  206. package/dist/discovery/collect.d.ts.map +1 -0
  207. package/dist/discovery/cycles.d.ts +23 -0
  208. package/dist/discovery/cycles.d.ts.map +1 -0
  209. package/dist/discovery/files.d.ts +7 -0
  210. package/dist/discovery/files.d.ts.map +1 -0
  211. package/dist/discovery/graph.d.ts +8 -0
  212. package/dist/discovery/graph.d.ts.map +1 -0
  213. package/dist/discovery/import.d.ts +9 -0
  214. package/dist/discovery/import.d.ts.map +1 -0
  215. package/dist/discovery/index.d.ts +25 -0
  216. package/dist/discovery/index.d.ts.map +1 -0
  217. package/dist/discovery/resolve.d.ts +10 -0
  218. package/dist/discovery/resolve.d.ts.map +1 -0
  219. package/dist/errors.d.ts +49 -0
  220. package/dist/errors.d.ts.map +1 -0
  221. package/dist/import/base-parser.d.ts +19 -0
  222. package/dist/import/base-parser.d.ts.map +1 -0
  223. package/dist/import/generator.d.ts +20 -0
  224. package/dist/import/generator.d.ts.map +1 -0
  225. package/dist/import/ir-utils.d.ts +25 -0
  226. package/dist/import/ir-utils.d.ts.map +1 -0
  227. package/dist/import/parser.d.ts +39 -0
  228. package/dist/import/parser.d.ts.map +1 -0
  229. package/dist/index.d.ts +66 -0
  230. package/dist/index.d.ts.map +1 -0
  231. package/dist/intrinsic-interpolation.d.ts +24 -0
  232. package/dist/intrinsic-interpolation.d.ts.map +1 -0
  233. package/dist/intrinsic.d.ts +24 -0
  234. package/dist/intrinsic.d.ts.map +1 -0
  235. package/dist/lexicon-integrity.d.ts +25 -0
  236. package/dist/lexicon-integrity.d.ts.map +1 -0
  237. package/dist/lexicon-manifest.d.ts +9 -0
  238. package/dist/lexicon-manifest.d.ts.map +1 -0
  239. package/dist/lexicon-output.d.ts +66 -0
  240. package/dist/lexicon-output.d.ts.map +1 -0
  241. package/dist/lexicon-plugin-helpers.d.ts +59 -0
  242. package/dist/lexicon-plugin-helpers.d.ts.map +1 -0
  243. package/dist/lexicon-schema.d.ts +82 -0
  244. package/dist/lexicon-schema.d.ts.map +1 -0
  245. package/dist/lexicon.d.ts +383 -0
  246. package/dist/lexicon.d.ts.map +1 -0
  247. package/dist/lifecycle/affected.d.ts +57 -0
  248. package/dist/lifecycle/affected.d.ts.map +1 -0
  249. package/dist/lifecycle/change-set.d.ts +90 -0
  250. package/dist/lifecycle/change-set.d.ts.map +1 -0
  251. package/dist/lifecycle/digest.d.ts +21 -0
  252. package/dist/lifecycle/digest.d.ts.map +1 -0
  253. package/dist/lifecycle/git.d.ts +72 -0
  254. package/dist/lifecycle/git.d.ts.map +1 -0
  255. package/dist/lifecycle/index.d.ts +8 -0
  256. package/dist/lifecycle/index.d.ts.map +1 -0
  257. package/dist/lifecycle/live-diff.d.ts +67 -0
  258. package/dist/lifecycle/live-diff.d.ts.map +1 -0
  259. package/dist/lifecycle/snapshot.d.ts +20 -0
  260. package/dist/lifecycle/snapshot.d.ts.map +1 -0
  261. package/dist/lifecycle/types.d.ts +61 -0
  262. package/dist/lifecycle/types.d.ts.map +1 -0
  263. package/dist/lint/config.d.ts +100 -0
  264. package/dist/lint/config.d.ts.map +1 -0
  265. package/dist/lint/declarative.d.ts +56 -0
  266. package/dist/lint/declarative.d.ts.map +1 -0
  267. package/dist/lint/discover.d.ts +35 -0
  268. package/dist/lint/discover.d.ts.map +1 -0
  269. package/dist/lint/engine.d.ts +21 -0
  270. package/dist/lint/engine.d.ts.map +1 -0
  271. package/dist/lint/named-checks.d.ts +19 -0
  272. package/dist/lint/named-checks.d.ts.map +1 -0
  273. package/dist/lint/parser.d.ts +9 -0
  274. package/dist/lint/parser.d.ts.map +1 -0
  275. package/dist/lint/policy.d.ts +22 -0
  276. package/dist/lint/policy.d.ts.map +1 -0
  277. package/dist/lint/post-synth.d.ts +69 -0
  278. package/dist/lint/post-synth.d.ts.map +1 -0
  279. package/dist/lint/presets/strict.json +19 -0
  280. package/dist/lint/rule-loader.d.ts +16 -0
  281. package/dist/lint/rule-loader.d.ts.map +1 -0
  282. package/dist/lint/rule-registry.d.ts +37 -0
  283. package/dist/lint/rule-registry.d.ts.map +1 -0
  284. package/dist/lint/rule.d.ts +82 -0
  285. package/dist/lint/rule.d.ts.map +1 -0
  286. package/dist/lint/rules/composite-scope.d.ts +18 -0
  287. package/dist/lint/rules/composite-scope.d.ts.map +1 -0
  288. package/dist/lint/rules/cor017-composite-name-match.d.ts +3 -0
  289. package/dist/lint/rules/cor017-composite-name-match.d.ts.map +1 -0
  290. package/dist/lint/rules/cor018-composite-prefer-lexicon-type.d.ts +3 -0
  291. package/dist/lint/rules/cor018-composite-prefer-lexicon-type.d.ts.map +1 -0
  292. package/dist/lint/rules/declarable-naming-convention.d.ts +3 -0
  293. package/dist/lint/rules/declarable-naming-convention.d.ts.map +1 -0
  294. package/dist/lint/rules/evl001-non-literal-expression.d.ts +3 -0
  295. package/dist/lint/rules/evl001-non-literal-expression.d.ts.map +1 -0
  296. package/dist/lint/rules/evl002-control-flow-resource.d.ts +3 -0
  297. package/dist/lint/rules/evl002-control-flow-resource.d.ts.map +1 -0
  298. package/dist/lint/rules/evl003-dynamic-property-access.d.ts +3 -0
  299. package/dist/lint/rules/evl003-dynamic-property-access.d.ts.map +1 -0
  300. package/dist/lint/rules/evl004-spread-non-const.d.ts +3 -0
  301. package/dist/lint/rules/evl004-spread-non-const.d.ts.map +1 -0
  302. package/dist/lint/rules/evl005-resource-block-body.d.ts +3 -0
  303. package/dist/lint/rules/evl005-resource-block-body.d.ts.map +1 -0
  304. package/dist/lint/rules/evl007-invalid-siblings.d.ts +3 -0
  305. package/dist/lint/rules/evl007-invalid-siblings.d.ts.map +1 -0
  306. package/dist/lint/rules/evl009-composite-no-constant.d.ts +3 -0
  307. package/dist/lint/rules/evl009-composite-no-constant.d.ts.map +1 -0
  308. package/dist/lint/rules/evl010-composite-no-transform.d.ts +3 -0
  309. package/dist/lint/rules/evl010-composite-no-transform.d.ts.map +1 -0
  310. package/dist/lint/rules/export-required.d.ts +3 -0
  311. package/dist/lint/rules/export-required.d.ts.map +1 -0
  312. package/dist/lint/rules/file-declarable-limit.d.ts +3 -0
  313. package/dist/lint/rules/file-declarable-limit.d.ts.map +1 -0
  314. package/dist/lint/rules/flat-declarations.d.ts +3 -0
  315. package/dist/lint/rules/flat-declarations.d.ts.map +1 -0
  316. package/dist/lint/rules/index.d.ts +31 -0
  317. package/dist/lint/rules/index.d.ts.map +1 -0
  318. package/dist/lint/rules/no-cyclic-declarable-ref.d.ts +3 -0
  319. package/dist/lint/rules/no-cyclic-declarable-ref.d.ts.map +1 -0
  320. package/dist/lint/rules/no-redundant-type-import.d.ts +10 -0
  321. package/dist/lint/rules/no-redundant-type-import.d.ts.map +1 -0
  322. package/dist/lint/rules/no-redundant-value-cast.d.ts +3 -0
  323. package/dist/lint/rules/no-redundant-value-cast.d.ts.map +1 -0
  324. package/dist/lint/rules/no-string-ref.d.ts +3 -0
  325. package/dist/lint/rules/no-string-ref.d.ts.map +1 -0
  326. package/dist/lint/rules/no-unused-declarable-import.d.ts +3 -0
  327. package/dist/lint/rules/no-unused-declarable-import.d.ts.map +1 -0
  328. package/dist/lint/rules/no-unused-declarable.d.ts +3 -0
  329. package/dist/lint/rules/no-unused-declarable.d.ts.map +1 -0
  330. package/dist/lint/rules/single-concern-file.d.ts +3 -0
  331. package/dist/lint/rules/single-concern-file.d.ts.map +1 -0
  332. package/dist/lint/selectors.d.ts +21 -0
  333. package/dist/lint/selectors.d.ts.map +1 -0
  334. package/dist/lsp/lexicon-providers.d.ts +61 -0
  335. package/dist/lsp/lexicon-providers.d.ts.map +1 -0
  336. package/dist/lsp/types.d.ts +64 -0
  337. package/dist/lsp/types.d.ts.map +1 -0
  338. package/dist/mcp/types.d.ts +21 -0
  339. package/dist/mcp/types.d.ts.map +1 -0
  340. package/dist/op/activity-registry.d.ts +57 -0
  341. package/dist/op/activity-registry.d.ts.map +1 -0
  342. package/dist/op/builders.d.ts +69 -0
  343. package/dist/op/builders.d.ts.map +1 -0
  344. package/dist/op/discover.d.ts +16 -0
  345. package/dist/op/discover.d.ts.map +1 -0
  346. package/dist/op/index.d.ts +11 -0
  347. package/dist/op/index.d.ts.map +1 -0
  348. package/dist/op/local-executor.d.ts +54 -0
  349. package/dist/op/local-executor.d.ts.map +1 -0
  350. package/dist/op/local-output.d.ts +16 -0
  351. package/dist/op/local-output.d.ts.map +1 -0
  352. package/dist/op/resource.d.ts +7 -0
  353. package/dist/op/resource.d.ts.map +1 -0
  354. package/dist/op/types.d.ts +71 -0
  355. package/dist/op/types.d.ts.map +1 -0
  356. package/dist/ownership.d.ts +75 -0
  357. package/dist/ownership.d.ts.map +1 -0
  358. package/dist/project-validation.d.ts +28 -0
  359. package/dist/project-validation.d.ts.map +1 -0
  360. package/dist/provenance.d.ts +26 -0
  361. package/dist/provenance.d.ts.map +1 -0
  362. package/dist/pseudo-parameter.d.ts +32 -0
  363. package/dist/pseudo-parameter.d.ts.map +1 -0
  364. package/dist/resource-attributes.d.ts +17 -0
  365. package/dist/resource-attributes.d.ts.map +1 -0
  366. package/dist/runtime-adapter.d.ts +41 -0
  367. package/dist/runtime-adapter.d.ts.map +1 -0
  368. package/dist/runtime.d.ts +23 -0
  369. package/dist/runtime.d.ts.map +1 -0
  370. package/dist/serializer-walker.d.ts +21 -0
  371. package/dist/serializer-walker.d.ts.map +1 -0
  372. package/dist/serializer.d.ts +64 -0
  373. package/dist/serializer.d.ts.map +1 -0
  374. package/dist/sort.d.ts +8 -0
  375. package/dist/sort.d.ts.map +1 -0
  376. package/dist/stack-output.d.ts +52 -0
  377. package/dist/stack-output.d.ts.map +1 -0
  378. package/dist/toml-emit.d.ts +22 -0
  379. package/dist/toml-emit.d.ts.map +1 -0
  380. package/dist/toml-parse.d.ts +14 -0
  381. package/dist/toml-parse.d.ts.map +1 -0
  382. package/dist/toml-utils.d.ts +20 -0
  383. package/dist/toml-utils.d.ts.map +1 -0
  384. package/dist/toml.d.ts +9 -0
  385. package/dist/toml.d.ts.map +1 -0
  386. package/dist/types.d.ts +47 -0
  387. package/dist/types.d.ts.map +1 -0
  388. package/dist/utils.d.ts +30 -0
  389. package/dist/utils.d.ts.map +1 -0
  390. package/dist/validation.d.ts +42 -0
  391. package/dist/validation.d.ts.map +1 -0
  392. package/dist/yaml.d.ts +42 -0
  393. package/dist/yaml.d.ts.map +1 -0
  394. package/package.json +31 -6
  395. package/src/audit/bundle-decoupling.test.ts +56 -0
  396. package/src/audit/catalog.test.ts +16 -1
  397. package/src/audit/catalog.ts +244 -4
  398. package/src/audit/core.test.ts +15 -0
  399. package/src/audit/core.ts +0 -0
  400. package/src/audit/detect-bundle.test.ts +35 -0
  401. package/src/audit/discover.test.ts +72 -0
  402. package/src/audit/discover.ts +329 -0
  403. package/src/audit/edge-init-safety.test.ts +43 -0
  404. package/src/audit/fetch.test.ts +136 -55
  405. package/src/audit/fetch.ts +175 -91
  406. package/src/audit/remote-discovery.test.ts +80 -0
  407. package/src/audit/report-html.ts +14 -6
  408. package/src/audit/report-model.ts +13 -1
  409. package/src/audit/report.test.ts +3 -1
  410. package/src/audit/report.ts +13 -5
  411. package/src/cli/commands/__fixtures__/audit-mixed/.github/workflows/ci.yml +5 -0
  412. package/src/cli/commands/__fixtures__/audit-mixed/Dockerfile +2 -0
  413. package/src/cli/commands/__fixtures__/audit-mixed/cfn/template.json +1 -0
  414. package/src/cli/commands/__fixtures__/audit-mixed/k8s/deploy.yaml +12 -0
  415. package/src/cli/commands/audit.test.ts +27 -19
  416. package/src/cli/commands/audit.ts +28 -272
  417. package/src/codegen/bound-property-types.test.ts +57 -0
  418. package/src/codegen/bound-property-types.ts +110 -0
  419. package/src/codegen/generate-post-synth-barrel.test.ts +37 -0
  420. package/src/codegen/generate-post-synth-barrel.ts +75 -0
  421. package/src/codegen/post-synth-barrel-guards.test.ts +63 -0
  422. package/src/codegen/validate.ts +13 -1
  423. package/src/lint/discover.ts +1 -1
  424. package/src/meta/peer-deps.test.ts +29 -0
@@ -1,21 +1,21 @@
1
1
  /**
2
- * Remote fetch — pull a repo's CI files from a git host so the auditor can run
3
- * on a URL, not just a local path. This is the ONLY audit module that touches
4
- * the network; the core stays pure.
2
+ * Remote fetch — pull a repo's candidate files (all lexicons) from a git host so
3
+ * the auditor can run on a URL, not just a local path. This is the ONLY audit
4
+ * module that touches the network; the core stays pure.
5
5
  *
6
6
  * SSRF posture: only an allowlisted set of hosts is accepted; request URLs are
7
7
  * built from the parsed owner/repo (never a user-controlled host); redirects
8
8
  * are refused; and file count / size / total bytes / time are all capped.
9
9
  */
10
10
 
11
- import type { AuditInput, AuditLexicon } from "./core";
11
+ import type { AuditLexicon } from "./core";
12
12
 
13
13
  export interface FetchOptions {
14
14
  /** Branch/tag/sha; defaults to the repo's default branch. */
15
15
  ref?: string;
16
16
  /** Server-side token (lifts rate limits). Never surfaced to callers. */
17
17
  token?: string;
18
- /** Max number of CI files to fetch (default 50). */
18
+ /** Max number of files to fetch (default 50). */
19
19
  maxFiles?: number;
20
20
  /** Max bytes for a single file; larger files are skipped (default 256 KiB). */
21
21
  maxBytesPerFile?: number;
@@ -80,15 +80,16 @@ export function parseRepoUrl(url: string): ParsedRepo {
80
80
  return { host, owner: parts[0], repo: parts[1].replace(/\.git$/, "") };
81
81
  }
82
82
 
83
- function authHeaders(kind: HostKind, token?: string): Record<string, string> {
84
- if (!token) return {};
85
- if (kind === "github") return { Authorization: `Bearer ${token}` };
86
- if (kind === "forgejo") return { Authorization: `token ${token}` };
87
- return { "PRIVATE-TOKEN": token };
88
- }
83
+ /** GitHub (and good manners) require a User-Agent — workerd's fetch sends none,
84
+ * so a missing UA 403s every request. Always set one, with or without a token. */
85
+ const USER_AGENT = "chant-audit (+https://github.com/intentius/chant)";
89
86
 
90
- function isYaml(name: string): boolean {
91
- return name.endsWith(".yml") || name.endsWith(".yaml");
87
+ function authHeaders(kind: HostKind, token?: string): Record<string, string> {
88
+ const base = { "User-Agent": USER_AGENT };
89
+ if (!token) return base;
90
+ if (kind === "github") return { ...base, Authorization: `Bearer ${token}` };
91
+ if (kind === "forgejo") return { ...base, Authorization: `token ${token}` };
92
+ return { ...base, "PRIVATE-TOKEN": token };
92
93
  }
93
94
 
94
95
  function timeoutSignal(ms: number): AbortSignal | undefined {
@@ -107,7 +108,150 @@ interface ContentsEntry {
107
108
  encoding?: string;
108
109
  }
109
110
 
110
- export async function fetchCiFiles(url: string, opts: FetchOptions = {}): Promise<AuditInput[]> {
111
+ // ── Whole-repo fetch (all lexicons, not just CI) ─────────────────────────────
112
+ // Mirrors the local walk: list the repo tree, keep candidate paths, fetch their
113
+ // contents (capped), and hand the raw {path, content} set to the shared
114
+ // classifier in discover.ts. SSRF posture is unchanged — allowlisted host,
115
+ // URLs built from parsed owner/repo, redirects refused, counts/bytes capped.
116
+
117
+ /** Module-level JSON GET that refuses redirects (reused by the tree-walk). */
118
+ async function getJsonAt(
119
+ url: string,
120
+ headers: Record<string, string>,
121
+ doFetch: typeof fetch,
122
+ timeoutMs: number,
123
+ ): Promise<{ status: number; body: unknown }> {
124
+ let res: Response;
125
+ try {
126
+ res = await doFetch(url, { headers, redirect: "manual", signal: timeoutSignal(timeoutMs) });
127
+ } catch (err) {
128
+ throw new FetchError(`Request failed: ${err instanceof Error ? err.message : String(err)}`);
129
+ }
130
+ if (res.status >= 300 && res.status < 400) throw new FetchError(`Refusing to follow redirect from ${url}`);
131
+ if (res.status === 404) return { status: 404, body: null };
132
+ if (!res.ok) throw new FetchError(`${url} returned ${res.status}`);
133
+ return { status: res.status, body: await res.json() };
134
+ }
135
+
136
+ function projectId(owner: string, repo: string): string {
137
+ return encodeURIComponent(`${owner}/${repo}`);
138
+ }
139
+
140
+ /** The repo's default branch, so the tree request has a concrete ref. */
141
+ async function defaultBranch(host: HostConfig, owner: string, repo: string, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<string> {
142
+ const url =
143
+ host.kind === "gitlab"
144
+ ? `${host.api}/projects/${projectId(owner, repo)}`
145
+ : `${host.api}/repos/${owner}/${repo}`;
146
+ const { body } = await getJsonAt(url, headers, doFetch, ms);
147
+ const branch = (body as { default_branch?: string } | null)?.default_branch;
148
+ return branch && typeof branch === "string" ? branch : "HEAD";
149
+ }
150
+
151
+ interface TreeEntry {
152
+ path: string;
153
+ type: string;
154
+ size?: number;
155
+ }
156
+
157
+ /** List every blob path in the repo (recursive), with size where the host reports it. */
158
+ async function listTree(host: HostConfig, owner: string, repo: string, ref: string, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<TreeEntry[]> {
159
+ if (host.kind === "gitlab") {
160
+ const out: TreeEntry[] = [];
161
+ const MAX_PAGES = 20; // 20 * 100 = 2000 entries — bounded; candidate filter + caps trim further
162
+ for (let page = 1; page <= MAX_PAGES; page++) {
163
+ const url = `${host.api}/projects/${projectId(owner, repo)}/repository/tree?recursive=true&per_page=100&page=${page}&ref=${encodeURIComponent(ref)}`;
164
+ const { body } = await getJsonAt(url, headers, doFetch, ms);
165
+ if (!Array.isArray(body) || body.length === 0) break;
166
+ for (const e of body as Array<{ path: string; type: string }>) {
167
+ if (e.type === "blob") out.push({ path: e.path, type: "blob" });
168
+ }
169
+ if (body.length < 100) break;
170
+ }
171
+ return out;
172
+ }
173
+ // GitHub / Forgejo (Gitea) share the git/trees recursive API.
174
+ const url = `${host.api}/repos/${owner}/${repo}/git/trees/${encodeURIComponent(ref)}?recursive=1`;
175
+ const { body } = await getJsonAt(url, headers, doFetch, ms);
176
+ const tree = (body as { tree?: TreeEntry[] } | null)?.tree;
177
+ if (!Array.isArray(tree)) return [];
178
+ return tree.filter((e) => e.type === "blob").map((e) => ({ path: e.path, type: "blob", size: e.size }));
179
+ }
180
+
181
+ /**
182
+ * Resilient GET → text. Returns undefined on any error or non-ok status, so one
183
+ * file's failure (a bad path, a 403 secondary rate-limit, a timeout) never
184
+ * aborts the whole walk. `redirect: "manual"` keeps the SSRF posture — a 3xx is
185
+ * skipped, never followed.
186
+ */
187
+ async function fetchText(url: string, headers: Record<string, string>, doFetch: typeof fetch, ms: number): Promise<string | undefined> {
188
+ let res: Response;
189
+ try {
190
+ res = await doFetch(url, { headers, redirect: "manual", signal: timeoutSignal(ms) });
191
+ } catch {
192
+ return undefined;
193
+ }
194
+ if (!res.ok) return undefined;
195
+ try {
196
+ return await res.text();
197
+ } catch {
198
+ return undefined;
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Fetch one file's content. GitHub reads from the raw CDN first — a burst of
204
+ * contents-API calls trips GitHub's *secondary* rate limit on large repos
205
+ * (dozens of files), whereas raw.githubusercontent.com isn't subject to it and
206
+ * needs no auth for public repos. The contents API is the fallback for private
207
+ * repos (raw 404s there without auth). GitLab uses its raw endpoint; Forgejo
208
+ * uses the contents API.
209
+ */
210
+ async function fetchFileContent(host: HostConfig, owner: string, repo: string, path: string, ref: string, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<string | undefined> {
211
+ // Encode each segment (keep the slashes) — paths can contain spaces and other
212
+ // characters that make an unencoded URL malformed and 403 (e.g. GitHub's
213
+ // changelog fragments like ".changes/v1.16/NEW FEATURES-….yaml").
214
+ const encodedPath = path.split("/").map(encodeURIComponent).join("/");
215
+
216
+ if (host.kind === "gitlab") {
217
+ const url = `${host.api}/projects/${projectId(owner, repo)}/repository/files/${encodeURIComponent(path)}/raw?ref=${encodeURIComponent(ref)}`;
218
+ return fetchText(url, headers, doFetch, ms);
219
+ }
220
+
221
+ if (host.kind === "github") {
222
+ // No token sent cross-host to the CDN; public repos need none.
223
+ const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${encodeURIComponent(ref)}/${encodedPath}`;
224
+ const raw = await fetchText(rawUrl, { "User-Agent": USER_AGENT }, doFetch, ms);
225
+ if (raw !== undefined) return raw;
226
+ // Private repo (raw 404s without auth) — fall through to the contents API.
227
+ }
228
+
229
+ // Contents API: base64 JSON. Forgejo always; GitHub private-repo fallback.
230
+ const url = `${host.api}/repos/${owner}/${repo}/contents/${encodedPath}?ref=${encodeURIComponent(ref)}`;
231
+ let res: Response;
232
+ try {
233
+ res = await doFetch(url, { headers, redirect: "manual", signal: timeoutSignal(ms) });
234
+ } catch {
235
+ return undefined;
236
+ }
237
+ if (!res.ok) return undefined;
238
+ let file: ContentsEntry | null;
239
+ try {
240
+ file = (await res.json()) as ContentsEntry | null;
241
+ } catch {
242
+ return undefined;
243
+ }
244
+ if (!file?.content) return undefined;
245
+ return Buffer.from(file.content, (file.encoding as BufferEncoding) ?? "base64").toString("utf-8");
246
+ }
247
+
248
+ /**
249
+ * Fetch every candidate file in a repo (all lexicons), so a URL audit covers the
250
+ * same ground as a local one. Returns raw {path, content}; classification (which
251
+ * needs the lexicon plugins) is the caller's job via `classifyFiles`.
252
+ */
253
+ export async function fetchRepoFiles(url: string, opts: FetchOptions = {}): Promise<Array<{ path: string; content: string }>> {
254
+ const { isCandidatePath } = await import("./discover");
111
255
  const { host, owner, repo } = parseRepoUrl(url);
112
256
  const doFetch = opts.fetchImpl ?? fetch;
113
257
  const cfg = {
@@ -117,58 +261,24 @@ export async function fetchCiFiles(url: string, opts: FetchOptions = {}): Promis
117
261
  timeoutMs: opts.timeoutMs ?? DEFAULTS.timeoutMs,
118
262
  };
119
263
  const headers = authHeaders(host.kind, opts.token);
264
+ const ref = opts.ref ?? (await defaultBranch(host, owner, repo, doFetch, headers, cfg.timeoutMs));
120
265
 
121
- async function getJson(apiUrl: string): Promise<{ status: number; body: unknown }> {
122
- let res: Response;
123
- try {
124
- res = await doFetch(apiUrl, { headers, redirect: "error", signal: timeoutSignal(cfg.timeoutMs) });
125
- } catch (err) {
126
- throw new FetchError(`Request failed: ${err instanceof Error ? err.message : String(err)}`);
127
- }
128
- if (res.status >= 300 && res.status < 400) {
129
- throw new FetchError(`Refusing to follow redirect from ${apiUrl}`);
130
- }
131
- if (res.status === 404) return { status: 404, body: null };
132
- if (!res.ok) throw new FetchError(`${apiUrl} returned ${res.status}`);
133
- return { status: res.status, body: await res.json() };
134
- }
135
-
136
- if (host.kind === "gitlab") {
137
- return fetchGitlab(host, owner, repo, opts.ref, cfg, doFetch, headers);
138
- }
139
-
140
- // GitHub / Forgejo share the Gitea-style contents API.
141
- const dirs = host.kind === "forgejo" ? [".forgejo/workflows", ".github/workflows"] : [".github/workflows"];
142
- const ref = opts.ref ? `?ref=${encodeURIComponent(opts.ref)}` : "";
143
-
144
- const candidates: ContentsEntry[] = [];
145
- const seen = new Set<string>();
146
- for (const dir of dirs) {
147
- const { body } = await getJson(`${host.api}/repos/${owner}/${repo}/contents/${dir}${ref}`);
148
- if (!Array.isArray(body)) continue;
149
- for (const entry of body as ContentsEntry[]) {
150
- if (entry.type === "file" && isYaml(entry.name) && !seen.has(entry.path)) {
151
- seen.add(entry.path);
152
- candidates.push(entry);
153
- }
154
- }
155
- }
266
+ const tree = await listTree(host, owner, repo, ref, doFetch, headers, cfg.timeoutMs);
267
+ const candidates = tree.filter((e) => isCandidatePath(e.path));
156
268
 
157
- const inputs: AuditInput[] = [];
269
+ const files: Array<{ path: string; content: string }> = [];
158
270
  let total = 0;
159
271
  for (const entry of candidates) {
160
- if (inputs.length >= cfg.maxFiles) break;
161
- if ((entry.size ?? 0) > cfg.maxBytesPerFile) continue; // skip oversize
162
- const { body } = await getJson(`${host.api}/repos/${owner}/${repo}/contents/${entry.path}${ref}`);
163
- const file = body as ContentsEntry | null;
164
- if (!file?.content) continue;
165
- const content = Buffer.from(file.content, (file.encoding as BufferEncoding) ?? "base64").toString("utf-8");
272
+ if (files.length >= cfg.maxFiles) break;
273
+ if ((entry.size ?? 0) > cfg.maxBytesPerFile) continue; // skip oversize when the host reports size
274
+ const content = await fetchFileContent(host, owner, repo, entry.path, ref, doFetch, headers, cfg.timeoutMs);
275
+ if (content === undefined) continue;
166
276
  if (content.length > cfg.maxBytesPerFile) continue;
167
277
  total += content.length;
168
- if (total > cfg.maxTotalBytes) throw new FetchError("Repository CI files exceed the total size cap.");
169
- inputs.push({ path: entry.path, content, lexicon: host.lexicon });
278
+ if (total > cfg.maxTotalBytes) throw new FetchError("Repository files exceed the total size cap.");
279
+ files.push({ path: entry.path, content });
170
280
  }
171
- return inputs;
281
+ return files;
172
282
  }
173
283
 
174
284
  const SHA40 = /^[0-9a-f]{40}$/;
@@ -193,9 +303,10 @@ export async function resolveActionSha(
193
303
  const res = await doFetch(url, {
194
304
  headers: {
195
305
  Accept: "application/vnd.github+json",
306
+ "User-Agent": USER_AGENT,
196
307
  ...(opts.token ? { Authorization: `Bearer ${opts.token}` } : {}),
197
308
  },
198
- redirect: "error",
309
+ redirect: "manual",
199
310
  signal: timeoutSignal(opts.timeoutMs ?? DEFAULTS.timeoutMs),
200
311
  });
201
312
  if (!res.ok) return undefined;
@@ -289,16 +400,16 @@ export async function resolveImageDigest(
289
400
  const manifestUrl = `https://${parsed.registry}/v2/${parsed.repository}/manifests/${encodeURIComponent(parsed.tag)}`;
290
401
 
291
402
  try {
292
- let res = await doFetch(manifestUrl, { headers: { Accept: accept }, redirect: "error", signal: timeoutSignal(ms) });
403
+ let res = await doFetch(manifestUrl, { headers: { Accept: accept, "User-Agent": USER_AGENT }, redirect: "manual", signal: timeoutSignal(ms) });
293
404
  if (res.status === 401) {
294
405
  const tokenUrl = tokenUrlFromChallenge(res.headers.get("www-authenticate") ?? "");
295
406
  if (!tokenUrl) return undefined;
296
- const tokRes = await doFetch(tokenUrl, { redirect: "error", signal: timeoutSignal(ms) });
407
+ const tokRes = await doFetch(tokenUrl, { headers: { "User-Agent": USER_AGENT }, redirect: "manual", signal: timeoutSignal(ms) });
297
408
  if (!tokRes.ok) return undefined;
298
409
  const tok = (await tokRes.json()) as { token?: string; access_token?: string };
299
410
  const bearer = tok.token ?? tok.access_token;
300
411
  if (!bearer) return undefined;
301
- res = await doFetch(manifestUrl, { headers: { Accept: accept, Authorization: `Bearer ${bearer}` }, redirect: "error", signal: timeoutSignal(ms) });
412
+ res = await doFetch(manifestUrl, { headers: { Accept: accept, "User-Agent": USER_AGENT, Authorization: `Bearer ${bearer}` }, redirect: "manual", signal: timeoutSignal(ms) });
302
413
  }
303
414
  if (!res.ok) return undefined;
304
415
  const digest = res.headers.get("docker-content-digest");
@@ -331,7 +442,7 @@ export async function resolveRepoCommit(
331
442
  ? `${host.api}/projects/${encodeURIComponent(`${owner}/${repo}`)}/repository/commits?per_page=1`
332
443
  : `${host.api}/repos/${owner}/${repo}/commits?per_page=1&limit=1`;
333
444
  try {
334
- const res = await doFetch(apiUrl, { headers, redirect: "error", signal: timeoutSignal(opts.timeoutMs ?? DEFAULTS.timeoutMs) });
445
+ const res = await doFetch(apiUrl, { headers, redirect: "manual", signal: timeoutSignal(opts.timeoutMs ?? DEFAULTS.timeoutMs) });
335
446
  if (!res.ok) return undefined;
336
447
  const body = (await res.json()) as Array<{ sha?: string; id?: string }>;
337
448
  if (!Array.isArray(body) || body.length === 0) return undefined;
@@ -342,30 +453,3 @@ export async function resolveRepoCommit(
342
453
  }
343
454
  }
344
455
 
345
- async function fetchGitlab(
346
- host: HostConfig,
347
- owner: string,
348
- repo: string,
349
- ref: string | undefined,
350
- cfg: { maxBytesPerFile: number; maxTotalBytes: number; timeoutMs: number },
351
- doFetch: typeof fetch,
352
- headers: Record<string, string>,
353
- ): Promise<AuditInput[]> {
354
- const projectId = encodeURIComponent(`${owner}/${repo}`);
355
- const refQ = `?ref=${encodeURIComponent(ref ?? "HEAD")}`;
356
- const apiUrl = `${host.api}/projects/${projectId}/repository/files/${encodeURIComponent(".gitlab-ci.yml")}/raw${refQ}`;
357
- let res: Response;
358
- try {
359
- res = await doFetch(apiUrl, { headers, redirect: "error", signal: timeoutSignal(cfg.timeoutMs) });
360
- } catch (err) {
361
- throw new FetchError(`Request failed: ${err instanceof Error ? err.message : String(err)}`);
362
- }
363
- if (res.status >= 300 && res.status < 400) throw new FetchError(`Refusing to follow redirect from ${apiUrl}`);
364
- if (res.status === 404) return [];
365
- if (!res.ok) throw new FetchError(`${apiUrl} returned ${res.status}`);
366
- const content = await res.text();
367
- if (content.length > cfg.maxBytesPerFile || content.length > cfg.maxTotalBytes) {
368
- throw new FetchError("Pipeline file exceeds the size cap.");
369
- }
370
- return [{ path: ".gitlab-ci.yml", content, lexicon: "gitlab" }];
371
- }
@@ -0,0 +1,80 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { fileURLToPath } from "url";
3
+ import { readdirSync, readFileSync, statSync } from "fs";
4
+ import { join, relative } from "path";
5
+ import { fetchRepoFiles, FetchError } from "./fetch";
6
+ import { discoverByDetection, classifyFiles } from "./discover";
7
+ import { loadAuditPlugins } from "./discover";
8
+
9
+ const MIXED = fileURLToPath(new URL("../cli/commands/__fixtures__/audit-mixed", import.meta.url));
10
+
11
+ /** Read a dir into relative-path → content (the "repo" a mock will serve). */
12
+ function readDir(root: string): Record<string, string> {
13
+ const out: Record<string, string> = {};
14
+ const walk = (dir: string) => {
15
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
16
+ const full = join(dir, e.name);
17
+ if (e.isDirectory()) walk(full);
18
+ else out[relative(root, full)] = readFileSync(full, "utf-8");
19
+ }
20
+ };
21
+ walk(root);
22
+ return out;
23
+ }
24
+
25
+ /** A GitHub-shaped mock: repo-info default_branch, recursive git/trees, base64 contents. */
26
+ function mockGithub(files: Record<string, string>): typeof fetch {
27
+ const b64 = (s: string) => Buffer.from(s, "utf-8").toString("base64");
28
+ return (async (url: string | URL | Request) => {
29
+ const u = String(url);
30
+ if (u.includes("/git/trees/")) {
31
+ const tree = Object.keys(files).map((path) => ({ path, type: "blob", size: files[path].length }));
32
+ return new Response(JSON.stringify({ tree }), { status: 200 });
33
+ }
34
+ const cm = u.match(/\/contents\/(.+?)\?/);
35
+ if (cm) {
36
+ const path = decodeURIComponent(cm[1]);
37
+ const content = files[path];
38
+ if (content === undefined) return new Response("not found", { status: 404 });
39
+ return new Response(JSON.stringify({ path, type: "file", content: b64(content), encoding: "base64" }), { status: 200 });
40
+ }
41
+ if (/\/repos\/[^/]+\/[^/]+(\?|$)/.test(u)) return new Response(JSON.stringify({ default_branch: "main" }), { status: 200 });
42
+ return new Response("not found", { status: 404 });
43
+ }) as unknown as typeof fetch;
44
+ }
45
+
46
+ const targets = (inputs: { lexicon: string; path: string }[]) => inputs.map((i) => `${i.lexicon}:${i.path}`).sort();
47
+
48
+ describe("remote audit covers all lexicons (not just CI)", () => {
49
+ test("a URL audit detects github + k8s + docker + aws in one repo", async () => {
50
+ const plugins = await loadAuditPlugins();
51
+ const files = await fetchRepoFiles("https://github.com/acme/widgets", { fetchImpl: mockGithub(readDir(MIXED)) });
52
+ const lexicons = new Set(classifyFiles(files, plugins).map((i) => i.lexicon));
53
+ expect([...lexicons].sort()).toEqual(["aws", "docker", "github", "k8s"]);
54
+ });
55
+
56
+ test("remote classification matches a local audit of the same repo (parity)", async () => {
57
+ const plugins = await loadAuditPlugins();
58
+ const local = discoverByDetection(MIXED, plugins);
59
+ const remote = classifyFiles(await fetchRepoFiles("https://github.com/acme/widgets", { fetchImpl: mockGithub(readDir(MIXED)) }), plugins);
60
+ expect(targets(remote)).toEqual(targets(local));
61
+ });
62
+
63
+ test("caps the number of files fetched", async () => {
64
+ const files = await fetchRepoFiles("https://github.com/acme/widgets", { fetchImpl: mockGithub(readDir(MIXED)), maxFiles: 1 });
65
+ expect(files.length).toBe(1);
66
+ });
67
+
68
+ test("rejects a non-allowlisted host before any fetch", async () => {
69
+ await expect(fetchRepoFiles("https://evil.example.com/o/r")).rejects.toThrow(/Host not allowed/);
70
+ });
71
+
72
+ test("refuses to follow a redirect on the tree request", async () => {
73
+ const impl = (async (url: string | URL | Request) => {
74
+ const u = String(url);
75
+ if (u.includes("/git/trees/")) return new Response(null, { status: 302 });
76
+ return new Response(JSON.stringify({ default_branch: "main" }), { status: 200 });
77
+ }) as unknown as typeof fetch;
78
+ await expect(fetchRepoFiles("https://github.com/acme/widgets", { fetchImpl: impl })).rejects.toThrow(/redirect/i);
79
+ });
80
+ });
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { AuditFinding } from "./core";
12
- import { ruleDocUrl } from "./catalog";
12
+ import { ruleDocUrl, type RuleMeta } from "./catalog";
13
13
  import { buildReportModel, buildReportJson, type AuditSnapshot, type BuildModelOptions, type EnrichedFinding, type GuidanceCluster, type QuickWinFile, type ReportCounts } from "./report-model";
14
14
 
15
15
  export type { AuditSnapshot } from "./report-model";
@@ -34,7 +34,7 @@ export interface RenderHtmlOptions extends BuildModelOptions {
34
34
  }
35
35
 
36
36
  const DEFAULT_ACCENT = "#7c3aed";
37
- const DEFAULT_TITLE = "CI Security Audit";
37
+ const DEFAULT_TITLE = "chant audit";
38
38
  const DEFAULT_FOOTER = 'Generated by <a href="https://intentius.io/chant/cli/audit/">chant audit</a>.';
39
39
 
40
40
  function esc(s: string): string {
@@ -46,6 +46,13 @@ function ruleLink(id: string): string {
46
46
  return `<a class="rule-id" href="${esc(ruleDocUrl(id))}">${esc(id)}</a>`;
47
47
  }
48
48
 
49
+ /** External backing for a merge-worthy rule (OSSF Scorecard, CIS, vendor docs). */
50
+ function authorityLinks(meta: RuleMeta): string {
51
+ if (!meta.authority?.length) return "";
52
+ const links = meta.authority.map((a) => `<a href="${esc(a.url)}">${esc(a.name)}</a>`).join(", ");
53
+ return ` <span class="muted">per ${links}</span>`;
54
+ }
55
+
49
56
  function renderDiff(diff: string): string {
50
57
  const rows = diff
51
58
  .split("\n")
@@ -60,11 +67,11 @@ function renderDiff(diff: string): string {
60
67
  function renderQuickWins(files: QuickWinFile[]): string {
61
68
  const cards = files
62
69
  .map((qw) => {
63
- const chips = qw.addressed.map((m) => `<span class="chip">${ruleLink(m.id)} ${esc(m.title)}</span>`).join(" ");
70
+ const chips = qw.addressed.map((m) => `<span class="chip">${ruleLink(m.id)} ${esc(m.title)}${authorityLinks(m)}</span>`).join(" ");
64
71
  const diff = qw.diff ? renderDiff(qw.diff) : "";
65
72
  const needs = qw.needsInput.length
66
73
  ? `<div class="needs"><strong>Needs a value to auto-patch:</strong><ul>${qw.needsInput
67
- .map((f) => `<li>${ruleLink(f.checkId)}${f.entity ? ` (<code>${esc(f.entity)}</code>)` : ""} — ${esc(f.meta.remediation)}</li>`)
74
+ .map((f) => `<li>${ruleLink(f.checkId)}${f.entity ? ` (<code>${esc(f.entity)}</code>)` : ""} — ${esc(f.meta.remediation)}${authorityLinks(f.meta)}</li>`)
68
75
  .join("")}</ul></div>`
69
76
  : "";
70
77
  return `<div class="card"><div class="card-head"><code class="file">${esc(qw.file)}</code> ${chips}</div>${diff}${needs}</div>`;
@@ -82,7 +89,7 @@ function renderNeedsReview(clusters: GuidanceCluster[], n: number): string {
82
89
  const locs = findings
83
90
  .map((f) => `<li><code>${esc(f.file)}</code>${f.entity ? ` (<code>${esc(f.entity)}</code>)` : ""} — ${esc(f.message)}</li>`)
84
91
  .join("");
85
- return `<div class="rule"><div><span class="sev ${findings[0].severity}"></span><strong>${ruleLink(meta.id)}</strong> — ${esc(meta.title)}. <span class="muted">${esc(meta.remediation)}</span></div><ul>${locs}</ul></div>`;
92
+ return `<div class="rule"><div><span class="sev ${findings[0].severity}"></span><strong>${ruleLink(meta.id)}</strong> — ${esc(meta.title)}. <span class="muted">${esc(meta.remediation)}</span>${authorityLinks(meta)}</div><ul>${locs}</ul></div>`;
86
93
  })
87
94
  .join("");
88
95
  return `<div class="cluster"><h3>${head}</h3>${rules}</div>`;
@@ -101,6 +108,7 @@ function renderReportOnly(findings: EnrichedFinding[], n: number): string {
101
108
  function renderHeader(counts: ReportCounts, snapshot: AuditSnapshot | undefined, notes: string[]): string {
102
109
  const sev = `<span class="sev error"></span>${counts.errors} error <span class="sev warning"></span>${counts.warnings} warning <span class="sev info"></span>${counts.infos} info`;
103
110
  const tiers = `<span class="chip">${counts.quickWin} quick-win</span> <span class="chip">${counts.needsReview} needs-review</span> <span class="chip">${counts.reportOnly} hygiene</span>`;
111
+ const cats = `<span class="chip">${counts.security} security</span> <span class="chip">${counts.correctness} correctness</span> <span class="chip">${counts.bestPractice} best-practice</span>`;
104
112
  const meta: string[] = [];
105
113
  if (snapshot) {
106
114
  if (snapshot.host) meta.push(esc(snapshot.host));
@@ -113,7 +121,7 @@ function renderHeader(counts: ReportCounts, snapshot: AuditSnapshot | undefined,
113
121
  meta.push(`chant ${esc(snapshot.toolVersion)}`);
114
122
  }
115
123
  const noteHtml = notes.map((n) => `<div class="note">${esc(n)}</div>`).join("");
116
- return `<div class="summary"><div class="meta">${meta.join(" · ")}</div><div class="counts">${sev}</div><div class="tiers">${tiers}</div>${noteHtml}</div>`;
124
+ return `<div class="summary"><div class="meta">${meta.join(" · ")}</div><div class="counts">${sev}</div><div class="tiers">${tiers}</div><div class="tiers">${cats}</div>${noteHtml}</div>`;
117
125
  }
118
126
 
119
127
  const STYLES = `
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { AuditFinding } from "./core";
9
- import { RULE_CATALOG, ruleDocUrl, type Authority, type FixKind, type RuleMeta, type Tier } from "./catalog";
9
+ import { RULE_CATALOG, ruleDocUrl, type Authority, type Category, type FixKind, type RuleMeta, type Tier } from "./catalog";
10
10
  import { proveFix, unifiedDiff, type ProveOptions } from "./proof";
11
11
  import type { Severity } from "../lint/rule";
12
12
 
@@ -16,6 +16,8 @@ import type { Severity } from "../lint/rule";
16
16
  * major. Consumers should check the major and ignore unknown fields. See
17
17
  * `docs/cli/audit`.
18
18
  */
19
+ // Additive changes (new fields like `category`, #415) keep the same version per
20
+ // the stability contract; only renamed/removed fields bump it.
19
21
  export const REPORT_SCHEMA_VERSION = "1.0";
20
22
 
21
23
  export const SEVERITY_WEIGHT: Record<Severity, number> = { error: 0, warning: 1, info: 2 };
@@ -55,6 +57,10 @@ export interface ReportCounts {
55
57
  errors: number;
56
58
  warnings: number;
57
59
  infos: number;
60
+ /** Findings by category (#415) — what kind of issue, not how confident the fix. */
61
+ security: number;
62
+ correctness: number;
63
+ bestPractice: number;
58
64
  }
59
65
 
60
66
  export interface ReportModel {
@@ -88,6 +94,8 @@ export interface SerializedFinding {
88
94
  lexicon: string;
89
95
  tier: Tier;
90
96
  fixKind: FixKind;
97
+ /** What kind of finding this is (security / correctness / best-practice). */
98
+ category: Category;
91
99
  title: string;
92
100
  remediation: string;
93
101
  authority: Authority[];
@@ -237,6 +245,9 @@ export function buildReportModel(findings: AuditFinding[], opts: BuildModelOptio
237
245
  errors: shown.filter((f) => f.severity === "error").length,
238
246
  warnings: shown.filter((f) => f.severity === "warning").length,
239
247
  infos: shown.filter((f) => f.severity === "info").length,
248
+ security: shown.filter((f) => f.meta.category === "security").length,
249
+ correctness: shown.filter((f) => f.meta.category === "correctness").length,
250
+ bestPractice: shown.filter((f) => f.meta.category === "best-practice").length,
240
251
  };
241
252
 
242
253
  return {
@@ -266,6 +277,7 @@ export function buildReportJson(findings: AuditFinding[], opts: { snapshot?: Aud
266
277
  lexicon: f.lexicon,
267
278
  tier: f.meta.tier,
268
279
  fixKind: f.meta.fixKind,
280
+ category: f.meta.category,
269
281
  title: f.meta.title,
270
282
  remediation: f.meta.remediation,
271
283
  authority: f.meta.authority ?? [],
@@ -31,7 +31,9 @@ describe("renderMarkdown — reworked structure", () => {
31
31
  test("quick wins show a real combined diff when file content is provided", () => {
32
32
  const out = renderMarkdown(FINDINGS, { files: [{ path: ".github/workflows/ci.yml", content: CI }] });
33
33
  expect(out).toContain("## Quick wins (deterministic)");
34
- expect(out).toContain("Addresses [GHA033](https://intentius.io/chant/lint-rules/audit-rules/#gha033) (Blanket write-all permissions):");
34
+ expect(out).toContain("Addresses [GHA033](https://intentius.io/chant/lint-rules/audit-rules/#gha033) (Blanket write-all permissions)");
35
+ // merge-worthy findings cite their external authority (#351 links surfaced in the layout)
36
+ expect(out).toContain("per [OSSF Scorecard — Token-Permissions](https://github.com/ossf/scorecard");
35
37
  expect(out).toContain("```diff");
36
38
  expect(out).toContain("-permissions: write-all");
37
39
  expect(out).toContain("+permissions:");
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import type { AuditFinding } from "./core";
14
- import { ruleDocUrl } from "./catalog";
14
+ import { ruleDocUrl, type RuleMeta } from "./catalog";
15
15
  import type { ProveOptions } from "./proof";
16
16
  import { buildReportModel, type EnrichedFinding, type GuidanceCluster, type QuickWinFile } from "./report-model";
17
17
 
@@ -20,6 +20,12 @@ function ruleLink(id: string): string {
20
20
  return `[${id}](${ruleDocUrl(id)})`;
21
21
  }
22
22
 
23
+ /** External backing for a merge-worthy rule (OSSF Scorecard, CIS, vendor docs). */
24
+ function authorityLinks(meta: RuleMeta): string {
25
+ if (!meta.authority?.length) return "";
26
+ return ` — per ${meta.authority.map((a) => `[${a.name}](${a.url})`).join(", ")}`;
27
+ }
28
+
23
29
  export interface RenderOptions {
24
30
  /** Repo URL or path shown in the header. */
25
31
  target?: string;
@@ -42,14 +48,14 @@ function renderQuickWins(files: QuickWinFile[]): string[] {
42
48
  for (const qw of files) {
43
49
  lines.push(`### \`${qw.file}\``);
44
50
  if (qw.diff) {
45
- const labels = qw.addressed.map((m) => `${ruleLink(m.id)} (${m.title})`).join(", ");
51
+ const labels = qw.addressed.map((m) => `${ruleLink(m.id)} (${m.title})${authorityLinks(m)}`).join(", ");
46
52
  lines.push("", `Addresses ${labels}:`, "", "```diff", qw.diff, "```");
47
53
  }
48
54
  if (qw.needsInput.length > 0) {
49
55
  lines.push("", "Needs a value before it can be auto-patched:");
50
56
  for (const f of qw.needsInput) {
51
57
  const where = f.entity ? ` (\`${f.entity}\`)` : "";
52
- lines.push(`- **${ruleLink(f.checkId)}**${where} — ${f.meta.remediation}`);
58
+ lines.push(`- **${ruleLink(f.checkId)}**${where} — ${f.meta.remediation}${authorityLinks(f.meta)}`);
53
59
  }
54
60
  }
55
61
  lines.push("");
@@ -62,7 +68,7 @@ function renderNeedsReview(clusters: GuidanceCluster[]): string[] {
62
68
  for (const cluster of clusters) {
63
69
  lines.push(`### ${cluster.url ? `[${cluster.name}](${cluster.url})` : cluster.name}`, "");
64
70
  for (const { meta, findings } of cluster.rules) {
65
- lines.push(`- **${ruleLink(meta.id)}** — ${meta.title} (${findings[0].severity}). ${meta.remediation}`);
71
+ lines.push(`- **${ruleLink(meta.id)}** — ${meta.title} (${findings[0].severity}). ${meta.remediation}${authorityLinks(meta)}`);
66
72
  for (const f of findings) {
67
73
  const where = f.entity ? `\`${f.file}\` (\`${f.entity}\`)` : `\`${f.file}\``;
68
74
  lines.push(` - ${where} — ${escapeCell(f.message)}`);
@@ -87,7 +93,7 @@ export function renderMarkdown(findings: AuditFinding[], opts: RenderOptions = {
87
93
  const model = buildReportModel(findings, opts);
88
94
  const { counts } = model;
89
95
 
90
- const lines: string[] = ["# CI security audit"];
96
+ const lines: string[] = ["# chant audit"];
91
97
  if (opts.target) lines.push("", `Target: ${opts.target}`);
92
98
  lines.push("");
93
99
  for (const note of opts.notes ?? []) lines.push(`> Note: ${note}`, "");
@@ -102,6 +108,8 @@ export function renderMarkdown(findings: AuditFinding[], opts: RenderOptions = {
102
108
  `${counts.quickWin} quick-win, ${counts.needsReview} needs-review, ${counts.reportOnly} report-only ` +
103
109
  `(${counts.errors} error, ${counts.warnings} warning, ${counts.infos} info).`,
104
110
  "",
111
+ `By category: ${counts.security} security, ${counts.correctness} correctness, ${counts.bestPractice} best-practice.`,
112
+ "",
105
113
  );
106
114
 
107
115
  if (model.quickWins.length > 0) lines.push(...renderQuickWins(model.quickWins));
@@ -0,0 +1,5 @@
1
+ on: push
2
+ permissions: write-all
3
+ jobs:
4
+ build:
5
+ runs-on: ubuntu-latest
@@ -0,0 +1,2 @@
1
+ FROM ubuntu
2
+ RUN apt-get update
@@ -0,0 +1 @@
1
+ {"AWSTemplateFormatVersion":"2010-09-09","Resources":{"B":{"Type":"AWS::S3::Bucket"}}}
@@ -0,0 +1,12 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: x
5
+ spec:
6
+ template:
7
+ spec:
8
+ containers:
9
+ - name: c
10
+ image: nginx:latest
11
+ securityContext:
12
+ privileged: true