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.cjs
CHANGED
|
@@ -36,7 +36,7 @@ var VERSION;
|
|
|
36
36
|
var init_header = __esm({
|
|
37
37
|
"src/types/_header.ts"() {
|
|
38
38
|
"use strict";
|
|
39
|
-
VERSION = "0.
|
|
39
|
+
VERSION = "0.25.1";
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
|
|
@@ -326,6 +326,43 @@ var init_defaults = __esm({
|
|
|
326
326
|
*/
|
|
327
327
|
testIntelligence: {
|
|
328
328
|
missingEdgeCase: false
|
|
329
|
+
},
|
|
330
|
+
/**
|
|
331
|
+
* v0.25.0: self-scan exclude paths. Defaults cover the three paths
|
|
332
|
+
* that are always false positives when scanning the slopbrick repo
|
|
333
|
+
* itself:
|
|
334
|
+
*
|
|
335
|
+
* - `src/rules/**` — rule definitions contain example patterns
|
|
336
|
+
* the rules themselves detect (self-fire). E.g. a
|
|
337
|
+
* `security/sql-construction` rule's source file has a SQL
|
|
338
|
+
* concat string literal that fires its own regex.
|
|
339
|
+
* - `tests/fixtures/**` — test fixtures contain intentional bad
|
|
340
|
+
* code that the rules must fire on to be useful (each fixture
|
|
341
|
+
* is a positive test case). Scanning them in a self-scan
|
|
342
|
+
* produces ~70 false-positive "issues" that are really just
|
|
343
|
+
* test data.
|
|
344
|
+
* - `tests/rules/**` — rule test files contain expected-issue
|
|
345
|
+
* assertions, also meta-code.
|
|
346
|
+
*
|
|
347
|
+
* Three patterns (broadened in v0.25.1 from the v0.25.0 narrow
|
|
348
|
+
* set, which missed `tests/engine/**`, `tests/cli/**`, and
|
|
349
|
+
* `snippet/**`), ~80 issues removed per self-scan. Combined
|
|
350
|
+
* with the v0.25.0 graded security cap, this restores the
|
|
351
|
+
* v9 plan's "security ≥ 80" criterion (unachievable in
|
|
352
|
+
* v0.24.0 due to 90 self-scan FPs collapsing the score to 0).
|
|
353
|
+
*
|
|
354
|
+
* Set `selfScan: { excludePaths: [] }` in `slopbrick.config.mjs`
|
|
355
|
+
* to opt out and scan every file (legacy behavior).
|
|
356
|
+
*/
|
|
357
|
+
selfScan: {
|
|
358
|
+
excludePaths: [
|
|
359
|
+
"**/src/rules/**",
|
|
360
|
+
// rule definitions are meta-code
|
|
361
|
+
"**/snippet/**",
|
|
362
|
+
// RULE_HINTS examples are intentional bad code
|
|
363
|
+
"**/tests/**"
|
|
364
|
+
// all test files (fixtures + unit + integration)
|
|
365
|
+
]
|
|
329
366
|
}
|
|
330
367
|
};
|
|
331
368
|
}
|
|
@@ -36375,6 +36412,7 @@ function parseSource(source, filePath) {
|
|
|
36375
36412
|
case "py":
|
|
36376
36413
|
case "go":
|
|
36377
36414
|
case "rs":
|
|
36415
|
+
case "java":
|
|
36378
36416
|
return parseBlankModule(source);
|
|
36379
36417
|
default:
|
|
36380
36418
|
return parseWithSwc(source, filePath);
|
|
@@ -49878,13 +49916,13 @@ var init_signal_strength = __esm({
|
|
|
49878
49916
|
defaultOff: true
|
|
49879
49917
|
},
|
|
49880
49918
|
"java/arraylist-vs-linkedlist": {
|
|
49881
|
-
recall:
|
|
49882
|
-
fpRate:
|
|
49883
|
-
ratio:
|
|
49884
|
-
precision: 0,
|
|
49885
|
-
lastCalibratedAt: "2026-07-
|
|
49919
|
+
recall: 35e-4,
|
|
49920
|
+
fpRate: 94e-4,
|
|
49921
|
+
ratio: 0.37,
|
|
49922
|
+
precision: 0.0446,
|
|
49923
|
+
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
49886
49924
|
verdict: "DORMANT",
|
|
49887
|
-
_calibrationNote: "v0.20: new rule (new LinkedList<>() \u2014 use ArrayList
|
|
49925
|
+
_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).",
|
|
49888
49926
|
aiSpecific: true,
|
|
49889
49927
|
_v7Verdict: "DORMANT",
|
|
49890
49928
|
_v7Lift: 1,
|
|
@@ -49893,16 +49931,21 @@ var init_signal_strength = __esm({
|
|
|
49893
49931
|
_v7Precision: 0,
|
|
49894
49932
|
_v8Verdict: "DORMANT",
|
|
49895
49933
|
_v8Lift: 1,
|
|
49934
|
+
_v9Verdict: "DORMANT",
|
|
49935
|
+
_v9Lift: 4.74,
|
|
49936
|
+
_v9Recall: 35e-4,
|
|
49937
|
+
_v9FpRate: 94e-4,
|
|
49938
|
+
_v9Precision: 0.0446,
|
|
49896
49939
|
defaultOff: true
|
|
49897
49940
|
},
|
|
49898
49941
|
"java/empty-catch-block": {
|
|
49899
|
-
recall:
|
|
49900
|
-
fpRate: 0,
|
|
49901
|
-
ratio:
|
|
49902
|
-
precision:
|
|
49903
|
-
lastCalibratedAt: "2026-07-
|
|
49942
|
+
recall: 8e-3,
|
|
49943
|
+
fpRate: 0.1118,
|
|
49944
|
+
ratio: 0.07,
|
|
49945
|
+
precision: 89e-4,
|
|
49946
|
+
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
49904
49947
|
verdict: "DORMANT",
|
|
49905
|
-
_calibrationNote: "v0.
|
|
49948
|
+
_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.",
|
|
49906
49949
|
aiSpecific: true,
|
|
49907
49950
|
_v7Verdict: "DORMANT",
|
|
49908
49951
|
_v7Lift: 1,
|
|
@@ -49911,16 +49954,21 @@ var init_signal_strength = __esm({
|
|
|
49911
49954
|
_v7Precision: 0,
|
|
49912
49955
|
_v8Verdict: "DORMANT",
|
|
49913
49956
|
_v8Lift: 1,
|
|
49957
|
+
_v9Verdict: "DORMANT",
|
|
49958
|
+
_v9Lift: 0.08,
|
|
49959
|
+
_v9Recall: 8e-3,
|
|
49960
|
+
_v9FpRate: 0.1118,
|
|
49961
|
+
_v9Precision: 89e-4,
|
|
49914
49962
|
defaultOff: true
|
|
49915
49963
|
},
|
|
49916
49964
|
"java/legacy-date-api": {
|
|
49917
|
-
recall: 0,
|
|
49918
|
-
fpRate: 0,
|
|
49919
|
-
ratio:
|
|
49920
|
-
precision: 0,
|
|
49921
|
-
lastCalibratedAt: "2026-07-
|
|
49965
|
+
recall: 0.0285,
|
|
49966
|
+
fpRate: 0.0511,
|
|
49967
|
+
ratio: 0.56,
|
|
49968
|
+
precision: 0.0657,
|
|
49969
|
+
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
49922
49970
|
verdict: "DORMANT",
|
|
49923
|
-
_calibrationNote: "v0.
|
|
49971
|
+
_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.",
|
|
49924
49972
|
aiSpecific: true,
|
|
49925
49973
|
_v7Verdict: "DORMANT",
|
|
49926
49974
|
_v7Lift: 1,
|
|
@@ -49929,16 +49977,21 @@ var init_signal_strength = __esm({
|
|
|
49929
49977
|
_v7Precision: 0,
|
|
49930
49978
|
_v8Verdict: "DORMANT",
|
|
49931
49979
|
_v8Lift: 1,
|
|
49980
|
+
_v9Verdict: "DORMANT",
|
|
49981
|
+
_v9Lift: 1.29,
|
|
49982
|
+
_v9Recall: 0.0285,
|
|
49983
|
+
_v9FpRate: 0.0511,
|
|
49984
|
+
_v9Precision: 0.0657,
|
|
49932
49985
|
defaultOff: true
|
|
49933
49986
|
},
|
|
49934
49987
|
"java/raw-type-overuse": {
|
|
49935
|
-
recall: 0,
|
|
49936
|
-
fpRate: 0,
|
|
49937
|
-
ratio:
|
|
49938
|
-
precision: 0,
|
|
49939
|
-
lastCalibratedAt: "2026-07-
|
|
49988
|
+
recall: 0.1093,
|
|
49989
|
+
fpRate: 0.2041,
|
|
49990
|
+
ratio: 0.54,
|
|
49991
|
+
precision: 0.0632,
|
|
49992
|
+
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
49940
49993
|
verdict: "DORMANT",
|
|
49941
|
-
_calibrationNote: "v0.20:
|
|
49994
|
+
_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.",
|
|
49942
49995
|
aiSpecific: true,
|
|
49943
49996
|
_v7Verdict: "DORMANT",
|
|
49944
49997
|
_v7Lift: 1,
|
|
@@ -49947,16 +50000,21 @@ var init_signal_strength = __esm({
|
|
|
49947
50000
|
_v7Precision: 0,
|
|
49948
50001
|
_v8Verdict: "DORMANT",
|
|
49949
50002
|
_v8Lift: 1,
|
|
50003
|
+
_v9Verdict: "DORMANT",
|
|
50004
|
+
_v9Lift: 0.31,
|
|
50005
|
+
_v9Recall: 0.1093,
|
|
50006
|
+
_v9FpRate: 0.2041,
|
|
50007
|
+
_v9Precision: 0.0632,
|
|
49950
50008
|
defaultOff: true
|
|
49951
50009
|
},
|
|
49952
50010
|
"java/string-concat-loop": {
|
|
49953
|
-
recall:
|
|
49954
|
-
fpRate: 0,
|
|
49955
|
-
ratio:
|
|
49956
|
-
precision: 0,
|
|
49957
|
-
lastCalibratedAt: "2026-07-
|
|
50011
|
+
recall: 46e-4,
|
|
50012
|
+
fpRate: 0.0358,
|
|
50013
|
+
ratio: 0.13,
|
|
50014
|
+
precision: 0.0158,
|
|
50015
|
+
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
49958
50016
|
verdict: "DORMANT",
|
|
49959
|
-
_calibrationNote: "v0.
|
|
50017
|
+
_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.",
|
|
49960
50018
|
aiSpecific: true,
|
|
49961
50019
|
_v7Verdict: "DORMANT",
|
|
49962
50020
|
_v7Lift: 1,
|
|
@@ -49965,16 +50023,21 @@ var init_signal_strength = __esm({
|
|
|
49965
50023
|
_v7Precision: 0,
|
|
49966
50024
|
_v8Verdict: "DORMANT",
|
|
49967
50025
|
_v8Lift: 1,
|
|
50026
|
+
_v9Verdict: "DORMANT",
|
|
50027
|
+
_v9Lift: 0.44,
|
|
50028
|
+
_v9Recall: 46e-4,
|
|
50029
|
+
_v9FpRate: 0.0358,
|
|
50030
|
+
_v9Precision: 0.0158,
|
|
49968
50031
|
defaultOff: true
|
|
49969
50032
|
},
|
|
49970
50033
|
"java/system-out-println": {
|
|
49971
|
-
recall: 0,
|
|
49972
|
-
fpRate: 0,
|
|
49973
|
-
ratio:
|
|
49974
|
-
precision: 0,
|
|
49975
|
-
lastCalibratedAt: "2026-07-
|
|
50034
|
+
recall: 0.1503,
|
|
50035
|
+
fpRate: 0.2533,
|
|
50036
|
+
ratio: 0.59,
|
|
50037
|
+
precision: 0.0695,
|
|
50038
|
+
lastCalibratedAt: "2026-07-02T19:34:00Z",
|
|
49976
50039
|
verdict: "DORMANT",
|
|
49977
|
-
_calibrationNote: "v0.
|
|
50040
|
+
_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.",
|
|
49978
50041
|
aiSpecific: true,
|
|
49979
50042
|
_v7Verdict: "DORMANT",
|
|
49980
50043
|
_v7Lift: 1,
|
|
@@ -49983,6 +50046,11 @@ var init_signal_strength = __esm({
|
|
|
49983
50046
|
_v7Precision: 0,
|
|
49984
50047
|
_v8Verdict: "DORMANT",
|
|
49985
50048
|
_v8Lift: 1,
|
|
50049
|
+
_v9Verdict: "DORMANT",
|
|
50050
|
+
_v9Lift: 0.27,
|
|
50051
|
+
_v9Recall: 0.1503,
|
|
50052
|
+
_v9FpRate: 0.2533,
|
|
50053
|
+
_v9Precision: 0.0695,
|
|
49986
50054
|
defaultOff: true
|
|
49987
50055
|
},
|
|
49988
50056
|
"layout/forced-layout": {
|
|
@@ -51555,7 +51623,23 @@ function applyRuleOverrides(issues, rules) {
|
|
|
51555
51623
|
}
|
|
51556
51624
|
return result;
|
|
51557
51625
|
}
|
|
51626
|
+
function isExcludedBySelfScan(filePath, cwd, excludePaths) {
|
|
51627
|
+
if (!excludePaths || excludePaths.length === 0) return false;
|
|
51628
|
+
const rel = (0, import_node_path13.relative)(cwd, filePath).split(import_node_path13.sep).join("/");
|
|
51629
|
+
return excludePaths.some((pattern) => (0, import_minimatch2.minimatch)(rel, pattern, { dot: true }));
|
|
51630
|
+
}
|
|
51558
51631
|
async function scanFile(filePath, config, registry, cwd = process.cwd()) {
|
|
51632
|
+
if (isExcludedBySelfScan(filePath, cwd, config.selfScan?.excludePaths)) {
|
|
51633
|
+
return {
|
|
51634
|
+
filePath,
|
|
51635
|
+
componentCount: 0,
|
|
51636
|
+
issues: [],
|
|
51637
|
+
gapValues: [],
|
|
51638
|
+
styleSources: [],
|
|
51639
|
+
elementTags: [],
|
|
51640
|
+
unmatchedStringLiterals: []
|
|
51641
|
+
};
|
|
51642
|
+
}
|
|
51559
51643
|
const cache = buildParserCacheConfig(cwd);
|
|
51560
51644
|
const ext = (0, import_node_path12.extname)(filePath).toLowerCase();
|
|
51561
51645
|
const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
|
|
@@ -51570,7 +51654,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
|
|
|
51570
51654
|
".h",
|
|
51571
51655
|
".hpp",
|
|
51572
51656
|
".hxx",
|
|
51573
|
-
".java",
|
|
51574
51657
|
".rb",
|
|
51575
51658
|
".php"
|
|
51576
51659
|
]);
|
|
@@ -51673,13 +51756,14 @@ function collectStyleSources(facts) {
|
|
|
51673
51756
|
}
|
|
51674
51757
|
return sources;
|
|
51675
51758
|
}
|
|
51676
|
-
var import_node_worker_threads, import_node_path12, import_node_path13;
|
|
51759
|
+
var import_node_worker_threads, import_node_path12, import_node_path13, import_minimatch2;
|
|
51677
51760
|
var init_worker = __esm({
|
|
51678
51761
|
"src/engine/worker.ts"() {
|
|
51679
51762
|
"use strict";
|
|
51680
51763
|
import_node_worker_threads = require("worker_threads");
|
|
51681
51764
|
import_node_path12 = require("path");
|
|
51682
51765
|
import_node_path13 = require("path");
|
|
51766
|
+
import_minimatch2 = require("minimatch");
|
|
51683
51767
|
init_dist2();
|
|
51684
51768
|
init_visitor();
|
|
51685
51769
|
init_registry();
|
|
@@ -51862,45 +51946,6 @@ var init_flywheel = __esm({
|
|
|
51862
51946
|
}
|
|
51863
51947
|
});
|
|
51864
51948
|
|
|
51865
|
-
// src/engine/ai-security-risk.ts
|
|
51866
|
-
function computeAiSecurityRisk(issues) {
|
|
51867
|
-
const findings = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
51868
|
-
for (const issue of issues) {
|
|
51869
|
-
switch (issue.severity) {
|
|
51870
|
-
case "high":
|
|
51871
|
-
findings.high += 1;
|
|
51872
|
-
break;
|
|
51873
|
-
case "medium":
|
|
51874
|
-
findings.medium += 1;
|
|
51875
|
-
break;
|
|
51876
|
-
case "low":
|
|
51877
|
-
findings.low += 1;
|
|
51878
|
-
break;
|
|
51879
|
-
default:
|
|
51880
|
-
findings.low += 1;
|
|
51881
|
-
}
|
|
51882
|
-
}
|
|
51883
|
-
let risk = "low";
|
|
51884
|
-
if (findings.critical >= 1 || findings.high >= 3) {
|
|
51885
|
-
risk = "critical";
|
|
51886
|
-
} else if (findings.high >= 1 || findings.medium >= 3) {
|
|
51887
|
-
risk = "high";
|
|
51888
|
-
} else if (findings.medium >= 1) {
|
|
51889
|
-
risk = "medium";
|
|
51890
|
-
}
|
|
51891
|
-
return { risk, findings };
|
|
51892
|
-
}
|
|
51893
|
-
function formatAiSecurityRiskLine(risk, findings) {
|
|
51894
|
-
const total = findings.critical + findings.high + findings.medium + findings.low;
|
|
51895
|
-
if (total === 0) return "AI Security Risk: low";
|
|
51896
|
-
return `AI Security Risk: ${risk.toUpperCase()} (${total} findings)`;
|
|
51897
|
-
}
|
|
51898
|
-
var init_ai_security_risk = __esm({
|
|
51899
|
-
"src/engine/ai-security-risk.ts"() {
|
|
51900
|
-
"use strict";
|
|
51901
|
-
}
|
|
51902
|
-
});
|
|
51903
|
-
|
|
51904
51949
|
// src/engine/test-quality.ts
|
|
51905
51950
|
var test_quality_exports = {};
|
|
51906
51951
|
__export(test_quality_exports, {
|
|
@@ -52076,14 +52121,10 @@ function aggregateReport(scores, issueGroups, config, compositeScores) {
|
|
|
52076
52121
|
});
|
|
52077
52122
|
}
|
|
52078
52123
|
}
|
|
52079
|
-
const
|
|
52080
|
-
|
|
52081
|
-
|
|
52082
|
-
|
|
52083
|
-
high: 33,
|
|
52084
|
-
critical: 0
|
|
52085
|
-
};
|
|
52086
|
-
const security = securityFromRisk[risk];
|
|
52124
|
+
const securityIssueCount = flatIssues.filter(
|
|
52125
|
+
(i) => i.category === "security" || (i.ruleId ?? "").startsWith("security/")
|
|
52126
|
+
).length;
|
|
52127
|
+
const security = Math.max(0, 100 / (1 + securityIssueCount / 5));
|
|
52087
52128
|
const engineeringCategoryScores = [
|
|
52088
52129
|
categoryScores.arch ?? 0,
|
|
52089
52130
|
categoryScores.logic ?? 0,
|
|
@@ -52142,7 +52183,6 @@ var init_metrics = __esm({
|
|
|
52142
52183
|
"src/engine/metrics.ts"() {
|
|
52143
52184
|
"use strict";
|
|
52144
52185
|
import_node_path15 = require("path");
|
|
52145
|
-
init_ai_security_risk();
|
|
52146
52186
|
init_test_quality();
|
|
52147
52187
|
SEVERITY_WEIGHTS = {
|
|
52148
52188
|
low: 1,
|
|
@@ -54074,6 +54114,45 @@ var init_business_logic = __esm({
|
|
|
54074
54114
|
}
|
|
54075
54115
|
});
|
|
54076
54116
|
|
|
54117
|
+
// src/engine/ai-security-risk.ts
|
|
54118
|
+
function computeAiSecurityRisk(issues) {
|
|
54119
|
+
const findings = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
54120
|
+
for (const issue of issues) {
|
|
54121
|
+
switch (issue.severity) {
|
|
54122
|
+
case "high":
|
|
54123
|
+
findings.high += 1;
|
|
54124
|
+
break;
|
|
54125
|
+
case "medium":
|
|
54126
|
+
findings.medium += 1;
|
|
54127
|
+
break;
|
|
54128
|
+
case "low":
|
|
54129
|
+
findings.low += 1;
|
|
54130
|
+
break;
|
|
54131
|
+
default:
|
|
54132
|
+
findings.low += 1;
|
|
54133
|
+
}
|
|
54134
|
+
}
|
|
54135
|
+
let risk = "low";
|
|
54136
|
+
if (findings.critical >= 1 || findings.high >= 3) {
|
|
54137
|
+
risk = "critical";
|
|
54138
|
+
} else if (findings.high >= 1 || findings.medium >= 3) {
|
|
54139
|
+
risk = "high";
|
|
54140
|
+
} else if (findings.medium >= 1) {
|
|
54141
|
+
risk = "medium";
|
|
54142
|
+
}
|
|
54143
|
+
return { risk, findings };
|
|
54144
|
+
}
|
|
54145
|
+
function formatAiSecurityRiskLine(risk, findings) {
|
|
54146
|
+
const total = findings.critical + findings.high + findings.medium + findings.low;
|
|
54147
|
+
if (total === 0) return "AI Security Risk: low";
|
|
54148
|
+
return `AI Security Risk: ${risk.toUpperCase()} (${total} findings)`;
|
|
54149
|
+
}
|
|
54150
|
+
var init_ai_security_risk = __esm({
|
|
54151
|
+
"src/engine/ai-security-risk.ts"() {
|
|
54152
|
+
"use strict";
|
|
54153
|
+
}
|
|
54154
|
+
});
|
|
54155
|
+
|
|
54077
54156
|
// src/engine/maintenance-cost.ts
|
|
54078
54157
|
var maintenance_cost_exports = {};
|
|
54079
54158
|
__export(maintenance_cost_exports, {
|
|
@@ -54813,7 +54892,7 @@ function computeDomainScores(issues) {
|
|
|
54813
54892
|
performance2.score = issueCountToScore(performance2.issueCount);
|
|
54814
54893
|
const security = tally(SECURITY_CATEGORIES);
|
|
54815
54894
|
security.name = "security";
|
|
54816
|
-
security.score =
|
|
54895
|
+
security.score = Math.max(0, 100 / (1 + security.issueCount / 5));
|
|
54817
54896
|
return { codeHygiene, accessibility, performance: performance2, security };
|
|
54818
54897
|
}
|
|
54819
54898
|
var COHERENCE_WEIGHTS, AI_DEBT_NUMERIC, CODE_HYGIENE_CATEGORIES, ACCESSIBILITY_CATEGORIES, PERFORMANCE_CATEGORIES, SECURITY_CATEGORIES;
|
|
@@ -57847,7 +57926,7 @@ async function runScan(options, explicitPaths) {
|
|
|
57847
57926
|
let files;
|
|
57848
57927
|
if (explicitPaths && explicitPaths.length > 0) {
|
|
57849
57928
|
const { globby: globby4 } = await import("globby");
|
|
57850
|
-
const { minimatch:
|
|
57929
|
+
const { minimatch: minimatch4 } = await import("minimatch");
|
|
57851
57930
|
const resolved = explicitPaths.map((p) => (0, import_node_path30.resolve)(cwd, p));
|
|
57852
57931
|
const expanded = [];
|
|
57853
57932
|
for (const p of resolved) {
|
|
@@ -57856,10 +57935,10 @@ async function runScan(options, explicitPaths) {
|
|
|
57856
57935
|
for (const f of found) {
|
|
57857
57936
|
if (!ALL_SOURCE_EXTENSIONS.has((0, import_node_path30.extname)(f).toLowerCase())) continue;
|
|
57858
57937
|
const rel = (0, import_node_path30.relative)(cwd, f).split(import_node_path30.sep).join("/");
|
|
57859
|
-
if (config.include.length > 0 && !config.include.some((pattern) =>
|
|
57938
|
+
if (config.include.length > 0 && !config.include.some((pattern) => minimatch4(rel, pattern))) {
|
|
57860
57939
|
continue;
|
|
57861
57940
|
}
|
|
57862
|
-
if (config.exclude.some((pattern) =>
|
|
57941
|
+
if (config.exclude.some((pattern) => minimatch4(rel, pattern, { dot: true }))) {
|
|
57863
57942
|
continue;
|
|
57864
57943
|
}
|
|
57865
57944
|
expanded.push(f);
|
|
@@ -61313,7 +61392,7 @@ var import_node_fs39 = require("fs");
|
|
|
61313
61392
|
var import_node_path52 = require("path");
|
|
61314
61393
|
var import_node_child_process3 = require("child_process");
|
|
61315
61394
|
var import_node_util2 = require("util");
|
|
61316
|
-
var
|
|
61395
|
+
var import_minimatch3 = require("minimatch");
|
|
61317
61396
|
init_git();
|
|
61318
61397
|
init_worker();
|
|
61319
61398
|
init_metrics();
|
|
@@ -61360,10 +61439,10 @@ async function discoverPrFiles(cwd, config, base, head, maxFiles) {
|
|
|
61360
61439
|
const ext = (0, import_node_path52.extname)(abs).toLowerCase();
|
|
61361
61440
|
if (!PR_EXTENSIONS.has(ext)) continue;
|
|
61362
61441
|
const rel = (0, import_node_path52.relative)(cwd, abs).split("\\").join("/");
|
|
61363
|
-
if (config.include.length > 0 && !config.include.some((pattern) => (0,
|
|
61442
|
+
if (config.include.length > 0 && !config.include.some((pattern) => (0, import_minimatch3.minimatch)(rel, pattern))) {
|
|
61364
61443
|
continue;
|
|
61365
61444
|
}
|
|
61366
|
-
if (config.exclude.some((pattern) => (0,
|
|
61445
|
+
if (config.exclude.some((pattern) => (0, import_minimatch3.minimatch)(rel, pattern))) {
|
|
61367
61446
|
continue;
|
|
61368
61447
|
}
|
|
61369
61448
|
sourceFiles.push(abs);
|
package/dist/index.d.cts
CHANGED
|
@@ -737,6 +737,27 @@ interface RuleContext {
|
|
|
737
737
|
supportsRsc?: boolean;
|
|
738
738
|
hotspotIssues?: Issue[];
|
|
739
739
|
}
|
|
740
|
+
/**
|
|
741
|
+
* v0.25.0: self-scan excludes. When `selfScan.excludePaths` is set, the
|
|
742
|
+
* scan worker skips files whose workspace-relative path matches any of
|
|
743
|
+
* the glob patterns. This is for *self-scanning* the slopbrick repo
|
|
744
|
+
* itself, where rule definitions (`src/rules/**`) and test fixtures
|
|
745
|
+
* (`tests/fixtures/**`, `tests/rules/**`) are meta-code — the rules
|
|
746
|
+
* contain examples of the patterns they detect (self-fire), and test
|
|
747
|
+
* fixtures contain intentional bad code that the rules must fire on
|
|
748
|
+
* to be useful. Both are false positives in the self-scan context.
|
|
749
|
+
*
|
|
750
|
+
* Default excludes (in `config/defaults.ts`) cover exactly these three
|
|
751
|
+
* paths. Users who scan a *different* repo can leave `selfScan` unset
|
|
752
|
+
* (or set `excludePaths: []`) to opt out. Empty array disables; absent
|
|
753
|
+
* field uses defaults.
|
|
754
|
+
*/
|
|
755
|
+
interface ScanSelfScanConfig {
|
|
756
|
+
/** Glob patterns (minimatch) to exclude from the scan. Matched against
|
|
757
|
+
* the workspace-relative POSIX-style path. Dot files are matched
|
|
758
|
+
* (`{ dot: true }` semantics). */
|
|
759
|
+
excludePaths: string[];
|
|
760
|
+
}
|
|
740
761
|
interface Rule<Context = unknown> {
|
|
741
762
|
id: string;
|
|
742
763
|
category: Category;
|
|
@@ -832,6 +853,19 @@ interface ResolvedConfig {
|
|
|
832
853
|
/** Walk production AST to find branches without test coverage. */
|
|
833
854
|
missingEdgeCase?: boolean;
|
|
834
855
|
};
|
|
856
|
+
/**
|
|
857
|
+
* v0.25.0: self-scan exclude paths. Applied at scan time (in
|
|
858
|
+
* `engine/worker.ts`) so files matching any glob in `excludePaths`
|
|
859
|
+
* short-circuit with empty issues. Defaults (in `config/defaults.ts`)
|
|
860
|
+
* cover the three "always false positive in self-scan" paths:
|
|
861
|
+
* rule definitions (`src/rules/**`), test fixtures
|
|
862
|
+
* (`tests/fixtures/**`), and rule test files (`tests/rules/**`).
|
|
863
|
+
*
|
|
864
|
+
* Empty array `excludePaths: []` disables exclusion entirely
|
|
865
|
+
* (legacy behavior — every file is scanned). Unset field uses
|
|
866
|
+
* defaults.
|
|
867
|
+
*/
|
|
868
|
+
selfScan?: ScanSelfScanConfig;
|
|
835
869
|
}
|
|
836
870
|
|
|
837
871
|
/**
|
|
@@ -1578,4 +1612,4 @@ declare function formatBadge(report: ProjectReport): string;
|
|
|
1578
1612
|
/** Render an array of values as a Unicode sparkline (▁▂▃▄▅▆▇█). */
|
|
1579
1613
|
declare function formatSparkline(values: number[]): string;
|
|
1580
1614
|
|
|
1581
|
-
export { AI_SECURITY_NUMERIC, type AiDebt, type AiMaintenanceCost, type AiMaintenanceCostResult, type AstroComponentFact, type AutoTunedRule, type BaselineCache, type BaselineMeta, type CachedFile, type Category, type ClassNameFact, type CommentFact, type ComponentFacts, type ComponentScore, type ComponentSizeFact, type ConsoleCallFact, type Constitution, DEFAULT_CONFIG, type DangerouslySetInnerHtmlFact, type DbDriftLevel, type DbFinding, type DialogCallFact, type DisabledLintRuleFact, type DocDriftLevel, type DocFinding, type DomQueryFact, type ElementFact, type EvalCallFact, type ExplicitAnyFact, type FetchCallFact, type FileScanResult, type FixSuggestion, type FlywheelOutput, type FlywheelState, type Framework$1 as Framework, type HealthFile, type HookCallFact, type HookDependencyArrayFact, type HookFact, type ImportFact, type InlineEventHandlerFact, type Issue, type JsxAttributeStringLiteralFact, type JsxTextLiteralFact, type KeyPropFact, type LogicalExpressionFact, type MagicNumberSpacingConfig, type MaintenanceAxes, type MaintenanceAxisHealth, type NonNullAssertionFact, type OptimisticUpdateFact, type ProjectReport, type PropMutationFact, type PropPassThroughFact, REPOSITORY_HEALTH_WEIGHTS, type ReportReadResult, type RepositoryHealth, type RepositoryHealthInputs, type ResearchMetrics, type ResolvedConfig, type Rule, type RuleContext, type RuleSeverity, type RuleSuggestion, type ScanCache, type ScanFacts, type ScanProjectOptions, type Severity, type SlopAuditRun, type StateBinding, type StateBindingFact, type StringLiteralFact, type StylePropFact, type TamaguiStylePropFact, type TopOffender, type UseEffectBodyFact, VERSION, baselineStatusMessage, colorForSlop, failedThresholdCount, filterByDisabledDirectives, filterIssues, formatBadge, formatReportFromFile, formatSparkline, loadConfig, readReportFile, runCli, runInitWizard, scanProject, serializeConfig, stagedGating, thresholdExceeded };
|
|
1615
|
+
export { AI_SECURITY_NUMERIC, type AiDebt, type AiMaintenanceCost, type AiMaintenanceCostResult, type AstroComponentFact, type AutoTunedRule, type BaselineCache, type BaselineMeta, type CachedFile, type Category, type ClassNameFact, type CommentFact, type ComponentFacts, type ComponentScore, type ComponentSizeFact, type ConsoleCallFact, type Constitution, DEFAULT_CONFIG, type DangerouslySetInnerHtmlFact, type DbDriftLevel, type DbFinding, type DialogCallFact, type DisabledLintRuleFact, type DocDriftLevel, type DocFinding, type DomQueryFact, type ElementFact, type EvalCallFact, type ExplicitAnyFact, type FetchCallFact, type FileScanResult, type FixSuggestion, type FlywheelOutput, type FlywheelState, type Framework$1 as Framework, type HealthFile, type HookCallFact, type HookDependencyArrayFact, type HookFact, type ImportFact, type InlineEventHandlerFact, type Issue, type JsxAttributeStringLiteralFact, type JsxTextLiteralFact, type KeyPropFact, type LogicalExpressionFact, type MagicNumberSpacingConfig, type MaintenanceAxes, type MaintenanceAxisHealth, type NonNullAssertionFact, type OptimisticUpdateFact, type ProjectReport, type PropMutationFact, type PropPassThroughFact, REPOSITORY_HEALTH_WEIGHTS, type ReportReadResult, type RepositoryHealth, type RepositoryHealthInputs, type ResearchMetrics, type ResolvedConfig, type Rule, type RuleContext, type RuleSeverity, type RuleSuggestion, type ScanCache, type ScanFacts, type ScanProjectOptions, type ScanSelfScanConfig, type Severity, type SlopAuditRun, type StateBinding, type StateBindingFact, type StringLiteralFact, type StylePropFact, type TamaguiStylePropFact, type TopOffender, type UseEffectBodyFact, VERSION, baselineStatusMessage, colorForSlop, failedThresholdCount, filterByDisabledDirectives, filterIssues, formatBadge, formatReportFromFile, formatSparkline, loadConfig, readReportFile, runCli, runInitWizard, scanProject, serializeConfig, stagedGating, thresholdExceeded };
|
package/dist/index.d.ts
CHANGED
|
@@ -737,6 +737,27 @@ interface RuleContext {
|
|
|
737
737
|
supportsRsc?: boolean;
|
|
738
738
|
hotspotIssues?: Issue[];
|
|
739
739
|
}
|
|
740
|
+
/**
|
|
741
|
+
* v0.25.0: self-scan excludes. When `selfScan.excludePaths` is set, the
|
|
742
|
+
* scan worker skips files whose workspace-relative path matches any of
|
|
743
|
+
* the glob patterns. This is for *self-scanning* the slopbrick repo
|
|
744
|
+
* itself, where rule definitions (`src/rules/**`) and test fixtures
|
|
745
|
+
* (`tests/fixtures/**`, `tests/rules/**`) are meta-code — the rules
|
|
746
|
+
* contain examples of the patterns they detect (self-fire), and test
|
|
747
|
+
* fixtures contain intentional bad code that the rules must fire on
|
|
748
|
+
* to be useful. Both are false positives in the self-scan context.
|
|
749
|
+
*
|
|
750
|
+
* Default excludes (in `config/defaults.ts`) cover exactly these three
|
|
751
|
+
* paths. Users who scan a *different* repo can leave `selfScan` unset
|
|
752
|
+
* (or set `excludePaths: []`) to opt out. Empty array disables; absent
|
|
753
|
+
* field uses defaults.
|
|
754
|
+
*/
|
|
755
|
+
interface ScanSelfScanConfig {
|
|
756
|
+
/** Glob patterns (minimatch) to exclude from the scan. Matched against
|
|
757
|
+
* the workspace-relative POSIX-style path. Dot files are matched
|
|
758
|
+
* (`{ dot: true }` semantics). */
|
|
759
|
+
excludePaths: string[];
|
|
760
|
+
}
|
|
740
761
|
interface Rule<Context = unknown> {
|
|
741
762
|
id: string;
|
|
742
763
|
category: Category;
|
|
@@ -832,6 +853,19 @@ interface ResolvedConfig {
|
|
|
832
853
|
/** Walk production AST to find branches without test coverage. */
|
|
833
854
|
missingEdgeCase?: boolean;
|
|
834
855
|
};
|
|
856
|
+
/**
|
|
857
|
+
* v0.25.0: self-scan exclude paths. Applied at scan time (in
|
|
858
|
+
* `engine/worker.ts`) so files matching any glob in `excludePaths`
|
|
859
|
+
* short-circuit with empty issues. Defaults (in `config/defaults.ts`)
|
|
860
|
+
* cover the three "always false positive in self-scan" paths:
|
|
861
|
+
* rule definitions (`src/rules/**`), test fixtures
|
|
862
|
+
* (`tests/fixtures/**`), and rule test files (`tests/rules/**`).
|
|
863
|
+
*
|
|
864
|
+
* Empty array `excludePaths: []` disables exclusion entirely
|
|
865
|
+
* (legacy behavior — every file is scanned). Unset field uses
|
|
866
|
+
* defaults.
|
|
867
|
+
*/
|
|
868
|
+
selfScan?: ScanSelfScanConfig;
|
|
835
869
|
}
|
|
836
870
|
|
|
837
871
|
/**
|
|
@@ -1578,4 +1612,4 @@ declare function formatBadge(report: ProjectReport): string;
|
|
|
1578
1612
|
/** Render an array of values as a Unicode sparkline (▁▂▃▄▅▆▇█). */
|
|
1579
1613
|
declare function formatSparkline(values: number[]): string;
|
|
1580
1614
|
|
|
1581
|
-
export { AI_SECURITY_NUMERIC, type AiDebt, type AiMaintenanceCost, type AiMaintenanceCostResult, type AstroComponentFact, type AutoTunedRule, type BaselineCache, type BaselineMeta, type CachedFile, type Category, type ClassNameFact, type CommentFact, type ComponentFacts, type ComponentScore, type ComponentSizeFact, type ConsoleCallFact, type Constitution, DEFAULT_CONFIG, type DangerouslySetInnerHtmlFact, type DbDriftLevel, type DbFinding, type DialogCallFact, type DisabledLintRuleFact, type DocDriftLevel, type DocFinding, type DomQueryFact, type ElementFact, type EvalCallFact, type ExplicitAnyFact, type FetchCallFact, type FileScanResult, type FixSuggestion, type FlywheelOutput, type FlywheelState, type Framework$1 as Framework, type HealthFile, type HookCallFact, type HookDependencyArrayFact, type HookFact, type ImportFact, type InlineEventHandlerFact, type Issue, type JsxAttributeStringLiteralFact, type JsxTextLiteralFact, type KeyPropFact, type LogicalExpressionFact, type MagicNumberSpacingConfig, type MaintenanceAxes, type MaintenanceAxisHealth, type NonNullAssertionFact, type OptimisticUpdateFact, type ProjectReport, type PropMutationFact, type PropPassThroughFact, REPOSITORY_HEALTH_WEIGHTS, type ReportReadResult, type RepositoryHealth, type RepositoryHealthInputs, type ResearchMetrics, type ResolvedConfig, type Rule, type RuleContext, type RuleSeverity, type RuleSuggestion, type ScanCache, type ScanFacts, type ScanProjectOptions, type Severity, type SlopAuditRun, type StateBinding, type StateBindingFact, type StringLiteralFact, type StylePropFact, type TamaguiStylePropFact, type TopOffender, type UseEffectBodyFact, VERSION, baselineStatusMessage, colorForSlop, failedThresholdCount, filterByDisabledDirectives, filterIssues, formatBadge, formatReportFromFile, formatSparkline, loadConfig, readReportFile, runCli, runInitWizard, scanProject, serializeConfig, stagedGating, thresholdExceeded };
|
|
1615
|
+
export { AI_SECURITY_NUMERIC, type AiDebt, type AiMaintenanceCost, type AiMaintenanceCostResult, type AstroComponentFact, type AutoTunedRule, type BaselineCache, type BaselineMeta, type CachedFile, type Category, type ClassNameFact, type CommentFact, type ComponentFacts, type ComponentScore, type ComponentSizeFact, type ConsoleCallFact, type Constitution, DEFAULT_CONFIG, type DangerouslySetInnerHtmlFact, type DbDriftLevel, type DbFinding, type DialogCallFact, type DisabledLintRuleFact, type DocDriftLevel, type DocFinding, type DomQueryFact, type ElementFact, type EvalCallFact, type ExplicitAnyFact, type FetchCallFact, type FileScanResult, type FixSuggestion, type FlywheelOutput, type FlywheelState, type Framework$1 as Framework, type HealthFile, type HookCallFact, type HookDependencyArrayFact, type HookFact, type ImportFact, type InlineEventHandlerFact, type Issue, type JsxAttributeStringLiteralFact, type JsxTextLiteralFact, type KeyPropFact, type LogicalExpressionFact, type MagicNumberSpacingConfig, type MaintenanceAxes, type MaintenanceAxisHealth, type NonNullAssertionFact, type OptimisticUpdateFact, type ProjectReport, type PropMutationFact, type PropPassThroughFact, REPOSITORY_HEALTH_WEIGHTS, type ReportReadResult, type RepositoryHealth, type RepositoryHealthInputs, type ResearchMetrics, type ResolvedConfig, type Rule, type RuleContext, type RuleSeverity, type RuleSuggestion, type ScanCache, type ScanFacts, type ScanProjectOptions, type ScanSelfScanConfig, type Severity, type SlopAuditRun, type StateBinding, type StateBindingFact, type StringLiteralFact, type StylePropFact, type TamaguiStylePropFact, type TopOffender, type UseEffectBodyFact, VERSION, baselineStatusMessage, colorForSlop, failedThresholdCount, filterByDisabledDirectives, filterIssues, formatBadge, formatReportFromFile, formatSparkline, loadConfig, readReportFile, runCli, runInitWizard, scanProject, serializeConfig, stagedGating, thresholdExceeded };
|