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/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.24.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: 0,
49882
- fpRate: 0,
49883
- ratio: 1,
49884
- precision: 0,
49885
- lastCalibratedAt: "2026-07-01T00:00:00Z",
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 (Effective Java Item 28)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
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: 0,
49900
- fpRate: 0,
49901
- ratio: 1,
49902
- precision: 0,
49903
- lastCalibratedAt: "2026-07-01T00:00:00Z",
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.20: new rule (Empty catch block \u2014 silently swallows exceptions). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
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: 1,
49920
- precision: 0,
49921
- lastCalibratedAt: "2026-07-01T00:00:00Z",
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.20: new rule (Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
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: 1,
49938
- precision: 0,
49939
- lastCalibratedAt: "2026-07-01T00:00:00Z",
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: new rule (Raw type usage \u2014 use generics). DORMANT until v9 Java corpus calibration.",
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: 0,
49954
- fpRate: 0,
49955
- ratio: 1,
49956
- precision: 0,
49957
- lastCalibratedAt: "2026-07-01T00:00:00Z",
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.20: new rule (String concat in loop \u2014 use StringBuilder). DORMANT until v9 Java corpus calibration.",
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: 1,
49974
- precision: 0,
49975
- lastCalibratedAt: "2026-07-01T00:00:00Z",
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.20: new rule (System.out.println in production code \u2014 use a logger). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
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 { risk } = computeAiSecurityRisk(flatIssues);
52080
- const securityFromRisk = {
52081
- low: 100,
52082
- medium: 67,
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 = security.issueCount > 0 ? 0 : 100;
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: minimatch3 } = await import("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) => minimatch3(rel, 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) => minimatch3(rel, pattern, { dot: true }))) {
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 import_minimatch2 = require("minimatch");
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, import_minimatch2.minimatch)(rel, pattern))) {
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, import_minimatch2.minimatch)(rel, pattern))) {
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 };