pr-checkmate 3.0.4 → 3.1.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 (378) hide show
  1. package/dist/{cli.js → cjs/cli.js} +30 -30
  2. package/dist/{config → cjs/config}/index.d.ts +2 -2
  3. package/dist/{config → cjs/config}/index.js +8 -7
  4. package/dist/{config → cjs/config}/loader.d.ts +1 -1
  5. package/dist/{config → cjs/config}/loader.js +8 -8
  6. package/dist/{core → cjs/core}/checks/dependencies/banned-imports.d.ts +1 -1
  7. package/dist/{core → cjs/core}/checks/dependencies/banned-imports.js +10 -10
  8. package/dist/{core → cjs/core}/checks/dependencies/circular-deps.d.ts +1 -1
  9. package/dist/{core → cjs/core}/checks/dependencies/circular-deps.js +27 -27
  10. package/dist/{core → cjs/core}/checks/dependencies/dependency-check.d.ts +1 -1
  11. package/dist/{core → cjs/core}/checks/dependencies/dependency-check.js +17 -17
  12. package/dist/{core → cjs/core}/checks/dependencies/grype-scan.d.ts +1 -1
  13. package/dist/{core → cjs/core}/checks/dependencies/grype-scan.js +18 -18
  14. package/dist/{core → cjs/core}/checks/dependencies/license-check.d.ts +1 -1
  15. package/dist/{core → cjs/core}/checks/dependencies/license-check.js +12 -12
  16. package/dist/{core → cjs/core}/checks/dependencies/npm-audit.d.ts +1 -1
  17. package/dist/{core → cjs/core}/checks/dependencies/npm-audit.js +14 -14
  18. package/dist/{core → cjs/core}/checks/dependencies/outdated-deps.d.ts +1 -1
  19. package/dist/{core → cjs/core}/checks/dependencies/outdated-deps.js +14 -14
  20. package/dist/{core → cjs/core}/checks/dependencies/vuln-scan.d.ts +1 -1
  21. package/dist/{core → cjs/core}/checks/dependencies/vuln-scan.js +14 -14
  22. package/dist/{core → cjs/core}/checks/findings.d.ts +1 -1
  23. package/dist/{core → cjs/core}/checks/findings.js +16 -16
  24. package/dist/{core → cjs/core}/checks/git/custom-rules.d.ts +1 -1
  25. package/dist/{core → cjs/core}/checks/git/custom-rules.js +19 -19
  26. package/dist/{core → cjs/core}/checks/git/diff-smell.d.ts +2 -2
  27. package/dist/{core → cjs/core}/checks/git/diff-smell.js +13 -13
  28. package/dist/{core → cjs/core}/checks/git/large-files.d.ts +1 -1
  29. package/dist/{core → cjs/core}/checks/git/large-files.js +15 -15
  30. package/dist/{core → cjs/core}/checks/git/leftover-debug.d.ts +1 -1
  31. package/dist/{core → cjs/core}/checks/git/leftover-debug.js +13 -13
  32. package/dist/{core → cjs/core}/checks/git/lockfile-drift.d.ts +1 -1
  33. package/dist/{core → cjs/core}/checks/git/lockfile-drift.js +12 -12
  34. package/dist/{core → cjs/core}/checks/git/merge-conflict.d.ts +1 -1
  35. package/dist/{core → cjs/core}/checks/git/merge-conflict.js +15 -15
  36. package/dist/{core → cjs/core}/checks/git/missing-tests.d.ts +1 -1
  37. package/dist/{core → cjs/core}/checks/git/missing-tests.js +15 -15
  38. package/dist/{core → cjs/core}/checks/git/todo-fixme.d.ts +1 -1
  39. package/dist/{core → cjs/core}/checks/git/todo-fixme.js +11 -11
  40. package/dist/{core → cjs/core}/checks/languages/cpp-format.d.ts +1 -1
  41. package/dist/{core → cjs/core}/checks/languages/cpp-format.js +18 -18
  42. package/dist/{core → cjs/core}/checks/languages/external-checks.d.ts +9 -9
  43. package/dist/{core → cjs/core}/checks/languages/external-checks.js +10 -10
  44. package/dist/{core → cjs/core}/checks/languages/external-runner.d.ts +2 -2
  45. package/dist/{core → cjs/core}/checks/languages/external-runner.js +21 -21
  46. package/dist/{core → cjs/core}/checks/languages/go-format.d.ts +1 -1
  47. package/dist/cjs/core/checks/languages/go-format.js +202 -0
  48. package/dist/cjs/core/checks/languages/lint.d.ts +2 -0
  49. package/dist/{core → cjs/core}/checks/languages/lint.js +21 -21
  50. package/dist/{core → cjs/core}/checks/languages/prettier.d.ts +1 -1
  51. package/dist/{core → cjs/core}/checks/languages/prettier.js +18 -18
  52. package/dist/{core → cjs/core}/checks/languages/python-format.d.ts +1 -1
  53. package/dist/{core → cjs/core}/checks/languages/python-format.js +14 -14
  54. package/dist/{core → cjs/core}/checks/languages/python-lint.d.ts +1 -1
  55. package/dist/{core → cjs/core}/checks/languages/python-lint.js +13 -13
  56. package/dist/{core → cjs/core}/checks/languages/python-typecheck.d.ts +1 -1
  57. package/dist/{core → cjs/core}/checks/languages/python-typecheck.js +29 -29
  58. package/dist/{core → cjs/core}/checks/languages/typecheck.d.ts +1 -1
  59. package/dist/{core → cjs/core}/checks/languages/typecheck.js +13 -13
  60. package/dist/{core → cjs/core}/checks/pr/commitlint.d.ts +1 -1
  61. package/dist/{core → cjs/core}/checks/pr/commitlint.js +12 -12
  62. package/dist/{core → cjs/core}/checks/pr/pr-body.d.ts +1 -1
  63. package/dist/{core → cjs/core}/checks/pr/pr-body.js +8 -8
  64. package/dist/{core → cjs/core}/checks/pr/pr-size-check.d.ts +1 -1
  65. package/dist/{core → cjs/core}/checks/pr/pr-size-check.js +10 -10
  66. package/dist/{core → cjs/core}/checks/pr/pr-title-lint.d.ts +1 -1
  67. package/dist/{core → cjs/core}/checks/pr/pr-title-lint.js +8 -8
  68. package/dist/{core → cjs/core}/checks/quality/case-collision.d.ts +1 -1
  69. package/dist/{core → cjs/core}/checks/quality/case-collision.js +7 -7
  70. package/dist/{core → cjs/core}/checks/quality/config-validation.d.ts +1 -1
  71. package/dist/{core → cjs/core}/checks/quality/config-validation.js +9 -9
  72. package/dist/{core → cjs/core}/checks/quality/coverage.d.ts +1 -1
  73. package/dist/{core → cjs/core}/checks/quality/coverage.js +4 -4
  74. package/dist/{core → cjs/core}/checks/quality/dead-code.d.ts +1 -1
  75. package/dist/{core → cjs/core}/checks/quality/dead-code.js +23 -23
  76. package/dist/{core → cjs/core}/checks/quality/duplicate-check.d.ts +1 -1
  77. package/dist/{core → cjs/core}/checks/quality/duplicate-check.js +22 -22
  78. package/dist/{core → cjs/core}/checks/quality/license-header.d.ts +1 -1
  79. package/dist/{core → cjs/core}/checks/quality/license-header.js +16 -16
  80. package/dist/{core → cjs/core}/checks/quality/markdown-lint.d.ts +1 -1
  81. package/dist/{core → cjs/core}/checks/quality/markdown-lint.js +17 -17
  82. package/dist/{core → cjs/core}/checks/quality/package-size.d.ts +1 -1
  83. package/dist/{core → cjs/core}/checks/quality/package-size.js +12 -12
  84. package/dist/{core → cjs/core}/checks/quality/spellcheck.d.ts +2 -2
  85. package/dist/{core → cjs/core}/checks/quality/spellcheck.js +18 -18
  86. package/dist/{core → cjs/core}/checks/quality/symlinks.d.ts +1 -1
  87. package/dist/{core → cjs/core}/checks/quality/symlinks.js +8 -8
  88. package/dist/{core → cjs/core}/checks/quality/yaml-lint.d.ts +1 -1
  89. package/dist/{core → cjs/core}/checks/quality/yaml-lint.js +12 -12
  90. package/dist/{core → cjs/core}/checks/security/diff-security.d.ts +1 -1
  91. package/dist/{core → cjs/core}/checks/security/diff-security.js +15 -15
  92. package/dist/{core → cjs/core}/checks/security/dockerfile-security.d.ts +1 -1
  93. package/dist/{core → cjs/core}/checks/security/dockerfile-security.js +16 -16
  94. package/dist/{core → cjs/core}/checks/security/migration-safety.d.ts +1 -1
  95. package/dist/{core → cjs/core}/checks/security/migration-safety.js +17 -17
  96. package/dist/{core → cjs/core}/checks/security/security.d.ts +1 -1
  97. package/dist/{core → cjs/core}/checks/security/security.js +21 -21
  98. package/dist/{core → cjs/core}/checks/security/sensitive-file-guard.d.ts +1 -1
  99. package/dist/{core → cjs/core}/checks/security/sensitive-file-guard.js +14 -14
  100. package/dist/{core → cjs/core}/checks/security/workflow-security.d.ts +2 -2
  101. package/dist/{core → cjs/core}/checks/security/workflow-security.js +14 -14
  102. package/dist/{core → cjs/core}/checks/tool-install.d.ts +1 -1
  103. package/dist/{core → cjs/core}/checks/tool-install.js +2 -2
  104. package/dist/{core → cjs/core}/context.d.ts +1 -1
  105. package/dist/{core → cjs/core}/context.js +16 -16
  106. package/dist/cjs/core/index.d.ts +4 -0
  107. package/dist/{core → cjs/core}/index.js +7 -7
  108. package/dist/{core → cjs/core}/install/cache.js +2 -2
  109. package/dist/{core → cjs/core}/install/doctor.d.ts +4 -4
  110. package/dist/{core → cjs/core}/install/doctor.js +34 -34
  111. package/dist/{core → cjs/core}/install/ensure.d.ts +2 -2
  112. package/dist/{core → cjs/core}/install/ensure.js +15 -14
  113. package/dist/{core → cjs/core}/install/lock.js +4 -4
  114. package/dist/{core → cjs/core}/install/prepare.d.ts +4 -4
  115. package/dist/{core → cjs/core}/install/prepare.js +13 -13
  116. package/dist/{core → cjs/core}/install/profiles.d.ts +2 -2
  117. package/dist/{core → cjs/core}/install/reuse.d.ts +1 -1
  118. package/dist/{core → cjs/core}/install/reuse.js +4 -4
  119. package/dist/{core → cjs/core}/registry.d.ts +1 -1
  120. package/dist/cjs/core/registry.js +112 -0
  121. package/dist/{core → cjs/core}/reporters.d.ts +1 -1
  122. package/dist/{core → cjs/core}/reporters.js +28 -28
  123. package/dist/{core → cjs/core}/run-checks.d.ts +2 -2
  124. package/dist/{core → cjs/core}/run-checks.js +31 -31
  125. package/dist/{core → cjs/core}/targets.d.ts +1 -1
  126. package/dist/{core → cjs/core}/targets.js +20 -20
  127. package/dist/{core → cjs/core}/types.d.ts +3 -3
  128. package/dist/cjs/index.d.ts +12 -0
  129. package/dist/{index.js → cjs/index.js} +18 -18
  130. package/dist/{init.js → cjs/init.js} +68 -56
  131. package/dist/{interfaces → cjs/interfaces}/index.d.ts +3 -3
  132. package/dist/cjs/package.json +1 -0
  133. package/dist/{scripts → cjs/scripts}/copy-configs.js +22 -7
  134. package/dist/{scripts → cjs/scripts}/generate-install-manifests.js +2 -2
  135. package/dist/{scripts → cjs/scripts}/publish-github.js +4 -4
  136. package/dist/{utils → cjs/utils}/detect-language.js +12 -12
  137. package/dist/cjs/utils/execa.js +46 -0
  138. package/dist/{utils → cjs/utils}/git.js +2 -2
  139. package/dist/{utils → cjs/utils}/http.js +2 -2
  140. package/dist/cjs/utils/index.d.ts +15 -0
  141. package/dist/{utils → cjs/utils}/index.js +13 -13
  142. package/dist/{utils → cjs/utils}/own-deps.d.ts +2 -0
  143. package/dist/{utils → cjs/utils}/own-deps.js +1 -0
  144. package/dist/{utils → cjs/utils}/package-version.js +2 -1
  145. package/dist/{utils → cjs/utils}/resolve-bin.js +2 -1
  146. package/dist/{utils → cjs/utils}/update-notifier.js +4 -4
  147. package/dist/esm/cli.d.ts +2 -0
  148. package/dist/esm/cli.js +137 -0
  149. package/dist/esm/config/constants.d.ts +153 -0
  150. package/dist/esm/config/constants.js +360 -0
  151. package/dist/esm/config/index.d.ts +40 -0
  152. package/dist/esm/config/index.js +60 -0
  153. package/dist/esm/config/loader.d.ts +6 -0
  154. package/dist/esm/config/loader.js +191 -0
  155. package/dist/esm/config/pr-checkmate-workflow-docker.yml +79 -0
  156. package/dist/esm/config/pr-checkmate-workflow.yml +92 -0
  157. package/dist/esm/core/checks/dependencies/banned-imports.d.ts +2 -0
  158. package/dist/esm/core/checks/dependencies/banned-imports.js +70 -0
  159. package/dist/esm/core/checks/dependencies/circular-deps.d.ts +2 -0
  160. package/dist/esm/core/checks/dependencies/circular-deps.js +155 -0
  161. package/dist/esm/core/checks/dependencies/dependency-check.d.ts +43 -0
  162. package/dist/esm/core/checks/dependencies/dependency-check.js +174 -0
  163. package/dist/esm/core/checks/dependencies/grype-scan.d.ts +22 -0
  164. package/dist/esm/core/checks/dependencies/grype-scan.js +112 -0
  165. package/dist/esm/core/checks/dependencies/license-check.d.ts +2 -0
  166. package/dist/esm/core/checks/dependencies/license-check.js +61 -0
  167. package/dist/esm/core/checks/dependencies/npm-audit.d.ts +6 -0
  168. package/dist/esm/core/checks/dependencies/npm-audit.js +80 -0
  169. package/dist/esm/core/checks/dependencies/outdated-deps.d.ts +2 -0
  170. package/dist/esm/core/checks/dependencies/outdated-deps.js +66 -0
  171. package/dist/esm/core/checks/dependencies/vuln-scan.d.ts +2 -0
  172. package/dist/esm/core/checks/dependencies/vuln-scan.js +54 -0
  173. package/dist/esm/core/checks/findings.d.ts +299 -0
  174. package/dist/esm/core/checks/findings.js +701 -0
  175. package/dist/esm/core/checks/git/custom-rules.d.ts +2 -0
  176. package/dist/esm/core/checks/git/custom-rules.js +106 -0
  177. package/dist/esm/core/checks/git/diff-smell.d.ts +19 -0
  178. package/dist/esm/core/checks/git/diff-smell.js +69 -0
  179. package/dist/esm/core/checks/git/large-files.d.ts +2 -0
  180. package/dist/esm/core/checks/git/large-files.js +112 -0
  181. package/dist/esm/core/checks/git/leftover-debug.d.ts +2 -0
  182. package/dist/esm/core/checks/git/leftover-debug.js +89 -0
  183. package/dist/esm/core/checks/git/lockfile-drift.d.ts +2 -0
  184. package/dist/esm/core/checks/git/lockfile-drift.js +114 -0
  185. package/dist/esm/core/checks/git/merge-conflict.d.ts +2 -0
  186. package/dist/esm/core/checks/git/merge-conflict.js +117 -0
  187. package/dist/esm/core/checks/git/missing-tests.d.ts +2 -0
  188. package/dist/esm/core/checks/git/missing-tests.js +105 -0
  189. package/dist/esm/core/checks/git/todo-fixme.d.ts +7 -0
  190. package/dist/esm/core/checks/git/todo-fixme.js +80 -0
  191. package/dist/esm/core/checks/languages/cpp-format.d.ts +7 -0
  192. package/dist/esm/core/checks/languages/cpp-format.js +91 -0
  193. package/dist/esm/core/checks/languages/external-checks.d.ts +77 -0
  194. package/dist/esm/core/checks/languages/external-checks.js +197 -0
  195. package/dist/esm/core/checks/languages/external-runner.d.ts +119 -0
  196. package/dist/esm/core/checks/languages/external-runner.js +138 -0
  197. package/dist/esm/core/checks/languages/go-format.d.ts +8 -0
  198. package/dist/{core → esm/core}/checks/languages/go-format.js +29 -35
  199. package/dist/esm/core/checks/languages/lint.d.ts +2 -0
  200. package/dist/esm/core/checks/languages/lint.js +92 -0
  201. package/dist/esm/core/checks/languages/prettier.d.ts +2 -0
  202. package/dist/esm/core/checks/languages/prettier.js +85 -0
  203. package/dist/esm/core/checks/languages/python-format.d.ts +7 -0
  204. package/dist/esm/core/checks/languages/python-format.js +61 -0
  205. package/dist/esm/core/checks/languages/python-lint.d.ts +9 -0
  206. package/dist/esm/core/checks/languages/python-lint.js +55 -0
  207. package/dist/esm/core/checks/languages/python-typecheck.d.ts +14 -0
  208. package/dist/esm/core/checks/languages/python-typecheck.js +184 -0
  209. package/dist/esm/core/checks/languages/ruff-shared.d.ts +10 -0
  210. package/dist/esm/core/checks/languages/ruff-shared.js +35 -0
  211. package/dist/esm/core/checks/languages/typecheck.d.ts +2 -0
  212. package/dist/esm/core/checks/languages/typecheck.js +61 -0
  213. package/dist/esm/core/checks/pr/commitlint.d.ts +2 -0
  214. package/dist/esm/core/checks/pr/commitlint.js +102 -0
  215. package/dist/esm/core/checks/pr/pr-body.d.ts +2 -0
  216. package/dist/esm/core/checks/pr/pr-body.js +43 -0
  217. package/dist/esm/core/checks/pr/pr-size-check.d.ts +2 -0
  218. package/dist/esm/core/checks/pr/pr-size-check.js +42 -0
  219. package/dist/esm/core/checks/pr/pr-title-lint.d.ts +2 -0
  220. package/dist/esm/core/checks/pr/pr-title-lint.js +31 -0
  221. package/dist/esm/core/checks/quality/case-collision.d.ts +8 -0
  222. package/dist/esm/core/checks/quality/case-collision.js +39 -0
  223. package/dist/esm/core/checks/quality/config-validation.d.ts +11 -0
  224. package/dist/esm/core/checks/quality/config-validation.js +188 -0
  225. package/dist/esm/core/checks/quality/coverage.d.ts +2 -0
  226. package/dist/esm/core/checks/quality/coverage.js +62 -0
  227. package/dist/esm/core/checks/quality/dead-code.d.ts +2 -0
  228. package/dist/esm/core/checks/quality/dead-code.js +124 -0
  229. package/dist/esm/core/checks/quality/duplicate-check.d.ts +2 -0
  230. package/dist/esm/core/checks/quality/duplicate-check.js +141 -0
  231. package/dist/esm/core/checks/quality/license-header.d.ts +7 -0
  232. package/dist/esm/core/checks/quality/license-header.js +81 -0
  233. package/dist/esm/core/checks/quality/markdown-lint.d.ts +2 -0
  234. package/dist/esm/core/checks/quality/markdown-lint.js +104 -0
  235. package/dist/esm/core/checks/quality/package-size.d.ts +36 -0
  236. package/dist/esm/core/checks/quality/package-size.js +196 -0
  237. package/dist/esm/core/checks/quality/spellcheck.d.ts +77 -0
  238. package/dist/esm/core/checks/quality/spellcheck.js +296 -0
  239. package/dist/esm/core/checks/quality/symlinks.d.ts +16 -0
  240. package/dist/esm/core/checks/quality/symlinks.js +57 -0
  241. package/dist/esm/core/checks/quality/yaml-lint.d.ts +20 -0
  242. package/dist/esm/core/checks/quality/yaml-lint.js +116 -0
  243. package/dist/esm/core/checks/security/diff-security.d.ts +2 -0
  244. package/dist/esm/core/checks/security/diff-security.js +354 -0
  245. package/dist/esm/core/checks/security/dockerfile-security.d.ts +2 -0
  246. package/dist/esm/core/checks/security/dockerfile-security.js +98 -0
  247. package/dist/esm/core/checks/security/migration-safety.d.ts +2 -0
  248. package/dist/esm/core/checks/security/migration-safety.js +137 -0
  249. package/dist/esm/core/checks/security/security.d.ts +63 -0
  250. package/dist/esm/core/checks/security/security.js +201 -0
  251. package/dist/esm/core/checks/security/sensitive-file-guard.d.ts +2 -0
  252. package/dist/esm/core/checks/security/sensitive-file-guard.js +160 -0
  253. package/dist/esm/core/checks/security/workflow-security.d.ts +12 -0
  254. package/dist/esm/core/checks/security/workflow-security.js +100 -0
  255. package/dist/esm/core/checks/tool-install.d.ts +102 -0
  256. package/dist/esm/core/checks/tool-install.js +66 -0
  257. package/dist/esm/core/commands.d.ts +27 -0
  258. package/dist/esm/core/commands.js +89 -0
  259. package/dist/esm/core/context.d.ts +62 -0
  260. package/dist/esm/core/context.js +241 -0
  261. package/dist/esm/core/index.d.ts +4 -0
  262. package/dist/esm/core/index.js +4 -0
  263. package/dist/esm/core/install/cache.d.ts +58 -0
  264. package/dist/esm/core/install/cache.js +142 -0
  265. package/dist/esm/core/install/doctor.d.ts +35 -0
  266. package/dist/esm/core/install/doctor.js +164 -0
  267. package/dist/esm/core/install/ensure.d.ts +77 -0
  268. package/dist/esm/core/install/ensure.js +269 -0
  269. package/dist/esm/core/install/lock.d.ts +34 -0
  270. package/dist/esm/core/install/lock.js +92 -0
  271. package/dist/esm/core/install/merge.d.ts +47 -0
  272. package/dist/esm/core/install/merge.js +85 -0
  273. package/dist/esm/core/install/prepare.d.ts +76 -0
  274. package/dist/esm/core/install/prepare.js +142 -0
  275. package/dist/esm/core/install/profiles.d.ts +78 -0
  276. package/dist/esm/core/install/profiles.js +192 -0
  277. package/dist/esm/core/install/reuse.d.ts +41 -0
  278. package/dist/esm/core/install/reuse.js +100 -0
  279. package/dist/esm/core/registry.d.ts +6 -0
  280. package/dist/esm/core/registry.js +109 -0
  281. package/dist/esm/core/reporters.d.ts +18 -0
  282. package/dist/esm/core/reporters.js +266 -0
  283. package/dist/esm/core/run-checks.d.ts +33 -0
  284. package/dist/esm/core/run-checks.js +293 -0
  285. package/dist/esm/core/targets.d.ts +140 -0
  286. package/dist/esm/core/targets.js +213 -0
  287. package/dist/esm/core/types.d.ts +195 -0
  288. package/dist/esm/core/types.js +38 -0
  289. package/dist/esm/index.d.ts +12 -0
  290. package/dist/esm/index.js +15 -0
  291. package/dist/esm/init.d.ts +38 -0
  292. package/dist/esm/init.js +421 -0
  293. package/dist/esm/interfaces/index.d.ts +432 -0
  294. package/dist/esm/interfaces/index.js +2 -0
  295. package/dist/esm/package.json +1 -0
  296. package/dist/esm/scripts/copy-configs.d.ts +1 -0
  297. package/dist/esm/scripts/copy-configs.js +64 -0
  298. package/dist/esm/scripts/generate-install-manifests.d.ts +39 -0
  299. package/dist/esm/scripts/generate-install-manifests.js +115 -0
  300. package/dist/esm/scripts/publish-github.d.ts +1 -0
  301. package/dist/esm/scripts/publish-github.js +45 -0
  302. package/dist/esm/utils/ci.d.ts +11 -0
  303. package/dist/esm/utils/ci.js +40 -0
  304. package/dist/esm/utils/detect-language.d.ts +48 -0
  305. package/dist/esm/utils/detect-language.js +234 -0
  306. package/dist/esm/utils/direct-deps.d.ts +21 -0
  307. package/dist/esm/utils/direct-deps.js +220 -0
  308. package/dist/esm/utils/execa.d.ts +22 -0
  309. package/dist/{utils → esm/utils}/execa.js +1 -4
  310. package/dist/esm/utils/git.d.ts +14 -0
  311. package/dist/esm/utils/git.js +21 -0
  312. package/dist/esm/utils/glob.d.ts +24 -0
  313. package/dist/esm/utils/glob.js +40 -0
  314. package/dist/esm/utils/http.d.ts +59 -0
  315. package/dist/esm/utils/http.js +115 -0
  316. package/dist/esm/utils/index.d.ts +15 -0
  317. package/dist/esm/utils/index.js +26 -0
  318. package/dist/esm/utils/logger.d.ts +65 -0
  319. package/dist/esm/utils/logger.js +157 -0
  320. package/dist/esm/utils/own-deps.d.ts +16 -0
  321. package/dist/esm/utils/own-deps.js +107 -0
  322. package/dist/esm/utils/package-version.d.ts +4 -0
  323. package/dist/esm/utils/package-version.js +38 -0
  324. package/dist/esm/utils/pool.d.ts +49 -0
  325. package/dist/esm/utils/pool.js +113 -0
  326. package/dist/esm/utils/resolve-bin.d.ts +67 -0
  327. package/dist/esm/utils/resolve-bin.js +156 -0
  328. package/dist/esm/utils/result-collector.d.ts +33 -0
  329. package/dist/esm/utils/result-collector.js +44 -0
  330. package/dist/esm/utils/update-notifier.d.ts +19 -0
  331. package/dist/esm/utils/update-notifier.js +108 -0
  332. package/install-manifests/core/package-lock.json +4 -4
  333. package/install-manifests/core/package.json +1 -1
  334. package/package.json +15 -13
  335. package/dist/core/checks/languages/lint.d.ts +0 -2
  336. package/dist/core/index.d.ts +0 -4
  337. package/dist/core/registry.js +0 -112
  338. package/dist/index.d.ts +0 -12
  339. package/dist/utils/index.d.ts +0 -15
  340. /package/dist/{cli.d.ts → cjs/cli.d.ts} +0 -0
  341. /package/dist/{config → cjs/config}/constants.d.ts +0 -0
  342. /package/dist/{config → cjs/config}/constants.js +0 -0
  343. /package/dist/{config → cjs/config}/pr-checkmate-workflow-docker.yml +0 -0
  344. /package/dist/{config → cjs/config}/pr-checkmate-workflow.yml +0 -0
  345. /package/dist/{core → cjs/core}/checks/languages/ruff-shared.d.ts +0 -0
  346. /package/dist/{core → cjs/core}/checks/languages/ruff-shared.js +0 -0
  347. /package/dist/{core → cjs/core}/commands.d.ts +0 -0
  348. /package/dist/{core → cjs/core}/commands.js +0 -0
  349. /package/dist/{core → cjs/core}/install/cache.d.ts +0 -0
  350. /package/dist/{core → cjs/core}/install/lock.d.ts +0 -0
  351. /package/dist/{core → cjs/core}/install/merge.d.ts +0 -0
  352. /package/dist/{core → cjs/core}/install/merge.js +0 -0
  353. /package/dist/{core → cjs/core}/install/profiles.js +0 -0
  354. /package/dist/{core → cjs/core}/types.js +0 -0
  355. /package/dist/{init.d.ts → cjs/init.d.ts} +0 -0
  356. /package/dist/{interfaces → cjs/interfaces}/index.js +0 -0
  357. /package/dist/{scripts → cjs/scripts}/copy-configs.d.ts +0 -0
  358. /package/dist/{scripts → cjs/scripts}/generate-install-manifests.d.ts +0 -0
  359. /package/dist/{scripts → cjs/scripts}/publish-github.d.ts +0 -0
  360. /package/dist/{utils → cjs/utils}/ci.d.ts +0 -0
  361. /package/dist/{utils → cjs/utils}/ci.js +0 -0
  362. /package/dist/{utils → cjs/utils}/detect-language.d.ts +0 -0
  363. /package/dist/{utils → cjs/utils}/direct-deps.d.ts +0 -0
  364. /package/dist/{utils → cjs/utils}/direct-deps.js +0 -0
  365. /package/dist/{utils → cjs/utils}/execa.d.ts +0 -0
  366. /package/dist/{utils → cjs/utils}/git.d.ts +0 -0
  367. /package/dist/{utils → cjs/utils}/glob.d.ts +0 -0
  368. /package/dist/{utils → cjs/utils}/glob.js +0 -0
  369. /package/dist/{utils → cjs/utils}/http.d.ts +0 -0
  370. /package/dist/{utils → cjs/utils}/logger.d.ts +0 -0
  371. /package/dist/{utils → cjs/utils}/logger.js +0 -0
  372. /package/dist/{utils → cjs/utils}/package-version.d.ts +0 -0
  373. /package/dist/{utils → cjs/utils}/pool.d.ts +0 -0
  374. /package/dist/{utils → cjs/utils}/pool.js +0 -0
  375. /package/dist/{utils → cjs/utils}/resolve-bin.d.ts +0 -0
  376. /package/dist/{utils → cjs/utils}/result-collector.d.ts +0 -0
  377. /package/dist/{utils → cjs/utils}/result-collector.js +0 -0
  378. /package/dist/{utils → cjs/utils}/update-notifier.d.ts +0 -0
@@ -0,0 +1,701 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { execa } from '../../utils/execa.js';
4
+ import { logger, matchesAnyGlob } from '../../utils/index.js';
5
+ import { listTrackedFiles } from '../targets.js';
6
+ import { fail, pass, warn } from '../types.js';
7
+ /**
8
+ * Resolve a check's effective severity (`configured` from config, else its
9
+ * `fallback`) into a matched logger, icon, and outcome constructor — so a check
10
+ * logs and returns consistently at whatever level the client chose.
11
+ */
12
+ export function severityEmitter(configured, fallback) {
13
+ const isError = (configured ?? fallback) === 'error';
14
+ return {
15
+ log: (isError ? logger.error : logger.warn).bind(logger),
16
+ icon: isError ? '❌' : '⚠️',
17
+ emit: (details) => (isError ? fail(details) : warn(details)),
18
+ };
19
+ }
20
+ /** Append `from`'s samples to `into`, merging labels both sides produced. */
21
+ function mergeBucket(into, from) {
22
+ for (const [label, items] of Object.entries(from))
23
+ (into[label] ?? (into[label] = [])).push(...items);
24
+ }
25
+ /**
26
+ * Fold one scan's findings into a check's running totals.
27
+ *
28
+ * Checks accumulate across several scans — one per language, or one per file on
29
+ * the full-scan path — so every accumulating check needs this, and each had the
30
+ * same pair of nested loops written out inline.
31
+ */
32
+ export function mergeSeverityBuckets(into, from) {
33
+ mergeBucket(into.errors, from.errors);
34
+ mergeBucket(into.warnings, from.warnings);
35
+ }
36
+ /** A fresh pair of empty buckets for a check to accumulate into. */
37
+ export function emptyBuckets() {
38
+ return { errors: {}, warnings: {} };
39
+ }
40
+ /**
41
+ * Inline opt-out honoured by every content check, on both the diff path and the
42
+ * whole-file path — a line muted in a PR must stay muted in a full scan.
43
+ */
44
+ const IGNORE_DIRECTIVE = 'pr-checkmate-ignore';
45
+ /**
46
+ * Run `git diff` over the given file globs, returning the raw unified diff
47
+ * (zero context, added/copied/modified/renamed files) for added-line scanning.
48
+ */
49
+ export async function diffAddedLines(ctx, globs) {
50
+ const args = [
51
+ 'diff',
52
+ '--unified=0',
53
+ '--diff-filter=ACMR',
54
+ ctx.baseSha,
55
+ ctx.headSha ?? 'HEAD',
56
+ '--',
57
+ ...globs,
58
+ ];
59
+ const cacheKey = `d:${args.join('')}`;
60
+ let stdout = ctx.cache?.get(cacheKey);
61
+ if (stdout === undefined) {
62
+ stdout = (await execa('git', args, { cwd: ctx.cwd })).stdout;
63
+ ctx.cache?.set(cacheKey, stdout);
64
+ }
65
+ // Universal inline suppression: strip added lines opted out with the
66
+ // `pr-checkmate-ignore` directive, so every content check that scans this
67
+ // diff honours it without repeating the check itself.
68
+ return stdout
69
+ .split('\n')
70
+ .filter(line => !(line.startsWith('+') && line.includes(IGNORE_DIRECTIVE)))
71
+ .join('\n');
72
+ }
73
+ /**
74
+ * Present tracked files as if every line had just been added, so a check written
75
+ * for a diff can review the whole repository unchanged.
76
+ *
77
+ * The patterns are anchored on the diff's `+` marker (`/^\+(?!\+)…/`), which is
78
+ * why `--full` used to skip most of the security surface: no diff, no lines
79
+ * starting with `+`, no findings. Rather than rewrite forty patterns — and lose
80
+ * the guarantee that both modes agree — the file contents are wrapped in the
81
+ * shape the pipeline already understands. Comment stripping, string blanking,
82
+ * `view: 'code'`, grouping and every client knob then work identically.
83
+ *
84
+ * Files are read one at a time and handed to `onFile` rather than concatenated:
85
+ * a monorepo would otherwise be assembled into a single multi-megabyte string
86
+ * before the first pattern ran.
87
+ */
88
+ export async function scanTrackedFiles(ctx, files, onFile) {
89
+ for (const file of files) {
90
+ let content;
91
+ try {
92
+ content = fs.readFileSync(path.join(ctx.cwd, file), 'utf-8');
93
+ }
94
+ catch {
95
+ // Unreadable or vanished between listing and reading — nothing to review.
96
+ continue;
97
+ }
98
+ // Binary files have no lines worth matching and would produce nonsense
99
+ // samples in the report.
100
+ if (content.includes('\0'))
101
+ continue;
102
+ // The `pr-checkmate-ignore` opt-out has to be honoured here too. It is applied
103
+ // in `diffAddedLines` for the delta path, and a line muted in a PR that then
104
+ // reappears in the full scan would make the directive look unreliable.
105
+ const lines = content
106
+ .split('\n')
107
+ .filter(line => !line.includes(IGNORE_DIRECTIVE))
108
+ .map(line => `+${line}`);
109
+ // The `+++` header tells `scanDiffPatterns` which comment syntax applies;
110
+ // the `@@` hunk header is what makes line numbers come out 1-based for the
111
+ // checks that report them (Merge Conflict). Without it they start at zero.
112
+ onFile([`+++ b/${file}`, `@@ -0,0 +1,${lines.length} @@`, ...lines].join('\n'));
113
+ }
114
+ }
115
+ /**
116
+ * Hand a check the lines it should review, from the diff when there is one and
117
+ * from the tracked files otherwise.
118
+ *
119
+ * The single entry point every content check uses, so the choice between the two
120
+ * modes is made once rather than re-implemented per check — and so a check cannot
121
+ * accidentally support only one of them, which is how `--full` ended up with no
122
+ * security coverage at all (ticket 067).
123
+ *
124
+ * `onChunk` is called once per diff in delta mode and once per file in full mode,
125
+ * which keeps a whole monorepo from being concatenated before matching starts.
126
+ *
127
+ * Returns whether the review actually happened. `false` means git could not
128
+ * produce a file list, and the caller has to say so rather than report a pass:
129
+ * a check that reviewed nothing looks exactly like a check that found nothing,
130
+ * and that is the whole of ticket 072. `fullScanBehaviour: "skip"` is the one
131
+ * case where `true` is returned having reviewed nothing on purpose — the client
132
+ * asked for that, so it is not a hole in coverage.
133
+ */
134
+ export async function reviewLines(ctx, globs, onChunk) {
135
+ if (ctx.baseSha) {
136
+ onChunk(await diffAddedLines(ctx, globs));
137
+ return true;
138
+ }
139
+ if (ctx.config.run?.fullScanBehaviour === 'skip')
140
+ return true;
141
+ const files = await listTrackedFiles(ctx, globs, ['.']);
142
+ if (files === null)
143
+ return false;
144
+ await scanTrackedFiles(ctx, files, onChunk);
145
+ return true;
146
+ }
147
+ const C_LIKE_EXTENSIONS = new Set([
148
+ 'ts',
149
+ 'tsx',
150
+ 'js',
151
+ 'jsx',
152
+ 'mjs',
153
+ 'cjs',
154
+ 'go',
155
+ 'java',
156
+ 'kt',
157
+ 'kts',
158
+ 'cs',
159
+ 'c',
160
+ 'cc',
161
+ 'cpp',
162
+ 'cxx',
163
+ 'h',
164
+ 'hpp',
165
+ 'php',
166
+ 'rs',
167
+ 'swift',
168
+ 'scala',
169
+ 'm',
170
+ 'mm',
171
+ ]);
172
+ const HASH_EXTENSIONS = new Set([
173
+ 'py',
174
+ 'pyi',
175
+ 'sh',
176
+ 'bash',
177
+ 'zsh',
178
+ 'ksh',
179
+ 'yml',
180
+ 'yaml',
181
+ 'rb',
182
+ 'toml',
183
+ 'tf',
184
+ 'pl',
185
+ 'r',
186
+ ]);
187
+ /** Comment syntax for a repo-relative path; `both` when the extension is unknown. */
188
+ export function commentSyntaxFor(file) {
189
+ const ext = file.slice(file.lastIndexOf('.') + 1).toLowerCase();
190
+ if (C_LIKE_EXTENSIONS.has(ext))
191
+ return 'c-like';
192
+ if (HASH_EXTENSIONS.has(ext))
193
+ return 'hash';
194
+ return 'both';
195
+ }
196
+ /**
197
+ * True when the `//` at `i` opens a comment rather than a regex literal.
198
+ *
199
+ * A regex literal can contain `//` (`line.replace(/\/\//g, '')`), and treating
200
+ * it as a comment truncates the line — dropping whatever a security pattern
201
+ * would have matched further along. Regex literals only appear where a value is
202
+ * expected, so the preceding non-space character is enough to tell them apart
203
+ * without tokenizing.
204
+ */
205
+ function isCommentSlash(line, i) {
206
+ const before = line.slice(0, i).trimEnd();
207
+ if (before.length === 0)
208
+ return true;
209
+ // These all precede a value, so a `/` after one opens a regex literal.
210
+ // `;` and `}` are deliberately absent: they close a statement, where a
211
+ // trailing `// comment` is overwhelmingly the more likely reading.
212
+ if (/[(,=:[!&|?]$/.test(before))
213
+ return false;
214
+ return !/\breturn$/.test(before);
215
+ }
216
+ /** Returned by a {@link walkStringState} visitor to end the walk early. */
217
+ const STOP = Symbol('stop');
218
+ /**
219
+ * Walk `line` once, reporting for each character whether it sits inside a string
220
+ * literal.
221
+ *
222
+ * The bookkeeping — which quote opened the literal, and that a backslash escapes
223
+ * the closing one — is the part {@link stripInlineComment} and
224
+ * {@link stripStringLiterals} genuinely have in common; only what each does with
225
+ * the answer differs. Kept in one place so a correction to the escape handling
226
+ * cannot land in one of them and be forgotten in the other.
227
+ *
228
+ * A visitor may return an index to resume the walk from, which is how a caller
229
+ * skips wholesale past a regex literal, or {@link STOP} to end the walk.
230
+ */
231
+ function walkStringState(line, visit) {
232
+ let quote = '';
233
+ for (let i = 0; i < line.length; i++) {
234
+ const c = line[i];
235
+ const closes = quote !== '' && c === quote && line[i - 1] !== '\\';
236
+ const opens = quote === '' && (c === '"' || c === "'" || c === '`');
237
+ const signal = visit({ c, i, inString: quote !== '', isDelimiter: closes || opens });
238
+ if (signal === STOP)
239
+ return;
240
+ if (typeof signal === 'number') {
241
+ i = signal;
242
+ continue;
243
+ }
244
+ if (closes)
245
+ quote = '';
246
+ else if (opens)
247
+ quote = c;
248
+ }
249
+ }
250
+ /**
251
+ * Strip a trailing line comment from a line, tracking quote state so a marker
252
+ * inside a string literal (a `#` in `"#fff"`, a `//` in a quoted URL) survives.
253
+ *
254
+ * Heuristic, not a tokenizer. `syntax` selects the applicable markers — passing
255
+ * the wrong one is how a valid `this.#handler = eval(payload)` used to be read
256
+ * as a comment and dropped entirely (ticket 059).
257
+ *
258
+ * Block comments are handled by the caller, which alone can track `/* *\/` state
259
+ * across lines; see {@link scanDiffPatterns}.
260
+ */
261
+ export function stripInlineComment(line, syntax = 'both') {
262
+ const hash = syntax !== 'c-like';
263
+ const slash = syntax !== 'hash';
264
+ let cut = -1;
265
+ walkStringState(line, ({ c, i, inString, isDelimiter }) => {
266
+ if (inString || isDelimiter)
267
+ return;
268
+ if (hash && c === '#') {
269
+ cut = i;
270
+ return STOP;
271
+ }
272
+ if (slash && c === '/') {
273
+ // A `/` where a value is expected opens a regex literal. Skipping past it
274
+ // wholesale is what handles the escaped slashes inside one — `/\/\//` ends
275
+ // in two literal `/` characters that would otherwise read as a comment.
276
+ if (!isCommentSlash(line, i))
277
+ return endOfRegexLiteral(line, i);
278
+ if (line[i + 1] === '/') {
279
+ cut = i;
280
+ return STOP;
281
+ }
282
+ }
283
+ });
284
+ return cut === -1 ? line : line.slice(0, cut);
285
+ }
286
+ /**
287
+ * Index of the closing `/` of the regex literal opening at `start`, or the end
288
+ * of the line when it never closes. Escapes and character classes both hide a
289
+ * `/` that does not terminate the literal.
290
+ */
291
+ function endOfRegexLiteral(line, start) {
292
+ let inClass = false;
293
+ for (let i = start + 1; i < line.length; i++) {
294
+ const c = line[i];
295
+ if (c === '\\') {
296
+ i++;
297
+ continue;
298
+ }
299
+ if (c === '[')
300
+ inClass = true;
301
+ else if (c === ']')
302
+ inClass = false;
303
+ else if (c === '/' && !inClass)
304
+ return i;
305
+ }
306
+ return line.length;
307
+ }
308
+ /**
309
+ * Blank out the contents of string literals, keeping the quotes and the original
310
+ * length so the rest of the line still reads as code.
311
+ *
312
+ * This is what lets a pattern ask "is a real call being made here" without a
313
+ * lookahead that guesses at quoting. The `eval` rule used to carry
314
+ * `(?![^'"`]*['"`])` — intended to skip `eval` written inside a string, but in
315
+ * practice it disabled the rule on any line containing a quote anywhere, so
316
+ * `eval(userInput + "x")` and ``eval(`${payload}`)`` both went unreported
317
+ * (ticket 058).
318
+ *
319
+ * Only for patterns that match *code*. Rules that look for string *contents* —
320
+ * an SQL keyword inside quotes, a hardcoded URL — must keep reading the raw
321
+ * line, which is what {@link DiffPattern.view} selects.
322
+ */
323
+ export function stripStringLiterals(line) {
324
+ let out = '';
325
+ // Both quotes are kept and only what they enclose is blanked, so the line
326
+ // still reads as code — `f("x")` stays a call with an argument.
327
+ walkStringState(line, ({ c, inString, isDelimiter }) => {
328
+ out += inString && !isDelimiter ? ' ' : c;
329
+ });
330
+ return out;
331
+ }
332
+ /** Characters kept per logged sample, so one minified added line can't flood the log. */
333
+ const SAMPLE_MAX_LENGTH = 120;
334
+ /**
335
+ * Cross-line taint tracking: the cheapest useful step beyond a per-line regex.
336
+ *
337
+ * A per-line pattern cannot see the shape that actually ships:
338
+ *
339
+ * ```ts
340
+ * const q = `SELECT * FROM u WHERE id=${id}`;
341
+ * db.query(q);
342
+ * ```
343
+ *
344
+ * Both halves are innocent alone — a template literal is not a query, and
345
+ * `query(q)` is not an interpolation — so the built-in rule, which requires the
346
+ * backtick *inside* the call, misses it entirely. That is the most ordinary way
347
+ * anyone writes an injection.
348
+ *
349
+ * This is deliberately not dataflow analysis. It remembers one thing (a variable
350
+ * assigned from an interpolated template literal), for a bounded number of added
351
+ * lines, inside one hunk, and reports when that name is handed to a query sink.
352
+ * No scopes, no reassignment, no function boundaries. Ticket 058 calls the
353
+ * ceiling out on purpose: an honest boundary beats an inflated promise, and the
354
+ * finding is rated `warn` because a heuristic must not block a build.
355
+ */
356
+ const TAINT_DECLARATION = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*`[^`]*\$\{/;
357
+ /** Sinks that turn a tainted string into a query. */
358
+ const TAINT_SINKS = 'query|execute|exec|raw';
359
+ /**
360
+ * How many added lines a taint survives, for the one check that asks for it.
361
+ *
362
+ * Exported because the default belongs to Diff Security, not to this function:
363
+ * `scanDiffPatterns` is shared by Diff Smells, Workflow Security and Dockerfile
364
+ * Security too, and defaulting the heuristic ON here leaked a finding labelled
365
+ * "SQL injection risk" into a code-smell report — under the wrong check, three
366
+ * times per run, off a config key (`diffSecurity.dataflowWindow`) that has no
367
+ * meaning for any of them. Caught by our own Diff Smells row.
368
+ */
369
+ export const DEFAULT_DATAFLOW_WINDOW = 10;
370
+ const CROSS_LINE_SQL_LABEL = 'Interpolated value reaches a query (SQL injection risk, cross-line)';
371
+ /** `@@ ... @@` — a new hunk, and the boundary taint never crosses. */
372
+ const DIFF_HUNK_HEADER = /^@@ /;
373
+ /**
374
+ * Whether this line hands `name` to a query sink.
375
+ *
376
+ * Built per lookup rather than cached: the set of tracked names is at most a
377
+ * handful per hunk, and a regex assembled from a captured identifier must not
378
+ * outlive the hunk that captured it.
379
+ */
380
+ function reachesSink(code, name) {
381
+ return new RegExp(`\\b(?:${TAINT_SINKS})\\s*\\(\\s*${name}\\s*[,)]`).test(code);
382
+ }
383
+ /**
384
+ * The per-hunk taint state, kept out of {@link scanDiffPatterns} so the scan loop
385
+ * stays one thing: match patterns, collect findings. A tracker built with a
386
+ * window of `0` answers `false` to everything and records nothing, which is how
387
+ * the heuristic switches off without a branch at the call site.
388
+ */
389
+ function taintTracker(window) {
390
+ let tainted = new Map();
391
+ let ordinal = 0;
392
+ return {
393
+ reset: () => {
394
+ tainted = new Map();
395
+ ordinal = 0;
396
+ },
397
+ inspect: (rawText, asCode) => {
398
+ if (window <= 0)
399
+ return false;
400
+ ordinal += 1;
401
+ // Sinks are looked for before new taints are recorded, so a line that both
402
+ // declares and consumes cannot report against itself — that shape is
403
+ // already covered by the built-in same-line rule.
404
+ let hit = false;
405
+ for (const [name, declaredAt] of tainted) {
406
+ if (ordinal - declaredAt > window) {
407
+ tainted.delete(name);
408
+ continue;
409
+ }
410
+ if (reachesSink(asCode, name))
411
+ hit = true;
412
+ }
413
+ // Matched on the raw text: `stripStringLiterals` blanks the `${...}` that
414
+ // is the entire reason the value is suspect.
415
+ const declared = TAINT_DECLARATION.exec(rawText);
416
+ if (declared)
417
+ tainted.set(declared[1], ordinal);
418
+ return hit;
419
+ },
420
+ };
421
+ }
422
+ /** `+++ b/path/to/file.ts` — the header that says which file follows. */
423
+ const DIFF_FILE_HEADER = /^\+\+\+ b\/(.+)$/;
424
+ /**
425
+ * Bucket added diff lines by the {@link DiffPattern}s they match, split by
426
+ * severity. Patterns are expected to anchor on the leading `+` themselves
427
+ * (`/^\+(?!\+)…/`), which is what keeps `+++` file headers and context lines out.
428
+ *
429
+ * Comment handling follows the file each hunk belongs to, read from the `+++`
430
+ * headers as the diff is walked — a single run reviews files of several
431
+ * languages, so a repo-wide setting would be wrong for most of them.
432
+ *
433
+ * `skipIgnored` re-applies the `pr-checkmate-ignore` opt-out for callers that run
434
+ * `git diff` themselves; a diff obtained from {@link diffAddedLines} is already
435
+ * filtered and should leave it off.
436
+ *
437
+ * `comments: 'off'` scans lines verbatim, which is how a client audits code that
438
+ * was commented out rather than deleted.
439
+ */
440
+ export function scanDiffPatterns(stdout, patterns, opts = {}) {
441
+ const errors = {};
442
+ const warnings = {};
443
+ const mode = opts.comments ?? 'language';
444
+ const ignorePaths = opts.ignorePaths ?? [];
445
+ let syntax = 'both';
446
+ // Block comments span lines, so the state belongs to the walk, not to one line.
447
+ let inBlockComment = false;
448
+ // Whole files can be excluded by glob; the flag persists until the next header.
449
+ let skippingFile = false;
450
+ // Cleared at every file and hunk boundary: two unrelated hunks are not two
451
+ // halves of one flow.
452
+ const taint = taintTracker(Math.max(0, opts.dataflowWindow ?? 0));
453
+ for (const line of stdout.split('\n')) {
454
+ const header = DIFF_FILE_HEADER.exec(line);
455
+ if (header) {
456
+ syntax = mode === 'language' ? commentSyntaxFor(header[1]) : 'both';
457
+ inBlockComment = false;
458
+ skippingFile = ignorePaths.length > 0 && matchesAnyGlob(header[1], ignorePaths);
459
+ taint.reset();
460
+ continue;
461
+ }
462
+ // Checked before the `+` filter below, which a hunk header does not satisfy.
463
+ if (DIFF_HUNK_HEADER.test(line)) {
464
+ taint.reset();
465
+ continue;
466
+ }
467
+ if (skippingFile)
468
+ continue;
469
+ // Only added lines carry block-comment state: a `-` line is code being
470
+ // removed, and letting it open a `/*` would blank the additions after it.
471
+ if (!line.startsWith('+'))
472
+ continue;
473
+ if (opts.skipIgnored && line.includes(IGNORE_DIRECTIVE))
474
+ continue;
475
+ const prepared = uncomment(line, { mode, syntax, inBlockComment });
476
+ inBlockComment = prepared.inBlockComment;
477
+ const asCode = stripStringLiterals(prepared.text);
478
+ for (const { pattern, label, severity, view } of patterns) {
479
+ if (pattern.test(view === 'code' ? asCode : prepared.text)) {
480
+ const bucket = severity === 'error' ? errors : warnings;
481
+ (bucket[label] ?? (bucket[label] = [])).push(line.slice(1).trim().substring(0, SAMPLE_MAX_LENGTH));
482
+ }
483
+ }
484
+ if (taint.inspect(prepared.text, asCode)) {
485
+ (warnings[CROSS_LINE_SQL_LABEL] ?? (warnings[CROSS_LINE_SQL_LABEL] = [])).push(line.slice(1).trim().substring(0, SAMPLE_MAX_LENGTH));
486
+ }
487
+ }
488
+ return { errors, warnings };
489
+ }
490
+ /**
491
+ * Reduce one added line to what should be matched against: block comments
492
+ * blanked (carrying the open state on), then the trailing line comment removed.
493
+ * `mode: 'off'` returns the line untouched.
494
+ */
495
+ function uncomment(line, state) {
496
+ const { mode, syntax } = state;
497
+ if (mode === 'off')
498
+ return { text: line, inBlockComment: false };
499
+ // `hash` languages have no `/* */`, and running the stripper over them would
500
+ // eat a legitimate `/*` appearing in, say, a shell glob.
501
+ if (syntax === 'hash') {
502
+ return { text: stripInlineComment(line, syntax), inBlockComment: false };
503
+ }
504
+ const { text, open } = stripBlockComments(line, state.inBlockComment);
505
+ return { text: stripInlineComment(text, syntax), inBlockComment: open };
506
+ }
507
+ /**
508
+ * Blank out `/* … *\/` regions, carrying the open state across lines. Without
509
+ * this a call commented out with a block comment reads as live code, and — worse
510
+ * — a line ending mid-comment leaves the rest of the hunk being scanned as if it
511
+ * were code.
512
+ */
513
+ function stripBlockComments(line, open) {
514
+ let out = '';
515
+ let i = 0;
516
+ let inside = open;
517
+ while (i < line.length) {
518
+ if (inside) {
519
+ const end = line.indexOf('*/', i);
520
+ if (end === -1)
521
+ return { text: out, open: true };
522
+ out += ' '.repeat(end + 2 - i);
523
+ i = end + 2;
524
+ inside = false;
525
+ continue;
526
+ }
527
+ const start = line.indexOf('/*', i);
528
+ if (start === -1) {
529
+ out += line.slice(i);
530
+ break;
531
+ }
532
+ out += line.slice(i, start);
533
+ i = start;
534
+ inside = true;
535
+ }
536
+ return { text: out, open: inside };
537
+ }
538
+ /** Total number of findings across all labels in a bucket. */
539
+ /** How many findings reach the log before the rest are counted instead. */
540
+ const MAX_LOGGED = 10;
541
+ /**
542
+ * Log a capped sample of raw output lines under a one-line summary.
543
+ *
544
+ * The cap is the point: a linter with 400 offences must not push the rest of the
545
+ * report out of a CI log's scrollback, and the count that follows the sample is
546
+ * what keeps the truncation from reading as the whole story.
547
+ *
548
+ * Deliberately not the private `logFindings` below, which this nearly collided
549
+ * with. That one takes findings already grouped into labelled buckets and pairs
550
+ * the log with a severity; this one takes lines a tool printed and does nothing
551
+ * but show them, which is what a check wrapping somebody else's output needs and
552
+ * the only thing it can honestly claim about them.
553
+ *
554
+ * Here rather than beside its first caller because three categories want it now —
555
+ * the language runner, the Python type check, and Duplicate Code — and a
556
+ * `quality/` check importing from `languages/` to get a logger was the wrong
557
+ * shape (ticket 079).
558
+ */
559
+ export function logSample(name, summary, lines) {
560
+ logger.warn(`[${name}]: ⚠️ ${summary}`);
561
+ lines.slice(0, MAX_LOGGED).forEach(line => logger.warn(` ${line}`));
562
+ if (lines.length > MAX_LOGGED)
563
+ logger.warn(` ... and ${lines.length - MAX_LOGGED} more`);
564
+ }
565
+ export function bucketTotal(bucket) {
566
+ return Object.values(bucket).reduce((sum, arr) => sum + arr.length, 0);
567
+ }
568
+ /**
569
+ * Report a bucket at the check's configured level, summarized as
570
+ * `9× console.*(), 1× debugger statement`.
571
+ *
572
+ * The phrasing every keyword-counting check uses: the label already says what
573
+ * was found, so only the count varies. Written out per check, the four-step tail
574
+ * — resolve the level, log at it, join the counts, emit at the same level — was
575
+ * copied verbatim, and `isError: sev.icon === '❌'` is exactly the kind of link
576
+ * between two of those steps that silently rots when only one side is edited.
577
+ */
578
+ export function reportCountedFindings(findings, ctx, opts) {
579
+ const sev = severityEmitter(opts.severity, opts.fallback ?? 'warn');
580
+ logFindings(findings, {
581
+ prefix: opts.prefix,
582
+ sampleLimit: opts.sampleLimit ?? ctx.config.log?.sampleLimit ?? 3,
583
+ isError: sev.icon === '❌',
584
+ });
585
+ const label = opts.summaryLabel ?? ((l) => l);
586
+ const summary = Object.entries(findings)
587
+ .map(([name, arr]) => `${arr.length}× ${label(name)}`)
588
+ .join(', ');
589
+ return sev.emit(summary + (opts.suffix ?? ''));
590
+ }
591
+ /**
592
+ * Log one bucket of findings using the shared check format.
593
+ *
594
+ * Internal: checks reach it through {@link reportCountedFindings} or
595
+ * {@link reportSeverityBuckets}, which pair the logging with the outcome so the
596
+ * two cannot report different levels.
597
+ */
598
+ function logFindings(bucket, opts) {
599
+ const emit = opts.isError ? logger.error.bind(logger) : logger.warn.bind(logger);
600
+ const icon = opts.isError ? '❌' : '⚠️';
601
+ const showCount = opts.showCount ?? true;
602
+ const { sampleLimit } = opts;
603
+ for (const [label, items] of Object.entries(bucket)) {
604
+ emit(`[${opts.prefix}]: ${icon} ${label}${showCount ? `: ${items.length} occurrence(s)` : ':'}`);
605
+ const shown = sampleLimit === undefined ? items : items.slice(0, sampleLimit);
606
+ shown.forEach(item => emit(` ${item}`));
607
+ if (sampleLimit !== undefined && items.length > sampleLimit) {
608
+ emit(` ... and ${items.length - sampleLimit} more`);
609
+ }
610
+ }
611
+ }
612
+ /**
613
+ * The half of {@link SeverityReportOptions} every check fills in identically:
614
+ * the client's own `<check>.ignore` / `failOn` / `severityOverrides`, the shared
615
+ * sample limit, and the repo-wide `severityDefaults` fallback.
616
+ *
617
+ * Only the phrasing (`prefix` and the two summaries) is ever check-specific, so
618
+ * spelling the rest out per check meant five lines of copy-paste that had to be
619
+ * kept in step by hand — and a check that forgot one line silently ignored a
620
+ * knob the client had set.
621
+ */
622
+ export function clientFindingOptions(ctx, cfg = {}) {
623
+ return {
624
+ sampleLimit: ctx.config.log?.sampleLimit ?? 3,
625
+ ignore: cfg.ignore,
626
+ failOn: cfg.failOn,
627
+ severityOverrides: cfg.severityOverrides,
628
+ defaults: ctx.config.severityDefaults,
629
+ };
630
+ }
631
+ /** Remove bucket entries whose label matches any ignore substring. */
632
+ function dropIgnored(bucket, ignore) {
633
+ if (ignore.length === 0)
634
+ return bucket;
635
+ const lowered = ignore.map(i => i.toLowerCase());
636
+ const kept = {};
637
+ for (const [label, items] of Object.entries(bucket)) {
638
+ if (!lowered.some(i => label.toLowerCase().includes(i)))
639
+ kept[label] = items;
640
+ }
641
+ return kept;
642
+ }
643
+ /**
644
+ * Re-file findings according to the client's per-label overrides, returning
645
+ * fresh buckets. A label matching several overrides takes the longest (most
646
+ * specific) match, so a broad rule and a narrow exception can coexist.
647
+ */
648
+ function applyOverrides(errors, warnings, overrides) {
649
+ const entries = Object.entries(overrides).map(([k, v]) => [k.toLowerCase(), v]);
650
+ if (entries.length === 0)
651
+ return { errors, warnings };
652
+ const out = { errors: {}, warnings: {} };
653
+ const place = (label, items, fallback) => {
654
+ const lowered = label.toLowerCase();
655
+ const matches = entries.filter(([key]) => lowered.includes(key));
656
+ const best = matches.sort((a, b) => b[0].length - a[0].length)[0];
657
+ const level = best ? best[1] : fallback;
658
+ if (level === 'off')
659
+ return;
660
+ const bucket = level === 'error' ? out.errors : out.warnings;
661
+ (bucket[label] ?? (bucket[label] = [])).push(...items);
662
+ };
663
+ for (const [label, items] of Object.entries(errors))
664
+ place(label, items, 'error');
665
+ for (const [label, items] of Object.entries(warnings))
666
+ place(label, items, 'warn');
667
+ return out;
668
+ }
669
+ /**
670
+ * Report error/warning finding buckets: log both, then produce the outcome.
671
+ *
672
+ * Order is the contract, and each step exists because the one before it is too
673
+ * blunt on its own:
674
+ * 1. `ignore` — drop the finding outright.
675
+ * 2. `severityOverrides` — re-file what survived.
676
+ * 3. `failOn` — decide what a remaining error means for the run.
677
+ * The universal `severity[<check name>]` map is applied later still, by the
678
+ * orchestrator, so a client can always override a check wholesale by name.
679
+ */
680
+ export function reportSeverityBuckets(errorsIn, warningsIn, opts) {
681
+ const ignore = opts.ignore ?? [];
682
+ const filed = applyOverrides(dropIgnored(errorsIn, ignore), dropIgnored(warningsIn, ignore), opts.severityOverrides ?? {});
683
+ const { errors, warnings } = filed;
684
+ const totalErrors = bucketTotal(errors);
685
+ const totalWarnings = bucketTotal(warnings);
686
+ if (totalErrors === 0 && totalWarnings === 0)
687
+ return pass();
688
+ const logOpts = { prefix: opts.prefix, showCount: opts.showCount, sampleLimit: opts.sampleLimit };
689
+ logFindings(errors, { ...logOpts, isError: true });
690
+ logFindings(warnings, { ...logOpts, isError: false });
691
+ const failOn = opts.failOn ?? opts.defaults?.failOn ?? 'error';
692
+ if (failOn === 'never')
693
+ return pass();
694
+ const parts = [];
695
+ if (totalErrors > 0)
696
+ parts.push(opts.errorSummary(totalErrors));
697
+ if (totalWarnings > 0)
698
+ parts.push(opts.warningSummary(totalWarnings));
699
+ const summary = parts.join(', ');
700
+ return failOn === 'error' && totalErrors > 0 ? fail(summary) : warn(summary);
701
+ }