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.
@@ -37,6 +37,7 @@ module.exports = __toCommonJS(worker_exports);
37
37
  var import_node_worker_threads = require("worker_threads");
38
38
  var import_node_path9 = require("path");
39
39
  var import_node_path10 = require("path");
40
+ var import_minimatch = require("minimatch");
40
41
 
41
42
  // ../engine/dist/index.js
42
43
  var import_promises = require("fs/promises");
@@ -4364,6 +4365,7 @@ function parseSource(source, filePath) {
4364
4365
  case "py":
4365
4366
  case "go":
4366
4367
  case "rs":
4368
+ case "java":
4367
4369
  return parseBlankModule(source);
4368
4370
  default:
4369
4371
  return parseWithSwc(source, filePath);
@@ -37820,6 +37822,43 @@ var DEFAULT_CONFIG3 = {
37820
37822
  */
37821
37823
  testIntelligence: {
37822
37824
  missingEdgeCase: false
37825
+ },
37826
+ /**
37827
+ * v0.25.0: self-scan exclude paths. Defaults cover the three paths
37828
+ * that are always false positives when scanning the slopbrick repo
37829
+ * itself:
37830
+ *
37831
+ * - `src/rules/**` — rule definitions contain example patterns
37832
+ * the rules themselves detect (self-fire). E.g. a
37833
+ * `security/sql-construction` rule's source file has a SQL
37834
+ * concat string literal that fires its own regex.
37835
+ * - `tests/fixtures/**` — test fixtures contain intentional bad
37836
+ * code that the rules must fire on to be useful (each fixture
37837
+ * is a positive test case). Scanning them in a self-scan
37838
+ * produces ~70 false-positive "issues" that are really just
37839
+ * test data.
37840
+ * - `tests/rules/**` — rule test files contain expected-issue
37841
+ * assertions, also meta-code.
37842
+ *
37843
+ * Three patterns (broadened in v0.25.1 from the v0.25.0 narrow
37844
+ * set, which missed `tests/engine/**`, `tests/cli/**`, and
37845
+ * `snippet/**`), ~80 issues removed per self-scan. Combined
37846
+ * with the v0.25.0 graded security cap, this restores the
37847
+ * v9 plan's "security ≥ 80" criterion (unachievable in
37848
+ * v0.24.0 due to 90 self-scan FPs collapsing the score to 0).
37849
+ *
37850
+ * Set `selfScan: { excludePaths: [] }` in `slopbrick.config.mjs`
37851
+ * to opt out and scan every file (legacy behavior).
37852
+ */
37853
+ selfScan: {
37854
+ excludePaths: [
37855
+ "**/src/rules/**",
37856
+ // rule definitions are meta-code
37857
+ "**/snippet/**",
37858
+ // RULE_HINTS examples are intentional bad code
37859
+ "**/tests/**"
37860
+ // all test files (fixtures + unit + integration)
37861
+ ]
37823
37862
  }
37824
37863
  };
37825
37864
 
@@ -43822,13 +43861,13 @@ var signal_strength_default = {
43822
43861
  defaultOff: true
43823
43862
  },
43824
43863
  "java/arraylist-vs-linkedlist": {
43825
- recall: 0,
43826
- fpRate: 0,
43827
- ratio: 1,
43828
- precision: 0,
43829
- lastCalibratedAt: "2026-07-01T00:00:00Z",
43864
+ recall: 35e-4,
43865
+ fpRate: 94e-4,
43866
+ ratio: 0.37,
43867
+ precision: 0.0446,
43868
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
43830
43869
  verdict: "DORMANT",
43831
- _calibrationNote: "v0.20: new rule (new LinkedList<>() \u2014 use ArrayList (Effective Java Item 28)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
43870
+ _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).",
43832
43871
  aiSpecific: true,
43833
43872
  _v7Verdict: "DORMANT",
43834
43873
  _v7Lift: 1,
@@ -43837,16 +43876,21 @@ var signal_strength_default = {
43837
43876
  _v7Precision: 0,
43838
43877
  _v8Verdict: "DORMANT",
43839
43878
  _v8Lift: 1,
43879
+ _v9Verdict: "DORMANT",
43880
+ _v9Lift: 4.74,
43881
+ _v9Recall: 35e-4,
43882
+ _v9FpRate: 94e-4,
43883
+ _v9Precision: 0.0446,
43840
43884
  defaultOff: true
43841
43885
  },
43842
43886
  "java/empty-catch-block": {
43843
- recall: 0,
43844
- fpRate: 0,
43845
- ratio: 1,
43846
- precision: 0,
43847
- lastCalibratedAt: "2026-07-01T00:00:00Z",
43887
+ recall: 8e-3,
43888
+ fpRate: 0.1118,
43889
+ ratio: 0.07,
43890
+ precision: 89e-4,
43891
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
43848
43892
  verdict: "DORMANT",
43849
- _calibrationNote: "v0.20: new rule (Empty catch block \u2014 silently swallows exceptions). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
43893
+ _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.",
43850
43894
  aiSpecific: true,
43851
43895
  _v7Verdict: "DORMANT",
43852
43896
  _v7Lift: 1,
@@ -43855,16 +43899,21 @@ var signal_strength_default = {
43855
43899
  _v7Precision: 0,
43856
43900
  _v8Verdict: "DORMANT",
43857
43901
  _v8Lift: 1,
43902
+ _v9Verdict: "DORMANT",
43903
+ _v9Lift: 0.08,
43904
+ _v9Recall: 8e-3,
43905
+ _v9FpRate: 0.1118,
43906
+ _v9Precision: 89e-4,
43858
43907
  defaultOff: true
43859
43908
  },
43860
43909
  "java/legacy-date-api": {
43861
- recall: 0,
43862
- fpRate: 0,
43863
- ratio: 1,
43864
- precision: 0,
43865
- lastCalibratedAt: "2026-07-01T00:00:00Z",
43910
+ recall: 0.0285,
43911
+ fpRate: 0.0511,
43912
+ ratio: 0.56,
43913
+ precision: 0.0657,
43914
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
43866
43915
  verdict: "DORMANT",
43867
- _calibrationNote: "v0.20: new rule (Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
43916
+ _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.",
43868
43917
  aiSpecific: true,
43869
43918
  _v7Verdict: "DORMANT",
43870
43919
  _v7Lift: 1,
@@ -43873,16 +43922,21 @@ var signal_strength_default = {
43873
43922
  _v7Precision: 0,
43874
43923
  _v8Verdict: "DORMANT",
43875
43924
  _v8Lift: 1,
43925
+ _v9Verdict: "DORMANT",
43926
+ _v9Lift: 1.29,
43927
+ _v9Recall: 0.0285,
43928
+ _v9FpRate: 0.0511,
43929
+ _v9Precision: 0.0657,
43876
43930
  defaultOff: true
43877
43931
  },
43878
43932
  "java/raw-type-overuse": {
43879
- recall: 0,
43880
- fpRate: 0,
43881
- ratio: 1,
43882
- precision: 0,
43883
- lastCalibratedAt: "2026-07-01T00:00:00Z",
43933
+ recall: 0.1093,
43934
+ fpRate: 0.2041,
43935
+ ratio: 0.54,
43936
+ precision: 0.0632,
43937
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
43884
43938
  verdict: "DORMANT",
43885
- _calibrationNote: "v0.20: new rule (Raw type usage \u2014 use generics). DORMANT until v9 Java corpus calibration.",
43939
+ _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.",
43886
43940
  aiSpecific: true,
43887
43941
  _v7Verdict: "DORMANT",
43888
43942
  _v7Lift: 1,
@@ -43891,16 +43945,21 @@ var signal_strength_default = {
43891
43945
  _v7Precision: 0,
43892
43946
  _v8Verdict: "DORMANT",
43893
43947
  _v8Lift: 1,
43948
+ _v9Verdict: "DORMANT",
43949
+ _v9Lift: 0.31,
43950
+ _v9Recall: 0.1093,
43951
+ _v9FpRate: 0.2041,
43952
+ _v9Precision: 0.0632,
43894
43953
  defaultOff: true
43895
43954
  },
43896
43955
  "java/string-concat-loop": {
43897
- recall: 0,
43898
- fpRate: 0,
43899
- ratio: 1,
43900
- precision: 0,
43901
- lastCalibratedAt: "2026-07-01T00:00:00Z",
43956
+ recall: 46e-4,
43957
+ fpRate: 0.0358,
43958
+ ratio: 0.13,
43959
+ precision: 0.0158,
43960
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
43902
43961
  verdict: "DORMANT",
43903
- _calibrationNote: "v0.20: new rule (String concat in loop \u2014 use StringBuilder). DORMANT until v9 Java corpus calibration.",
43962
+ _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.",
43904
43963
  aiSpecific: true,
43905
43964
  _v7Verdict: "DORMANT",
43906
43965
  _v7Lift: 1,
@@ -43909,16 +43968,21 @@ var signal_strength_default = {
43909
43968
  _v7Precision: 0,
43910
43969
  _v8Verdict: "DORMANT",
43911
43970
  _v8Lift: 1,
43971
+ _v9Verdict: "DORMANT",
43972
+ _v9Lift: 0.44,
43973
+ _v9Recall: 46e-4,
43974
+ _v9FpRate: 0.0358,
43975
+ _v9Precision: 0.0158,
43912
43976
  defaultOff: true
43913
43977
  },
43914
43978
  "java/system-out-println": {
43915
- recall: 0,
43916
- fpRate: 0,
43917
- ratio: 1,
43918
- precision: 0,
43919
- lastCalibratedAt: "2026-07-01T00:00:00Z",
43979
+ recall: 0.1503,
43980
+ fpRate: 0.2533,
43981
+ ratio: 0.59,
43982
+ precision: 0.0695,
43983
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
43920
43984
  verdict: "DORMANT",
43921
- _calibrationNote: "v0.20: new rule (System.out.println in production code \u2014 use a logger). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
43985
+ _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.",
43922
43986
  aiSpecific: true,
43923
43987
  _v7Verdict: "DORMANT",
43924
43988
  _v7Lift: 1,
@@ -43927,6 +43991,11 @@ var signal_strength_default = {
43927
43991
  _v7Precision: 0,
43928
43992
  _v8Verdict: "DORMANT",
43929
43993
  _v8Lift: 1,
43994
+ _v9Verdict: "DORMANT",
43995
+ _v9Lift: 0.27,
43996
+ _v9Recall: 0.1503,
43997
+ _v9FpRate: 0.2533,
43998
+ _v9Precision: 0.0695,
43930
43999
  defaultOff: true
43931
44000
  },
43932
44001
  "layout/forced-layout": {
@@ -45471,7 +45540,23 @@ function applyRuleOverrides(issues, rules) {
45471
45540
  }
45472
45541
  return result;
45473
45542
  }
45543
+ function isExcludedBySelfScan(filePath, cwd, excludePaths) {
45544
+ if (!excludePaths || excludePaths.length === 0) return false;
45545
+ const rel = (0, import_node_path10.relative)(cwd, filePath).split(import_node_path10.sep).join("/");
45546
+ return excludePaths.some((pattern) => (0, import_minimatch.minimatch)(rel, pattern, { dot: true }));
45547
+ }
45474
45548
  async function scanFile(filePath, config, registry, cwd = process.cwd()) {
45549
+ if (isExcludedBySelfScan(filePath, cwd, config.selfScan?.excludePaths)) {
45550
+ return {
45551
+ filePath,
45552
+ componentCount: 0,
45553
+ issues: [],
45554
+ gapValues: [],
45555
+ styleSources: [],
45556
+ elementTags: [],
45557
+ unmatchedStringLiterals: []
45558
+ };
45559
+ }
45475
45560
  const cache = buildParserCacheConfig(cwd);
45476
45561
  const ext = (0, import_node_path9.extname)(filePath).toLowerCase();
45477
45562
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
@@ -45486,7 +45571,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
45486
45571
  ".h",
45487
45572
  ".hpp",
45488
45573
  ".hxx",
45489
- ".java",
45490
45574
  ".rb",
45491
45575
  ".php"
45492
45576
  ]);
@@ -7,7 +7,8 @@ var __export = (target, all) => {
7
7
  // src/engine/worker.ts
8
8
  import { isMainThread, parentPort, workerData } from "worker_threads";
9
9
  import { extname as extname4 } from "path";
10
- import { join as join11 } from "path";
10
+ import { join as join11, relative as relative2, sep } from "path";
11
+ import { minimatch } from "minimatch";
11
12
 
12
13
  // ../engine/dist/index.js
13
14
  import { readFile, writeFile, mkdir, access } from "fs/promises";
@@ -4335,6 +4336,7 @@ function parseSource(source, filePath) {
4335
4336
  case "py":
4336
4337
  case "go":
4337
4338
  case "rs":
4339
+ case "java":
4338
4340
  return parseBlankModule(source);
4339
4341
  default:
4340
4342
  return parseWithSwc(source, filePath);
@@ -37791,6 +37793,43 @@ var DEFAULT_CONFIG3 = {
37791
37793
  */
37792
37794
  testIntelligence: {
37793
37795
  missingEdgeCase: false
37796
+ },
37797
+ /**
37798
+ * v0.25.0: self-scan exclude paths. Defaults cover the three paths
37799
+ * that are always false positives when scanning the slopbrick repo
37800
+ * itself:
37801
+ *
37802
+ * - `src/rules/**` — rule definitions contain example patterns
37803
+ * the rules themselves detect (self-fire). E.g. a
37804
+ * `security/sql-construction` rule's source file has a SQL
37805
+ * concat string literal that fires its own regex.
37806
+ * - `tests/fixtures/**` — test fixtures contain intentional bad
37807
+ * code that the rules must fire on to be useful (each fixture
37808
+ * is a positive test case). Scanning them in a self-scan
37809
+ * produces ~70 false-positive "issues" that are really just
37810
+ * test data.
37811
+ * - `tests/rules/**` — rule test files contain expected-issue
37812
+ * assertions, also meta-code.
37813
+ *
37814
+ * Three patterns (broadened in v0.25.1 from the v0.25.0 narrow
37815
+ * set, which missed `tests/engine/**`, `tests/cli/**`, and
37816
+ * `snippet/**`), ~80 issues removed per self-scan. Combined
37817
+ * with the v0.25.0 graded security cap, this restores the
37818
+ * v9 plan's "security ≥ 80" criterion (unachievable in
37819
+ * v0.24.0 due to 90 self-scan FPs collapsing the score to 0).
37820
+ *
37821
+ * Set `selfScan: { excludePaths: [] }` in `slopbrick.config.mjs`
37822
+ * to opt out and scan every file (legacy behavior).
37823
+ */
37824
+ selfScan: {
37825
+ excludePaths: [
37826
+ "**/src/rules/**",
37827
+ // rule definitions are meta-code
37828
+ "**/snippet/**",
37829
+ // RULE_HINTS examples are intentional bad code
37830
+ "**/tests/**"
37831
+ // all test files (fixtures + unit + integration)
37832
+ ]
37794
37833
  }
37795
37834
  };
37796
37835
 
@@ -43793,13 +43832,13 @@ var signal_strength_default = {
43793
43832
  defaultOff: true
43794
43833
  },
43795
43834
  "java/arraylist-vs-linkedlist": {
43796
- recall: 0,
43797
- fpRate: 0,
43798
- ratio: 1,
43799
- precision: 0,
43800
- lastCalibratedAt: "2026-07-01T00:00:00Z",
43835
+ recall: 35e-4,
43836
+ fpRate: 94e-4,
43837
+ ratio: 0.37,
43838
+ precision: 0.0446,
43839
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
43801
43840
  verdict: "DORMANT",
43802
- _calibrationNote: "v0.20: new rule (new LinkedList<>() \u2014 use ArrayList (Effective Java Item 28)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
43841
+ _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).",
43803
43842
  aiSpecific: true,
43804
43843
  _v7Verdict: "DORMANT",
43805
43844
  _v7Lift: 1,
@@ -43808,16 +43847,21 @@ var signal_strength_default = {
43808
43847
  _v7Precision: 0,
43809
43848
  _v8Verdict: "DORMANT",
43810
43849
  _v8Lift: 1,
43850
+ _v9Verdict: "DORMANT",
43851
+ _v9Lift: 4.74,
43852
+ _v9Recall: 35e-4,
43853
+ _v9FpRate: 94e-4,
43854
+ _v9Precision: 0.0446,
43811
43855
  defaultOff: true
43812
43856
  },
43813
43857
  "java/empty-catch-block": {
43814
- recall: 0,
43815
- fpRate: 0,
43816
- ratio: 1,
43817
- precision: 0,
43818
- lastCalibratedAt: "2026-07-01T00:00:00Z",
43858
+ recall: 8e-3,
43859
+ fpRate: 0.1118,
43860
+ ratio: 0.07,
43861
+ precision: 89e-4,
43862
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
43819
43863
  verdict: "DORMANT",
43820
- _calibrationNote: "v0.20: new rule (Empty catch block \u2014 silently swallows exceptions). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
43864
+ _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.",
43821
43865
  aiSpecific: true,
43822
43866
  _v7Verdict: "DORMANT",
43823
43867
  _v7Lift: 1,
@@ -43826,16 +43870,21 @@ var signal_strength_default = {
43826
43870
  _v7Precision: 0,
43827
43871
  _v8Verdict: "DORMANT",
43828
43872
  _v8Lift: 1,
43873
+ _v9Verdict: "DORMANT",
43874
+ _v9Lift: 0.08,
43875
+ _v9Recall: 8e-3,
43876
+ _v9FpRate: 0.1118,
43877
+ _v9Precision: 89e-4,
43829
43878
  defaultOff: true
43830
43879
  },
43831
43880
  "java/legacy-date-api": {
43832
- recall: 0,
43833
- fpRate: 0,
43834
- ratio: 1,
43835
- precision: 0,
43836
- lastCalibratedAt: "2026-07-01T00:00:00Z",
43881
+ recall: 0.0285,
43882
+ fpRate: 0.0511,
43883
+ ratio: 0.56,
43884
+ precision: 0.0657,
43885
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
43837
43886
  verdict: "DORMANT",
43838
- _calibrationNote: "v0.20: new rule (Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
43887
+ _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.",
43839
43888
  aiSpecific: true,
43840
43889
  _v7Verdict: "DORMANT",
43841
43890
  _v7Lift: 1,
@@ -43844,16 +43893,21 @@ var signal_strength_default = {
43844
43893
  _v7Precision: 0,
43845
43894
  _v8Verdict: "DORMANT",
43846
43895
  _v8Lift: 1,
43896
+ _v9Verdict: "DORMANT",
43897
+ _v9Lift: 1.29,
43898
+ _v9Recall: 0.0285,
43899
+ _v9FpRate: 0.0511,
43900
+ _v9Precision: 0.0657,
43847
43901
  defaultOff: true
43848
43902
  },
43849
43903
  "java/raw-type-overuse": {
43850
- recall: 0,
43851
- fpRate: 0,
43852
- ratio: 1,
43853
- precision: 0,
43854
- lastCalibratedAt: "2026-07-01T00:00:00Z",
43904
+ recall: 0.1093,
43905
+ fpRate: 0.2041,
43906
+ ratio: 0.54,
43907
+ precision: 0.0632,
43908
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
43855
43909
  verdict: "DORMANT",
43856
- _calibrationNote: "v0.20: new rule (Raw type usage \u2014 use generics). DORMANT until v9 Java corpus calibration.",
43910
+ _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.",
43857
43911
  aiSpecific: true,
43858
43912
  _v7Verdict: "DORMANT",
43859
43913
  _v7Lift: 1,
@@ -43862,16 +43916,21 @@ var signal_strength_default = {
43862
43916
  _v7Precision: 0,
43863
43917
  _v8Verdict: "DORMANT",
43864
43918
  _v8Lift: 1,
43919
+ _v9Verdict: "DORMANT",
43920
+ _v9Lift: 0.31,
43921
+ _v9Recall: 0.1093,
43922
+ _v9FpRate: 0.2041,
43923
+ _v9Precision: 0.0632,
43865
43924
  defaultOff: true
43866
43925
  },
43867
43926
  "java/string-concat-loop": {
43868
- recall: 0,
43869
- fpRate: 0,
43870
- ratio: 1,
43871
- precision: 0,
43872
- lastCalibratedAt: "2026-07-01T00:00:00Z",
43927
+ recall: 46e-4,
43928
+ fpRate: 0.0358,
43929
+ ratio: 0.13,
43930
+ precision: 0.0158,
43931
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
43873
43932
  verdict: "DORMANT",
43874
- _calibrationNote: "v0.20: new rule (String concat in loop \u2014 use StringBuilder). DORMANT until v9 Java corpus calibration.",
43933
+ _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.",
43875
43934
  aiSpecific: true,
43876
43935
  _v7Verdict: "DORMANT",
43877
43936
  _v7Lift: 1,
@@ -43880,16 +43939,21 @@ var signal_strength_default = {
43880
43939
  _v7Precision: 0,
43881
43940
  _v8Verdict: "DORMANT",
43882
43941
  _v8Lift: 1,
43942
+ _v9Verdict: "DORMANT",
43943
+ _v9Lift: 0.44,
43944
+ _v9Recall: 46e-4,
43945
+ _v9FpRate: 0.0358,
43946
+ _v9Precision: 0.0158,
43883
43947
  defaultOff: true
43884
43948
  },
43885
43949
  "java/system-out-println": {
43886
- recall: 0,
43887
- fpRate: 0,
43888
- ratio: 1,
43889
- precision: 0,
43890
- lastCalibratedAt: "2026-07-01T00:00:00Z",
43950
+ recall: 0.1503,
43951
+ fpRate: 0.2533,
43952
+ ratio: 0.59,
43953
+ precision: 0.0695,
43954
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
43891
43955
  verdict: "DORMANT",
43892
- _calibrationNote: "v0.20: new rule (System.out.println in production code \u2014 use a logger). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
43956
+ _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.",
43893
43957
  aiSpecific: true,
43894
43958
  _v7Verdict: "DORMANT",
43895
43959
  _v7Lift: 1,
@@ -43898,6 +43962,11 @@ var signal_strength_default = {
43898
43962
  _v7Precision: 0,
43899
43963
  _v8Verdict: "DORMANT",
43900
43964
  _v8Lift: 1,
43965
+ _v9Verdict: "DORMANT",
43966
+ _v9Lift: 0.27,
43967
+ _v9Recall: 0.1503,
43968
+ _v9FpRate: 0.2533,
43969
+ _v9Precision: 0.0695,
43901
43970
  defaultOff: true
43902
43971
  },
43903
43972
  "layout/forced-layout": {
@@ -45442,7 +45511,23 @@ function applyRuleOverrides(issues, rules) {
45442
45511
  }
45443
45512
  return result;
45444
45513
  }
45514
+ function isExcludedBySelfScan(filePath, cwd, excludePaths) {
45515
+ if (!excludePaths || excludePaths.length === 0) return false;
45516
+ const rel = relative2(cwd, filePath).split(sep).join("/");
45517
+ return excludePaths.some((pattern) => minimatch(rel, pattern, { dot: true }));
45518
+ }
45445
45519
  async function scanFile(filePath, config, registry, cwd = process.cwd()) {
45520
+ if (isExcludedBySelfScan(filePath, cwd, config.selfScan?.excludePaths)) {
45521
+ return {
45522
+ filePath,
45523
+ componentCount: 0,
45524
+ issues: [],
45525
+ gapValues: [],
45526
+ styleSources: [],
45527
+ elementTags: [],
45528
+ unmatchedStringLiterals: []
45529
+ };
45530
+ }
45446
45531
  const cache = buildParserCacheConfig(cwd);
45447
45532
  const ext = extname4(filePath).toLowerCase();
45448
45533
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
@@ -45457,7 +45542,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
45457
45542
  ".h",
45458
45543
  ".hpp",
45459
45544
  ".hxx",
45460
- ".java",
45461
45545
  ".rb",
45462
45546
  ".php"
45463
45547
  ]);