@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.
- package/README.md +177 -12
- package/dist/api.d.ts +40 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +56 -0
- package/dist/api.js.map +1 -0
- package/dist/ast/cross-file-taint.d.ts +43 -0
- package/dist/ast/cross-file-taint.d.ts.map +1 -0
- package/dist/ast/cross-file-taint.js +713 -0
- package/dist/ast/cross-file-taint.js.map +1 -0
- package/dist/ast/index.d.ts +4 -0
- package/dist/ast/index.d.ts.map +1 -1
- package/dist/ast/index.js +5 -0
- package/dist/ast/index.js.map +1 -1
- package/dist/ast/structural-parser.d.ts.map +1 -1
- package/dist/ast/structural-parser.js +66 -11
- package/dist/ast/structural-parser.js.map +1 -1
- package/dist/ast/taint-tracker.d.ts +35 -0
- package/dist/ast/taint-tracker.d.ts.map +1 -0
- package/dist/ast/taint-tracker.js +518 -0
- package/dist/ast/taint-tracker.js.map +1 -0
- package/dist/ast/types.d.ts +2 -0
- package/dist/ast/types.d.ts.map +1 -1
- package/dist/ast/typescript-ast.d.ts.map +1 -1
- package/dist/ast/typescript-ast.js +25 -5
- package/dist/ast/typescript-ast.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -9
- package/dist/config.js.map +1 -1
- package/dist/dedup.d.ts +19 -0
- package/dist/dedup.d.ts.map +1 -0
- package/dist/dedup.js +222 -0
- package/dist/dedup.js.map +1 -0
- package/dist/errors.d.ts +37 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +57 -0
- package/dist/errors.js.map +1 -0
- package/dist/evaluators/accessibility.d.ts +1 -1
- package/dist/evaluators/accessibility.d.ts.map +1 -1
- package/dist/evaluators/accessibility.js +22 -16
- package/dist/evaluators/accessibility.js.map +1 -1
- package/dist/evaluators/agent-instructions.d.ts +1 -1
- package/dist/evaluators/agent-instructions.d.ts.map +1 -1
- package/dist/evaluators/agent-instructions.js +1 -2
- package/dist/evaluators/agent-instructions.js.map +1 -1
- package/dist/evaluators/ai-code-safety.d.ts +1 -1
- package/dist/evaluators/ai-code-safety.d.ts.map +1 -1
- package/dist/evaluators/ai-code-safety.js +2 -6
- package/dist/evaluators/ai-code-safety.js.map +1 -1
- package/dist/evaluators/api-design.d.ts +1 -1
- package/dist/evaluators/api-design.d.ts.map +1 -1
- package/dist/evaluators/api-design.js +2 -1
- package/dist/evaluators/api-design.js.map +1 -1
- package/dist/evaluators/app-builder.d.ts +34 -0
- package/dist/evaluators/app-builder.d.ts.map +1 -0
- package/dist/evaluators/app-builder.js +156 -0
- package/dist/evaluators/app-builder.js.map +1 -0
- package/dist/evaluators/authentication.d.ts +1 -1
- package/dist/evaluators/authentication.d.ts.map +1 -1
- package/dist/evaluators/authentication.js +2 -66
- package/dist/evaluators/authentication.js.map +1 -1
- package/dist/evaluators/backwards-compatibility.d.ts +1 -1
- package/dist/evaluators/backwards-compatibility.d.ts.map +1 -1
- package/dist/evaluators/backwards-compatibility.js.map +1 -1
- package/dist/evaluators/caching.d.ts +1 -1
- package/dist/evaluators/caching.d.ts.map +1 -1
- package/dist/evaluators/caching.js.map +1 -1
- package/dist/evaluators/ci-cd.d.ts +1 -1
- package/dist/evaluators/ci-cd.d.ts.map +1 -1
- package/dist/evaluators/ci-cd.js +4 -4
- package/dist/evaluators/ci-cd.js.map +1 -1
- package/dist/evaluators/cloud-readiness.d.ts +1 -1
- package/dist/evaluators/cloud-readiness.d.ts.map +1 -1
- package/dist/evaluators/cloud-readiness.js.map +1 -1
- package/dist/evaluators/code-structure.d.ts +1 -1
- package/dist/evaluators/code-structure.d.ts.map +1 -1
- package/dist/evaluators/code-structure.js +2 -6
- package/dist/evaluators/code-structure.js.map +1 -1
- package/dist/evaluators/compliance.d.ts +1 -1
- package/dist/evaluators/compliance.d.ts.map +1 -1
- package/dist/evaluators/compliance.js +15 -6
- package/dist/evaluators/compliance.js.map +1 -1
- package/dist/evaluators/concurrency.d.ts +1 -1
- package/dist/evaluators/concurrency.d.ts.map +1 -1
- package/dist/evaluators/concurrency.js +9 -4
- package/dist/evaluators/concurrency.js.map +1 -1
- package/dist/evaluators/configuration-management.d.ts +1 -1
- package/dist/evaluators/configuration-management.d.ts.map +1 -1
- package/dist/evaluators/configuration-management.js +7 -2
- package/dist/evaluators/configuration-management.js.map +1 -1
- package/dist/evaluators/cost-effectiveness.d.ts +1 -1
- package/dist/evaluators/cost-effectiveness.d.ts.map +1 -1
- package/dist/evaluators/cost-effectiveness.js +1 -3
- package/dist/evaluators/cost-effectiveness.js.map +1 -1
- package/dist/evaluators/cybersecurity.d.ts +1 -1
- package/dist/evaluators/cybersecurity.d.ts.map +1 -1
- package/dist/evaluators/cybersecurity.js +50 -1
- package/dist/evaluators/cybersecurity.js.map +1 -1
- package/dist/evaluators/data-security.d.ts +1 -1
- package/dist/evaluators/data-security.d.ts.map +1 -1
- package/dist/evaluators/data-security.js +9 -66
- package/dist/evaluators/data-security.js.map +1 -1
- package/dist/evaluators/data-sovereignty.d.ts +1 -1
- package/dist/evaluators/data-sovereignty.d.ts.map +1 -1
- package/dist/evaluators/data-sovereignty.js +4 -2
- package/dist/evaluators/data-sovereignty.js.map +1 -1
- package/dist/evaluators/database.d.ts +1 -1
- package/dist/evaluators/database.d.ts.map +1 -1
- package/dist/evaluators/database.js +3 -1
- package/dist/evaluators/database.js.map +1 -1
- package/dist/evaluators/dependencies.d.ts +6 -0
- package/dist/evaluators/dependencies.d.ts.map +1 -0
- package/dist/evaluators/dependencies.js +204 -0
- package/dist/evaluators/dependencies.js.map +1 -0
- package/dist/evaluators/dependency-health.d.ts +1 -1
- package/dist/evaluators/dependency-health.d.ts.map +1 -1
- package/dist/evaluators/dependency-health.js +198 -6
- package/dist/evaluators/dependency-health.js.map +1 -1
- package/dist/evaluators/documentation.d.ts +1 -1
- package/dist/evaluators/documentation.d.ts.map +1 -1
- package/dist/evaluators/documentation.js +5 -2
- package/dist/evaluators/documentation.js.map +1 -1
- package/dist/evaluators/error-handling.d.ts +1 -1
- package/dist/evaluators/error-handling.d.ts.map +1 -1
- package/dist/evaluators/error-handling.js.map +1 -1
- package/dist/evaluators/ethics-bias.d.ts +1 -1
- package/dist/evaluators/ethics-bias.d.ts.map +1 -1
- package/dist/evaluators/ethics-bias.js +10 -5
- package/dist/evaluators/ethics-bias.js.map +1 -1
- package/dist/evaluators/framework-safety.d.ts +13 -0
- package/dist/evaluators/framework-safety.d.ts.map +1 -0
- package/dist/evaluators/framework-safety.js +424 -0
- package/dist/evaluators/framework-safety.js.map +1 -0
- package/dist/evaluators/index.d.ts +20 -24
- package/dist/evaluators/index.d.ts.map +1 -1
- package/dist/evaluators/index.js +294 -728
- package/dist/evaluators/index.js.map +1 -1
- package/dist/evaluators/internationalization.d.ts +1 -1
- package/dist/evaluators/internationalization.d.ts.map +1 -1
- package/dist/evaluators/internationalization.js +14 -6
- package/dist/evaluators/internationalization.js.map +1 -1
- package/dist/evaluators/logging-privacy.d.ts +1 -1
- package/dist/evaluators/logging-privacy.d.ts.map +1 -1
- package/dist/evaluators/logging-privacy.js +3 -1
- package/dist/evaluators/logging-privacy.js.map +1 -1
- package/dist/evaluators/maintainability.d.ts +1 -1
- package/dist/evaluators/maintainability.d.ts.map +1 -1
- package/dist/evaluators/maintainability.js +15 -9
- package/dist/evaluators/maintainability.js.map +1 -1
- package/dist/evaluators/observability.d.ts +1 -1
- package/dist/evaluators/observability.d.ts.map +1 -1
- package/dist/evaluators/observability.js +2 -1
- package/dist/evaluators/observability.js.map +1 -1
- package/dist/evaluators/performance.d.ts +1 -1
- package/dist/evaluators/performance.d.ts.map +1 -1
- package/dist/evaluators/performance.js +181 -4
- package/dist/evaluators/performance.js.map +1 -1
- package/dist/evaluators/portability.d.ts +1 -1
- package/dist/evaluators/portability.d.ts.map +1 -1
- package/dist/evaluators/portability.js +2 -1
- package/dist/evaluators/portability.js.map +1 -1
- package/dist/evaluators/project.d.ts +16 -0
- package/dist/evaluators/project.d.ts.map +1 -0
- package/dist/evaluators/project.js +353 -0
- package/dist/evaluators/project.js.map +1 -0
- package/dist/evaluators/rate-limiting.d.ts +1 -1
- package/dist/evaluators/rate-limiting.d.ts.map +1 -1
- package/dist/evaluators/rate-limiting.js.map +1 -1
- package/dist/evaluators/reliability.d.ts +1 -1
- package/dist/evaluators/reliability.d.ts.map +1 -1
- package/dist/evaluators/reliability.js.map +1 -1
- package/dist/evaluators/scalability.d.ts +1 -1
- package/dist/evaluators/scalability.d.ts.map +1 -1
- package/dist/evaluators/scalability.js +3 -1
- package/dist/evaluators/scalability.js.map +1 -1
- package/dist/evaluators/shared.d.ts +24 -2
- package/dist/evaluators/shared.d.ts.map +1 -1
- package/dist/evaluators/shared.js +190 -2
- package/dist/evaluators/shared.js.map +1 -1
- package/dist/evaluators/software-practices.d.ts +1 -1
- package/dist/evaluators/software-practices.d.ts.map +1 -1
- package/dist/evaluators/software-practices.js +3 -3
- package/dist/evaluators/software-practices.js.map +1 -1
- package/dist/evaluators/testing.d.ts +1 -1
- package/dist/evaluators/testing.d.ts.map +1 -1
- package/dist/evaluators/testing.js +12 -4
- package/dist/evaluators/testing.js.map +1 -1
- package/dist/evaluators/ux.d.ts +1 -1
- package/dist/evaluators/ux.d.ts.map +1 -1
- package/dist/evaluators/ux.js.map +1 -1
- package/dist/evaluators/v2.d.ts +1 -1
- package/dist/evaluators/v2.d.ts.map +1 -1
- package/dist/evaluators/v2.js +13 -35
- package/dist/evaluators/v2.js.map +1 -1
- package/dist/formatters/sarif.d.ts +75 -0
- package/dist/formatters/sarif.d.ts.map +1 -0
- package/dist/formatters/sarif.js +93 -0
- package/dist/formatters/sarif.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -806
- package/dist/index.js.map +1 -1
- package/dist/judges/accessibility.d.ts +1 -1
- package/dist/judges/accessibility.d.ts.map +1 -1
- package/dist/judges/agent-instructions.d.ts +1 -1
- package/dist/judges/agent-instructions.d.ts.map +1 -1
- package/dist/judges/ai-code-safety.d.ts +1 -1
- package/dist/judges/ai-code-safety.d.ts.map +1 -1
- package/dist/judges/api-design.d.ts +1 -1
- package/dist/judges/api-design.d.ts.map +1 -1
- package/dist/judges/authentication.d.ts +1 -1
- package/dist/judges/authentication.d.ts.map +1 -1
- package/dist/judges/backwards-compatibility.d.ts +1 -1
- package/dist/judges/backwards-compatibility.d.ts.map +1 -1
- package/dist/judges/caching.d.ts +1 -1
- package/dist/judges/caching.d.ts.map +1 -1
- package/dist/judges/ci-cd.d.ts +1 -1
- package/dist/judges/ci-cd.d.ts.map +1 -1
- package/dist/judges/cloud-readiness.d.ts +1 -1
- package/dist/judges/cloud-readiness.d.ts.map +1 -1
- package/dist/judges/code-structure.d.ts +1 -1
- package/dist/judges/code-structure.d.ts.map +1 -1
- package/dist/judges/code-structure.js +7 -1
- package/dist/judges/code-structure.js.map +1 -1
- package/dist/judges/compliance.d.ts +1 -1
- package/dist/judges/compliance.d.ts.map +1 -1
- package/dist/judges/concurrency.d.ts +1 -1
- package/dist/judges/concurrency.d.ts.map +1 -1
- package/dist/judges/configuration-management.d.ts +1 -1
- package/dist/judges/configuration-management.d.ts.map +1 -1
- package/dist/judges/cost-effectiveness.d.ts +1 -1
- package/dist/judges/cost-effectiveness.d.ts.map +1 -1
- package/dist/judges/cybersecurity.d.ts +1 -1
- package/dist/judges/cybersecurity.d.ts.map +1 -1
- package/dist/judges/data-security.d.ts +1 -1
- package/dist/judges/data-security.d.ts.map +1 -1
- package/dist/judges/data-sovereignty.d.ts +1 -1
- package/dist/judges/data-sovereignty.d.ts.map +1 -1
- package/dist/judges/database.d.ts +1 -1
- package/dist/judges/database.d.ts.map +1 -1
- package/dist/judges/dependency-health.d.ts +1 -1
- package/dist/judges/dependency-health.d.ts.map +1 -1
- package/dist/judges/documentation.d.ts +1 -1
- package/dist/judges/documentation.d.ts.map +1 -1
- package/dist/judges/error-handling.d.ts +1 -1
- package/dist/judges/error-handling.d.ts.map +1 -1
- package/dist/judges/ethics-bias.d.ts +1 -1
- package/dist/judges/ethics-bias.d.ts.map +1 -1
- package/dist/judges/framework-safety.d.ts +3 -0
- package/dist/judges/framework-safety.d.ts.map +1 -0
- package/dist/judges/framework-safety.js +31 -0
- package/dist/judges/framework-safety.js.map +1 -0
- package/dist/judges/index.d.ts +1 -1
- package/dist/judges/index.d.ts.map +1 -1
- package/dist/judges/index.js +74 -0
- package/dist/judges/index.js.map +1 -1
- package/dist/judges/internationalization.d.ts +1 -1
- package/dist/judges/internationalization.d.ts.map +1 -1
- package/dist/judges/logging-privacy.d.ts +1 -1
- package/dist/judges/logging-privacy.d.ts.map +1 -1
- package/dist/judges/maintainability.d.ts +1 -1
- package/dist/judges/maintainability.d.ts.map +1 -1
- package/dist/judges/observability.d.ts +1 -1
- package/dist/judges/observability.d.ts.map +1 -1
- package/dist/judges/performance.d.ts +1 -1
- package/dist/judges/performance.d.ts.map +1 -1
- package/dist/judges/portability.d.ts +1 -1
- package/dist/judges/portability.d.ts.map +1 -1
- package/dist/judges/rate-limiting.d.ts +1 -1
- package/dist/judges/rate-limiting.d.ts.map +1 -1
- package/dist/judges/reliability.d.ts +1 -1
- package/dist/judges/reliability.d.ts.map +1 -1
- package/dist/judges/scalability.d.ts +1 -1
- package/dist/judges/scalability.d.ts.map +1 -1
- package/dist/judges/software-practices.d.ts +1 -1
- package/dist/judges/software-practices.d.ts.map +1 -1
- package/dist/judges/testing.d.ts +1 -1
- package/dist/judges/testing.d.ts.map +1 -1
- package/dist/judges/ux.d.ts +1 -1
- package/dist/judges/ux.d.ts.map +1 -1
- package/dist/language-patterns.d.ts +37 -0
- package/dist/language-patterns.d.ts.map +1 -1
- package/dist/language-patterns.js +58 -3
- package/dist/language-patterns.js.map +1 -1
- package/dist/patches/index.d.ts +10 -0
- package/dist/patches/index.d.ts.map +1 -0
- package/dist/patches/index.js +533 -0
- package/dist/patches/index.js.map +1 -0
- package/dist/reports/public-repo-report.d.ts +1 -1
- package/dist/reports/public-repo-report.d.ts.map +1 -1
- package/dist/scoring.d.ts +18 -0
- package/dist/scoring.d.ts.map +1 -0
- package/dist/scoring.js +178 -0
- package/dist/scoring.js.map +1 -0
- package/dist/tools/deep-review.d.ts +4 -0
- package/dist/tools/deep-review.d.ts.map +1 -0
- package/dist/tools/deep-review.js +56 -0
- package/dist/tools/deep-review.js.map +1 -0
- package/dist/tools/prompts.d.ts +8 -0
- package/dist/tools/prompts.d.ts.map +1 -0
- package/dist/tools/prompts.js +66 -0
- package/dist/tools/prompts.js.map +1 -0
- package/dist/tools/register-evaluation.d.ts +7 -0
- package/dist/tools/register-evaluation.d.ts.map +1 -0
- package/dist/tools/register-evaluation.js +303 -0
- package/dist/tools/register-evaluation.js.map +1 -0
- package/dist/tools/register-workflow.d.ts +7 -0
- package/dist/tools/register-workflow.d.ts.map +1 -0
- package/dist/tools/register-workflow.js +395 -0
- package/dist/tools/register-workflow.js.map +1 -0
- package/dist/tools/register.d.ts +7 -0
- package/dist/tools/register.d.ts.map +1 -0
- package/dist/tools/register.js +14 -0
- package/dist/tools/register.js.map +1 -0
- package/dist/tools/schemas.d.ts +26 -0
- package/dist/tools/schemas.d.ts.map +1 -0
- package/dist/tools/schemas.js +42 -0
- package/dist/tools/schemas.js.map +1 -0
- package/dist/types.d.ts +29 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +42 -3
- package/server.json +51 -3
package/dist/evaluators/index.js
CHANGED
|
@@ -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
|
-
// ───
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
158
|
+
return matched;
|
|
151
159
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
|
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
|
-
|
|
166
|
-
|
|
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
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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:
|
|
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
|
-
|
|
297
|
-
const
|
|
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
|
|
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) +
|
|
310
|
-
|
|
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
|
-
|
|
330
|
-
|
|
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.
|
|
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
|
-
|
|
651
|
-
|
|
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
|
-
|
|
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
|