slopbrick 0.24.0 → 0.25.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/dist/engine/worker.cjs +121 -37
- package/dist/engine/worker.js +122 -38
- package/dist/index.cjs +173 -94
- package/dist/index.d.cts +35 -1
- package/dist/index.d.ts +35 -1
- package/dist/index.js +203 -124
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ var VERSION;
|
|
|
19
19
|
var init_header = __esm({
|
|
20
20
|
"src/types/_header.ts"() {
|
|
21
21
|
"use strict";
|
|
22
|
-
VERSION = "0.
|
|
22
|
+
VERSION = "0.25.1";
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -309,6 +309,43 @@ var init_defaults = __esm({
|
|
|
309
309
|
*/
|
|
310
310
|
testIntelligence: {
|
|
311
311
|
missingEdgeCase: false
|
|
312
|
+
},
|
|
313
|
+
/**
|
|
314
|
+
* v0.25.0: self-scan exclude paths. Defaults cover the three paths
|
|
315
|
+
* that are always false positives when scanning the slopbrick repo
|
|
316
|
+
* itself:
|
|
317
|
+
*
|
|
318
|
+
* - `src/rules/**` — rule definitions contain example patterns
|
|
319
|
+
* the rules themselves detect (self-fire). E.g. a
|
|
320
|
+
* `security/sql-construction` rule's source file has a SQL
|
|
321
|
+
* concat string literal that fires its own regex.
|
|
322
|
+
* - `tests/fixtures/**` — test fixtures contain intentional bad
|
|
323
|
+
* code that the rules must fire on to be useful (each fixture
|
|
324
|
+
* is a positive test case). Scanning them in a self-scan
|
|
325
|
+
* produces ~70 false-positive "issues" that are really just
|
|
326
|
+
* test data.
|
|
327
|
+
* - `tests/rules/**` — rule test files contain expected-issue
|
|
328
|
+
* assertions, also meta-code.
|
|
329
|
+
*
|
|
330
|
+
* Three patterns (broadened in v0.25.1 from the v0.25.0 narrow
|
|
331
|
+
* set, which missed `tests/engine/**`, `tests/cli/**`, and
|
|
332
|
+
* `snippet/**`), ~80 issues removed per self-scan. Combined
|
|
333
|
+
* with the v0.25.0 graded security cap, this restores the
|
|
334
|
+
* v9 plan's "security ≥ 80" criterion (unachievable in
|
|
335
|
+
* v0.24.0 due to 90 self-scan FPs collapsing the score to 0).
|
|
336
|
+
*
|
|
337
|
+
* Set `selfScan: { excludePaths: [] }` in `slopbrick.config.mjs`
|
|
338
|
+
* to opt out and scan every file (legacy behavior).
|
|
339
|
+
*/
|
|
340
|
+
selfScan: {
|
|
341
|
+
excludePaths: [
|
|
342
|
+
"**/src/rules/**",
|
|
343
|
+
// rule definitions are meta-code
|
|
344
|
+
"**/snippet/**",
|
|
345
|
+
// RULE_HINTS examples are intentional bad code
|
|
346
|
+
"**/tests/**"
|
|
347
|
+
// all test files (fixtures + unit + integration)
|
|
348
|
+
]
|
|
312
349
|
}
|
|
313
350
|
};
|
|
314
351
|
}
|
|
@@ -36365,6 +36402,7 @@ function parseSource(source, filePath) {
|
|
|
36365
36402
|
case "py":
|
|
36366
36403
|
case "go":
|
|
36367
36404
|
case "rs":
|
|
36405
|
+
case "java":
|
|
36368
36406
|
return parseBlankModule(source);
|
|
36369
36407
|
default:
|
|
36370
36408
|
return parseWithSwc(source, filePath);
|
|
@@ -49856,13 +49894,13 @@ var init_signal_strength = __esm({
|
|
|
49856
49894
|
defaultOff: true
|
|
49857
49895
|
},
|
|
49858
49896
|
"java/arraylist-vs-linkedlist": {
|
|
49859
|
-
recall:
|
|
49860
|
-
fpRate:
|
|
49861
|
-
ratio:
|
|
49862
|
-
precision: 0,
|
|
49863
|
-
lastCalibratedAt: "2026-07-
|
|
49897
|
+
recall: 35e-4,
|
|
49898
|
+
fpRate: 94e-4,
|
|
49899
|
+
ratio: 0.37,
|
|
49900
|
+
precision: 0.0446,
|
|
49901
|
+
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
49864
49902
|
verdict: "DORMANT",
|
|
49865
|
-
_calibrationNote: "v0.20: new rule (new LinkedList<>() \u2014 use ArrayList
|
|
49903
|
+
_calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files = 92196 total. v9 TP=36, FP=771, P=4.46%, FPR=0.94%, recall=0.35%, ratio=0.37, lift=P/FPR=4.74. Rule fires more on legacy enterprise Java (neg) than on AI/ML-integration Java (pos) because modern Java idiom defaults to ArrayList. v0.20: new rule (new LinkedList<>() \u2014 use ArrayList, Effective Java Item 28).",
|
|
49866
49904
|
aiSpecific: true,
|
|
49867
49905
|
_v7Verdict: "DORMANT",
|
|
49868
49906
|
_v7Lift: 1,
|
|
@@ -49871,16 +49909,21 @@ var init_signal_strength = __esm({
|
|
|
49871
49909
|
_v7Precision: 0,
|
|
49872
49910
|
_v8Verdict: "DORMANT",
|
|
49873
49911
|
_v8Lift: 1,
|
|
49912
|
+
_v9Verdict: "DORMANT",
|
|
49913
|
+
_v9Lift: 4.74,
|
|
49914
|
+
_v9Recall: 35e-4,
|
|
49915
|
+
_v9FpRate: 94e-4,
|
|
49916
|
+
_v9Precision: 0.0446,
|
|
49874
49917
|
defaultOff: true
|
|
49875
49918
|
},
|
|
49876
49919
|
"java/empty-catch-block": {
|
|
49877
|
-
recall:
|
|
49878
|
-
fpRate: 0,
|
|
49879
|
-
ratio:
|
|
49880
|
-
precision:
|
|
49881
|
-
lastCalibratedAt: "2026-07-
|
|
49920
|
+
recall: 8e-3,
|
|
49921
|
+
fpRate: 0.1118,
|
|
49922
|
+
ratio: 0.07,
|
|
49923
|
+
precision: 89e-4,
|
|
49924
|
+
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
49882
49925
|
verdict: "DORMANT",
|
|
49883
|
-
_calibrationNote: "v0.
|
|
49926
|
+
_calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files. v9 TP=82, FP=9159, P=0.89%, FPR=11.18%, recall=0.80%, ratio=0.07, lift=P/FPR=0.08. Strongly INVERTED: empty catch blocks are 14x more common in legacy enterprise Java than in AI/Java-modern pos repos. Heuristic likely needs to be tightened (e.g., require at least a comment) before it can serve as a meaningful discriminator. v0.20: new rule.",
|
|
49884
49927
|
aiSpecific: true,
|
|
49885
49928
|
_v7Verdict: "DORMANT",
|
|
49886
49929
|
_v7Lift: 1,
|
|
@@ -49889,16 +49932,21 @@ var init_signal_strength = __esm({
|
|
|
49889
49932
|
_v7Precision: 0,
|
|
49890
49933
|
_v8Verdict: "DORMANT",
|
|
49891
49934
|
_v8Lift: 1,
|
|
49935
|
+
_v9Verdict: "DORMANT",
|
|
49936
|
+
_v9Lift: 0.08,
|
|
49937
|
+
_v9Recall: 8e-3,
|
|
49938
|
+
_v9FpRate: 0.1118,
|
|
49939
|
+
_v9Precision: 89e-4,
|
|
49892
49940
|
defaultOff: true
|
|
49893
49941
|
},
|
|
49894
49942
|
"java/legacy-date-api": {
|
|
49895
|
-
recall: 0,
|
|
49896
|
-
fpRate: 0,
|
|
49897
|
-
ratio:
|
|
49898
|
-
precision: 0,
|
|
49899
|
-
lastCalibratedAt: "2026-07-
|
|
49943
|
+
recall: 0.0285,
|
|
49944
|
+
fpRate: 0.0511,
|
|
49945
|
+
ratio: 0.56,
|
|
49946
|
+
precision: 0.0657,
|
|
49947
|
+
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
49900
49948
|
verdict: "DORMANT",
|
|
49901
|
-
_calibrationNote: "v0.
|
|
49949
|
+
_calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files. v9 TP=294, FP=4181, P=6.57%, FPR=5.11%, recall=2.85%, ratio=0.56, lift=P/FPR=1.29. INVERTED-to-NOISY: legacy date API is more common in older enterprise Java (neg) than in modern AI/Java repos (pos). The heuristic correctly flags the legacy pattern, but the legacy pattern is over-represented in the neg baseline (older codebases). v0.20: new rule.",
|
|
49902
49950
|
aiSpecific: true,
|
|
49903
49951
|
_v7Verdict: "DORMANT",
|
|
49904
49952
|
_v7Lift: 1,
|
|
@@ -49907,16 +49955,21 @@ var init_signal_strength = __esm({
|
|
|
49907
49955
|
_v7Precision: 0,
|
|
49908
49956
|
_v8Verdict: "DORMANT",
|
|
49909
49957
|
_v8Lift: 1,
|
|
49958
|
+
_v9Verdict: "DORMANT",
|
|
49959
|
+
_v9Lift: 1.29,
|
|
49960
|
+
_v9Recall: 0.0285,
|
|
49961
|
+
_v9FpRate: 0.0511,
|
|
49962
|
+
_v9Precision: 0.0657,
|
|
49910
49963
|
defaultOff: true
|
|
49911
49964
|
},
|
|
49912
49965
|
"java/raw-type-overuse": {
|
|
49913
|
-
recall: 0,
|
|
49914
|
-
fpRate: 0,
|
|
49915
|
-
ratio:
|
|
49916
|
-
precision: 0,
|
|
49917
|
-
lastCalibratedAt: "2026-07-
|
|
49966
|
+
recall: 0.1093,
|
|
49967
|
+
fpRate: 0.2041,
|
|
49968
|
+
ratio: 0.54,
|
|
49969
|
+
precision: 0.0632,
|
|
49970
|
+
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
49918
49971
|
verdict: "DORMANT",
|
|
49919
|
-
_calibrationNote: "v0.20:
|
|
49972
|
+
_calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files. v9 TP=1126, FP=16712, P=6.32%, FPR=20.41%, recall=10.93%, ratio=0.54, lift=P/FPR=0.31. Strongly INVERTED: raw types (no generics) are 2x more common in legacy enterprise Java (neg) than in modern Java repos (pos). Heuristic is correctly identifying the legacy anti-pattern, but it's an age marker more than an AI-fingerprint. v0.20: new rule.",
|
|
49920
49973
|
aiSpecific: true,
|
|
49921
49974
|
_v7Verdict: "DORMANT",
|
|
49922
49975
|
_v7Lift: 1,
|
|
@@ -49925,16 +49978,21 @@ var init_signal_strength = __esm({
|
|
|
49925
49978
|
_v7Precision: 0,
|
|
49926
49979
|
_v8Verdict: "DORMANT",
|
|
49927
49980
|
_v8Lift: 1,
|
|
49981
|
+
_v9Verdict: "DORMANT",
|
|
49982
|
+
_v9Lift: 0.31,
|
|
49983
|
+
_v9Recall: 0.1093,
|
|
49984
|
+
_v9FpRate: 0.2041,
|
|
49985
|
+
_v9Precision: 0.0632,
|
|
49928
49986
|
defaultOff: true
|
|
49929
49987
|
},
|
|
49930
49988
|
"java/string-concat-loop": {
|
|
49931
|
-
recall:
|
|
49932
|
-
fpRate: 0,
|
|
49933
|
-
ratio:
|
|
49934
|
-
precision: 0,
|
|
49935
|
-
lastCalibratedAt: "2026-07-
|
|
49989
|
+
recall: 46e-4,
|
|
49990
|
+
fpRate: 0.0358,
|
|
49991
|
+
ratio: 0.13,
|
|
49992
|
+
precision: 0.0158,
|
|
49993
|
+
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
49936
49994
|
verdict: "DORMANT",
|
|
49937
|
-
_calibrationNote: "v0.
|
|
49995
|
+
_calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files. v9 TP=47, FP=2933, P=1.58%, FPR=3.58%, recall=0.46%, ratio=0.13, lift=P/FPR=0.44. Strongly INVERTED: string concatenation in a loop is 8x more common in legacy enterprise Java (neg) than in AI/ML/Java-modern pos repos. Modern Java training data and modern IDE warnings discourage the pattern. v0.20: new rule.",
|
|
49938
49996
|
aiSpecific: true,
|
|
49939
49997
|
_v7Verdict: "DORMANT",
|
|
49940
49998
|
_v7Lift: 1,
|
|
@@ -49943,16 +50001,21 @@ var init_signal_strength = __esm({
|
|
|
49943
50001
|
_v7Precision: 0,
|
|
49944
50002
|
_v8Verdict: "DORMANT",
|
|
49945
50003
|
_v8Lift: 1,
|
|
50004
|
+
_v9Verdict: "DORMANT",
|
|
50005
|
+
_v9Lift: 0.44,
|
|
50006
|
+
_v9Recall: 46e-4,
|
|
50007
|
+
_v9FpRate: 0.0358,
|
|
50008
|
+
_v9Precision: 0.0158,
|
|
49946
50009
|
defaultOff: true
|
|
49947
50010
|
},
|
|
49948
50011
|
"java/system-out-println": {
|
|
49949
|
-
recall: 0,
|
|
49950
|
-
fpRate: 0,
|
|
49951
|
-
ratio:
|
|
49952
|
-
precision: 0,
|
|
49953
|
-
lastCalibratedAt: "2026-07-
|
|
50012
|
+
recall: 0.1503,
|
|
50013
|
+
fpRate: 0.2533,
|
|
50014
|
+
ratio: 0.59,
|
|
50015
|
+
precision: 0.0695,
|
|
50016
|
+
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
49954
50017
|
verdict: "DORMANT",
|
|
49955
|
-
_calibrationNote: "v0.
|
|
50018
|
+
_calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files. v9 TP=1549, FP=20737, P=6.95%, FPR=25.32%, recall=15.03%, ratio=0.59, lift=P/FPR=0.27. INVERTED: System.out.println is more common in legacy enterprise Java (neg) than in modern AI/Java repos (pos). Modern Java defaults to SLF4J/Log4j, AI-generated Java starts with a logger import. The pattern is the opposite of the v0.20 design intent (which assumed println was the AI-fingerprint signal). v0.25.x: consider gating this rule by Java era (recent file mtime) or making it an age-fingerprint rather than AI-fingerprint. v0.20: new rule.",
|
|
49956
50019
|
aiSpecific: true,
|
|
49957
50020
|
_v7Verdict: "DORMANT",
|
|
49958
50021
|
_v7Lift: 1,
|
|
@@ -49961,6 +50024,11 @@ var init_signal_strength = __esm({
|
|
|
49961
50024
|
_v7Precision: 0,
|
|
49962
50025
|
_v8Verdict: "DORMANT",
|
|
49963
50026
|
_v8Lift: 1,
|
|
50027
|
+
_v9Verdict: "DORMANT",
|
|
50028
|
+
_v9Lift: 0.27,
|
|
50029
|
+
_v9Recall: 0.1503,
|
|
50030
|
+
_v9FpRate: 0.2533,
|
|
50031
|
+
_v9Precision: 0.0695,
|
|
49964
50032
|
defaultOff: true
|
|
49965
50033
|
},
|
|
49966
50034
|
"layout/forced-layout": {
|
|
@@ -51516,7 +51584,8 @@ var init_signal_strength2 = __esm({
|
|
|
51516
51584
|
// src/engine/worker.ts
|
|
51517
51585
|
import { isMainThread, parentPort, workerData } from "worker_threads";
|
|
51518
51586
|
import { extname as extname5 } from "path";
|
|
51519
|
-
import { join as join13 } from "path";
|
|
51587
|
+
import { join as join13, relative as relative5, sep as sep2 } from "path";
|
|
51588
|
+
import { minimatch as minimatch2 } from "minimatch";
|
|
51520
51589
|
function buildParserCacheConfig(cwd) {
|
|
51521
51590
|
const envVal = process.env.SLOP_AUDIT_CACHE;
|
|
51522
51591
|
const enabled = envVal === "1" || envVal === "true";
|
|
@@ -51536,7 +51605,23 @@ function applyRuleOverrides(issues, rules) {
|
|
|
51536
51605
|
}
|
|
51537
51606
|
return result;
|
|
51538
51607
|
}
|
|
51608
|
+
function isExcludedBySelfScan(filePath, cwd, excludePaths) {
|
|
51609
|
+
if (!excludePaths || excludePaths.length === 0) return false;
|
|
51610
|
+
const rel = relative5(cwd, filePath).split(sep2).join("/");
|
|
51611
|
+
return excludePaths.some((pattern) => minimatch2(rel, pattern, { dot: true }));
|
|
51612
|
+
}
|
|
51539
51613
|
async function scanFile(filePath, config, registry, cwd = process.cwd()) {
|
|
51614
|
+
if (isExcludedBySelfScan(filePath, cwd, config.selfScan?.excludePaths)) {
|
|
51615
|
+
return {
|
|
51616
|
+
filePath,
|
|
51617
|
+
componentCount: 0,
|
|
51618
|
+
issues: [],
|
|
51619
|
+
gapValues: [],
|
|
51620
|
+
styleSources: [],
|
|
51621
|
+
elementTags: [],
|
|
51622
|
+
unmatchedStringLiterals: []
|
|
51623
|
+
};
|
|
51624
|
+
}
|
|
51540
51625
|
const cache = buildParserCacheConfig(cwd);
|
|
51541
51626
|
const ext = extname5(filePath).toLowerCase();
|
|
51542
51627
|
const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
|
|
@@ -51551,7 +51636,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
|
|
|
51551
51636
|
".h",
|
|
51552
51637
|
".hpp",
|
|
51553
51638
|
".hxx",
|
|
51554
|
-
".java",
|
|
51555
51639
|
".rb",
|
|
51556
51640
|
".php"
|
|
51557
51641
|
]);
|
|
@@ -51839,45 +51923,6 @@ var init_flywheel = __esm({
|
|
|
51839
51923
|
}
|
|
51840
51924
|
});
|
|
51841
51925
|
|
|
51842
|
-
// src/engine/ai-security-risk.ts
|
|
51843
|
-
function computeAiSecurityRisk(issues) {
|
|
51844
|
-
const findings = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
51845
|
-
for (const issue of issues) {
|
|
51846
|
-
switch (issue.severity) {
|
|
51847
|
-
case "high":
|
|
51848
|
-
findings.high += 1;
|
|
51849
|
-
break;
|
|
51850
|
-
case "medium":
|
|
51851
|
-
findings.medium += 1;
|
|
51852
|
-
break;
|
|
51853
|
-
case "low":
|
|
51854
|
-
findings.low += 1;
|
|
51855
|
-
break;
|
|
51856
|
-
default:
|
|
51857
|
-
findings.low += 1;
|
|
51858
|
-
}
|
|
51859
|
-
}
|
|
51860
|
-
let risk = "low";
|
|
51861
|
-
if (findings.critical >= 1 || findings.high >= 3) {
|
|
51862
|
-
risk = "critical";
|
|
51863
|
-
} else if (findings.high >= 1 || findings.medium >= 3) {
|
|
51864
|
-
risk = "high";
|
|
51865
|
-
} else if (findings.medium >= 1) {
|
|
51866
|
-
risk = "medium";
|
|
51867
|
-
}
|
|
51868
|
-
return { risk, findings };
|
|
51869
|
-
}
|
|
51870
|
-
function formatAiSecurityRiskLine(risk, findings) {
|
|
51871
|
-
const total = findings.critical + findings.high + findings.medium + findings.low;
|
|
51872
|
-
if (total === 0) return "AI Security Risk: low";
|
|
51873
|
-
return `AI Security Risk: ${risk.toUpperCase()} (${total} findings)`;
|
|
51874
|
-
}
|
|
51875
|
-
var init_ai_security_risk = __esm({
|
|
51876
|
-
"src/engine/ai-security-risk.ts"() {
|
|
51877
|
-
"use strict";
|
|
51878
|
-
}
|
|
51879
|
-
});
|
|
51880
|
-
|
|
51881
51926
|
// src/engine/test-quality.ts
|
|
51882
51927
|
var test_quality_exports = {};
|
|
51883
51928
|
__export(test_quality_exports, {
|
|
@@ -51946,7 +51991,7 @@ var init_test_quality = __esm({
|
|
|
51946
51991
|
});
|
|
51947
51992
|
|
|
51948
51993
|
// src/engine/metrics.ts
|
|
51949
|
-
import { isAbsolute as isAbsolute2, relative as
|
|
51994
|
+
import { isAbsolute as isAbsolute2, relative as relative6 } from "path";
|
|
51950
51995
|
function bucketFor(ruleId) {
|
|
51951
51996
|
return RULE_TO_BUCKET[ruleId] ?? "visual";
|
|
51952
51997
|
}
|
|
@@ -51970,7 +52015,7 @@ function scoreFile(result, frameworkMultiplier, config, baseline, cwd) {
|
|
|
51970
52015
|
100,
|
|
51971
52016
|
rawScore * frameworkMultiplier * CONTEXT_DENSITY_MULTIPLIER
|
|
51972
52017
|
);
|
|
51973
|
-
const baselineKey = cwd ? isAbsolute2(result.filePath) ?
|
|
52018
|
+
const baselineKey = cwd ? isAbsolute2(result.filePath) ? relative6(cwd, result.filePath) : result.filePath : result.filePath;
|
|
51974
52019
|
const baselineScore = baseline?.scores[baselineKey]?.baselineScore ?? 0;
|
|
51975
52020
|
const adjustedScore = baseline ? Math.max(0, componentScore - baselineScore) : componentScore;
|
|
51976
52021
|
return {
|
|
@@ -52054,14 +52099,10 @@ function aggregateReport(scores, issueGroups, config, compositeScores) {
|
|
|
52054
52099
|
});
|
|
52055
52100
|
}
|
|
52056
52101
|
}
|
|
52057
|
-
const
|
|
52058
|
-
|
|
52059
|
-
|
|
52060
|
-
|
|
52061
|
-
high: 33,
|
|
52062
|
-
critical: 0
|
|
52063
|
-
};
|
|
52064
|
-
const security = securityFromRisk[risk];
|
|
52102
|
+
const securityIssueCount = flatIssues.filter(
|
|
52103
|
+
(i) => i.category === "security" || (i.ruleId ?? "").startsWith("security/")
|
|
52104
|
+
).length;
|
|
52105
|
+
const security = Math.max(0, 100 / (1 + securityIssueCount / 5));
|
|
52065
52106
|
const engineeringCategoryScores = [
|
|
52066
52107
|
categoryScores.arch ?? 0,
|
|
52067
52108
|
categoryScores.logic ?? 0,
|
|
@@ -52119,7 +52160,6 @@ var SEVERITY_WEIGHTS, CONTEXT_DENSITY_MULTIPLIER, COMPOSITE_WEIGHTS, RULE_TO_BUC
|
|
|
52119
52160
|
var init_metrics = __esm({
|
|
52120
52161
|
"src/engine/metrics.ts"() {
|
|
52121
52162
|
"use strict";
|
|
52122
|
-
init_ai_security_risk();
|
|
52123
52163
|
init_test_quality();
|
|
52124
52164
|
SEVERITY_WEIGHTS = {
|
|
52125
52165
|
low: 1,
|
|
@@ -54051,6 +54091,45 @@ var init_business_logic = __esm({
|
|
|
54051
54091
|
}
|
|
54052
54092
|
});
|
|
54053
54093
|
|
|
54094
|
+
// src/engine/ai-security-risk.ts
|
|
54095
|
+
function computeAiSecurityRisk(issues) {
|
|
54096
|
+
const findings = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
54097
|
+
for (const issue of issues) {
|
|
54098
|
+
switch (issue.severity) {
|
|
54099
|
+
case "high":
|
|
54100
|
+
findings.high += 1;
|
|
54101
|
+
break;
|
|
54102
|
+
case "medium":
|
|
54103
|
+
findings.medium += 1;
|
|
54104
|
+
break;
|
|
54105
|
+
case "low":
|
|
54106
|
+
findings.low += 1;
|
|
54107
|
+
break;
|
|
54108
|
+
default:
|
|
54109
|
+
findings.low += 1;
|
|
54110
|
+
}
|
|
54111
|
+
}
|
|
54112
|
+
let risk = "low";
|
|
54113
|
+
if (findings.critical >= 1 || findings.high >= 3) {
|
|
54114
|
+
risk = "critical";
|
|
54115
|
+
} else if (findings.high >= 1 || findings.medium >= 3) {
|
|
54116
|
+
risk = "high";
|
|
54117
|
+
} else if (findings.medium >= 1) {
|
|
54118
|
+
risk = "medium";
|
|
54119
|
+
}
|
|
54120
|
+
return { risk, findings };
|
|
54121
|
+
}
|
|
54122
|
+
function formatAiSecurityRiskLine(risk, findings) {
|
|
54123
|
+
const total = findings.critical + findings.high + findings.medium + findings.low;
|
|
54124
|
+
if (total === 0) return "AI Security Risk: low";
|
|
54125
|
+
return `AI Security Risk: ${risk.toUpperCase()} (${total} findings)`;
|
|
54126
|
+
}
|
|
54127
|
+
var init_ai_security_risk = __esm({
|
|
54128
|
+
"src/engine/ai-security-risk.ts"() {
|
|
54129
|
+
"use strict";
|
|
54130
|
+
}
|
|
54131
|
+
});
|
|
54132
|
+
|
|
54054
54133
|
// src/engine/maintenance-cost.ts
|
|
54055
54134
|
var maintenance_cost_exports = {};
|
|
54056
54135
|
__export(maintenance_cost_exports, {
|
|
@@ -54327,7 +54406,7 @@ __export(db_health_exports, {
|
|
|
54327
54406
|
buildDbHealth: () => buildDbHealth
|
|
54328
54407
|
});
|
|
54329
54408
|
import { readFileSync as readFileSync17 } from "fs";
|
|
54330
|
-
import { relative as
|
|
54409
|
+
import { relative as relative7 } from "path";
|
|
54331
54410
|
import { globby as globby3 } from "globby";
|
|
54332
54411
|
import { parse as parseSql6, loadModule as loadSqlModule6 } from "pgsql-parser";
|
|
54333
54412
|
function ensureSqlModule() {
|
|
@@ -54378,7 +54457,7 @@ async function buildDbHealth(cwd, _config, options = {}) {
|
|
|
54378
54457
|
for (const abs of sqlFiles.slice(0, maxFiles)) {
|
|
54379
54458
|
const parsed = await parseSqlFile(abs);
|
|
54380
54459
|
if (!parsed) continue;
|
|
54381
|
-
const relPath =
|
|
54460
|
+
const relPath = relative7(cwd, abs);
|
|
54382
54461
|
parsedFiles.push({ relPath, parsed });
|
|
54383
54462
|
for (const stmt of parsed.statements) {
|
|
54384
54463
|
if (stmt.type !== "IndexStmt") continue;
|
|
@@ -54433,7 +54512,7 @@ async function buildDbHealth(cwd, _config, options = {}) {
|
|
|
54433
54512
|
} catch {
|
|
54434
54513
|
continue;
|
|
54435
54514
|
}
|
|
54436
|
-
const relPath =
|
|
54515
|
+
const relPath = relative7(cwd, abs);
|
|
54437
54516
|
const context = { config: _config, filePath: relPath, cwd };
|
|
54438
54517
|
const facts = { filePath: relPath, v2: { _source: source } };
|
|
54439
54518
|
const ruleContext = sqlConcatRule.create(context);
|
|
@@ -54790,7 +54869,7 @@ function computeDomainScores(issues) {
|
|
|
54790
54869
|
performance2.score = issueCountToScore(performance2.issueCount);
|
|
54791
54870
|
const security = tally(SECURITY_CATEGORIES);
|
|
54792
54871
|
security.name = "security";
|
|
54793
|
-
security.score =
|
|
54872
|
+
security.score = Math.max(0, 100 / (1 + security.issueCount / 5));
|
|
54794
54873
|
return { codeHygiene, accessibility, performance: performance2, security };
|
|
54795
54874
|
}
|
|
54796
54875
|
var COHERENCE_WEIGHTS, AI_DEBT_NUMERIC, CODE_HYGIENE_CATEGORIES, ACCESSIBILITY_CATEGORIES, PERFORMANCE_CATEGORIES, SECURITY_CATEGORIES;
|
|
@@ -54818,7 +54897,7 @@ var init_coherence = __esm({
|
|
|
54818
54897
|
|
|
54819
54898
|
// src/cli/report/enrichReport.ts
|
|
54820
54899
|
import { readFileSync as readFileSync18 } from "fs";
|
|
54821
|
-
import { relative as
|
|
54900
|
+
import { relative as relative8 } from "path";
|
|
54822
54901
|
function collectBusinessLogicIssues(cwd, filePaths) {
|
|
54823
54902
|
const issues = [];
|
|
54824
54903
|
for (const absPath of filePaths) {
|
|
@@ -54832,7 +54911,7 @@ function collectBusinessLogicIssues(cwd, filePaths) {
|
|
|
54832
54911
|
for (const issue of fileIssues) {
|
|
54833
54912
|
issues.push({
|
|
54834
54913
|
...issue,
|
|
54835
|
-
filePath:
|
|
54914
|
+
filePath: relative8(cwd, absPath) || absPath
|
|
54836
54915
|
});
|
|
54837
54916
|
}
|
|
54838
54917
|
}
|
|
@@ -55223,7 +55302,7 @@ import {
|
|
|
55223
55302
|
statSync as statSync6
|
|
55224
55303
|
} from "fs";
|
|
55225
55304
|
import { createHash as createHash10 } from "crypto";
|
|
55226
|
-
import { dirname as dirname11, join as join17, relative as
|
|
55305
|
+
import { dirname as dirname11, join as join17, relative as relative9 } from "path";
|
|
55227
55306
|
function telemetryPath2(cwd) {
|
|
55228
55307
|
return join17(cwd, TELEMETRY_DIR, TELEMETRY_FILE2);
|
|
55229
55308
|
}
|
|
@@ -55232,7 +55311,7 @@ function hashString(input) {
|
|
|
55232
55311
|
}
|
|
55233
55312
|
function safeRelative(cwd, filePath) {
|
|
55234
55313
|
try {
|
|
55235
|
-
return
|
|
55314
|
+
return relative9(cwd, filePath);
|
|
55236
55315
|
} catch {
|
|
55237
55316
|
return filePath;
|
|
55238
55317
|
}
|
|
@@ -55580,7 +55659,7 @@ var init_structure_md = __esm({
|
|
|
55580
55659
|
|
|
55581
55660
|
// src/cli/report/persistRun.ts
|
|
55582
55661
|
import { existsSync as existsSync18, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
|
|
55583
|
-
import { join as join19, relative as
|
|
55662
|
+
import { join as join19, relative as relative10 } from "path";
|
|
55584
55663
|
async function persistRun(input) {
|
|
55585
55664
|
const {
|
|
55586
55665
|
cwd,
|
|
@@ -55642,7 +55721,7 @@ async function persistRun(input) {
|
|
|
55642
55721
|
const recentTopHashes = telemetryPayloads.map(
|
|
55643
55722
|
(payload) => [...payload.files].sort((a, b) => b.score - a.score).slice(0, 10).map((file) => file.hash)
|
|
55644
55723
|
);
|
|
55645
|
-
const currentTopFiles = [...report.components].sort((a, b) => b.adjustedScore - a.adjustedScore).slice(0, 10).map((c) => ({ filePath: c.filePath, hash: hashFile(
|
|
55724
|
+
const currentTopFiles = [...report.components].sort((a, b) => b.adjustedScore - a.adjustedScore).slice(0, 10).map((c) => ({ filePath: c.filePath, hash: hashFile(relative10(cwd, c.filePath)) }));
|
|
55646
55725
|
const unmatchedStringLiterals = results.flatMap((r) => r.unmatchedStringLiterals ?? []);
|
|
55647
55726
|
const flywheelOutput = computeFlywheelOutput(
|
|
55648
55727
|
runs,
|
|
@@ -55847,11 +55926,11 @@ var init_finalizeReport = __esm({
|
|
|
55847
55926
|
});
|
|
55848
55927
|
|
|
55849
55928
|
// src/cli/report/baseline-cache.ts
|
|
55850
|
-
import { relative as
|
|
55929
|
+
import { relative as relative11 } from "path";
|
|
55851
55930
|
function buildBaselineCache(report, configHash, gitHead, cwd) {
|
|
55852
55931
|
const scores = {};
|
|
55853
55932
|
for (const component of report.components) {
|
|
55854
|
-
scores[
|
|
55933
|
+
scores[relative11(cwd, component.filePath)] = {
|
|
55855
55934
|
baselineScore: component.componentScore,
|
|
55856
55935
|
componentCount: component.componentCount
|
|
55857
55936
|
};
|
|
@@ -55929,14 +56008,14 @@ var init_json = __esm({
|
|
|
55929
56008
|
// src/report/sarif.ts
|
|
55930
56009
|
import { createHash as createHash11 } from "crypto";
|
|
55931
56010
|
import { readFileSync as readFileSync21 } from "fs";
|
|
55932
|
-
import { basename as basename3, isAbsolute as isAbsolute3, relative as
|
|
56011
|
+
import { basename as basename3, isAbsolute as isAbsolute3, relative as relative12, resolve as resolve9 } from "path";
|
|
55933
56012
|
function buildArtifactUri(filePath, cwd) {
|
|
55934
56013
|
if (!filePath) {
|
|
55935
56014
|
return ".";
|
|
55936
56015
|
}
|
|
55937
56016
|
if (cwd) {
|
|
55938
56017
|
const absoluteFilePath = isAbsolute3(filePath) ? filePath : resolve9(cwd, filePath);
|
|
55939
|
-
const rel =
|
|
56018
|
+
const rel = relative12(cwd, absoluteFilePath);
|
|
55940
56019
|
if (rel.startsWith("..")) {
|
|
55941
56020
|
return basename3(filePath);
|
|
55942
56021
|
}
|
|
@@ -57311,7 +57390,7 @@ var init_layout_token = __esm({
|
|
|
57311
57390
|
|
|
57312
57391
|
// src/report/unified-diff.ts
|
|
57313
57392
|
import { existsSync as existsSync19, readFileSync as readFileSync23 } from "fs";
|
|
57314
|
-
import { relative as
|
|
57393
|
+
import { relative as relative13 } from "path";
|
|
57315
57394
|
function collectAllFixes(issue) {
|
|
57316
57395
|
return [...issue.fix ? [issue.fix] : [], ...issue.fixes ?? []];
|
|
57317
57396
|
}
|
|
@@ -57397,7 +57476,7 @@ function formatUnifiedDiff(report, cwd) {
|
|
|
57397
57476
|
parts.push("");
|
|
57398
57477
|
hasHunk = true;
|
|
57399
57478
|
}
|
|
57400
|
-
const rel =
|
|
57479
|
+
const rel = relative13(cwd, filePath);
|
|
57401
57480
|
parts.push(`--- a/${rel}`);
|
|
57402
57481
|
parts.push(`+++ b/${rel}`);
|
|
57403
57482
|
parts.push(...formatHunk(original, patched));
|
|
@@ -57421,7 +57500,7 @@ __export(heatmap_exports, {
|
|
|
57421
57500
|
buildHeatmap: () => buildHeatmap,
|
|
57422
57501
|
formatHeatmap: () => formatHeatmap
|
|
57423
57502
|
});
|
|
57424
|
-
import { relative as
|
|
57503
|
+
import { relative as relative14 } from "path";
|
|
57425
57504
|
function isWithinDays(lastModified, reference, days) {
|
|
57426
57505
|
const msPerDay = 1e3 * 60 * 60 * 24;
|
|
57427
57506
|
const daysAgo = (reference.getTime() - lastModified.getTime()) / msPerDay;
|
|
@@ -57437,7 +57516,7 @@ async function buildHeatmap(report, cwd, helpers = { getFileEditCount, getFileLa
|
|
|
57437
57516
|
const reference = new Date(report.generatedAt);
|
|
57438
57517
|
const entries = await Promise.all(
|
|
57439
57518
|
report.components.map(async (component) => {
|
|
57440
|
-
const relPath =
|
|
57519
|
+
const relPath = relative14(cwd, component.filePath) || component.filePath;
|
|
57441
57520
|
const [edits, lastModified] = await Promise.all([
|
|
57442
57521
|
helpers.getFileEditCount(cwd, relPath, RECENCY_DAYS),
|
|
57443
57522
|
helpers.getFileLastModifiedDate(cwd, relPath)
|
|
@@ -57782,7 +57861,7 @@ __export(scan_exports, {
|
|
|
57782
57861
|
watchProject: () => watchProject
|
|
57783
57862
|
});
|
|
57784
57863
|
import { existsSync as existsSync20, statSync as statSync8 } from "fs";
|
|
57785
|
-
import { resolve as resolve12, relative as
|
|
57864
|
+
import { resolve as resolve12, relative as relative15, extname as extname7, sep as sep3 } from "path";
|
|
57786
57865
|
import { randomUUID } from "crypto";
|
|
57787
57866
|
async function runScan(options, explicitPaths) {
|
|
57788
57867
|
setLoggerQuiet(!!options.quiet);
|
|
@@ -57830,7 +57909,7 @@ async function runScan(options, explicitPaths) {
|
|
|
57830
57909
|
let files;
|
|
57831
57910
|
if (explicitPaths && explicitPaths.length > 0) {
|
|
57832
57911
|
const { globby: globby4 } = await import("globby");
|
|
57833
|
-
const { minimatch:
|
|
57912
|
+
const { minimatch: minimatch4 } = await import("minimatch");
|
|
57834
57913
|
const resolved = explicitPaths.map((p) => resolve12(cwd, p));
|
|
57835
57914
|
const expanded = [];
|
|
57836
57915
|
for (const p of resolved) {
|
|
@@ -57838,11 +57917,11 @@ async function runScan(options, explicitPaths) {
|
|
|
57838
57917
|
const found = await globby4(`${p}/**/*`, { absolute: true, onlyFiles: true });
|
|
57839
57918
|
for (const f of found) {
|
|
57840
57919
|
if (!ALL_SOURCE_EXTENSIONS.has(extname7(f).toLowerCase())) continue;
|
|
57841
|
-
const rel =
|
|
57842
|
-
if (config.include.length > 0 && !config.include.some((pattern) =>
|
|
57920
|
+
const rel = relative15(cwd, f).split(sep3).join("/");
|
|
57921
|
+
if (config.include.length > 0 && !config.include.some((pattern) => minimatch4(rel, pattern))) {
|
|
57843
57922
|
continue;
|
|
57844
57923
|
}
|
|
57845
|
-
if (config.exclude.some((pattern) =>
|
|
57924
|
+
if (config.exclude.some((pattern) => minimatch4(rel, pattern, { dot: true }))) {
|
|
57846
57925
|
continue;
|
|
57847
57926
|
}
|
|
57848
57927
|
expanded.push(f);
|
|
@@ -61076,7 +61155,7 @@ import { resolve as resolve26 } from "path";
|
|
|
61076
61155
|
init_discover();
|
|
61077
61156
|
init_patterns();
|
|
61078
61157
|
import { readFileSync as readFileSync30 } from "fs";
|
|
61079
|
-
import { basename as basename4, relative as
|
|
61158
|
+
import { basename as basename4, relative as relative16 } from "path";
|
|
61080
61159
|
async function runDrift(cwd, config, options = {}) {
|
|
61081
61160
|
const maxFiles = options.maxFiles ?? 1e3;
|
|
61082
61161
|
const allFiles = await discoverFiles(cwd, config);
|
|
@@ -61098,7 +61177,7 @@ async function runDrift(cwd, config, options = {}) {
|
|
|
61098
61177
|
byCategory[v.category] = (byCategory[v.category] ?? 0) + 1;
|
|
61099
61178
|
byFile.push({
|
|
61100
61179
|
file: absPath,
|
|
61101
|
-
relPath:
|
|
61180
|
+
relPath: relative16(cwd, absPath),
|
|
61102
61181
|
category: v.category,
|
|
61103
61182
|
import: v.import,
|
|
61104
61183
|
declared: v.declared,
|
|
@@ -61220,10 +61299,10 @@ init_metrics();
|
|
|
61220
61299
|
init_patterns();
|
|
61221
61300
|
init_discover();
|
|
61222
61301
|
import { readFileSync as readFileSync31 } from "fs";
|
|
61223
|
-
import { extname as extname9, relative as
|
|
61302
|
+
import { extname as extname9, relative as relative17, resolve as resolve27 } from "path";
|
|
61224
61303
|
import { execFile as execFileCb } from "child_process";
|
|
61225
61304
|
import { promisify as promisify2 } from "util";
|
|
61226
|
-
import { minimatch as
|
|
61305
|
+
import { minimatch as minimatch3 } from "minimatch";
|
|
61227
61306
|
var execFile2 = promisify2(execFileCb);
|
|
61228
61307
|
var PR_EXTENSIONS = /* @__PURE__ */ new Set([...SOURCE_EXTENSIONS, ".mdx"]);
|
|
61229
61308
|
async function refExists(cwd, ref) {
|
|
@@ -61264,11 +61343,11 @@ async function discoverPrFiles(cwd, config, base, head, maxFiles) {
|
|
|
61264
61343
|
const abs = resolve27(cwd, relOrAbs);
|
|
61265
61344
|
const ext = extname9(abs).toLowerCase();
|
|
61266
61345
|
if (!PR_EXTENSIONS.has(ext)) continue;
|
|
61267
|
-
const rel =
|
|
61268
|
-
if (config.include.length > 0 && !config.include.some((pattern) =>
|
|
61346
|
+
const rel = relative17(cwd, abs).split("\\").join("/");
|
|
61347
|
+
if (config.include.length > 0 && !config.include.some((pattern) => minimatch3(rel, pattern))) {
|
|
61269
61348
|
continue;
|
|
61270
61349
|
}
|
|
61271
|
-
if (config.exclude.some((pattern) =>
|
|
61350
|
+
if (config.exclude.some((pattern) => minimatch3(rel, pattern))) {
|
|
61272
61351
|
continue;
|
|
61273
61352
|
}
|
|
61274
61353
|
sourceFiles.push(abs);
|
|
@@ -61335,7 +61414,7 @@ async function runPrScan(cwd, config, options = {}) {
|
|
|
61335
61414
|
totalScore += fileScore;
|
|
61336
61415
|
files.push({
|
|
61337
61416
|
file: absPath,
|
|
61338
|
-
relPath:
|
|
61417
|
+
relPath: relative17(cwd, absPath).split("\\").join("/"),
|
|
61339
61418
|
score: fileScore,
|
|
61340
61419
|
issueCount: issues.length,
|
|
61341
61420
|
constitutionViolationCount: constitutionViolations.length,
|
|
@@ -61712,7 +61791,7 @@ import { resolve as resolve33 } from "path";
|
|
|
61712
61791
|
init_discover();
|
|
61713
61792
|
init_business_logic();
|
|
61714
61793
|
import { readFileSync as readFileSync32 } from "fs";
|
|
61715
|
-
import { relative as
|
|
61794
|
+
import { relative as relative18 } from "path";
|
|
61716
61795
|
async function runBusinessLogicScan(cwd, config, options = {}) {
|
|
61717
61796
|
const maxFiles = options.maxFiles ?? 500;
|
|
61718
61797
|
const allFiles = await discoverFiles(cwd, config);
|
|
@@ -61731,7 +61810,7 @@ async function runBusinessLogicScan(cwd, config, options = {}) {
|
|
|
61731
61810
|
for (const issue of fileIssues) {
|
|
61732
61811
|
issues.push({
|
|
61733
61812
|
...issue,
|
|
61734
|
-
filePath:
|
|
61813
|
+
filePath: relative18(cwd, absPath) || absPath
|
|
61735
61814
|
});
|
|
61736
61815
|
}
|
|
61737
61816
|
}
|
|
@@ -62293,7 +62372,7 @@ import { resolve as resolve37 } from "path";
|
|
|
62293
62372
|
init_patterns();
|
|
62294
62373
|
init_discover();
|
|
62295
62374
|
import { readFileSync as readFileSync33 } from "fs";
|
|
62296
|
-
import { basename as basename5, relative as
|
|
62375
|
+
import { basename as basename5, relative as relative19 } from "path";
|
|
62297
62376
|
var PATTERN_CATEGORIES = [
|
|
62298
62377
|
"modal",
|
|
62299
62378
|
"button",
|
|
@@ -62385,7 +62464,7 @@ function detectApiFromFiles(files, cwd) {
|
|
|
62385
62464
|
const out = [];
|
|
62386
62465
|
for (const f of files) {
|
|
62387
62466
|
if (API_PATH_RE2.test(f)) {
|
|
62388
|
-
out.push(
|
|
62467
|
+
out.push(relative19(cwd, f).split("\\").join("/"));
|
|
62389
62468
|
}
|
|
62390
62469
|
}
|
|
62391
62470
|
return out;
|