@kevinrabun/judges 2.3.0 → 3.0.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 (321) hide show
  1. package/README.md +177 -12
  2. package/dist/api.d.ts +40 -0
  3. package/dist/api.d.ts.map +1 -0
  4. package/dist/api.js +56 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/ast/cross-file-taint.d.ts +43 -0
  7. package/dist/ast/cross-file-taint.d.ts.map +1 -0
  8. package/dist/ast/cross-file-taint.js +713 -0
  9. package/dist/ast/cross-file-taint.js.map +1 -0
  10. package/dist/ast/index.d.ts +4 -0
  11. package/dist/ast/index.d.ts.map +1 -1
  12. package/dist/ast/index.js +5 -0
  13. package/dist/ast/index.js.map +1 -1
  14. package/dist/ast/structural-parser.d.ts.map +1 -1
  15. package/dist/ast/structural-parser.js +66 -11
  16. package/dist/ast/structural-parser.js.map +1 -1
  17. package/dist/ast/taint-tracker.d.ts +35 -0
  18. package/dist/ast/taint-tracker.d.ts.map +1 -0
  19. package/dist/ast/taint-tracker.js +518 -0
  20. package/dist/ast/taint-tracker.js.map +1 -0
  21. package/dist/ast/types.d.ts +2 -0
  22. package/dist/ast/types.d.ts.map +1 -1
  23. package/dist/ast/typescript-ast.d.ts.map +1 -1
  24. package/dist/ast/typescript-ast.js +25 -5
  25. package/dist/ast/typescript-ast.js.map +1 -1
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +10 -9
  28. package/dist/config.js.map +1 -1
  29. package/dist/dedup.d.ts +19 -0
  30. package/dist/dedup.d.ts.map +1 -0
  31. package/dist/dedup.js +222 -0
  32. package/dist/dedup.js.map +1 -0
  33. package/dist/errors.d.ts +37 -0
  34. package/dist/errors.d.ts.map +1 -0
  35. package/dist/errors.js +57 -0
  36. package/dist/errors.js.map +1 -0
  37. package/dist/evaluators/accessibility.d.ts +1 -1
  38. package/dist/evaluators/accessibility.d.ts.map +1 -1
  39. package/dist/evaluators/accessibility.js +22 -16
  40. package/dist/evaluators/accessibility.js.map +1 -1
  41. package/dist/evaluators/agent-instructions.d.ts +1 -1
  42. package/dist/evaluators/agent-instructions.d.ts.map +1 -1
  43. package/dist/evaluators/agent-instructions.js +1 -2
  44. package/dist/evaluators/agent-instructions.js.map +1 -1
  45. package/dist/evaluators/ai-code-safety.d.ts +1 -1
  46. package/dist/evaluators/ai-code-safety.d.ts.map +1 -1
  47. package/dist/evaluators/ai-code-safety.js +2 -6
  48. package/dist/evaluators/ai-code-safety.js.map +1 -1
  49. package/dist/evaluators/api-design.d.ts +1 -1
  50. package/dist/evaluators/api-design.d.ts.map +1 -1
  51. package/dist/evaluators/api-design.js +2 -1
  52. package/dist/evaluators/api-design.js.map +1 -1
  53. package/dist/evaluators/app-builder.d.ts +34 -0
  54. package/dist/evaluators/app-builder.d.ts.map +1 -0
  55. package/dist/evaluators/app-builder.js +156 -0
  56. package/dist/evaluators/app-builder.js.map +1 -0
  57. package/dist/evaluators/authentication.d.ts +1 -1
  58. package/dist/evaluators/authentication.d.ts.map +1 -1
  59. package/dist/evaluators/authentication.js +2 -66
  60. package/dist/evaluators/authentication.js.map +1 -1
  61. package/dist/evaluators/backwards-compatibility.d.ts +1 -1
  62. package/dist/evaluators/backwards-compatibility.d.ts.map +1 -1
  63. package/dist/evaluators/backwards-compatibility.js.map +1 -1
  64. package/dist/evaluators/caching.d.ts +1 -1
  65. package/dist/evaluators/caching.d.ts.map +1 -1
  66. package/dist/evaluators/caching.js.map +1 -1
  67. package/dist/evaluators/ci-cd.d.ts +1 -1
  68. package/dist/evaluators/ci-cd.d.ts.map +1 -1
  69. package/dist/evaluators/ci-cd.js +4 -4
  70. package/dist/evaluators/ci-cd.js.map +1 -1
  71. package/dist/evaluators/cloud-readiness.d.ts +1 -1
  72. package/dist/evaluators/cloud-readiness.d.ts.map +1 -1
  73. package/dist/evaluators/cloud-readiness.js.map +1 -1
  74. package/dist/evaluators/code-structure.d.ts +1 -1
  75. package/dist/evaluators/code-structure.d.ts.map +1 -1
  76. package/dist/evaluators/code-structure.js +2 -6
  77. package/dist/evaluators/code-structure.js.map +1 -1
  78. package/dist/evaluators/compliance.d.ts +1 -1
  79. package/dist/evaluators/compliance.d.ts.map +1 -1
  80. package/dist/evaluators/compliance.js +15 -6
  81. package/dist/evaluators/compliance.js.map +1 -1
  82. package/dist/evaluators/concurrency.d.ts +1 -1
  83. package/dist/evaluators/concurrency.d.ts.map +1 -1
  84. package/dist/evaluators/concurrency.js +9 -4
  85. package/dist/evaluators/concurrency.js.map +1 -1
  86. package/dist/evaluators/configuration-management.d.ts +1 -1
  87. package/dist/evaluators/configuration-management.d.ts.map +1 -1
  88. package/dist/evaluators/configuration-management.js +7 -2
  89. package/dist/evaluators/configuration-management.js.map +1 -1
  90. package/dist/evaluators/cost-effectiveness.d.ts +1 -1
  91. package/dist/evaluators/cost-effectiveness.d.ts.map +1 -1
  92. package/dist/evaluators/cost-effectiveness.js +1 -3
  93. package/dist/evaluators/cost-effectiveness.js.map +1 -1
  94. package/dist/evaluators/cybersecurity.d.ts +1 -1
  95. package/dist/evaluators/cybersecurity.d.ts.map +1 -1
  96. package/dist/evaluators/cybersecurity.js +50 -1
  97. package/dist/evaluators/cybersecurity.js.map +1 -1
  98. package/dist/evaluators/data-security.d.ts +1 -1
  99. package/dist/evaluators/data-security.d.ts.map +1 -1
  100. package/dist/evaluators/data-security.js +9 -66
  101. package/dist/evaluators/data-security.js.map +1 -1
  102. package/dist/evaluators/data-sovereignty.d.ts +1 -1
  103. package/dist/evaluators/data-sovereignty.d.ts.map +1 -1
  104. package/dist/evaluators/data-sovereignty.js +4 -2
  105. package/dist/evaluators/data-sovereignty.js.map +1 -1
  106. package/dist/evaluators/database.d.ts +1 -1
  107. package/dist/evaluators/database.d.ts.map +1 -1
  108. package/dist/evaluators/database.js +3 -1
  109. package/dist/evaluators/database.js.map +1 -1
  110. package/dist/evaluators/dependencies.d.ts +6 -0
  111. package/dist/evaluators/dependencies.d.ts.map +1 -0
  112. package/dist/evaluators/dependencies.js +204 -0
  113. package/dist/evaluators/dependencies.js.map +1 -0
  114. package/dist/evaluators/dependency-health.d.ts +1 -1
  115. package/dist/evaluators/dependency-health.d.ts.map +1 -1
  116. package/dist/evaluators/dependency-health.js +198 -6
  117. package/dist/evaluators/dependency-health.js.map +1 -1
  118. package/dist/evaluators/documentation.d.ts +1 -1
  119. package/dist/evaluators/documentation.d.ts.map +1 -1
  120. package/dist/evaluators/documentation.js +5 -2
  121. package/dist/evaluators/documentation.js.map +1 -1
  122. package/dist/evaluators/error-handling.d.ts +1 -1
  123. package/dist/evaluators/error-handling.d.ts.map +1 -1
  124. package/dist/evaluators/error-handling.js.map +1 -1
  125. package/dist/evaluators/ethics-bias.d.ts +1 -1
  126. package/dist/evaluators/ethics-bias.d.ts.map +1 -1
  127. package/dist/evaluators/ethics-bias.js +10 -5
  128. package/dist/evaluators/ethics-bias.js.map +1 -1
  129. package/dist/evaluators/framework-safety.d.ts +13 -0
  130. package/dist/evaluators/framework-safety.d.ts.map +1 -0
  131. package/dist/evaluators/framework-safety.js +424 -0
  132. package/dist/evaluators/framework-safety.js.map +1 -0
  133. package/dist/evaluators/index.d.ts +20 -24
  134. package/dist/evaluators/index.d.ts.map +1 -1
  135. package/dist/evaluators/index.js +294 -728
  136. package/dist/evaluators/index.js.map +1 -1
  137. package/dist/evaluators/internationalization.d.ts +1 -1
  138. package/dist/evaluators/internationalization.d.ts.map +1 -1
  139. package/dist/evaluators/internationalization.js +14 -6
  140. package/dist/evaluators/internationalization.js.map +1 -1
  141. package/dist/evaluators/logging-privacy.d.ts +1 -1
  142. package/dist/evaluators/logging-privacy.d.ts.map +1 -1
  143. package/dist/evaluators/logging-privacy.js +3 -1
  144. package/dist/evaluators/logging-privacy.js.map +1 -1
  145. package/dist/evaluators/maintainability.d.ts +1 -1
  146. package/dist/evaluators/maintainability.d.ts.map +1 -1
  147. package/dist/evaluators/maintainability.js +15 -9
  148. package/dist/evaluators/maintainability.js.map +1 -1
  149. package/dist/evaluators/observability.d.ts +1 -1
  150. package/dist/evaluators/observability.d.ts.map +1 -1
  151. package/dist/evaluators/observability.js +2 -1
  152. package/dist/evaluators/observability.js.map +1 -1
  153. package/dist/evaluators/performance.d.ts +1 -1
  154. package/dist/evaluators/performance.d.ts.map +1 -1
  155. package/dist/evaluators/performance.js +181 -4
  156. package/dist/evaluators/performance.js.map +1 -1
  157. package/dist/evaluators/portability.d.ts +1 -1
  158. package/dist/evaluators/portability.d.ts.map +1 -1
  159. package/dist/evaluators/portability.js +2 -1
  160. package/dist/evaluators/portability.js.map +1 -1
  161. package/dist/evaluators/project.d.ts +16 -0
  162. package/dist/evaluators/project.d.ts.map +1 -0
  163. package/dist/evaluators/project.js +353 -0
  164. package/dist/evaluators/project.js.map +1 -0
  165. package/dist/evaluators/rate-limiting.d.ts +1 -1
  166. package/dist/evaluators/rate-limiting.d.ts.map +1 -1
  167. package/dist/evaluators/rate-limiting.js.map +1 -1
  168. package/dist/evaluators/reliability.d.ts +1 -1
  169. package/dist/evaluators/reliability.d.ts.map +1 -1
  170. package/dist/evaluators/reliability.js.map +1 -1
  171. package/dist/evaluators/scalability.d.ts +1 -1
  172. package/dist/evaluators/scalability.d.ts.map +1 -1
  173. package/dist/evaluators/scalability.js +3 -1
  174. package/dist/evaluators/scalability.js.map +1 -1
  175. package/dist/evaluators/shared.d.ts +24 -2
  176. package/dist/evaluators/shared.d.ts.map +1 -1
  177. package/dist/evaluators/shared.js +190 -2
  178. package/dist/evaluators/shared.js.map +1 -1
  179. package/dist/evaluators/software-practices.d.ts +1 -1
  180. package/dist/evaluators/software-practices.d.ts.map +1 -1
  181. package/dist/evaluators/software-practices.js +3 -3
  182. package/dist/evaluators/software-practices.js.map +1 -1
  183. package/dist/evaluators/testing.d.ts +1 -1
  184. package/dist/evaluators/testing.d.ts.map +1 -1
  185. package/dist/evaluators/testing.js +12 -4
  186. package/dist/evaluators/testing.js.map +1 -1
  187. package/dist/evaluators/ux.d.ts +1 -1
  188. package/dist/evaluators/ux.d.ts.map +1 -1
  189. package/dist/evaluators/ux.js.map +1 -1
  190. package/dist/evaluators/v2.d.ts +1 -1
  191. package/dist/evaluators/v2.d.ts.map +1 -1
  192. package/dist/evaluators/v2.js +13 -35
  193. package/dist/evaluators/v2.js.map +1 -1
  194. package/dist/formatters/sarif.d.ts +75 -0
  195. package/dist/formatters/sarif.d.ts.map +1 -0
  196. package/dist/formatters/sarif.js +93 -0
  197. package/dist/formatters/sarif.js.map +1 -0
  198. package/dist/index.d.ts +4 -1
  199. package/dist/index.d.ts.map +1 -1
  200. package/dist/index.js +9 -806
  201. package/dist/index.js.map +1 -1
  202. package/dist/judges/accessibility.d.ts +1 -1
  203. package/dist/judges/accessibility.d.ts.map +1 -1
  204. package/dist/judges/agent-instructions.d.ts +1 -1
  205. package/dist/judges/agent-instructions.d.ts.map +1 -1
  206. package/dist/judges/ai-code-safety.d.ts +1 -1
  207. package/dist/judges/ai-code-safety.d.ts.map +1 -1
  208. package/dist/judges/api-design.d.ts +1 -1
  209. package/dist/judges/api-design.d.ts.map +1 -1
  210. package/dist/judges/authentication.d.ts +1 -1
  211. package/dist/judges/authentication.d.ts.map +1 -1
  212. package/dist/judges/backwards-compatibility.d.ts +1 -1
  213. package/dist/judges/backwards-compatibility.d.ts.map +1 -1
  214. package/dist/judges/caching.d.ts +1 -1
  215. package/dist/judges/caching.d.ts.map +1 -1
  216. package/dist/judges/ci-cd.d.ts +1 -1
  217. package/dist/judges/ci-cd.d.ts.map +1 -1
  218. package/dist/judges/cloud-readiness.d.ts +1 -1
  219. package/dist/judges/cloud-readiness.d.ts.map +1 -1
  220. package/dist/judges/code-structure.d.ts +1 -1
  221. package/dist/judges/code-structure.d.ts.map +1 -1
  222. package/dist/judges/code-structure.js +7 -1
  223. package/dist/judges/code-structure.js.map +1 -1
  224. package/dist/judges/compliance.d.ts +1 -1
  225. package/dist/judges/compliance.d.ts.map +1 -1
  226. package/dist/judges/concurrency.d.ts +1 -1
  227. package/dist/judges/concurrency.d.ts.map +1 -1
  228. package/dist/judges/configuration-management.d.ts +1 -1
  229. package/dist/judges/configuration-management.d.ts.map +1 -1
  230. package/dist/judges/cost-effectiveness.d.ts +1 -1
  231. package/dist/judges/cost-effectiveness.d.ts.map +1 -1
  232. package/dist/judges/cybersecurity.d.ts +1 -1
  233. package/dist/judges/cybersecurity.d.ts.map +1 -1
  234. package/dist/judges/data-security.d.ts +1 -1
  235. package/dist/judges/data-security.d.ts.map +1 -1
  236. package/dist/judges/data-sovereignty.d.ts +1 -1
  237. package/dist/judges/data-sovereignty.d.ts.map +1 -1
  238. package/dist/judges/database.d.ts +1 -1
  239. package/dist/judges/database.d.ts.map +1 -1
  240. package/dist/judges/dependency-health.d.ts +1 -1
  241. package/dist/judges/dependency-health.d.ts.map +1 -1
  242. package/dist/judges/documentation.d.ts +1 -1
  243. package/dist/judges/documentation.d.ts.map +1 -1
  244. package/dist/judges/error-handling.d.ts +1 -1
  245. package/dist/judges/error-handling.d.ts.map +1 -1
  246. package/dist/judges/ethics-bias.d.ts +1 -1
  247. package/dist/judges/ethics-bias.d.ts.map +1 -1
  248. package/dist/judges/framework-safety.d.ts +3 -0
  249. package/dist/judges/framework-safety.d.ts.map +1 -0
  250. package/dist/judges/framework-safety.js +31 -0
  251. package/dist/judges/framework-safety.js.map +1 -0
  252. package/dist/judges/index.d.ts +1 -1
  253. package/dist/judges/index.d.ts.map +1 -1
  254. package/dist/judges/index.js +74 -0
  255. package/dist/judges/index.js.map +1 -1
  256. package/dist/judges/internationalization.d.ts +1 -1
  257. package/dist/judges/internationalization.d.ts.map +1 -1
  258. package/dist/judges/logging-privacy.d.ts +1 -1
  259. package/dist/judges/logging-privacy.d.ts.map +1 -1
  260. package/dist/judges/maintainability.d.ts +1 -1
  261. package/dist/judges/maintainability.d.ts.map +1 -1
  262. package/dist/judges/observability.d.ts +1 -1
  263. package/dist/judges/observability.d.ts.map +1 -1
  264. package/dist/judges/performance.d.ts +1 -1
  265. package/dist/judges/performance.d.ts.map +1 -1
  266. package/dist/judges/portability.d.ts +1 -1
  267. package/dist/judges/portability.d.ts.map +1 -1
  268. package/dist/judges/rate-limiting.d.ts +1 -1
  269. package/dist/judges/rate-limiting.d.ts.map +1 -1
  270. package/dist/judges/reliability.d.ts +1 -1
  271. package/dist/judges/reliability.d.ts.map +1 -1
  272. package/dist/judges/scalability.d.ts +1 -1
  273. package/dist/judges/scalability.d.ts.map +1 -1
  274. package/dist/judges/software-practices.d.ts +1 -1
  275. package/dist/judges/software-practices.d.ts.map +1 -1
  276. package/dist/judges/testing.d.ts +1 -1
  277. package/dist/judges/testing.d.ts.map +1 -1
  278. package/dist/judges/ux.d.ts +1 -1
  279. package/dist/judges/ux.d.ts.map +1 -1
  280. package/dist/language-patterns.d.ts +37 -0
  281. package/dist/language-patterns.d.ts.map +1 -1
  282. package/dist/language-patterns.js +58 -3
  283. package/dist/language-patterns.js.map +1 -1
  284. package/dist/patches/index.d.ts +10 -0
  285. package/dist/patches/index.d.ts.map +1 -0
  286. package/dist/patches/index.js +533 -0
  287. package/dist/patches/index.js.map +1 -0
  288. package/dist/reports/public-repo-report.d.ts +1 -1
  289. package/dist/reports/public-repo-report.d.ts.map +1 -1
  290. package/dist/scoring.d.ts +18 -0
  291. package/dist/scoring.d.ts.map +1 -0
  292. package/dist/scoring.js +178 -0
  293. package/dist/scoring.js.map +1 -0
  294. package/dist/tools/deep-review.d.ts +4 -0
  295. package/dist/tools/deep-review.d.ts.map +1 -0
  296. package/dist/tools/deep-review.js +56 -0
  297. package/dist/tools/deep-review.js.map +1 -0
  298. package/dist/tools/prompts.d.ts +8 -0
  299. package/dist/tools/prompts.d.ts.map +1 -0
  300. package/dist/tools/prompts.js +66 -0
  301. package/dist/tools/prompts.js.map +1 -0
  302. package/dist/tools/register-evaluation.d.ts +7 -0
  303. package/dist/tools/register-evaluation.d.ts.map +1 -0
  304. package/dist/tools/register-evaluation.js +303 -0
  305. package/dist/tools/register-evaluation.js.map +1 -0
  306. package/dist/tools/register-workflow.d.ts +7 -0
  307. package/dist/tools/register-workflow.d.ts.map +1 -0
  308. package/dist/tools/register-workflow.js +395 -0
  309. package/dist/tools/register-workflow.js.map +1 -0
  310. package/dist/tools/register.d.ts +7 -0
  311. package/dist/tools/register.d.ts.map +1 -0
  312. package/dist/tools/register.js +14 -0
  313. package/dist/tools/register.js.map +1 -0
  314. package/dist/tools/schemas.d.ts +26 -0
  315. package/dist/tools/schemas.d.ts.map +1 -0
  316. package/dist/tools/schemas.js +42 -0
  317. package/dist/tools/schemas.js.map +1 -0
  318. package/dist/types.d.ts +29 -2
  319. package/dist/types.d.ts.map +1 -1
  320. package/package.json +42 -3
  321. package/server.json +51 -3
@@ -2,415 +2,343 @@
2
2
  // Re-exports the evaluation engine: analyser routing, scoring, formatting.
3
3
  // ──────────────────────────────────────────────────────────────────────────────
4
4
  import { JUDGES } from "../judges/index.js";
5
+ import { analyzeStructure } from "../ast/index.js";
6
+ import { analyzeTaintFlows } from "../ast/index.js";
5
7
  // ─── Shared Utilities ────────────────────────────────────────────────────────
6
- import { calculateScore, deriveVerdict, buildSummary, buildTribunalSummary, formatVerdictAsMarkdown, formatEvaluationAsMarkdown, } from "./shared.js";
7
- // ─── Individual Analyzers ────────────────────────────────────────────────────
8
- import { analyzeDataSecurity } from "./data-security.js";
9
- import { analyzeCybersecurity } from "./cybersecurity.js";
10
- import { analyzeCostEffectiveness } from "./cost-effectiveness.js";
11
- import { analyzeScalability } from "./scalability.js";
12
- import { analyzeCloudReadiness } from "./cloud-readiness.js";
13
- import { analyzeSoftwarePractices } from "./software-practices.js";
14
- import { analyzeAccessibility } from "./accessibility.js";
15
- import { analyzeApiDesign } from "./api-design.js";
16
- import { analyzeReliability } from "./reliability.js";
17
- import { analyzeObservability } from "./observability.js";
18
- import { analyzePerformance } from "./performance.js";
19
- import { analyzeCompliance } from "./compliance.js";
20
- import { analyzeDataSovereignty } from "./data-sovereignty.js";
21
- import { analyzeTesting } from "./testing.js";
22
- import { analyzeDocumentation } from "./documentation.js";
23
- import { analyzeInternationalization } from "./internationalization.js";
24
- import { analyzeDependencyHealth } from "./dependency-health.js";
25
- import { analyzeConcurrency } from "./concurrency.js";
26
- import { analyzeEthicsBias } from "./ethics-bias.js";
27
- import { analyzeMaintainability } from "./maintainability.js";
28
- import { analyzeErrorHandling } from "./error-handling.js";
29
- import { analyzeAuthentication } from "./authentication.js";
30
- import { analyzeDatabase } from "./database.js";
31
- import { analyzeCaching } from "./caching.js";
32
- import { analyzeConfigurationManagement } from "./configuration-management.js";
33
- import { analyzeBackwardsCompatibility } from "./backwards-compatibility.js";
34
- import { analyzePortability } from "./portability.js";
35
- import { analyzeUx } from "./ux.js";
36
- import { analyzeLoggingPrivacy } from "./logging-privacy.js";
37
- import { analyzeRateLimiting } from "./rate-limiting.js";
38
- import { analyzeCiCd } from "./ci-cd.js";
39
- import { analyzeCodeStructure } from "./code-structure.js";
40
- import { analyzeAgentInstructions } from "./agent-instructions.js";
41
- import { analyzeAiCodeSafety } from "./ai-code-safety.js";
42
- const DEFAULT_MUST_FIX_PREFIXES = [
43
- "AUTH-",
44
- "CYBER-",
45
- "DATA-",
46
- "ERR-",
47
- "REL-",
48
- "RATE-",
49
- "DB-",
50
- "COMP-",
51
- "LOGPRIV-",
52
- "AICS-",
53
- ];
54
- function evaluateMustFixGate(findings, options) {
55
- if (!options?.enabled) {
56
- return undefined;
8
+ import { calculateScore, deriveVerdict, buildSummary, buildTribunalSummary, formatVerdictAsMarkdown, formatEvaluationAsMarkdown, classifyFile, shouldRunAbsenceRules, applyConfig, } from "./shared.js";
9
+ // ─── Extracted Modules ───────────────────────────────────────────────────────
10
+ import { evaluateMustFixGate, clampConfidence, applyConfidenceThreshold, isAbsenceBasedFinding, } from "../scoring.js";
11
+ import { enrichWithPatches } from "../patches/index.js";
12
+ import { crossEvaluatorDedup } from "../dedup.js";
13
+ // ── AST-aware post-processing ───────────────────────────────────────────────
14
+ /**
15
+ * Known sanitization/security library names. When one is imported, related
16
+ * findings can have confidence reduced because the developer has taken steps
17
+ * to mitigate the issue.
18
+ */
19
+ const SECURITY_IMPORTS = {
20
+ xss: ["dompurify", "sanitize-html", "xss", "isomorphic-dompurify", "xss-filters"],
21
+ headers: ["helmet", "secure-headers", "django-security"],
22
+ rateLimit: ["express-rate-limit", "rate-limiter-flexible", "bottleneck", "limiter", "rate-limit"],
23
+ validation: ["joi", "zod", "yup", "ajv", "class-validator", "express-validator"],
24
+ csrf: ["csurf", "csrf", "csrf-csrf"],
25
+ crypto: ["bcrypt", "argon2", "scrypt"],
26
+ jwt: ["jsonwebtoken", "jose", "passport-jwt"],
27
+ };
28
+ /**
29
+ * Returns the containing function for a given line number, if any.
30
+ */
31
+ function getContainingFunction(line, structure) {
32
+ return structure.functions.find((f) => line >= f.startLine && line <= f.endLine);
33
+ }
34
+ const TEST_FUNCTION_PATTERN = /^(?:test|it|describe|beforeEach|afterEach|beforeAll|afterAll|setUp|tearDown|test_|spec_)/i;
35
+ // ─── Taint Flow Finding Matching ───────────────────────────────────────────
36
+ /** Map taint-sink kinds to finding title/ruleId patterns they confirm */
37
+ const TAINT_SINK_TO_FINDING = {
38
+ "code-execution": /eval|code.?inject|code.?exec|dynamic.?code/i,
39
+ "command-exec": /command.?inject|os.?command|shell.?inject|exec/i,
40
+ "sql-query": /sql.?inject|query.?inject|unsanitized.?query/i,
41
+ xss: /xss|cross.?site\s*script|innerhtml|html.?inject/i,
42
+ "path-traversal": /path.?travers|directory.?travers|file.?inclu/i,
43
+ redirect: /open.?redirect|unvalidated.?redirect/i,
44
+ deserialization: /deseri|unsafe.?parse|untrusted.?data.?parse/i,
45
+ template: /template.?inject|ssti/i,
46
+ };
47
+ /**
48
+ * Apply AST-aware refinements to findings:
49
+ * - Remove findings on dead code lines
50
+ * - Lower confidence for findings inside test-like functions
51
+ * - Adjust confidence based on imported security libraries
52
+ * - Boost/annotate findings confirmed by taint flow analysis
53
+ */
54
+ function applyAstRefinements(findings, structure, taintFlows) {
55
+ const deadSet = new Set(structure.deadCodeLines);
56
+ const importNames = new Set(structure.imports
57
+ .map((i) => {
58
+ // Extract package name from path: "@scope/pkg" → "@scope/pkg", "helmet" → "helmet"
59
+ const parts = i.split("/");
60
+ return i.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
61
+ })
62
+ .map((n) => n.toLowerCase()));
63
+ // Determine which security categories have mitigations imported
64
+ const mitigatedCategories = new Set();
65
+ for (const [category, libs] of Object.entries(SECURITY_IMPORTS)) {
66
+ if (libs.some((lib) => importNames.has(lib))) {
67
+ mitigatedCategories.add(category);
68
+ }
57
69
  }
58
- const minConfidence = clampConfidence(options.minConfidence ?? 0.85);
59
- const prefixes = options.dangerousRulePrefixes?.length
60
- ? options.dangerousRulePrefixes
61
- : DEFAULT_MUST_FIX_PREFIXES;
62
- const dangerSignal = /(injection|command\s*execution|sql|xss|ssrf|deseriali[sz]ation|auth(?:entication|orization)?\s*bypass|hardcoded\s+(?:secret|credential|password|token)|unsafe\s+eval|\beval\(|\bexec\()/i;
63
- const matched = findings.filter((finding) => {
64
- const severityMatch = finding.severity === "critical" || finding.severity === "high";
65
- if (!severityMatch)
66
- return false;
67
- const confidence = finding.confidence ?? 0;
68
- if (confidence < minConfidence)
70
+ // Build a map of sink lines → taint flows for fast lookup
71
+ const flowsBySinkLine = new Map();
72
+ if (taintFlows) {
73
+ for (const flow of taintFlows) {
74
+ const existing = flowsBySinkLine.get(flow.sink.line) ?? [];
75
+ existing.push(flow);
76
+ flowsBySinkLine.set(flow.sink.line, existing);
77
+ }
78
+ }
79
+ return findings
80
+ .filter((f) => {
81
+ // Remove findings where ALL referenced lines are dead code
82
+ if (f.lineNumbers && f.lineNumbers.length > 0 && f.lineNumbers.every((l) => deadSet.has(l))) {
69
83
  return false;
70
- const prefixMatch = prefixes.some((prefix) => finding.ruleId.startsWith(prefix));
71
- const contentMatch = dangerSignal.test(`${finding.title} ${finding.description} ${finding.recommendation}`);
72
- return prefixMatch || contentMatch;
84
+ }
85
+ return true;
86
+ })
87
+ .map((f) => {
88
+ let confidenceAdj = 0;
89
+ let descriptionSuffix = "";
90
+ // Lower confidence for findings inside test-like functions
91
+ if (f.lineNumbers && f.lineNumbers.length > 0) {
92
+ const primaryLine = f.lineNumbers[0];
93
+ const fn = getContainingFunction(primaryLine, structure);
94
+ if (fn && TEST_FUNCTION_PATTERN.test(fn.name)) {
95
+ confidenceAdj -= 0.15;
96
+ }
97
+ }
98
+ // Adjust confidence based on security library imports
99
+ const title = f.title.toLowerCase();
100
+ if (mitigatedCategories.has("xss") && /xss|innerhtml|cross.?site\s*script/i.test(title)) {
101
+ confidenceAdj -= 0.2;
102
+ }
103
+ if (mitigatedCategories.has("headers") && /security\s*header|helmet/i.test(title)) {
104
+ confidenceAdj -= 0.25;
105
+ }
106
+ if (mitigatedCategories.has("rateLimit") && /rate\s*limit|throttl/i.test(title)) {
107
+ confidenceAdj -= 0.2;
108
+ }
109
+ if (mitigatedCategories.has("validation") && /input\s*valid|sanitiz|unsanitized/i.test(title)) {
110
+ confidenceAdj -= 0.15;
111
+ }
112
+ if (mitigatedCategories.has("csrf") && /csrf|cross.?site\s*request/i.test(title)) {
113
+ confidenceAdj -= 0.25;
114
+ }
115
+ // ── Taint flow confirmation ────────────────────────────────────────
116
+ if (taintFlows && taintFlows.length > 0 && f.lineNumbers && f.lineNumbers.length > 0) {
117
+ const matchingFlows = findMatchingTaintFlows(f, flowsBySinkLine);
118
+ if (matchingFlows.length > 0) {
119
+ // Confirmed: user input reaches this sink → boost confidence
120
+ confidenceAdj += 0.2;
121
+ const flow = matchingFlows[0];
122
+ const via = flow.intermediates.length > 0 ? ` via ${flow.intermediates.map((i) => i.variable).join(" → ")}` : "";
123
+ descriptionSuffix = `\n\n**Confirmed data flow**: \`${flow.source.expression}\` (line ${flow.source.line})${via} → sink at line ${flow.sink.line}`;
124
+ }
125
+ }
126
+ if (confidenceAdj !== 0 || descriptionSuffix) {
127
+ const currentConf = f.confidence ?? 0.5;
128
+ return {
129
+ ...f,
130
+ confidence: clampConfidence(currentConf + confidenceAdj),
131
+ ...(descriptionSuffix ? { description: (f.description ?? "") + descriptionSuffix } : {}),
132
+ };
133
+ }
134
+ return f;
73
135
  });
74
- const matchedRuleIds = [...new Set(matched.map((finding) => finding.ruleId))];
75
- const triggered = matched.length > 0;
76
- return {
77
- enabled: true,
78
- triggered,
79
- minConfidence,
80
- matchedCount: matched.length,
81
- matchedRuleIds,
82
- summary: triggered
83
- ? `Must-fix gate triggered by ${matched.length} high-confidence dangerous finding(s).`
84
- : "Must-fix gate passed with no high-confidence dangerous findings.",
85
- };
86
- }
87
- function clampConfidence(value) {
88
- if (!Number.isFinite(value))
89
- return 0;
90
- return Math.max(0, Math.min(1, value));
91
136
  }
92
- function estimateFindingConfidence(finding) {
93
- const existing = typeof finding.confidence === "number" ? finding.confidence : undefined;
94
- if (typeof existing === "number" && Number.isFinite(existing)) {
95
- return clampConfidence(existing);
96
- }
97
- let score = 0.4;
98
- const lineCount = finding.lineNumbers?.length ?? 0;
99
- if (lineCount === 0) {
100
- score -= 0.12;
101
- }
102
- else if (lineCount <= 3) {
103
- score += 0.22;
104
- }
105
- else if (lineCount <= 8) {
106
- score += 0.14;
107
- }
108
- else {
109
- score += 0.06;
110
- }
111
- const hasReference = Boolean(finding.reference);
112
- const hasSuggestedFix = Boolean(finding.suggestedFix);
113
- const hasRichDescription = finding.description.length >= 120;
114
- const hasRichRecommendation = finding.recommendation.length >= 90;
115
- if (hasReference)
116
- score += 0.1;
117
- if (hasSuggestedFix)
118
- score += 0.12;
119
- if (hasRichDescription)
120
- score += 0.05;
121
- if (hasRichRecommendation)
122
- score += 0.05;
123
- const richEvidenceCount = [
124
- hasReference,
125
- hasSuggestedFix,
126
- hasRichDescription,
127
- hasRichRecommendation,
128
- ].filter(Boolean).length;
129
- if (lineCount > 0 && richEvidenceCount >= 3) {
130
- score += 0.08;
131
- }
132
- if (lineCount > 0 && richEvidenceCount === 4) {
133
- score += 0.05;
134
- }
135
- const noisyPrefixes = [
136
- "API-",
137
- "COMP-",
138
- "CONC-",
139
- "CYBER-",
140
- "DB-",
141
- "DEPS-",
142
- "ETHICS-",
143
- "LOGPRIV-",
144
- "OBS-",
145
- "PERF-",
146
- ];
147
- if (noisyPrefixes.some((prefix) => finding.ruleId.startsWith(prefix)) && richEvidenceCount < 4) {
148
- score = Math.min(score, 0.89);
137
+ /**
138
+ * Find taint flows that confirm a given finding.
139
+ * Matches by checking if any of the finding's referenced lines correspond to
140
+ * a taint sink and the sink kind matches the finding's topic.
141
+ */
142
+ function findMatchingTaintFlows(finding, flowsBySinkLine) {
143
+ if (!finding.lineNumbers || finding.lineNumbers.length === 0)
144
+ return [];
145
+ const title = (finding.title + " " + (finding.ruleId ?? "")).toLowerCase();
146
+ const matched = [];
147
+ for (const line of finding.lineNumbers) {
148
+ const flows = flowsBySinkLine.get(line);
149
+ if (!flows)
150
+ continue;
151
+ for (const flow of flows) {
152
+ const pattern = TAINT_SINK_TO_FINDING[flow.sink.kind];
153
+ if (pattern && pattern.test(title)) {
154
+ matched.push(flow);
155
+ }
156
+ }
149
157
  }
150
- return Number(clampConfidence(score).toFixed(2));
158
+ return matched;
151
159
  }
152
- function applyConfidenceThreshold(findings, options) {
153
- const minConfidence = clampConfidence(options?.minConfidence ?? 0);
154
- const normalized = findings.map((finding) => ({
155
- ...finding,
156
- confidence: estimateFindingConfidence(finding),
157
- }));
158
- if (minConfidence <= 0) {
159
- return normalized;
160
+ // ── Inline suppression comment support ──────────────────────────────────────
161
+ /**
162
+ * Scan source code for inline `// judges-ignore RULE-ID` or
163
+ * `// judges-ignore-next-line RULE-ID` comments. Returns a set of suppressed
164
+ * {ruleId, line} pairs and a set of globally suppressed rule IDs.
165
+ */
166
+ function parseInlineSuppressions(code) {
167
+ const lines = code.split("\n");
168
+ const lineSuppressed = new Map();
169
+ const globalSuppressed = new Set();
170
+ // Pattern: // judges-ignore RULE-ID [, RULE-ID ...]
171
+ // // judges-ignore-next-line RULE-ID [, RULE-ID ...]
172
+ // # judges-ignore RULE-ID (Python, YAML, etc.)
173
+ const suppressPattern = /(?:\/\/|#|\/\*)\s*judges-ignore(?:-next-line)?\s+([\w*,\s-]+)/gi;
174
+ const isNextLine = /judges-ignore-next-line/i;
175
+ for (let i = 0; i < lines.length; i++) {
176
+ const line = lines[i];
177
+ let match;
178
+ suppressPattern.lastIndex = 0;
179
+ while ((match = suppressPattern.exec(line)) !== null) {
180
+ const ruleIds = match[1].split(/[,\s]+/).filter(Boolean);
181
+ const targetLine = isNextLine.test(match[0]) ? i + 2 : i + 1; // 1-indexed
182
+ for (const ruleId of ruleIds) {
183
+ if (ruleId === "*") {
184
+ // Wildcard: suppress all rules on this line
185
+ const set = lineSuppressed.get(targetLine) ?? new Set();
186
+ set.add("*");
187
+ lineSuppressed.set(targetLine, set);
188
+ }
189
+ else {
190
+ const set = lineSuppressed.get(targetLine) ?? new Set();
191
+ set.add(ruleId.toUpperCase());
192
+ lineSuppressed.set(targetLine, set);
193
+ }
194
+ }
195
+ }
196
+ // File-level suppression: // judges-file-ignore RULE-ID
197
+ const filePattern = /(?:\/\/|#|\/\*)\s*judges-file-ignore\s+([\w*,\s-]+)/gi;
198
+ let fileMatch;
199
+ filePattern.lastIndex = 0;
200
+ while ((fileMatch = filePattern.exec(line)) !== null) {
201
+ const ruleIds = fileMatch[1].split(/[,\s]+/).filter(Boolean);
202
+ for (const ruleId of ruleIds) {
203
+ globalSuppressed.add(ruleId === "*" ? "*" : ruleId.toUpperCase());
204
+ }
205
+ }
160
206
  }
161
- return normalized.filter((finding) => (finding.confidence ?? 0) >= minConfidence);
207
+ return { lineSuppressed, globalSuppressed };
208
+ }
209
+ /**
210
+ * Filter findings based on inline suppression comments in the source code.
211
+ */
212
+ export function applyInlineSuppressions(findings, code) {
213
+ const { lineSuppressed, globalSuppressed } = parseInlineSuppressions(code);
214
+ if (lineSuppressed.size === 0 && globalSuppressed.size === 0) {
215
+ return findings;
216
+ }
217
+ return findings.filter((f) => {
218
+ const ruleUpper = f.ruleId.toUpperCase();
219
+ // Check file-level suppression
220
+ if (globalSuppressed.has("*") || globalSuppressed.has(ruleUpper)) {
221
+ return false;
222
+ }
223
+ // Check prefix wildcards: "AUTH-*" suppresses "AUTH-001"
224
+ for (const suppressed of globalSuppressed) {
225
+ if (suppressed.endsWith("-*") && ruleUpper.startsWith(suppressed.slice(0, -1))) {
226
+ return false;
227
+ }
228
+ }
229
+ // Check line-level suppressions
230
+ if (f.lineNumbers && f.lineNumbers.length > 0) {
231
+ for (const lineNum of f.lineNumbers) {
232
+ const suppressed = lineSuppressed.get(lineNum);
233
+ if (suppressed) {
234
+ if (suppressed.has("*") || suppressed.has(ruleUpper)) {
235
+ return false;
236
+ }
237
+ for (const s of suppressed) {
238
+ if (s.endsWith("-*") && ruleUpper.startsWith(s.slice(0, -1))) {
239
+ return false;
240
+ }
241
+ }
242
+ }
243
+ }
244
+ }
245
+ return true;
246
+ });
162
247
  }
163
248
  function resolveJudgeSet(options) {
164
249
  const includeAstFindings = options?.includeAstFindings ?? true;
165
- if (includeAstFindings) {
166
- return JUDGES;
250
+ let judges = includeAstFindings ? JUDGES : JUDGES.filter((judge) => judge.id !== "code-structure");
251
+ // Apply config-based judge filtering
252
+ if (options?.config?.disabledJudges && options.config.disabledJudges.length > 0) {
253
+ const disabled = new Set(options.config.disabledJudges);
254
+ judges = judges.filter((j) => !disabled.has(j.id));
167
255
  }
168
- return JUDGES.filter((judge) => judge.id !== "code-structure");
256
+ return judges;
169
257
  }
170
258
  /**
171
259
  * Run a single judge against the provided code.
172
260
  */
173
261
  export function evaluateWithJudge(judge, code, language, context, options) {
174
262
  const findings = [];
175
- switch (judge.id) {
176
- case "data-security":
177
- findings.push(...analyzeDataSecurity(code, language));
178
- break;
179
- case "cybersecurity":
180
- findings.push(...analyzeCybersecurity(code, language));
181
- break;
182
- case "cost-effectiveness":
183
- findings.push(...analyzeCostEffectiveness(code, language));
184
- break;
185
- case "scalability":
186
- findings.push(...analyzeScalability(code, language));
187
- break;
188
- case "cloud-readiness":
189
- findings.push(...analyzeCloudReadiness(code, language));
190
- break;
191
- case "software-practices":
192
- findings.push(...analyzeSoftwarePractices(code, language));
193
- break;
194
- case "accessibility":
195
- findings.push(...analyzeAccessibility(code, language));
196
- break;
197
- case "api-design":
198
- findings.push(...analyzeApiDesign(code, language));
199
- break;
200
- case "reliability":
201
- findings.push(...analyzeReliability(code, language));
202
- break;
203
- case "observability":
204
- findings.push(...analyzeObservability(code, language));
205
- break;
206
- case "performance":
207
- findings.push(...analyzePerformance(code, language));
208
- break;
209
- case "compliance":
210
- findings.push(...analyzeCompliance(code, language));
211
- break;
212
- case "data-sovereignty":
213
- findings.push(...analyzeDataSovereignty(code, language));
214
- break;
215
- case "testing":
216
- findings.push(...analyzeTesting(code, language));
217
- break;
218
- case "documentation":
219
- findings.push(...analyzeDocumentation(code, language));
220
- break;
221
- case "internationalization":
222
- findings.push(...analyzeInternationalization(code, language));
223
- break;
224
- case "dependency-health":
225
- findings.push(...analyzeDependencyHealth(code, language));
226
- break;
227
- case "concurrency":
228
- findings.push(...analyzeConcurrency(code, language));
229
- break;
230
- case "ethics-bias":
231
- findings.push(...analyzeEthicsBias(code, language));
232
- break;
233
- case "maintainability":
234
- findings.push(...analyzeMaintainability(code, language));
235
- break;
236
- case "error-handling":
237
- findings.push(...analyzeErrorHandling(code, language));
238
- break;
239
- case "authentication":
240
- findings.push(...analyzeAuthentication(code, language));
241
- break;
242
- case "database":
243
- findings.push(...analyzeDatabase(code, language));
244
- break;
245
- case "caching":
246
- findings.push(...analyzeCaching(code, language));
247
- break;
248
- case "configuration-management":
249
- findings.push(...analyzeConfigurationManagement(code, language));
250
- break;
251
- case "backwards-compatibility":
252
- findings.push(...analyzeBackwardsCompatibility(code, language));
253
- break;
254
- case "portability":
255
- findings.push(...analyzePortability(code, language));
256
- break;
257
- case "ux":
258
- findings.push(...analyzeUx(code, language));
259
- break;
260
- case "logging-privacy":
261
- findings.push(...analyzeLoggingPrivacy(code, language));
262
- break;
263
- case "rate-limiting":
264
- findings.push(...analyzeRateLimiting(code, language));
265
- break;
266
- case "ci-cd":
267
- findings.push(...analyzeCiCd(code, language));
268
- break;
269
- case "code-structure":
270
- findings.push(...analyzeCodeStructure(code, language));
271
- break;
272
- case "agent-instructions":
273
- findings.push(...analyzeAgentInstructions(code, language));
274
- break;
275
- case "ai-code-safety":
276
- findings.push(...analyzeAiCodeSafety(code, language));
277
- break;
278
- }
279
- const filteredFindings = applyConfidenceThreshold(findings, options);
280
- const score = calculateScore(filteredFindings);
281
- const verdict = deriveVerdict(filteredFindings, score);
282
- const summary = buildSummary(judge, filteredFindings, score, verdict);
263
+ // ── Registry-based dispatch: each judge carries its own analyze() method ──
264
+ if (judge.analyze) {
265
+ findings.push(...judge.analyze(code, language));
266
+ }
267
+ // ── File-type gating: suppress absence-based findings on non-server files ──
268
+ const fileCategory = classifyFile(code, language, options?.filePath);
269
+ const gatedFindings = shouldRunAbsenceRules(fileCategory)
270
+ ? findings
271
+ : findings.filter((f) => !isAbsenceBasedFinding(f));
272
+ // ── AST-aware refinements: dead code removal, scope context, import awareness, taint flows ──
273
+ const astStructure = options?._astCache;
274
+ const refinedFindings = astStructure
275
+ ? applyAstRefinements(gatedFindings, astStructure, options?._taintFlows)
276
+ : gatedFindings;
277
+ // ── Inline suppression: respect // judges-ignore RULE-ID comments ──
278
+ const unsuppressed = applyInlineSuppressions(refinedFindings, code);
279
+ // ── Auto-fix patches: attach machine-applicable patches where possible ──
280
+ const patchEnriched = enrichWithPatches(unsuppressed, code);
281
+ const filteredFindings = applyConfidenceThreshold(patchEnriched, options);
282
+ const configFiltered = applyConfig(filteredFindings, options?.config);
283
+ const score = calculateScore(configFiltered, code);
284
+ const verdict = deriveVerdict(configFiltered, score);
285
+ const summary = buildSummary(judge, configFiltered, score, verdict);
283
286
  return {
284
287
  judgeId: judge.id,
285
288
  judgeName: judge.name,
286
289
  verdict,
287
290
  score,
288
291
  summary,
289
- findings: filteredFindings,
292
+ findings: configFiltered,
290
293
  };
291
294
  }
292
295
  /**
293
296
  * Run the full tribunal — all judges evaluate the code.
294
297
  */
295
298
  export function evaluateWithTribunal(code, language, context, options) {
296
- const judges = resolveJudgeSet(options);
297
- const evaluations = judges.map((judge) => evaluateWithJudge(judge, code, language, context, options));
299
+ // Compute AST once and share across all judges via options
300
+ const includeAst = options?.includeAstFindings ?? true;
301
+ const enrichedOptions = {
302
+ ...options,
303
+ ...(includeAst && !options?._astCache ? { _astCache: analyzeStructure(code, language) } : {}),
304
+ ...(!options?._taintFlows ? { _taintFlows: analyzeTaintFlows(code, language) } : {}),
305
+ };
306
+ const judges = resolveJudgeSet(enrichedOptions);
307
+ const evaluations = judges.map((judge) => evaluateWithJudge(judge, code, language, context, enrichedOptions));
298
308
  const overallScore = Math.round(evaluations.reduce((sum, e) => sum + e.score, 0) / evaluations.length);
299
309
  const overallVerdict = evaluations.some((e) => e.verdict === "fail")
300
310
  ? "fail"
301
311
  : evaluations.some((e) => e.verdict === "warning")
302
312
  ? "warning"
303
313
  : "pass";
304
- const allFindings = evaluations.flatMap((e) => e.findings);
314
+ const rawFindings = evaluations.flatMap((e) => e.findings);
315
+ const dedupedFindings = crossEvaluatorDedup(rawFindings);
316
+ const allFindings = applyConfig(dedupedFindings, options?.config);
305
317
  const mustFixGate = evaluateMustFixGate(allFindings, options?.mustFixGate);
306
318
  const criticalCount = allFindings.filter((f) => f.severity === "critical").length;
307
319
  const highCount = allFindings.filter((f) => f.severity === "high").length;
308
320
  const effectiveVerdict = mustFixGate?.triggered ? "fail" : overallVerdict;
309
- const summary = buildTribunalSummary(evaluations, effectiveVerdict, overallScore, criticalCount, highCount) + (mustFixGate
310
- ? `\n\n## Must-Fix Gate\n\n- Status: **${mustFixGate.triggered ? "TRIGGERED" : "PASS"}**\n- Minimum confidence: **${Math.round(mustFixGate.minConfidence * 100)}%**\n- Matched findings: **${mustFixGate.matchedCount}**\n- Matched rule IDs: ${mustFixGate.matchedRuleIds.length > 0 ? mustFixGate.matchedRuleIds.map((id) => `\`${id}\``).join(", ") : "none"}\n`
311
- : "");
321
+ const summary = buildTribunalSummary(evaluations, effectiveVerdict, overallScore, criticalCount, highCount) +
322
+ (mustFixGate
323
+ ? `\n\n## Must-Fix Gate\n\n- Status: **${mustFixGate.triggered ? "TRIGGERED" : "PASS"}**\n- Minimum confidence: **${Math.round(mustFixGate.minConfidence * 100)}%**\n- Matched findings: **${mustFixGate.matchedCount}**\n- Matched rule IDs: ${mustFixGate.matchedRuleIds.length > 0 ? mustFixGate.matchedRuleIds.map((id) => `\`${id}\``).join(", ") : "none"}\n`
324
+ : "");
312
325
  return {
313
326
  overallVerdict: effectiveVerdict,
314
327
  overallScore,
315
328
  summary,
316
329
  evaluations,
330
+ findings: allFindings,
317
331
  criticalCount,
318
332
  highCount,
319
333
  timestamp: new Date().toISOString(),
320
334
  mustFixGate,
321
335
  };
322
336
  }
323
- // ─── Project-level Multi-file Analysis ────────────────────────────────────────
324
- /**
325
- * Evaluate multiple files as a project. Runs the full tribunal on each file,
326
- * then detects cross-file architectural issues.
327
- */
337
+ // ─── Project-level Multi-file Analysis (delegated to project.ts) ─────────────
338
+ import { evaluateProject as _evaluateProject } from "./project.js";
328
339
  export function evaluateProject(files, context, options) {
329
- // Per-file evaluations
330
- const fileResults = files.map((f) => {
331
- const verdict = evaluateWithTribunal(f.content, f.language, context, options);
332
- return {
333
- path: f.path,
334
- language: f.language,
335
- findings: verdict.evaluations.flatMap((e) => e.findings),
336
- score: verdict.overallScore,
337
- };
338
- });
339
- // Cross-file architectural findings
340
- const architecturalFindings = [];
341
- const allCode = files.map((f) => f.content).join("\n");
342
- let archRule = 1;
343
- // Check for duplicated logic across files
344
- const functionDefs = new Map();
345
- for (const f of files) {
346
- const fns = f.content.match(/(?:function|def|fn|func)\s+(\w+)/g) ?? [];
347
- for (const fn of fns) {
348
- const name = fn.replace(/(?:function|def|fn|func)\s+/, "");
349
- const paths = functionDefs.get(name) ?? [];
350
- paths.push(f.path);
351
- functionDefs.set(name, paths);
352
- }
353
- }
354
- const duplicated = [...functionDefs.entries()].filter(([, paths]) => paths.length > 1);
355
- if (duplicated.length > 0) {
356
- architecturalFindings.push({
357
- ruleId: `ARCH-${String(archRule++).padStart(3, "0")}`,
358
- severity: "medium",
359
- title: "Potentially duplicated function names across files",
360
- description: `Functions with identical names found in multiple files: ${duplicated.slice(0, 5).map(([name, paths]) => `${name} (${paths.join(", ")})`).join("; ")}. This may indicate code duplication.`,
361
- recommendation: "Extract shared logic into a common module and import it where needed.",
362
- });
363
- }
364
- // Check for inconsistent error handling patterns
365
- const errorPatterns = files.map((f) => ({
366
- path: f.path,
367
- hasTryCatch: /try\s*\{/.test(f.content),
368
- hasResultType: /Result<|Result\(|Either/.test(f.content),
369
- hasExceptions: /throw\s+new|raise\s+|panic!/.test(f.content),
370
- }));
371
- const distinctPatterns = new Set(errorPatterns.map((e) => [e.hasTryCatch, e.hasResultType, e.hasExceptions].toString()));
372
- if (distinctPatterns.size > 1 && files.length > 2) {
373
- architecturalFindings.push({
374
- ruleId: `ARCH-${String(archRule++).padStart(3, "0")}`,
375
- severity: "low",
376
- title: "Inconsistent error handling patterns across files",
377
- description: "Different files use different error handling approaches (try/catch vs Result types vs raw throws). This makes the codebase harder to reason about.",
378
- recommendation: "Standardize on a single error handling strategy across the project.",
379
- });
380
- }
381
- const filteredArchitecturalFindings = applyConfidenceThreshold(architecturalFindings, options);
382
- // Check for circular-looking dependency indicators
383
- const importMap = new Map();
384
- for (const f of files) {
385
- const imports = f.content.match(/(?:import|from|require)\s*[\s(]['"]\.{1,2}\/([^'"]+)['"]/g) ?? [];
386
- importMap.set(f.path, imports.map((i) => i.replace(/.*['"]\.{1,2}\/([^'"]+)['"].*/, "$1")));
387
- }
388
- // Overall scores
389
- const allFindings = fileResults.flatMap((f) => f.findings);
390
- const crossFindings = [...allFindings, ...filteredArchitecturalFindings];
391
- const overallScore = fileResults.length > 0
392
- ? Math.round(fileResults.reduce((sum, f) => sum + f.score, 0) /
393
- fileResults.length)
394
- : 100;
395
- const criticalCount = crossFindings.filter((f) => f.severity === "critical").length;
396
- const highCount = crossFindings.filter((f) => f.severity === "high").length;
397
- const overallVerdict = criticalCount > 0 || overallScore < 60
398
- ? "fail"
399
- : highCount > 0 || overallScore < 80
400
- ? "warning"
401
- : "pass";
402
- const summary = `Project analysis: ${files.length} files, ${crossFindings.length} findings, score ${overallScore}/100 — ${overallVerdict.toUpperCase()}`;
403
- return {
404
- overallVerdict,
405
- overallScore,
406
- summary,
407
- evaluations: [],
408
- criticalCount,
409
- highCount,
410
- timestamp: new Date().toISOString(),
411
- fileResults,
412
- architecturalFindings: filteredArchitecturalFindings,
413
- };
340
+ const runner = { evaluateWithTribunal };
341
+ return _evaluateProject(runner, files, context, options);
414
342
  }
415
343
  // ─── Diff-based Incremental Analysis ──────────────────────────────────────────
416
344
  /**
@@ -419,7 +347,7 @@ export function evaluateProject(files, context, options) {
419
347
  */
420
348
  export function evaluateDiff(code, language, changedLines, context, options) {
421
349
  const verdict = evaluateWithTribunal(code, language, context, options);
422
- const allFindings = verdict.evaluations.flatMap((e) => e.findings);
350
+ const allFindings = verdict.findings;
423
351
  // Filter findings to only those touching changed lines
424
352
  const changedSet = new Set(changedLines);
425
353
  const diffFindings = allFindings.filter((f) => {
@@ -427,7 +355,7 @@ export function evaluateDiff(code, language, changedLines, context, options) {
427
355
  return false;
428
356
  return f.lineNumbers.some((ln) => changedSet.has(ln));
429
357
  });
430
- const score = calculateScore(diffFindings);
358
+ const score = calculateScore(diffFindings, code);
431
359
  const diffVerdict = deriveVerdict(diffFindings, score);
432
360
  return {
433
361
  linesAnalyzed: changedLines.length,
@@ -437,378 +365,16 @@ export function evaluateDiff(code, language, changedLines, context, options) {
437
365
  summary: `Diff analysis: ${changedLines.length} changed lines, ${diffFindings.length} findings in changed code, score ${score}/100 — ${diffVerdict.toUpperCase()}`,
438
366
  };
439
367
  }
440
- // ─── Dependency / Supply-chain Analysis ───────────────────────────────────────
441
- /**
442
- * Parse a manifest file and analyze dependencies for supply-chain risks.
443
- */
444
- export function analyzeDependencies(manifest, manifestType) {
445
- const dependencies = [];
446
- const findings = [];
447
- let ruleNum = 1;
448
- const prefix = "SUPPLY";
449
- // Parse manifest based on type
450
- if (manifestType === "package.json") {
451
- try {
452
- const pkg = JSON.parse(manifest);
453
- for (const [name, version] of Object.entries(pkg.dependencies ?? {})) {
454
- dependencies.push({
455
- name,
456
- version: String(version),
457
- isDev: false,
458
- source: manifestType,
459
- });
460
- }
461
- for (const [name, version] of Object.entries(pkg.devDependencies ?? {})) {
462
- dependencies.push({
463
- name,
464
- version: String(version),
465
- isDev: true,
466
- source: manifestType,
467
- });
468
- }
469
- }
470
- catch {
471
- findings.push({
472
- ruleId: `${prefix}-${String(ruleNum++).padStart(3, "0")}`,
473
- severity: "high",
474
- title: "Invalid package.json",
475
- description: "Failed to parse package.json. The file may be malformed.",
476
- recommendation: "Validate and fix the JSON structure.",
477
- });
478
- }
479
- }
480
- else if (manifestType === "requirements.txt") {
481
- for (const line of manifest.split("\n")) {
482
- const trimmed = line.trim();
483
- if (!trimmed || trimmed.startsWith("#"))
484
- continue;
485
- const match = trimmed.match(/^([a-zA-Z0-9_-]+)\s*(?:[>=<~!]+\s*(.+))?$/);
486
- if (match) {
487
- dependencies.push({
488
- name: match[1],
489
- version: match[2] ?? "*",
490
- isDev: false,
491
- source: manifestType,
492
- });
493
- }
494
- }
495
- }
496
- else if (manifestType === "Cargo.toml") {
497
- // Match [dependencies] section up to the next [section] header or EOF
498
- const depSection = manifest.match(/\[dependencies\]\s*\n([\s\S]*?)(?=\n\s*\[|\s*$)/)?.[1];
499
- if (depSection) {
500
- for (const line of depSection.split("\n")) {
501
- // Simple: name = "version"
502
- const simple = line.match(/^(\w[\w-]*)\s*=\s*"([^"]+)"/);
503
- if (simple) {
504
- dependencies.push({
505
- name: simple[1],
506
- version: simple[2],
507
- isDev: false,
508
- source: manifestType,
509
- });
510
- continue;
511
- }
512
- // Inline table: name = { version = "...", ... }
513
- const table = line.match(/^(\w[\w-]*)\s*=\s*\{[^}]*version\s*=\s*"([^"]+)"/);
514
- if (table) {
515
- dependencies.push({
516
- name: table[1],
517
- version: table[2],
518
- isDev: false,
519
- source: manifestType,
520
- });
521
- }
522
- }
523
- }
524
- }
525
- else if (manifestType === "go.mod") {
526
- for (const line of manifest.split("\n")) {
527
- const match = line
528
- .trim()
529
- .match(/^([\w./\-@]+)\s+(v[\d.]+(?:-[\w.]+)?)/);
530
- if (match) {
531
- dependencies.push({
532
- name: match[1],
533
- version: match[2],
534
- isDev: false,
535
- source: manifestType,
536
- });
537
- }
538
- }
539
- }
540
- else if (manifestType === "pom.xml") {
541
- const depRegex = /<dependency>[\s\S]*?<groupId>([^<]+)<\/groupId>[\s\S]*?<artifactId>([^<]+)<\/artifactId>[\s\S]*?(?:<version>([^<]*)<\/version>)?[\s\S]*?<\/dependency>/g;
542
- let m;
543
- while ((m = depRegex.exec(manifest)) !== null) {
544
- dependencies.push({
545
- name: `${m[1]}:${m[2]}`,
546
- version: m[3] ?? "managed",
547
- isDev: false,
548
- source: manifestType,
549
- });
550
- }
551
- }
552
- else if (manifestType === "csproj") {
553
- const pkgRegex = /<PackageReference\s+Include="([^"]+)"\s+Version="([^"]*)"/g;
554
- let m;
555
- while ((m = pkgRegex.exec(manifest)) !== null) {
556
- dependencies.push({
557
- name: m[1],
558
- version: m[2],
559
- isDev: false,
560
- source: manifestType,
561
- });
562
- }
563
- }
564
- // Supply-chain analysis rules
565
- // Wildcard / unpinned versions
566
- const unpinned = dependencies.filter((d) => d.version === "*" ||
567
- d.version === "latest" ||
568
- /^\^/.test(d.version) ||
569
- /^~/.test(d.version) ||
570
- />=/.test(d.version));
571
- if (unpinned.length > 0) {
572
- findings.push({
573
- ruleId: `${prefix}-${String(ruleNum++).padStart(3, "0")}`,
574
- severity: "medium",
575
- title: "Unpinned dependency versions",
576
- description: `${unpinned.length} dependencies use unpinned/loose version ranges: ${unpinned.slice(0, 5).map((d) => `${d.name}@${d.version}`).join(", ")}. This can lead to unexpected breaking changes and supply-chain attacks.`,
577
- recommendation: "Pin dependencies to exact versions or use a lockfile (package-lock.json, Cargo.lock, go.sum).",
578
- reference: "Supply Chain Security Best Practices",
579
- });
580
- }
581
- // Too many dependencies
582
- if (dependencies.filter((d) => !d.isDev).length > 50) {
583
- findings.push({
584
- ruleId: `${prefix}-${String(ruleNum++).padStart(3, "0")}`,
585
- severity: "low",
586
- title: "Large number of production dependencies",
587
- description: `${dependencies.filter((d) => !d.isDev).length} production dependencies detected. Each dependency increases attack surface and maintenance burden.`,
588
- recommendation: "Audit dependencies regularly. Remove unused packages. Consider inlining small utilities.",
589
- reference: "Dependency Minimization Best Practices",
590
- });
591
- }
592
- // Known risky package name patterns (typosquatting indicators)
593
- const knownPrefixes = [
594
- "lodash",
595
- "express",
596
- "react",
597
- "vue",
598
- "angular",
599
- "axios",
600
- "moment",
601
- ];
602
- const suspicious = dependencies.filter((d) => knownPrefixes.some((p) => d.name !== p &&
603
- d.name.startsWith(p) &&
604
- d.name.length <= p.length + 3));
605
- if (suspicious.length > 0) {
606
- findings.push({
607
- ruleId: `${prefix}-${String(ruleNum++).padStart(3, "0")}`,
608
- severity: "high",
609
- title: "Potentially typosquatted package names",
610
- description: `Suspicious package names detected that are similar to popular packages: ${suspicious.map((d) => d.name).join(", ")}. These may be typosquatting attempts.`,
611
- recommendation: "Verify these package names are intentional and not typos of well-known packages.",
612
- reference: "NPM Typosquatting / Supply Chain Attacks",
613
- });
614
- }
615
- // Dev dependencies in production flag
616
- const devInProd = dependencies.filter((d) => !d.isDev &&
617
- /test|jest|mocha|chai|sinon|eslint|prettier|typescript|ts-node|nodemon/i.test(d.name));
618
- if (devInProd.length > 0) {
619
- findings.push({
620
- ruleId: `${prefix}-${String(ruleNum++).padStart(3, "0")}`,
621
- severity: "medium",
622
- title: "Development tools in production dependencies",
623
- description: `The following look like dev tools but are listed as production dependencies: ${devInProd.map((d) => d.name).join(", ")}. This inflates deployment size and attack surface.`,
624
- recommendation: "Move development tools to devDependencies (or equivalent dev scope).",
625
- });
626
- }
627
- // No lockfile hint
628
- if (manifestType === "package.json" &&
629
- !manifest.includes("lockfileVersion")) {
630
- findings.push({
631
- ruleId: `${prefix}-${String(ruleNum++).padStart(3, "0")}`,
632
- severity: "info",
633
- title: "Reminder: ensure a lockfile is committed",
634
- description: "This analysis is based on the manifest. Ensure a lockfile (package-lock.json, yarn.lock) is committed for reproducible builds.",
635
- recommendation: "Commit your lockfile to version control. Run npm ci in CI/CD instead of npm install.",
636
- });
637
- }
638
- const score = calculateScore(findings);
639
- const verdict = deriveVerdict(findings, score);
640
- return {
641
- totalDependencies: dependencies.length,
642
- findings,
643
- dependencies,
644
- score,
645
- verdict,
646
- summary: `Dependency analysis: ${dependencies.length} dependencies, ${findings.length} findings, score ${score}/100 — ${verdict.toUpperCase()}`,
647
- };
648
- }
368
+ // ─── Dependency / Supply-chain Analysis (delegated to dependencies.ts) ───────
369
+ export { analyzeDependencies } from "./dependencies.js";
649
370
  // ─── App Builder Flow (Review → Translate → Task Plan) ─────────────────────
650
- function severityRank(severity) {
651
- switch (severity) {
652
- case "critical":
653
- return 5;
654
- case "high":
655
- return 4;
656
- case "medium":
657
- return 3;
658
- case "low":
659
- return 2;
660
- case "info":
661
- return 1;
662
- }
663
- }
664
- function dedupeFindings(findings) {
665
- const seen = new Set();
666
- const result = [];
667
- for (const finding of findings) {
668
- const key = `${finding.ruleId}|${finding.title}|${finding.severity}`;
669
- if (seen.has(key))
670
- continue;
671
- seen.add(key);
672
- result.push(finding);
673
- }
674
- return result;
675
- }
676
- function decideRelease(criticalCount, highCount, score) {
677
- if (criticalCount > 0 || score < 60)
678
- return "do-not-ship";
679
- if (highCount > 0 || score < 80)
680
- return "ship-with-caution";
681
- return "ship-now";
682
- }
683
- function toPlainLanguageFinding(finding) {
684
- const severityImpact = {
685
- critical: "This can directly cause security incidents, outages, or serious compliance exposure.",
686
- high: "This is likely to impact users or operations if left unresolved.",
687
- medium: "This can create reliability, maintainability, or quality issues over time.",
688
- low: "This is a quality improvement that reduces friction and future rework.",
689
- info: "This is guidance to strengthen consistency and engineering hygiene.",
690
- };
691
- return {
692
- ruleId: finding.ruleId,
693
- severity: finding.severity,
694
- title: finding.title,
695
- whatIsWrong: `${finding.title}: ${finding.description}`,
696
- whyItMatters: severityImpact[finding.severity],
697
- nextAction: finding.recommendation,
698
- };
699
- }
700
- function pickOwner(finding) {
701
- if (/^(UX|A11Y|I18N|ETHICS|COMPAT)-/.test(finding.ruleId))
702
- return "product";
703
- if (/^(DOC|TEST|MAINT|ERR|CFG)-/.test(finding.ruleId))
704
- return "ai";
705
- return "developer";
706
- }
707
- function pickPriority(severity) {
708
- if (severity === "critical")
709
- return "P0";
710
- if (severity === "high")
711
- return "P1";
712
- return "P2";
713
- }
714
- function pickEffort(finding) {
715
- if (finding.severity === "critical")
716
- return "L";
717
- if (finding.severity === "high")
718
- return "M";
719
- return finding.lineNumbers && finding.lineNumbers.length > 3 ? "M" : "S";
720
- }
721
- function toWorkflowTask(finding) {
722
- const owner = pickOwner(finding);
723
- const priority = pickPriority(finding.severity);
724
- const effort = pickEffort(finding);
725
- const aiFixable = owner !== "product";
726
- return {
727
- priority,
728
- owner,
729
- effort,
730
- ruleId: finding.ruleId,
731
- task: `${finding.title} — ${finding.recommendation}`,
732
- doneWhen: `A follow-up review no longer reports ${finding.ruleId} and related tests/checks pass.`,
733
- aiFixable,
734
- };
735
- }
371
+ import { runAppBuilderWorkflow as _runAppBuilderWorkflow } from "./app-builder.js";
372
+ const engine = { evaluateWithTribunal, evaluateProject, evaluateDiff };
736
373
  export function runAppBuilderWorkflow(params) {
737
- const maxFindings = Math.max(1, params.maxFindings ?? 10);
738
- const maxTasks = Math.max(1, params.maxTasks ?? 20);
739
- let mode;
740
- let verdict;
741
- let score;
742
- let findings;
743
- if (params.files && params.files.length > 0) {
744
- mode = "project";
745
- const result = evaluateProject(params.files, params.context, {
746
- includeAstFindings: params.includeAstFindings,
747
- minConfidence: params.minConfidence,
748
- });
749
- verdict = result.overallVerdict;
750
- score = result.overallScore;
751
- findings = [
752
- ...result.fileResults.flatMap((fr) => fr.findings),
753
- ...result.architecturalFindings,
754
- ];
755
- }
756
- else if (params.changedLines && params.changedLines.length > 0) {
757
- if (!params.code || !params.language) {
758
- throw new Error("changedLines mode requires both code and language inputs");
759
- }
760
- mode = "diff";
761
- const result = evaluateDiff(params.code, params.language, params.changedLines, params.context, {
762
- includeAstFindings: params.includeAstFindings,
763
- minConfidence: params.minConfidence,
764
- });
765
- verdict = result.verdict;
766
- score = result.score;
767
- findings = result.findings;
768
- }
769
- else {
770
- if (!params.code || !params.language) {
771
- throw new Error("code mode requires both code and language, or provide files for project mode");
772
- }
773
- mode = "code";
774
- const result = evaluateWithTribunal(params.code, params.language, params.context, {
775
- includeAstFindings: params.includeAstFindings,
776
- minConfidence: params.minConfidence,
777
- });
778
- verdict = result.overallVerdict;
779
- score = result.overallScore;
780
- findings = result.evaluations.flatMap((evaluation) => evaluation.findings);
781
- }
782
- const dedupedFindings = dedupeFindings(findings).sort((a, b) => severityRank(b.severity) - severityRank(a.severity));
783
- const criticalCount = dedupedFindings.filter((finding) => finding.severity === "critical").length;
784
- const highCount = dedupedFindings.filter((finding) => finding.severity === "high").length;
785
- const mediumCount = dedupedFindings.filter((finding) => finding.severity === "medium").length;
786
- const releaseDecision = decideRelease(criticalCount, highCount, score);
787
- const topFindings = dedupedFindings
788
- .filter((finding) => ["critical", "high", "medium"].includes(finding.severity))
789
- .slice(0, maxFindings);
790
- const plainLanguageFindings = topFindings.map(toPlainLanguageFinding);
791
- const tasks = dedupedFindings.slice(0, maxTasks).map(toWorkflowTask);
792
- const aiFixableNow = tasks.filter((task) => task.aiFixable && (task.priority === "P0" || task.priority === "P1"));
793
- const summary = releaseDecision === "do-not-ship"
794
- ? "Do not ship yet. Resolve critical risks before release."
795
- : releaseDecision === "ship-with-caution"
796
- ? "Ship with caution. Address high-priority gaps and monitor closely."
797
- : "Ship now. No blocking risks were detected in this review pass.";
798
- return {
799
- mode,
800
- verdict,
801
- score,
802
- criticalCount,
803
- highCount,
804
- mediumCount,
805
- releaseDecision,
806
- summary,
807
- plainLanguageFindings,
808
- tasks,
809
- aiFixableNow,
810
- };
374
+ return _runAppBuilderWorkflow(engine, params);
811
375
  }
812
376
  // ─── Re-exports ──────────────────────────────────────────────────────────────
813
377
  export { formatVerdictAsMarkdown, formatEvaluationAsMarkdown };
378
+ export { enrichWithPatches } from "../patches/index.js";
379
+ export { crossEvaluatorDedup } from "../dedup.js";
814
380
  //# sourceMappingURL=index.js.map