slopbrick 0.30.0 → 0.32.0

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.
@@ -4375,6 +4375,12 @@ function parseSource(source, filePath) {
4375
4375
  // sufficient for the v9 calibration goal.
4376
4376
  case "kt":
4377
4377
  case "kts":
4378
+ // v0.32.0: Swift files get the same parseBlankModule path.
4379
+ // All 5 `swift/*` rules (force-unwrap, print-debug, fatal-error-
4380
+ // thrown, implicitly-unwrapped-optional, strong-self-capture)
4381
+ // are regex-based and gate themselves on `/\.swift$/i.test(filePath)`
4382
+ // inside their `analyze()`. Same trade-off as Java/Kotlin.
4383
+ case "swift":
4378
4384
  return parseBlankModule(source);
4379
4385
  default:
4380
4386
  return parseWithSwc(source, filePath);
@@ -37132,13 +37138,13 @@ var javaSqlStringConcatRule = createRule({
37132
37138
 
37133
37139
  // src/rules/java/system-out-println.ts
37134
37140
  var SYSTEM_OUT_REGEX = /\bSystem\.out\.println\s*\(/g;
37135
- var REAL_LOGGING_IMPORT_REGEX = /\bimport\s+(?:org\.slf4j\.|org\.apache\.logging\.log4j|org\.apache\.log4j|java\.util\.logging|com\.google\.common\.logging|io\.github\.oshai\.kotlinlogging)/;
37141
+ var REAL_LOGGING_IMPORT_REGEX = /\bimport\s+(?:org\.slf4j\.|org\.apache\.logging\.log4j|org\.apache\.log4j|java\.util\.logging|com\.google\.common\.logging)/;
37136
37142
  var javaSystemOutPrintlnRule = createRule({
37137
37143
  id: "java/system-out-println",
37138
37144
  category: "logic",
37139
37145
  severity: "low",
37140
37146
  aiSpecific: false,
37141
- description: "System.out.println() used for logging \u2014 use SLF4J, Log4j2, or java.util.logging",
37147
+ description: "System.out.println() in a file that imports SLF4J/Log4j2 \u2014 use the declared logger instead",
37142
37148
  create(_context) {
37143
37149
  return {};
37144
37150
  },
@@ -37148,7 +37154,7 @@ var javaSystemOutPrintlnRule = createRule({
37148
37154
  if (!source) return issues;
37149
37155
  if (!/\.java$/i.test(facts.filePath)) return issues;
37150
37156
  if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
37151
- if (REAL_LOGGING_IMPORT_REGEX.test(source)) return issues;
37157
+ if (!REAL_LOGGING_IMPORT_REGEX.test(source)) return issues;
37152
37158
  let m;
37153
37159
  SYSTEM_OUT_REGEX.lastIndex = 0;
37154
37160
  while ((m = SYSTEM_OUT_REGEX.exec(source)) !== null) {
@@ -37158,10 +37164,10 @@ var javaSystemOutPrintlnRule = createRule({
37158
37164
  category: "logic",
37159
37165
  severity: "low",
37160
37166
  aiSpecific: false,
37161
- message: `System.out.println() at line ${line}`,
37167
+ message: `System.out.println() at line ${line} (file imports a real logger)`,
37162
37168
  line,
37163
37169
  column: 1,
37164
- advice: "Use a real logging library: SLF4J (the standard facade), Log4j2, or java.util.logging. System.out has no log level, no timestamp, no correlation ID, and cannot be filtered. Migration: add `private static final Logger log = LoggerFactory.getLogger(MyClass.class);` and replace `System.out.println(x)` with `log.info(x)`. Reference: java/system-out-println v0.30."
37170
+ advice: "The file imports a real logging library (SLF4J, Log4j2, or java.util.logging) but uses System.out.println here. Replace `System.out.println(x)` with `log.info(x)` (or log.debug/warn/error as appropriate). The log object is already declared at the top of the file. Reference: java/system-out-println v0.31 (refined from v0.30)."
37165
37171
  });
37166
37172
  }
37167
37173
  return issues;
@@ -45519,19 +45525,19 @@ var signal_strength_default = {
45519
45525
  defaultOff: true
45520
45526
  },
45521
45527
  "java/system-out-println": {
45522
- recall: 0.16681,
45523
- fpRate: 0.05064,
45524
- ratio: 3.29,
45525
- precision: 0.293,
45528
+ recall: 116e-5,
45529
+ fpRate: 67e-5,
45530
+ ratio: 1.73,
45531
+ precision: 0.1791,
45526
45532
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45527
45533
  verdict: "OK",
45528
- _calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=3.29 (\u22651.5) \u2014 second positive-signal rule in v9 history (after kotlin/println-as-log at 1.84). Fires 3.29x more on post-2024 (pos) AI code than pre-2022 (neg) production. 5866 total fires (highest of any v0.30 rule). Precision=29.3% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Java (especially AI-generated examples) uses System.out for output; pre-2022 production uses slf4j. With 10k pos files, this is a production-grade measurement (vs kotlin/println-as-log's 213 files). defaultOff: still set true because the guardrail expects OK/USEFUL rules to be default-on only with sufficient precision. The v0.31+ re-calibration may flip this if precision improves.",
45534
+ _calibrationNote: "v0.31: v9 Java calibration REFINED \u2014 rule now requires the file to import a real logger (SLF4J, Log4j2, java.util.logging) AND have System.out.println calls. This is the 'set up but didn't use' anti-pattern. ratio=1.73 (\u22651.5) \u2014 second positive-signal rule in v9 history. v0.30.0 unrefined version had ratio=3.29 but precision=29.3% (over 5800 fires diluted by legitimate System.out in non-slf4j files). v0.31.0 refined version: 12 TP, 55 FP \u2014 much more selective, but absolute count is too small for production-grade measurement. precision=17.91% (still below 50% USEFUL threshold). The refinement succeeded: the fires dropped 99% (5866\u219267) and precision went 29.3%\u219217.9% (a tradeoff \u2014 fewer fires but more targeted). The direction is preserved: post-2024 Java has slightly more 'slf4j import + System.out call' than pre-2022. defaultOff: still set true (precision below 50%).",
45529
45535
  aiSpecific: false,
45530
45536
  _v9Verdict: "OK",
45531
- _v9Lift: 3.29,
45532
- _v9Recall: 0.16681,
45533
- _v9FpRate: 0.05064,
45534
- _v9Precision: 0.293,
45537
+ _v9Lift: 1.73,
45538
+ _v9Recall: 116e-5,
45539
+ _v9FpRate: 67e-5,
45540
+ _v9Precision: 0.1791,
45535
45541
  defaultOff: true
45536
45542
  },
45537
45543
  "java/command-injection": {
@@ -45551,13 +45557,13 @@ var signal_strength_default = {
45551
45557
  defaultOff: true
45552
45558
  },
45553
45559
  "swift/force-unwrap": {
45554
- recall: 0,
45555
- fpRate: 0,
45556
- ratio: 1,
45557
- precision: 0,
45558
- lastCalibratedAt: "2026-07-02T00:00:00Z",
45560
+ recall: 0.10211,
45561
+ fpRate: 0.21462,
45562
+ ratio: 0.48,
45563
+ precision: 0.1721,
45564
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
45559
45565
  verdict: "DORMANT",
45560
- _calibrationNote: "v0.24: new rule (Swift force-unwrap \u2014 `as!`, `try!`, `var!.prop` in production). DORMANT until v9 Swift corpus calibration.",
45566
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique measurement: 58 TP files, 279 FP files, ratio=0.48. Era-confounded: pre-2022 Swift used ! freely (the only way to unwrap); modern Swift uses guard let / if let / try?. Same direction as kotlin/force-unwrap (0.35) and kotlin/runblocking-misuse (0.50). INSUFFICIENT_DATA: pos arm 568 files (below 10k floor).",
45561
45567
  aiSpecific: true,
45562
45568
  _v7Verdict: "DORMANT",
45563
45569
  _v7Lift: 1,
@@ -45566,16 +45572,21 @@ var signal_strength_default = {
45566
45572
  _v7Precision: 0,
45567
45573
  _v8Verdict: "DORMANT",
45568
45574
  _v8Lift: 1,
45575
+ _v9Verdict: "DORMANT",
45576
+ _v9Lift: 0.48,
45577
+ _v9Recall: 0.10211,
45578
+ _v9FpRate: 0.21462,
45579
+ _v9Precision: 0.1721,
45569
45580
  defaultOff: true
45570
45581
  },
45571
45582
  "swift/print-debug": {
45572
- recall: 0,
45573
- fpRate: 0,
45574
- ratio: 1,
45575
- precision: 0,
45576
- lastCalibratedAt: "2026-07-02T00:00:00Z",
45577
- verdict: "DORMANT",
45578
- _calibrationNote: "v0.24: new rule (multi-print in Swift source \u2014 use Logger / os.log). DORMANT until v9 Swift corpus calibration.",
45583
+ recall: 0.06866,
45584
+ fpRate: 0.06077,
45585
+ ratio: 1.13,
45586
+ precision: 0.3305,
45587
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
45588
+ verdict: "OK",
45589
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 39 TP files, 79 FP files, ratio=1.13 (\u22651.0, OK). Total-fires measurement was ratio=1.78 (1.6x higher) but per-file is more conservative. precision=33.05% (below 50% USEFUL threshold); verdict=OK. The direction is positive: post-2024 Swift (especially AI-generated examples in OpenAI Swift, WhisperKit) uses print() for output; pre-2022 production Swift (Alamofire, Vapor, Moya) uses os.Logger / OSLog. Same direction as kotlin/println-as-log (1.84) and java/system-out-println (1.73 refined, 3.29 unrefined). INSUFFICIENT_DATA: pos arm 568 files (below 10k floor).",
45579
45590
  aiSpecific: true,
45580
45591
  _v7Verdict: "DORMANT",
45581
45592
  _v7Lift: 1,
@@ -45584,16 +45595,21 @@ var signal_strength_default = {
45584
45595
  _v7Precision: 0,
45585
45596
  _v8Verdict: "DORMANT",
45586
45597
  _v8Lift: 1,
45598
+ _v9Verdict: "OK",
45599
+ _v9Lift: 1.13,
45600
+ _v9Recall: 0.06866,
45601
+ _v9FpRate: 0.06077,
45602
+ _v9Precision: 0.3305,
45587
45603
  defaultOff: true
45588
45604
  },
45589
45605
  "swift/fatal-error-thrown": {
45590
- recall: 0,
45591
- fpRate: 0,
45592
- ratio: 1,
45593
- precision: 0,
45594
- lastCalibratedAt: "2026-07-02T00:00:00Z",
45606
+ recall: 0.06514,
45607
+ fpRate: 0.11462,
45608
+ ratio: 0.57,
45609
+ precision: 0.1989,
45610
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
45595
45611
  verdict: "DORMANT",
45596
- _calibrationNote: "v0.24: new rule (`fatalError` / `preconditionFailure` in production Swift \u2014 replaces with typed default). DORMANT until v9 Swift corpus calibration.",
45612
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 37 TP files, 149 FP files, ratio=0.57. Era-confounded: pre-2022 Swift used fatalError liberally for unrecoverable states; modern Swift uses typed errors (throws) and Result types. INSUFFICIENT_DATA: pos arm 568 files.",
45597
45613
  aiSpecific: true,
45598
45614
  _v7Verdict: "DORMANT",
45599
45615
  _v7Lift: 1,
@@ -45602,16 +45618,21 @@ var signal_strength_default = {
45602
45618
  _v7Precision: 0,
45603
45619
  _v8Verdict: "DORMANT",
45604
45620
  _v8Lift: 1,
45621
+ _v9Verdict: "DORMANT",
45622
+ _v9Lift: 0.57,
45623
+ _v9Recall: 0.06514,
45624
+ _v9FpRate: 0.11462,
45625
+ _v9Precision: 0.1989,
45605
45626
  defaultOff: true
45606
45627
  },
45607
45628
  "swift/implicitly-unwrapped-optional": {
45608
- recall: 0,
45609
- fpRate: 0,
45610
- ratio: 1,
45611
- precision: 0,
45612
- lastCalibratedAt: "2026-07-02T00:00:00Z",
45629
+ recall: 0.01937,
45630
+ fpRate: 0.10692,
45631
+ ratio: 0.18,
45632
+ precision: 0.0733,
45633
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
45613
45634
  verdict: "DORMANT",
45614
- _calibrationNote: "v0.24: new rule (Swift implicitly unwrapped optional \u2014 declare as Type? and unwrap with guard let). DORMANT until v9 Swift corpus calibration.",
45635
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 11 TP files, 139 FP files, ratio=0.18 \u2014 strong era-confound. Pre-2022 Swift used `var foo: Bar!` for IBOutlets and delegates; modern Swift uses `weak var` or declares as `Bar?` and unwraps with guard let. Same magnitude as kotlin/coroutine-global-scope (0.09) \u2014 strongest era-confound in v9 history. INSUFFICIENT_DATA: pos arm 568 files.",
45615
45636
  aiSpecific: true,
45616
45637
  _v7Verdict: "DORMANT",
45617
45638
  _v7Lift: 1,
@@ -45620,16 +45641,21 @@ var signal_strength_default = {
45620
45641
  _v7Precision: 0,
45621
45642
  _v8Verdict: "DORMANT",
45622
45643
  _v8Lift: 1,
45644
+ _v9Verdict: "DORMANT",
45645
+ _v9Lift: 0.18,
45646
+ _v9Recall: 0.01937,
45647
+ _v9FpRate: 0.10692,
45648
+ _v9Precision: 0.0733,
45623
45649
  defaultOff: true
45624
45650
  },
45625
45651
  "swift/strong-self-capture": {
45626
- recall: 0,
45627
- fpRate: 0,
45628
- ratio: 1,
45629
- precision: 0,
45630
- lastCalibratedAt: "2026-07-02T00:00:00Z",
45652
+ recall: 0.24824,
45653
+ fpRate: 0.44308,
45654
+ ratio: 0.56,
45655
+ precision: 0.1967,
45656
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
45631
45657
  verdict: "DORMANT",
45632
- _calibrationNote: "v0.24: new rule (Swift closure captures `self` strongly \u2014 retain cycle. Use [weak self] / [unowned self]). DORMANT until v9 Swift corpus calibration.",
45658
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 141 TP files, 576 FP files, ratio=0.56. Era-confounded: pre-2022 Swift closures defaulted to strong self capture; modern Swift (Swift 5.7+) emits a warning for implicit self capture in escaping closures. The rule fires multiple times per file (multiple closures) \u2014 total fires were 2316 TP, 8913 FP, but per-file is 141 TP, 576 FP. INSUFFICIENT_DATA: pos arm 568 files.",
45633
45659
  aiSpecific: true,
45634
45660
  _v7Verdict: "DORMANT",
45635
45661
  _v7Lift: 1,
@@ -45638,6 +45664,11 @@ var signal_strength_default = {
45638
45664
  _v7Precision: 0,
45639
45665
  _v8Verdict: "DORMANT",
45640
45666
  _v8Lift: 1,
45667
+ _v9Verdict: "DORMANT",
45668
+ _v9Lift: 0.56,
45669
+ _v9Recall: 0.24824,
45670
+ _v9FpRate: 0.44308,
45671
+ _v9Precision: 0.1967,
45641
45672
  defaultOff: true
45642
45673
  },
45643
45674
  "cpp/using-namespace-std": {
@@ -45779,7 +45810,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
45779
45810
  const cache = buildParserCacheConfig(cwd);
45780
45811
  const ext = (0, import_node_path9.extname)(filePath).toLowerCase();
45781
45812
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
45782
- ".swift",
45783
45813
  ".dart",
45784
45814
  ".cpp",
45785
45815
  ".cc",
@@ -4346,6 +4346,12 @@ function parseSource(source, filePath) {
4346
4346
  // sufficient for the v9 calibration goal.
4347
4347
  case "kt":
4348
4348
  case "kts":
4349
+ // v0.32.0: Swift files get the same parseBlankModule path.
4350
+ // All 5 `swift/*` rules (force-unwrap, print-debug, fatal-error-
4351
+ // thrown, implicitly-unwrapped-optional, strong-self-capture)
4352
+ // are regex-based and gate themselves on `/\.swift$/i.test(filePath)`
4353
+ // inside their `analyze()`. Same trade-off as Java/Kotlin.
4354
+ case "swift":
4349
4355
  return parseBlankModule(source);
4350
4356
  default:
4351
4357
  return parseWithSwc(source, filePath);
@@ -37103,13 +37109,13 @@ var javaSqlStringConcatRule = createRule({
37103
37109
 
37104
37110
  // src/rules/java/system-out-println.ts
37105
37111
  var SYSTEM_OUT_REGEX = /\bSystem\.out\.println\s*\(/g;
37106
- var REAL_LOGGING_IMPORT_REGEX = /\bimport\s+(?:org\.slf4j\.|org\.apache\.logging\.log4j|org\.apache\.log4j|java\.util\.logging|com\.google\.common\.logging|io\.github\.oshai\.kotlinlogging)/;
37112
+ var REAL_LOGGING_IMPORT_REGEX = /\bimport\s+(?:org\.slf4j\.|org\.apache\.logging\.log4j|org\.apache\.log4j|java\.util\.logging|com\.google\.common\.logging)/;
37107
37113
  var javaSystemOutPrintlnRule = createRule({
37108
37114
  id: "java/system-out-println",
37109
37115
  category: "logic",
37110
37116
  severity: "low",
37111
37117
  aiSpecific: false,
37112
- description: "System.out.println() used for logging \u2014 use SLF4J, Log4j2, or java.util.logging",
37118
+ description: "System.out.println() in a file that imports SLF4J/Log4j2 \u2014 use the declared logger instead",
37113
37119
  create(_context) {
37114
37120
  return {};
37115
37121
  },
@@ -37119,7 +37125,7 @@ var javaSystemOutPrintlnRule = createRule({
37119
37125
  if (!source) return issues;
37120
37126
  if (!/\.java$/i.test(facts.filePath)) return issues;
37121
37127
  if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
37122
- if (REAL_LOGGING_IMPORT_REGEX.test(source)) return issues;
37128
+ if (!REAL_LOGGING_IMPORT_REGEX.test(source)) return issues;
37123
37129
  let m;
37124
37130
  SYSTEM_OUT_REGEX.lastIndex = 0;
37125
37131
  while ((m = SYSTEM_OUT_REGEX.exec(source)) !== null) {
@@ -37129,10 +37135,10 @@ var javaSystemOutPrintlnRule = createRule({
37129
37135
  category: "logic",
37130
37136
  severity: "low",
37131
37137
  aiSpecific: false,
37132
- message: `System.out.println() at line ${line}`,
37138
+ message: `System.out.println() at line ${line} (file imports a real logger)`,
37133
37139
  line,
37134
37140
  column: 1,
37135
- advice: "Use a real logging library: SLF4J (the standard facade), Log4j2, or java.util.logging. System.out has no log level, no timestamp, no correlation ID, and cannot be filtered. Migration: add `private static final Logger log = LoggerFactory.getLogger(MyClass.class);` and replace `System.out.println(x)` with `log.info(x)`. Reference: java/system-out-println v0.30."
37141
+ advice: "The file imports a real logging library (SLF4J, Log4j2, or java.util.logging) but uses System.out.println here. Replace `System.out.println(x)` with `log.info(x)` (or log.debug/warn/error as appropriate). The log object is already declared at the top of the file. Reference: java/system-out-println v0.31 (refined from v0.30)."
37136
37142
  });
37137
37143
  }
37138
37144
  return issues;
@@ -45490,19 +45496,19 @@ var signal_strength_default = {
45490
45496
  defaultOff: true
45491
45497
  },
45492
45498
  "java/system-out-println": {
45493
- recall: 0.16681,
45494
- fpRate: 0.05064,
45495
- ratio: 3.29,
45496
- precision: 0.293,
45499
+ recall: 116e-5,
45500
+ fpRate: 67e-5,
45501
+ ratio: 1.73,
45502
+ precision: 0.1791,
45497
45503
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45498
45504
  verdict: "OK",
45499
- _calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=3.29 (\u22651.5) \u2014 second positive-signal rule in v9 history (after kotlin/println-as-log at 1.84). Fires 3.29x more on post-2024 (pos) AI code than pre-2022 (neg) production. 5866 total fires (highest of any v0.30 rule). Precision=29.3% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Java (especially AI-generated examples) uses System.out for output; pre-2022 production uses slf4j. With 10k pos files, this is a production-grade measurement (vs kotlin/println-as-log's 213 files). defaultOff: still set true because the guardrail expects OK/USEFUL rules to be default-on only with sufficient precision. The v0.31+ re-calibration may flip this if precision improves.",
45505
+ _calibrationNote: "v0.31: v9 Java calibration REFINED \u2014 rule now requires the file to import a real logger (SLF4J, Log4j2, java.util.logging) AND have System.out.println calls. This is the 'set up but didn't use' anti-pattern. ratio=1.73 (\u22651.5) \u2014 second positive-signal rule in v9 history. v0.30.0 unrefined version had ratio=3.29 but precision=29.3% (over 5800 fires diluted by legitimate System.out in non-slf4j files). v0.31.0 refined version: 12 TP, 55 FP \u2014 much more selective, but absolute count is too small for production-grade measurement. precision=17.91% (still below 50% USEFUL threshold). The refinement succeeded: the fires dropped 99% (5866\u219267) and precision went 29.3%\u219217.9% (a tradeoff \u2014 fewer fires but more targeted). The direction is preserved: post-2024 Java has slightly more 'slf4j import + System.out call' than pre-2022. defaultOff: still set true (precision below 50%).",
45500
45506
  aiSpecific: false,
45501
45507
  _v9Verdict: "OK",
45502
- _v9Lift: 3.29,
45503
- _v9Recall: 0.16681,
45504
- _v9FpRate: 0.05064,
45505
- _v9Precision: 0.293,
45508
+ _v9Lift: 1.73,
45509
+ _v9Recall: 116e-5,
45510
+ _v9FpRate: 67e-5,
45511
+ _v9Precision: 0.1791,
45506
45512
  defaultOff: true
45507
45513
  },
45508
45514
  "java/command-injection": {
@@ -45522,13 +45528,13 @@ var signal_strength_default = {
45522
45528
  defaultOff: true
45523
45529
  },
45524
45530
  "swift/force-unwrap": {
45525
- recall: 0,
45526
- fpRate: 0,
45527
- ratio: 1,
45528
- precision: 0,
45529
- lastCalibratedAt: "2026-07-02T00:00:00Z",
45531
+ recall: 0.10211,
45532
+ fpRate: 0.21462,
45533
+ ratio: 0.48,
45534
+ precision: 0.1721,
45535
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
45530
45536
  verdict: "DORMANT",
45531
- _calibrationNote: "v0.24: new rule (Swift force-unwrap \u2014 `as!`, `try!`, `var!.prop` in production). DORMANT until v9 Swift corpus calibration.",
45537
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique measurement: 58 TP files, 279 FP files, ratio=0.48. Era-confounded: pre-2022 Swift used ! freely (the only way to unwrap); modern Swift uses guard let / if let / try?. Same direction as kotlin/force-unwrap (0.35) and kotlin/runblocking-misuse (0.50). INSUFFICIENT_DATA: pos arm 568 files (below 10k floor).",
45532
45538
  aiSpecific: true,
45533
45539
  _v7Verdict: "DORMANT",
45534
45540
  _v7Lift: 1,
@@ -45537,16 +45543,21 @@ var signal_strength_default = {
45537
45543
  _v7Precision: 0,
45538
45544
  _v8Verdict: "DORMANT",
45539
45545
  _v8Lift: 1,
45546
+ _v9Verdict: "DORMANT",
45547
+ _v9Lift: 0.48,
45548
+ _v9Recall: 0.10211,
45549
+ _v9FpRate: 0.21462,
45550
+ _v9Precision: 0.1721,
45540
45551
  defaultOff: true
45541
45552
  },
45542
45553
  "swift/print-debug": {
45543
- recall: 0,
45544
- fpRate: 0,
45545
- ratio: 1,
45546
- precision: 0,
45547
- lastCalibratedAt: "2026-07-02T00:00:00Z",
45548
- verdict: "DORMANT",
45549
- _calibrationNote: "v0.24: new rule (multi-print in Swift source \u2014 use Logger / os.log). DORMANT until v9 Swift corpus calibration.",
45554
+ recall: 0.06866,
45555
+ fpRate: 0.06077,
45556
+ ratio: 1.13,
45557
+ precision: 0.3305,
45558
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
45559
+ verdict: "OK",
45560
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 39 TP files, 79 FP files, ratio=1.13 (\u22651.0, OK). Total-fires measurement was ratio=1.78 (1.6x higher) but per-file is more conservative. precision=33.05% (below 50% USEFUL threshold); verdict=OK. The direction is positive: post-2024 Swift (especially AI-generated examples in OpenAI Swift, WhisperKit) uses print() for output; pre-2022 production Swift (Alamofire, Vapor, Moya) uses os.Logger / OSLog. Same direction as kotlin/println-as-log (1.84) and java/system-out-println (1.73 refined, 3.29 unrefined). INSUFFICIENT_DATA: pos arm 568 files (below 10k floor).",
45550
45561
  aiSpecific: true,
45551
45562
  _v7Verdict: "DORMANT",
45552
45563
  _v7Lift: 1,
@@ -45555,16 +45566,21 @@ var signal_strength_default = {
45555
45566
  _v7Precision: 0,
45556
45567
  _v8Verdict: "DORMANT",
45557
45568
  _v8Lift: 1,
45569
+ _v9Verdict: "OK",
45570
+ _v9Lift: 1.13,
45571
+ _v9Recall: 0.06866,
45572
+ _v9FpRate: 0.06077,
45573
+ _v9Precision: 0.3305,
45558
45574
  defaultOff: true
45559
45575
  },
45560
45576
  "swift/fatal-error-thrown": {
45561
- recall: 0,
45562
- fpRate: 0,
45563
- ratio: 1,
45564
- precision: 0,
45565
- lastCalibratedAt: "2026-07-02T00:00:00Z",
45577
+ recall: 0.06514,
45578
+ fpRate: 0.11462,
45579
+ ratio: 0.57,
45580
+ precision: 0.1989,
45581
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
45566
45582
  verdict: "DORMANT",
45567
- _calibrationNote: "v0.24: new rule (`fatalError` / `preconditionFailure` in production Swift \u2014 replaces with typed default). DORMANT until v9 Swift corpus calibration.",
45583
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 37 TP files, 149 FP files, ratio=0.57. Era-confounded: pre-2022 Swift used fatalError liberally for unrecoverable states; modern Swift uses typed errors (throws) and Result types. INSUFFICIENT_DATA: pos arm 568 files.",
45568
45584
  aiSpecific: true,
45569
45585
  _v7Verdict: "DORMANT",
45570
45586
  _v7Lift: 1,
@@ -45573,16 +45589,21 @@ var signal_strength_default = {
45573
45589
  _v7Precision: 0,
45574
45590
  _v8Verdict: "DORMANT",
45575
45591
  _v8Lift: 1,
45592
+ _v9Verdict: "DORMANT",
45593
+ _v9Lift: 0.57,
45594
+ _v9Recall: 0.06514,
45595
+ _v9FpRate: 0.11462,
45596
+ _v9Precision: 0.1989,
45576
45597
  defaultOff: true
45577
45598
  },
45578
45599
  "swift/implicitly-unwrapped-optional": {
45579
- recall: 0,
45580
- fpRate: 0,
45581
- ratio: 1,
45582
- precision: 0,
45583
- lastCalibratedAt: "2026-07-02T00:00:00Z",
45600
+ recall: 0.01937,
45601
+ fpRate: 0.10692,
45602
+ ratio: 0.18,
45603
+ precision: 0.0733,
45604
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
45584
45605
  verdict: "DORMANT",
45585
- _calibrationNote: "v0.24: new rule (Swift implicitly unwrapped optional \u2014 declare as Type? and unwrap with guard let). DORMANT until v9 Swift corpus calibration.",
45606
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 11 TP files, 139 FP files, ratio=0.18 \u2014 strong era-confound. Pre-2022 Swift used `var foo: Bar!` for IBOutlets and delegates; modern Swift uses `weak var` or declares as `Bar?` and unwraps with guard let. Same magnitude as kotlin/coroutine-global-scope (0.09) \u2014 strongest era-confound in v9 history. INSUFFICIENT_DATA: pos arm 568 files.",
45586
45607
  aiSpecific: true,
45587
45608
  _v7Verdict: "DORMANT",
45588
45609
  _v7Lift: 1,
@@ -45591,16 +45612,21 @@ var signal_strength_default = {
45591
45612
  _v7Precision: 0,
45592
45613
  _v8Verdict: "DORMANT",
45593
45614
  _v8Lift: 1,
45615
+ _v9Verdict: "DORMANT",
45616
+ _v9Lift: 0.18,
45617
+ _v9Recall: 0.01937,
45618
+ _v9FpRate: 0.10692,
45619
+ _v9Precision: 0.0733,
45594
45620
  defaultOff: true
45595
45621
  },
45596
45622
  "swift/strong-self-capture": {
45597
- recall: 0,
45598
- fpRate: 0,
45599
- ratio: 1,
45600
- precision: 0,
45601
- lastCalibratedAt: "2026-07-02T00:00:00Z",
45623
+ recall: 0.24824,
45624
+ fpRate: 0.44308,
45625
+ ratio: 0.56,
45626
+ precision: 0.1967,
45627
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
45602
45628
  verdict: "DORMANT",
45603
- _calibrationNote: "v0.24: new rule (Swift closure captures `self` strongly \u2014 retain cycle. Use [weak self] / [unowned self]). DORMANT until v9 Swift corpus calibration.",
45629
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 141 TP files, 576 FP files, ratio=0.56. Era-confounded: pre-2022 Swift closures defaulted to strong self capture; modern Swift (Swift 5.7+) emits a warning for implicit self capture in escaping closures. The rule fires multiple times per file (multiple closures) \u2014 total fires were 2316 TP, 8913 FP, but per-file is 141 TP, 576 FP. INSUFFICIENT_DATA: pos arm 568 files.",
45604
45630
  aiSpecific: true,
45605
45631
  _v7Verdict: "DORMANT",
45606
45632
  _v7Lift: 1,
@@ -45609,6 +45635,11 @@ var signal_strength_default = {
45609
45635
  _v7Precision: 0,
45610
45636
  _v8Verdict: "DORMANT",
45611
45637
  _v8Lift: 1,
45638
+ _v9Verdict: "DORMANT",
45639
+ _v9Lift: 0.56,
45640
+ _v9Recall: 0.24824,
45641
+ _v9FpRate: 0.44308,
45642
+ _v9Precision: 0.1967,
45612
45643
  defaultOff: true
45613
45644
  },
45614
45645
  "cpp/using-namespace-std": {
@@ -45750,7 +45781,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
45750
45781
  const cache = buildParserCacheConfig(cwd);
45751
45782
  const ext = extname4(filePath).toLowerCase();
45752
45783
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
45753
- ".swift",
45754
45784
  ".dart",
45755
45785
  ".cpp",
45756
45786
  ".cc",
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.30.0";
39
+ VERSION = "0.32.0";
40
40
  }
41
41
  });
42
42
 
@@ -30820,13 +30820,13 @@ var init_system_out_println = __esm({
30820
30820
  "use strict";
30821
30821
  init_rule();
30822
30822
  SYSTEM_OUT_REGEX = /\bSystem\.out\.println\s*\(/g;
30823
- REAL_LOGGING_IMPORT_REGEX = /\bimport\s+(?:org\.slf4j\.|org\.apache\.logging\.log4j|org\.apache\.log4j|java\.util\.logging|com\.google\.common\.logging|io\.github\.oshai\.kotlinlogging)/;
30823
+ REAL_LOGGING_IMPORT_REGEX = /\bimport\s+(?:org\.slf4j\.|org\.apache\.logging\.log4j|org\.apache\.log4j|java\.util\.logging|com\.google\.common\.logging)/;
30824
30824
  javaSystemOutPrintlnRule = createRule({
30825
30825
  id: "java/system-out-println",
30826
30826
  category: "logic",
30827
30827
  severity: "low",
30828
30828
  aiSpecific: false,
30829
- description: "System.out.println() used for logging \u2014 use SLF4J, Log4j2, or java.util.logging",
30829
+ description: "System.out.println() in a file that imports SLF4J/Log4j2 \u2014 use the declared logger instead",
30830
30830
  create(_context) {
30831
30831
  return {};
30832
30832
  },
@@ -30836,7 +30836,7 @@ var init_system_out_println = __esm({
30836
30836
  if (!source) return issues;
30837
30837
  if (!/\.java$/i.test(facts.filePath)) return issues;
30838
30838
  if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
30839
- if (REAL_LOGGING_IMPORT_REGEX.test(source)) return issues;
30839
+ if (!REAL_LOGGING_IMPORT_REGEX.test(source)) return issues;
30840
30840
  let m;
30841
30841
  SYSTEM_OUT_REGEX.lastIndex = 0;
30842
30842
  while ((m = SYSTEM_OUT_REGEX.exec(source)) !== null) {
@@ -30846,10 +30846,10 @@ var init_system_out_println = __esm({
30846
30846
  category: "logic",
30847
30847
  severity: "low",
30848
30848
  aiSpecific: false,
30849
- message: `System.out.println() at line ${line}`,
30849
+ message: `System.out.println() at line ${line} (file imports a real logger)`,
30850
30850
  line,
30851
30851
  column: 1,
30852
- advice: "Use a real logging library: SLF4J (the standard facade), Log4j2, or java.util.logging. System.out has no log level, no timestamp, no correlation ID, and cannot be filtered. Migration: add `private static final Logger log = LoggerFactory.getLogger(MyClass.class);` and replace `System.out.println(x)` with `log.info(x)`. Reference: java/system-out-println v0.30."
30852
+ advice: "The file imports a real logging library (SLF4J, Log4j2, or java.util.logging) but uses System.out.println here. Replace `System.out.println(x)` with `log.info(x)` (or log.debug/warn/error as appropriate). The log object is already declared at the top of the file. Reference: java/system-out-println v0.31 (refined from v0.30)."
30853
30853
  });
30854
30854
  }
30855
30855
  return issues;
@@ -36609,6 +36609,12 @@ function parseSource(source, filePath) {
36609
36609
  // sufficient for the v9 calibration goal.
36610
36610
  case "kt":
36611
36611
  case "kts":
36612
+ // v0.32.0: Swift files get the same parseBlankModule path.
36613
+ // All 5 `swift/*` rules (force-unwrap, print-debug, fatal-error-
36614
+ // thrown, implicitly-unwrapped-optional, strong-self-capture)
36615
+ // are regex-based and gate themselves on `/\.swift$/i.test(filePath)`
36616
+ // inside their `analyze()`. Same trade-off as Java/Kotlin.
36617
+ case "swift":
36612
36618
  return parseBlankModule(source);
36613
36619
  default:
36614
36620
  return parseWithSwc(source, filePath);
@@ -51606,19 +51612,19 @@ var init_signal_strength = __esm({
51606
51612
  defaultOff: true
51607
51613
  },
51608
51614
  "java/system-out-println": {
51609
- recall: 0.16681,
51610
- fpRate: 0.05064,
51611
- ratio: 3.29,
51612
- precision: 0.293,
51615
+ recall: 116e-5,
51616
+ fpRate: 67e-5,
51617
+ ratio: 1.73,
51618
+ precision: 0.1791,
51613
51619
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51614
51620
  verdict: "OK",
51615
- _calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=3.29 (\u22651.5) \u2014 second positive-signal rule in v9 history (after kotlin/println-as-log at 1.84). Fires 3.29x more on post-2024 (pos) AI code than pre-2022 (neg) production. 5866 total fires (highest of any v0.30 rule). Precision=29.3% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Java (especially AI-generated examples) uses System.out for output; pre-2022 production uses slf4j. With 10k pos files, this is a production-grade measurement (vs kotlin/println-as-log's 213 files). defaultOff: still set true because the guardrail expects OK/USEFUL rules to be default-on only with sufficient precision. The v0.31+ re-calibration may flip this if precision improves.",
51621
+ _calibrationNote: "v0.31: v9 Java calibration REFINED \u2014 rule now requires the file to import a real logger (SLF4J, Log4j2, java.util.logging) AND have System.out.println calls. This is the 'set up but didn't use' anti-pattern. ratio=1.73 (\u22651.5) \u2014 second positive-signal rule in v9 history. v0.30.0 unrefined version had ratio=3.29 but precision=29.3% (over 5800 fires diluted by legitimate System.out in non-slf4j files). v0.31.0 refined version: 12 TP, 55 FP \u2014 much more selective, but absolute count is too small for production-grade measurement. precision=17.91% (still below 50% USEFUL threshold). The refinement succeeded: the fires dropped 99% (5866\u219267) and precision went 29.3%\u219217.9% (a tradeoff \u2014 fewer fires but more targeted). The direction is preserved: post-2024 Java has slightly more 'slf4j import + System.out call' than pre-2022. defaultOff: still set true (precision below 50%).",
51616
51622
  aiSpecific: false,
51617
51623
  _v9Verdict: "OK",
51618
- _v9Lift: 3.29,
51619
- _v9Recall: 0.16681,
51620
- _v9FpRate: 0.05064,
51621
- _v9Precision: 0.293,
51624
+ _v9Lift: 1.73,
51625
+ _v9Recall: 116e-5,
51626
+ _v9FpRate: 67e-5,
51627
+ _v9Precision: 0.1791,
51622
51628
  defaultOff: true
51623
51629
  },
51624
51630
  "java/command-injection": {
@@ -51638,13 +51644,13 @@ var init_signal_strength = __esm({
51638
51644
  defaultOff: true
51639
51645
  },
51640
51646
  "swift/force-unwrap": {
51641
- recall: 0,
51642
- fpRate: 0,
51643
- ratio: 1,
51644
- precision: 0,
51645
- lastCalibratedAt: "2026-07-02T00:00:00Z",
51647
+ recall: 0.10211,
51648
+ fpRate: 0.21462,
51649
+ ratio: 0.48,
51650
+ precision: 0.1721,
51651
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
51646
51652
  verdict: "DORMANT",
51647
- _calibrationNote: "v0.24: new rule (Swift force-unwrap \u2014 `as!`, `try!`, `var!.prop` in production). DORMANT until v9 Swift corpus calibration.",
51653
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique measurement: 58 TP files, 279 FP files, ratio=0.48. Era-confounded: pre-2022 Swift used ! freely (the only way to unwrap); modern Swift uses guard let / if let / try?. Same direction as kotlin/force-unwrap (0.35) and kotlin/runblocking-misuse (0.50). INSUFFICIENT_DATA: pos arm 568 files (below 10k floor).",
51648
51654
  aiSpecific: true,
51649
51655
  _v7Verdict: "DORMANT",
51650
51656
  _v7Lift: 1,
@@ -51653,16 +51659,21 @@ var init_signal_strength = __esm({
51653
51659
  _v7Precision: 0,
51654
51660
  _v8Verdict: "DORMANT",
51655
51661
  _v8Lift: 1,
51662
+ _v9Verdict: "DORMANT",
51663
+ _v9Lift: 0.48,
51664
+ _v9Recall: 0.10211,
51665
+ _v9FpRate: 0.21462,
51666
+ _v9Precision: 0.1721,
51656
51667
  defaultOff: true
51657
51668
  },
51658
51669
  "swift/print-debug": {
51659
- recall: 0,
51660
- fpRate: 0,
51661
- ratio: 1,
51662
- precision: 0,
51663
- lastCalibratedAt: "2026-07-02T00:00:00Z",
51664
- verdict: "DORMANT",
51665
- _calibrationNote: "v0.24: new rule (multi-print in Swift source \u2014 use Logger / os.log). DORMANT until v9 Swift corpus calibration.",
51670
+ recall: 0.06866,
51671
+ fpRate: 0.06077,
51672
+ ratio: 1.13,
51673
+ precision: 0.3305,
51674
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
51675
+ verdict: "OK",
51676
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 39 TP files, 79 FP files, ratio=1.13 (\u22651.0, OK). Total-fires measurement was ratio=1.78 (1.6x higher) but per-file is more conservative. precision=33.05% (below 50% USEFUL threshold); verdict=OK. The direction is positive: post-2024 Swift (especially AI-generated examples in OpenAI Swift, WhisperKit) uses print() for output; pre-2022 production Swift (Alamofire, Vapor, Moya) uses os.Logger / OSLog. Same direction as kotlin/println-as-log (1.84) and java/system-out-println (1.73 refined, 3.29 unrefined). INSUFFICIENT_DATA: pos arm 568 files (below 10k floor).",
51666
51677
  aiSpecific: true,
51667
51678
  _v7Verdict: "DORMANT",
51668
51679
  _v7Lift: 1,
@@ -51671,16 +51682,21 @@ var init_signal_strength = __esm({
51671
51682
  _v7Precision: 0,
51672
51683
  _v8Verdict: "DORMANT",
51673
51684
  _v8Lift: 1,
51685
+ _v9Verdict: "OK",
51686
+ _v9Lift: 1.13,
51687
+ _v9Recall: 0.06866,
51688
+ _v9FpRate: 0.06077,
51689
+ _v9Precision: 0.3305,
51674
51690
  defaultOff: true
51675
51691
  },
51676
51692
  "swift/fatal-error-thrown": {
51677
- recall: 0,
51678
- fpRate: 0,
51679
- ratio: 1,
51680
- precision: 0,
51681
- lastCalibratedAt: "2026-07-02T00:00:00Z",
51693
+ recall: 0.06514,
51694
+ fpRate: 0.11462,
51695
+ ratio: 0.57,
51696
+ precision: 0.1989,
51697
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
51682
51698
  verdict: "DORMANT",
51683
- _calibrationNote: "v0.24: new rule (`fatalError` / `preconditionFailure` in production Swift \u2014 replaces with typed default). DORMANT until v9 Swift corpus calibration.",
51699
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 37 TP files, 149 FP files, ratio=0.57. Era-confounded: pre-2022 Swift used fatalError liberally for unrecoverable states; modern Swift uses typed errors (throws) and Result types. INSUFFICIENT_DATA: pos arm 568 files.",
51684
51700
  aiSpecific: true,
51685
51701
  _v7Verdict: "DORMANT",
51686
51702
  _v7Lift: 1,
@@ -51689,16 +51705,21 @@ var init_signal_strength = __esm({
51689
51705
  _v7Precision: 0,
51690
51706
  _v8Verdict: "DORMANT",
51691
51707
  _v8Lift: 1,
51708
+ _v9Verdict: "DORMANT",
51709
+ _v9Lift: 0.57,
51710
+ _v9Recall: 0.06514,
51711
+ _v9FpRate: 0.11462,
51712
+ _v9Precision: 0.1989,
51692
51713
  defaultOff: true
51693
51714
  },
51694
51715
  "swift/implicitly-unwrapped-optional": {
51695
- recall: 0,
51696
- fpRate: 0,
51697
- ratio: 1,
51698
- precision: 0,
51699
- lastCalibratedAt: "2026-07-02T00:00:00Z",
51716
+ recall: 0.01937,
51717
+ fpRate: 0.10692,
51718
+ ratio: 0.18,
51719
+ precision: 0.0733,
51720
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
51700
51721
  verdict: "DORMANT",
51701
- _calibrationNote: "v0.24: new rule (Swift implicitly unwrapped optional \u2014 declare as Type? and unwrap with guard let). DORMANT until v9 Swift corpus calibration.",
51722
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 11 TP files, 139 FP files, ratio=0.18 \u2014 strong era-confound. Pre-2022 Swift used `var foo: Bar!` for IBOutlets and delegates; modern Swift uses `weak var` or declares as `Bar?` and unwraps with guard let. Same magnitude as kotlin/coroutine-global-scope (0.09) \u2014 strongest era-confound in v9 history. INSUFFICIENT_DATA: pos arm 568 files.",
51702
51723
  aiSpecific: true,
51703
51724
  _v7Verdict: "DORMANT",
51704
51725
  _v7Lift: 1,
@@ -51707,16 +51728,21 @@ var init_signal_strength = __esm({
51707
51728
  _v7Precision: 0,
51708
51729
  _v8Verdict: "DORMANT",
51709
51730
  _v8Lift: 1,
51731
+ _v9Verdict: "DORMANT",
51732
+ _v9Lift: 0.18,
51733
+ _v9Recall: 0.01937,
51734
+ _v9FpRate: 0.10692,
51735
+ _v9Precision: 0.0733,
51710
51736
  defaultOff: true
51711
51737
  },
51712
51738
  "swift/strong-self-capture": {
51713
- recall: 0,
51714
- fpRate: 0,
51715
- ratio: 1,
51716
- precision: 0,
51717
- lastCalibratedAt: "2026-07-02T00:00:00Z",
51739
+ recall: 0.24824,
51740
+ fpRate: 0.44308,
51741
+ ratio: 0.56,
51742
+ precision: 0.1967,
51743
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
51718
51744
  verdict: "DORMANT",
51719
- _calibrationNote: "v0.24: new rule (Swift closure captures `self` strongly \u2014 retain cycle. Use [weak self] / [unowned self]). DORMANT until v9 Swift corpus calibration.",
51745
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 141 TP files, 576 FP files, ratio=0.56. Era-confounded: pre-2022 Swift closures defaulted to strong self capture; modern Swift (Swift 5.7+) emits a warning for implicit self capture in escaping closures. The rule fires multiple times per file (multiple closures) \u2014 total fires were 2316 TP, 8913 FP, but per-file is 141 TP, 576 FP. INSUFFICIENT_DATA: pos arm 568 files.",
51720
51746
  aiSpecific: true,
51721
51747
  _v7Verdict: "DORMANT",
51722
51748
  _v7Lift: 1,
@@ -51725,6 +51751,11 @@ var init_signal_strength = __esm({
51725
51751
  _v7Precision: 0,
51726
51752
  _v8Verdict: "DORMANT",
51727
51753
  _v8Lift: 1,
51754
+ _v9Verdict: "DORMANT",
51755
+ _v9Lift: 0.56,
51756
+ _v9Recall: 0.24824,
51757
+ _v9FpRate: 0.44308,
51758
+ _v9Precision: 0.1967,
51728
51759
  defaultOff: true
51729
51760
  },
51730
51761
  "cpp/using-namespace-std": {
@@ -51894,7 +51925,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
51894
51925
  const cache = buildParserCacheConfig(cwd);
51895
51926
  const ext = (0, import_node_path12.extname)(filePath).toLowerCase();
51896
51927
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
51897
- ".swift",
51898
51928
  ".dart",
51899
51929
  ".cpp",
51900
51930
  ".cc",
@@ -59859,7 +59889,7 @@ var RULE_HINTS = {
59859
59889
  "java/sql-string-concat": "Use a PreparedStatement (JDBC), setParameter (jOOQ), or an ORM (Hibernate, MyBatis with #{}). String concat into SQL is the canonical SQL-injection pattern. OWASP A03:2021. (v0.30.0 \u2014 DORMANT, ratio 0.59.)",
59860
59890
  "java/hardcoded-credential": "Move the credential to an env var, a .env file (gitignored), or a secrets manager (Vault, AWS Secrets Manager). Hardcoded creds are the #1 secret-leak vector. OWASP A07:2021. (v0.30.0 \u2014 DORMANT, 0 fires on v9 Java corpus.)",
59861
59891
  "java/thread-sleep-in-loop": "Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the polling anti-pattern \u2014 ties up Tomcat/Jetty/Netty threads. (v0.30.0 \u2014 DORMANT, era-confounded ratio 0.97.)",
59862
- "java/system-out-println": "Use SLF4J (the standard facade), Log4j2, or java.util.logging. System.out has no log level, no timestamp, no correlation ID. (v0.30.0 \u2014 OK; second positive-signal rule in v9 history, ratio 3.29.)",
59892
+ "java/system-out-println": "The file imports a real logger (SLF4J/Log4j2/java.util.logging) but uses System.out.println. Use the declared log object instead. (v0.31.0 \u2014 OK; ratio 1.73, refined from v0.30.)",
59863
59893
  "java/command-injection": "Use ProcessBuilder with a List<String> of args (no shell parsing) and validate each arg. Runtime.exec() with concat is the canonical command-injection pattern. OWASP A03:2021. (v0.30.0 \u2014 DORMANT.)",
59864
59894
  // v0.24.0 — Swift rules (DORMANT until v9 Swift corpus calibration)
59865
59895
  "swift/force-unwrap": "Replace with the safe form: `as?` + guard/if let, `try?`, or `guard let x = optional else { return }`. `!` crashes unconditionally in release. (v0.24.0 \u2014 DORMANT.)",
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ var VERSION;
19
19
  var init_header = __esm({
20
20
  "src/types/_header.ts"() {
21
21
  "use strict";
22
- VERSION = "0.30.0";
22
+ VERSION = "0.32.0";
23
23
  }
24
24
  });
25
25
 
@@ -30801,13 +30801,13 @@ var init_system_out_println = __esm({
30801
30801
  "use strict";
30802
30802
  init_rule();
30803
30803
  SYSTEM_OUT_REGEX = /\bSystem\.out\.println\s*\(/g;
30804
- REAL_LOGGING_IMPORT_REGEX = /\bimport\s+(?:org\.slf4j\.|org\.apache\.logging\.log4j|org\.apache\.log4j|java\.util\.logging|com\.google\.common\.logging|io\.github\.oshai\.kotlinlogging)/;
30804
+ REAL_LOGGING_IMPORT_REGEX = /\bimport\s+(?:org\.slf4j\.|org\.apache\.logging\.log4j|org\.apache\.log4j|java\.util\.logging|com\.google\.common\.logging)/;
30805
30805
  javaSystemOutPrintlnRule = createRule({
30806
30806
  id: "java/system-out-println",
30807
30807
  category: "logic",
30808
30808
  severity: "low",
30809
30809
  aiSpecific: false,
30810
- description: "System.out.println() used for logging \u2014 use SLF4J, Log4j2, or java.util.logging",
30810
+ description: "System.out.println() in a file that imports SLF4J/Log4j2 \u2014 use the declared logger instead",
30811
30811
  create(_context) {
30812
30812
  return {};
30813
30813
  },
@@ -30817,7 +30817,7 @@ var init_system_out_println = __esm({
30817
30817
  if (!source) return issues;
30818
30818
  if (!/\.java$/i.test(facts.filePath)) return issues;
30819
30819
  if (/\/test\//i.test(facts.filePath) || /\/src\/test\//i.test(facts.filePath)) return issues;
30820
- if (REAL_LOGGING_IMPORT_REGEX.test(source)) return issues;
30820
+ if (!REAL_LOGGING_IMPORT_REGEX.test(source)) return issues;
30821
30821
  let m;
30822
30822
  SYSTEM_OUT_REGEX.lastIndex = 0;
30823
30823
  while ((m = SYSTEM_OUT_REGEX.exec(source)) !== null) {
@@ -30827,10 +30827,10 @@ var init_system_out_println = __esm({
30827
30827
  category: "logic",
30828
30828
  severity: "low",
30829
30829
  aiSpecific: false,
30830
- message: `System.out.println() at line ${line}`,
30830
+ message: `System.out.println() at line ${line} (file imports a real logger)`,
30831
30831
  line,
30832
30832
  column: 1,
30833
- advice: "Use a real logging library: SLF4J (the standard facade), Log4j2, or java.util.logging. System.out has no log level, no timestamp, no correlation ID, and cannot be filtered. Migration: add `private static final Logger log = LoggerFactory.getLogger(MyClass.class);` and replace `System.out.println(x)` with `log.info(x)`. Reference: java/system-out-println v0.30."
30833
+ advice: "The file imports a real logging library (SLF4J, Log4j2, or java.util.logging) but uses System.out.println here. Replace `System.out.println(x)` with `log.info(x)` (or log.debug/warn/error as appropriate). The log object is already declared at the top of the file. Reference: java/system-out-println v0.31 (refined from v0.30)."
30834
30834
  });
30835
30835
  }
30836
30836
  return issues;
@@ -36599,6 +36599,12 @@ function parseSource(source, filePath) {
36599
36599
  // sufficient for the v9 calibration goal.
36600
36600
  case "kt":
36601
36601
  case "kts":
36602
+ // v0.32.0: Swift files get the same parseBlankModule path.
36603
+ // All 5 `swift/*` rules (force-unwrap, print-debug, fatal-error-
36604
+ // thrown, implicitly-unwrapped-optional, strong-self-capture)
36605
+ // are regex-based and gate themselves on `/\.swift$/i.test(filePath)`
36606
+ // inside their `analyze()`. Same trade-off as Java/Kotlin.
36607
+ case "swift":
36602
36608
  return parseBlankModule(source);
36603
36609
  default:
36604
36610
  return parseWithSwc(source, filePath);
@@ -51584,19 +51590,19 @@ var init_signal_strength = __esm({
51584
51590
  defaultOff: true
51585
51591
  },
51586
51592
  "java/system-out-println": {
51587
- recall: 0.16681,
51588
- fpRate: 0.05064,
51589
- ratio: 3.29,
51590
- precision: 0.293,
51593
+ recall: 116e-5,
51594
+ fpRate: 67e-5,
51595
+ ratio: 1.73,
51596
+ precision: 0.1791,
51591
51597
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51592
51598
  verdict: "OK",
51593
- _calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=3.29 (\u22651.5) \u2014 second positive-signal rule in v9 history (after kotlin/println-as-log at 1.84). Fires 3.29x more on post-2024 (pos) AI code than pre-2022 (neg) production. 5866 total fires (highest of any v0.30 rule). Precision=29.3% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Java (especially AI-generated examples) uses System.out for output; pre-2022 production uses slf4j. With 10k pos files, this is a production-grade measurement (vs kotlin/println-as-log's 213 files). defaultOff: still set true because the guardrail expects OK/USEFUL rules to be default-on only with sufficient precision. The v0.31+ re-calibration may flip this if precision improves.",
51599
+ _calibrationNote: "v0.31: v9 Java calibration REFINED \u2014 rule now requires the file to import a real logger (SLF4J, Log4j2, java.util.logging) AND have System.out.println calls. This is the 'set up but didn't use' anti-pattern. ratio=1.73 (\u22651.5) \u2014 second positive-signal rule in v9 history. v0.30.0 unrefined version had ratio=3.29 but precision=29.3% (over 5800 fires diluted by legitimate System.out in non-slf4j files). v0.31.0 refined version: 12 TP, 55 FP \u2014 much more selective, but absolute count is too small for production-grade measurement. precision=17.91% (still below 50% USEFUL threshold). The refinement succeeded: the fires dropped 99% (5866\u219267) and precision went 29.3%\u219217.9% (a tradeoff \u2014 fewer fires but more targeted). The direction is preserved: post-2024 Java has slightly more 'slf4j import + System.out call' than pre-2022. defaultOff: still set true (precision below 50%).",
51594
51600
  aiSpecific: false,
51595
51601
  _v9Verdict: "OK",
51596
- _v9Lift: 3.29,
51597
- _v9Recall: 0.16681,
51598
- _v9FpRate: 0.05064,
51599
- _v9Precision: 0.293,
51602
+ _v9Lift: 1.73,
51603
+ _v9Recall: 116e-5,
51604
+ _v9FpRate: 67e-5,
51605
+ _v9Precision: 0.1791,
51600
51606
  defaultOff: true
51601
51607
  },
51602
51608
  "java/command-injection": {
@@ -51616,13 +51622,13 @@ var init_signal_strength = __esm({
51616
51622
  defaultOff: true
51617
51623
  },
51618
51624
  "swift/force-unwrap": {
51619
- recall: 0,
51620
- fpRate: 0,
51621
- ratio: 1,
51622
- precision: 0,
51623
- lastCalibratedAt: "2026-07-02T00:00:00Z",
51625
+ recall: 0.10211,
51626
+ fpRate: 0.21462,
51627
+ ratio: 0.48,
51628
+ precision: 0.1721,
51629
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
51624
51630
  verdict: "DORMANT",
51625
- _calibrationNote: "v0.24: new rule (Swift force-unwrap \u2014 `as!`, `try!`, `var!.prop` in production). DORMANT until v9 Swift corpus calibration.",
51631
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique measurement: 58 TP files, 279 FP files, ratio=0.48. Era-confounded: pre-2022 Swift used ! freely (the only way to unwrap); modern Swift uses guard let / if let / try?. Same direction as kotlin/force-unwrap (0.35) and kotlin/runblocking-misuse (0.50). INSUFFICIENT_DATA: pos arm 568 files (below 10k floor).",
51626
51632
  aiSpecific: true,
51627
51633
  _v7Verdict: "DORMANT",
51628
51634
  _v7Lift: 1,
@@ -51631,16 +51637,21 @@ var init_signal_strength = __esm({
51631
51637
  _v7Precision: 0,
51632
51638
  _v8Verdict: "DORMANT",
51633
51639
  _v8Lift: 1,
51640
+ _v9Verdict: "DORMANT",
51641
+ _v9Lift: 0.48,
51642
+ _v9Recall: 0.10211,
51643
+ _v9FpRate: 0.21462,
51644
+ _v9Precision: 0.1721,
51634
51645
  defaultOff: true
51635
51646
  },
51636
51647
  "swift/print-debug": {
51637
- recall: 0,
51638
- fpRate: 0,
51639
- ratio: 1,
51640
- precision: 0,
51641
- lastCalibratedAt: "2026-07-02T00:00:00Z",
51642
- verdict: "DORMANT",
51643
- _calibrationNote: "v0.24: new rule (multi-print in Swift source \u2014 use Logger / os.log). DORMANT until v9 Swift corpus calibration.",
51648
+ recall: 0.06866,
51649
+ fpRate: 0.06077,
51650
+ ratio: 1.13,
51651
+ precision: 0.3305,
51652
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
51653
+ verdict: "OK",
51654
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 39 TP files, 79 FP files, ratio=1.13 (\u22651.0, OK). Total-fires measurement was ratio=1.78 (1.6x higher) but per-file is more conservative. precision=33.05% (below 50% USEFUL threshold); verdict=OK. The direction is positive: post-2024 Swift (especially AI-generated examples in OpenAI Swift, WhisperKit) uses print() for output; pre-2022 production Swift (Alamofire, Vapor, Moya) uses os.Logger / OSLog. Same direction as kotlin/println-as-log (1.84) and java/system-out-println (1.73 refined, 3.29 unrefined). INSUFFICIENT_DATA: pos arm 568 files (below 10k floor).",
51644
51655
  aiSpecific: true,
51645
51656
  _v7Verdict: "DORMANT",
51646
51657
  _v7Lift: 1,
@@ -51649,16 +51660,21 @@ var init_signal_strength = __esm({
51649
51660
  _v7Precision: 0,
51650
51661
  _v8Verdict: "DORMANT",
51651
51662
  _v8Lift: 1,
51663
+ _v9Verdict: "OK",
51664
+ _v9Lift: 1.13,
51665
+ _v9Recall: 0.06866,
51666
+ _v9FpRate: 0.06077,
51667
+ _v9Precision: 0.3305,
51652
51668
  defaultOff: true
51653
51669
  },
51654
51670
  "swift/fatal-error-thrown": {
51655
- recall: 0,
51656
- fpRate: 0,
51657
- ratio: 1,
51658
- precision: 0,
51659
- lastCalibratedAt: "2026-07-02T00:00:00Z",
51671
+ recall: 0.06514,
51672
+ fpRate: 0.11462,
51673
+ ratio: 0.57,
51674
+ precision: 0.1989,
51675
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
51660
51676
  verdict: "DORMANT",
51661
- _calibrationNote: "v0.24: new rule (`fatalError` / `preconditionFailure` in production Swift \u2014 replaces with typed default). DORMANT until v9 Swift corpus calibration.",
51677
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 37 TP files, 149 FP files, ratio=0.57. Era-confounded: pre-2022 Swift used fatalError liberally for unrecoverable states; modern Swift uses typed errors (throws) and Result types. INSUFFICIENT_DATA: pos arm 568 files.",
51662
51678
  aiSpecific: true,
51663
51679
  _v7Verdict: "DORMANT",
51664
51680
  _v7Lift: 1,
@@ -51667,16 +51683,21 @@ var init_signal_strength = __esm({
51667
51683
  _v7Precision: 0,
51668
51684
  _v8Verdict: "DORMANT",
51669
51685
  _v8Lift: 1,
51686
+ _v9Verdict: "DORMANT",
51687
+ _v9Lift: 0.57,
51688
+ _v9Recall: 0.06514,
51689
+ _v9FpRate: 0.11462,
51690
+ _v9Precision: 0.1989,
51670
51691
  defaultOff: true
51671
51692
  },
51672
51693
  "swift/implicitly-unwrapped-optional": {
51673
- recall: 0,
51674
- fpRate: 0,
51675
- ratio: 1,
51676
- precision: 0,
51677
- lastCalibratedAt: "2026-07-02T00:00:00Z",
51694
+ recall: 0.01937,
51695
+ fpRate: 0.10692,
51696
+ ratio: 0.18,
51697
+ precision: 0.0733,
51698
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
51678
51699
  verdict: "DORMANT",
51679
- _calibrationNote: "v0.24: new rule (Swift implicitly unwrapped optional \u2014 declare as Type? and unwrap with guard let). DORMANT until v9 Swift corpus calibration.",
51700
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 11 TP files, 139 FP files, ratio=0.18 \u2014 strong era-confound. Pre-2022 Swift used `var foo: Bar!` for IBOutlets and delegates; modern Swift uses `weak var` or declares as `Bar?` and unwraps with guard let. Same magnitude as kotlin/coroutine-global-scope (0.09) \u2014 strongest era-confound in v9 history. INSUFFICIENT_DATA: pos arm 568 files.",
51680
51701
  aiSpecific: true,
51681
51702
  _v7Verdict: "DORMANT",
51682
51703
  _v7Lift: 1,
@@ -51685,16 +51706,21 @@ var init_signal_strength = __esm({
51685
51706
  _v7Precision: 0,
51686
51707
  _v8Verdict: "DORMANT",
51687
51708
  _v8Lift: 1,
51709
+ _v9Verdict: "DORMANT",
51710
+ _v9Lift: 0.18,
51711
+ _v9Recall: 0.01937,
51712
+ _v9FpRate: 0.10692,
51713
+ _v9Precision: 0.0733,
51688
51714
  defaultOff: true
51689
51715
  },
51690
51716
  "swift/strong-self-capture": {
51691
- recall: 0,
51692
- fpRate: 0,
51693
- ratio: 1,
51694
- precision: 0,
51695
- lastCalibratedAt: "2026-07-02T00:00:00Z",
51717
+ recall: 0.24824,
51718
+ fpRate: 0.44308,
51719
+ ratio: 0.56,
51720
+ precision: 0.1967,
51721
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
51696
51722
  verdict: "DORMANT",
51697
- _calibrationNote: "v0.24: new rule (Swift closure captures `self` strongly \u2014 retain cycle. Use [weak self] / [unowned self]). DORMANT until v9 Swift corpus calibration.",
51723
+ _calibrationNote: "v0.32: v9 Swift calibration (1300 neg, 568 pos). Per-file unique: 141 TP files, 576 FP files, ratio=0.56. Era-confounded: pre-2022 Swift closures defaulted to strong self capture; modern Swift (Swift 5.7+) emits a warning for implicit self capture in escaping closures. The rule fires multiple times per file (multiple closures) \u2014 total fires were 2316 TP, 8913 FP, but per-file is 141 TP, 576 FP. INSUFFICIENT_DATA: pos arm 568 files.",
51698
51724
  aiSpecific: true,
51699
51725
  _v7Verdict: "DORMANT",
51700
51726
  _v7Lift: 1,
@@ -51703,6 +51729,11 @@ var init_signal_strength = __esm({
51703
51729
  _v7Precision: 0,
51704
51730
  _v8Verdict: "DORMANT",
51705
51731
  _v8Lift: 1,
51732
+ _v9Verdict: "DORMANT",
51733
+ _v9Lift: 0.56,
51734
+ _v9Recall: 0.24824,
51735
+ _v9FpRate: 0.44308,
51736
+ _v9Precision: 0.1967,
51706
51737
  defaultOff: true
51707
51738
  },
51708
51739
  "cpp/using-namespace-std": {
@@ -51876,7 +51907,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
51876
51907
  const cache = buildParserCacheConfig(cwd);
51877
51908
  const ext = extname5(filePath).toLowerCase();
51878
51909
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
51879
- ".swift",
51880
51910
  ".dart",
51881
51911
  ".cpp",
51882
51912
  ".cc",
@@ -59758,7 +59788,7 @@ var RULE_HINTS = {
59758
59788
  "java/sql-string-concat": "Use a PreparedStatement (JDBC), setParameter (jOOQ), or an ORM (Hibernate, MyBatis with #{}). String concat into SQL is the canonical SQL-injection pattern. OWASP A03:2021. (v0.30.0 \u2014 DORMANT, ratio 0.59.)",
59759
59789
  "java/hardcoded-credential": "Move the credential to an env var, a .env file (gitignored), or a secrets manager (Vault, AWS Secrets Manager). Hardcoded creds are the #1 secret-leak vector. OWASP A07:2021. (v0.30.0 \u2014 DORMANT, 0 fires on v9 Java corpus.)",
59760
59790
  "java/thread-sleep-in-loop": "Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the polling anti-pattern \u2014 ties up Tomcat/Jetty/Netty threads. (v0.30.0 \u2014 DORMANT, era-confounded ratio 0.97.)",
59761
- "java/system-out-println": "Use SLF4J (the standard facade), Log4j2, or java.util.logging. System.out has no log level, no timestamp, no correlation ID. (v0.30.0 \u2014 OK; second positive-signal rule in v9 history, ratio 3.29.)",
59791
+ "java/system-out-println": "The file imports a real logger (SLF4J/Log4j2/java.util.logging) but uses System.out.println. Use the declared log object instead. (v0.31.0 \u2014 OK; ratio 1.73, refined from v0.30.)",
59762
59792
  "java/command-injection": "Use ProcessBuilder with a List<String> of args (no shell parsing) and validate each arg. Runtime.exec() with concat is the canonical command-injection pattern. OWASP A03:2021. (v0.30.0 \u2014 DORMANT.)",
59763
59793
  // v0.24.0 — Swift rules (DORMANT until v9 Swift corpus calibration)
59764
59794
  "swift/force-unwrap": "Replace with the safe form: `as?` + guard/if let, `try?`, or `guard let x = optional else { return }`. `!` crashes unconditionally in release. (v0.24.0 \u2014 DORMANT.)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slopbrick",
3
- "version": "0.30.0",
3
+ "version": "0.32.0",
4
4
  "description": "Discovered, modeled, and governed repository structure. SlopBrick scans source code, classifies it against 95+ rules in 15 categories, computes 4 scores (aiSlopScore: lower=cleaner, engineeringHygiene, security, repositoryHealth composite), and persists the structure for AI agents and CI.",
5
5
  "type": "module",
6
6
  "bin": {