slopbrick 0.34.6 → 0.34.8

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.
@@ -34575,6 +34575,7 @@ var PRINTF_FAMILY_REGEX = /\b(?:printf|fprintf|sprintf|snprintf)\s*\(/g;
34575
34575
  var COUT_LITERAL_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*"[^"]*"/g;
34576
34576
  var STD_COUT_BARE_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*'[^']*'/g;
34577
34577
  var THRESHOLD_DEFAULT = 1;
34578
+ var TEST_FILE_REGEX = /(?:\/tests?\/|_test\.cc|_test\.cpp|Test\.cc|Test\.cpp|Tests\.cc|Tests\.cpp|_unittest\.cc|_unittest\.cpp)/;
34578
34579
  var cppPrintfDebugRule = createRule({
34579
34580
  id: "cpp/printf-debug",
34580
34581
  category: "typo",
@@ -34589,6 +34590,7 @@ var cppPrintfDebugRule = createRule({
34589
34590
  const source = facts.v2?._source;
34590
34591
  if (!source) return issues;
34591
34592
  if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
34593
+ if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
34592
34594
  const printfCount = (source.match(PRINTF_FAMILY_REGEX) ?? []).length;
34593
34595
  let viaPrintf = false;
34594
34596
  if (printfCount > context.threshold) {
@@ -34605,7 +34607,7 @@ var cppPrintfDebugRule = createRule({
34605
34607
  message: viaPrintf ? `${printfCount} printf-family calls \u2014 use spdlog / glog / AbslLog` : "std::cout/cerr/clog with a string literal \u2014 use spdlog / glog / AbslLog",
34606
34608
  line: 1,
34607
34609
  column: 1,
34608
- advice: "Replace with `spdlog::info(...)`, `LOG(INFO) << ...` (glog), or `ABSL_LOG(INFO) << ...` (Abseil). All of these are level-aware and have a configurable sink, and they route to stderr by default. `printf` / `std::cout` have no levels, no redaction, no sink routing, and can't be silenced in release builds. AI agents reach for these because their training data has countless C++ textbook examples with them. Reference: cpp/printf-debug v0.24."
34610
+ advice: "Replace with `spdlog::info(...)`, `LOG(INFO) << ...` (glog), or `ABSL_LOG(INFO) << ...` (Abseil). All of these are level-aware and have a configurable sink, and they route to stderr by default. `printf` / `std::cout` have no levels, no redaction, no sink routing, and can't be silenced in release builds. AI agents reach for these because their training data has countless C++ textbook examples with them. Reference: cpp/printf-debug v0.34.7 (refined to skip test files for higher precision)."
34609
34611
  });
34610
34612
  return issues;
34611
34613
  }
@@ -37583,7 +37585,7 @@ var kotlinObjectSingletonMisuseRule = createRule({
37583
37585
  // src/rules/kotlin/println-as-log.ts
37584
37586
  var PRINTLN_REGEX = /\bprintln\s*\(/g;
37585
37587
  var REAL_LOGGING_IMPORT_REGEX2 = /\bimport\s+(?:android\.util\.Log|org\.slf4j\.|io\.github\.oshai\.kotlinlogging|kotlin\.logging|co\.touchlab\.kermit|com\.github\.ajalt\.timber|org\.apache\.logging\.log4j)/;
37586
- var TEST_FILE_REGEX = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
37588
+ var TEST_FILE_REGEX2 = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
37587
37589
  var kotlinPrintlnAsLogRule = createRule({
37588
37590
  id: "kotlin/println-as-log",
37589
37591
  category: "logic",
@@ -37598,7 +37600,7 @@ var kotlinPrintlnAsLogRule = createRule({
37598
37600
  const source = facts.v2?._source;
37599
37601
  if (!source) return issues;
37600
37602
  if (!/\.kts?$/i.test(facts.filePath)) return issues;
37601
- if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
37603
+ if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
37602
37604
  if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
37603
37605
  let m;
37604
37606
  PRINTLN_REGEX.lastIndex = 0;
@@ -37699,9 +37701,9 @@ var kotlinRunBlockingMisuseRule = createRule({
37699
37701
  });
37700
37702
 
37701
37703
  // src/rules/kotlin/sql-string-concat.ts
37702
- var SQL_KEYWORD_REGEX2 = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
37704
+ var SQL_KEYWORD_REGEX2 = /\b(SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
37703
37705
  var UNSAFE_REGEX2 = /(?:\+|\$\{)/;
37704
- var SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\\?\\?|\?\\s*,)/;
37706
+ var SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\?|\?)/;
37705
37707
  var kotlinSqlStringConcatRule = createRule({
37706
37708
  id: "kotlin/sql-string-concat",
37707
37709
  category: "security",
@@ -37719,7 +37721,14 @@ var kotlinSqlStringConcatRule = createRule({
37719
37721
  const lines = source.split("\n");
37720
37722
  for (let i = 0; i < lines.length; i++) {
37721
37723
  const line = lines[i];
37722
- if (!SQL_KEYWORD_REGEX2.test(line)) continue;
37724
+ const keywordMatch = SQL_KEYWORD_REGEX2.exec(line);
37725
+ if (!keywordMatch) continue;
37726
+ const keywordIdx = keywordMatch.index;
37727
+ let j = keywordIdx - 1;
37728
+ while (j >= 0 && /\s/.test(line[j])) j--;
37729
+ if (j < 0) continue;
37730
+ const prevChar = line[j];
37731
+ if (prevChar !== '"' && prevChar !== "'" && prevChar !== "=") continue;
37723
37732
  if (!UNSAFE_REGEX2.test(line)) continue;
37724
37733
  if (SAFE_REGEX2.test(line)) continue;
37725
37734
  issues.push({
@@ -37730,7 +37739,7 @@ var kotlinSqlStringConcatRule = createRule({
37730
37739
  message: `SQL query built via string concat/template at line ${i + 1}`,
37731
37740
  line: i + 1,
37732
37741
  column: 1,
37733
- advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.29 (OWASP A03:2021).'
37742
+ advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.34.8 (refined: require SQL keyword to start a string literal; fixed SAFE_REGEX bug).'
37734
37743
  });
37735
37744
  }
37736
37745
  return issues;
@@ -40635,7 +40644,7 @@ var swiftImplicitlyUnwrappedOptionalRule = createRule({
40635
40644
  // src/rules/swift/print-debug.ts
40636
40645
  var PRINT_REGEX = /\bprint\s*\(/g;
40637
40646
  var DEFAULT_THRESHOLD2 = 1;
40638
- var TEST_FILE_REGEX2 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
40647
+ var TEST_FILE_REGEX3 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
40639
40648
  var swiftPrintDebugRule = createRule({
40640
40649
  id: "swift/print-debug",
40641
40650
  category: "typo",
@@ -40650,7 +40659,7 @@ var swiftPrintDebugRule = createRule({
40650
40659
  const source = facts.v2?._source;
40651
40660
  if (!source) return issues;
40652
40661
  if (!/\.swift$/i.test(facts.filePath)) return issues;
40653
- if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
40662
+ if (TEST_FILE_REGEX3.test(facts.filePath)) return issues;
40654
40663
  const matches = [];
40655
40664
  let m;
40656
40665
  PRINT_REGEX.lastIndex = 0;
@@ -45538,7 +45547,7 @@ var signal_strength_default = {
45538
45547
  precision: 0.0556,
45539
45548
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45540
45549
  verdict: "DORMANT",
45541
- _calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.75 (DORMANT). 18 fires total \u2014 modern Kotlin mostly uses ORMs (Room/Exposed) so SQL string concat is rare in both arms. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: rule is loadable but invisible by default until v0.30 re-calibration with a larger pos arm.",
45550
+ _calibrationNote: 'v0.34.8: REFINED \u2014 rule now requires the SQL keyword to be the start of a string literal (preceded by `"`, `\'`, or `=`), and fixed a bug in the SAFE_REGEX (the `:??` was malformed, should be `:?`). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): 1 TP file, 17 FP files, ratio=0.75 (DORMANT). 18 total fires. The refinement is expected to push precision from 5.6% to 25%+ by removing lines where SELECT/INSERT appears in string values (e.g. `val msg = "Selected 1 row: \\$count"`). Also fixed the bug where `:??` in SAFE_REGEX didn\'t match Java/Kotlin named parameters (`:id`). The full v9 re-calibration is part of the broader v0.34.8/v0.34.9 pipeline. INSUFFICIENT_DATA: pos arm 213 files (below 10k floor).',
45542
45551
  aiSpecific: false,
45543
45552
  _v9Verdict: "DORMANT",
45544
45553
  _v9Lift: 0.75,
@@ -45882,7 +45891,7 @@ var signal_strength_default = {
45882
45891
  precision: 0.4407,
45883
45892
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45884
45893
  verdict: "OK",
45885
- _calibrationNote: "v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 156 TP files, 198 FP files, ratio=2.43 (\u22651.5) \u2014 fourth positive-signal rule in v9 history (and the strongest ratio so far). precision=44.07% (just below 50% USEFUL threshold); verdict=OK. Total fires: 182 TP, 198 FP. The signal is real: post-2024 C++ (mostly AI-generated demos in llama.cpp, whisper.cpp, openai-cpp) uses printf/cout for output; pre-2022 production C++ (folly, protobuf, abseil) uses spdlog / glog / AbslLog. Same direction as kotlin/println-as-log (1.84), java/system-out-println (1.73 refined), swift/print-debug (1.13). INSUFFICIENT_DATA: pos arm 1655 files (below 10k floor).",
45894
+ _calibrationNote: "v0.34.7: REFINED \u2014 rule now skips test files (gtest, catch2, doctest conventions: *_test.cpp, *_test.cc, /tests/ dir, *Test.cpp, *Test.cc, *_unittest.cpp). Per-file unique v9 C++ calibration (5107 neg, 1655 pos): 156 TP files, 198 FP files, ratio=2.43 (OK, fourth positive-signal in v9 history). precision=44.07% (below 50% USEFUL threshold); verdict=OK. The refinement is expected to push precision from 44% to 50%+ by removing gtest test output fires (which were a significant portion of FPs in the v0.33.0 measurement). The full v9 re-calibration is part of the broader v0.34.5/v0.34.7 pipeline; see v0.34.1's Four-language validation section. Same direction as kotlin/println-as-log (1.84), java/system-out-println (1.73 refined, 3.29 unrefined), swift/print-debug (1.13). INSUFFICIENT_DATA: pos arm 1655 files (below 10k floor).",
45886
45895
  aiSpecific: true,
45887
45896
  _v7Verdict: "DORMANT",
45888
45897
  _v7Lift: 1,
@@ -34546,6 +34546,7 @@ var PRINTF_FAMILY_REGEX = /\b(?:printf|fprintf|sprintf|snprintf)\s*\(/g;
34546
34546
  var COUT_LITERAL_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*"[^"]*"/g;
34547
34547
  var STD_COUT_BARE_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*'[^']*'/g;
34548
34548
  var THRESHOLD_DEFAULT = 1;
34549
+ var TEST_FILE_REGEX = /(?:\/tests?\/|_test\.cc|_test\.cpp|Test\.cc|Test\.cpp|Tests\.cc|Tests\.cpp|_unittest\.cc|_unittest\.cpp)/;
34549
34550
  var cppPrintfDebugRule = createRule({
34550
34551
  id: "cpp/printf-debug",
34551
34552
  category: "typo",
@@ -34560,6 +34561,7 @@ var cppPrintfDebugRule = createRule({
34560
34561
  const source = facts.v2?._source;
34561
34562
  if (!source) return issues;
34562
34563
  if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
34564
+ if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
34563
34565
  const printfCount = (source.match(PRINTF_FAMILY_REGEX) ?? []).length;
34564
34566
  let viaPrintf = false;
34565
34567
  if (printfCount > context.threshold) {
@@ -34576,7 +34578,7 @@ var cppPrintfDebugRule = createRule({
34576
34578
  message: viaPrintf ? `${printfCount} printf-family calls \u2014 use spdlog / glog / AbslLog` : "std::cout/cerr/clog with a string literal \u2014 use spdlog / glog / AbslLog",
34577
34579
  line: 1,
34578
34580
  column: 1,
34579
- advice: "Replace with `spdlog::info(...)`, `LOG(INFO) << ...` (glog), or `ABSL_LOG(INFO) << ...` (Abseil). All of these are level-aware and have a configurable sink, and they route to stderr by default. `printf` / `std::cout` have no levels, no redaction, no sink routing, and can't be silenced in release builds. AI agents reach for these because their training data has countless C++ textbook examples with them. Reference: cpp/printf-debug v0.24."
34581
+ advice: "Replace with `spdlog::info(...)`, `LOG(INFO) << ...` (glog), or `ABSL_LOG(INFO) << ...` (Abseil). All of these are level-aware and have a configurable sink, and they route to stderr by default. `printf` / `std::cout` have no levels, no redaction, no sink routing, and can't be silenced in release builds. AI agents reach for these because their training data has countless C++ textbook examples with them. Reference: cpp/printf-debug v0.34.7 (refined to skip test files for higher precision)."
34580
34582
  });
34581
34583
  return issues;
34582
34584
  }
@@ -37554,7 +37556,7 @@ var kotlinObjectSingletonMisuseRule = createRule({
37554
37556
  // src/rules/kotlin/println-as-log.ts
37555
37557
  var PRINTLN_REGEX = /\bprintln\s*\(/g;
37556
37558
  var REAL_LOGGING_IMPORT_REGEX2 = /\bimport\s+(?:android\.util\.Log|org\.slf4j\.|io\.github\.oshai\.kotlinlogging|kotlin\.logging|co\.touchlab\.kermit|com\.github\.ajalt\.timber|org\.apache\.logging\.log4j)/;
37557
- var TEST_FILE_REGEX = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
37559
+ var TEST_FILE_REGEX2 = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
37558
37560
  var kotlinPrintlnAsLogRule = createRule({
37559
37561
  id: "kotlin/println-as-log",
37560
37562
  category: "logic",
@@ -37569,7 +37571,7 @@ var kotlinPrintlnAsLogRule = createRule({
37569
37571
  const source = facts.v2?._source;
37570
37572
  if (!source) return issues;
37571
37573
  if (!/\.kts?$/i.test(facts.filePath)) return issues;
37572
- if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
37574
+ if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
37573
37575
  if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
37574
37576
  let m;
37575
37577
  PRINTLN_REGEX.lastIndex = 0;
@@ -37670,9 +37672,9 @@ var kotlinRunBlockingMisuseRule = createRule({
37670
37672
  });
37671
37673
 
37672
37674
  // src/rules/kotlin/sql-string-concat.ts
37673
- var SQL_KEYWORD_REGEX2 = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
37675
+ var SQL_KEYWORD_REGEX2 = /\b(SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
37674
37676
  var UNSAFE_REGEX2 = /(?:\+|\$\{)/;
37675
- var SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\\?\\?|\?\\s*,)/;
37677
+ var SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\?|\?)/;
37676
37678
  var kotlinSqlStringConcatRule = createRule({
37677
37679
  id: "kotlin/sql-string-concat",
37678
37680
  category: "security",
@@ -37690,7 +37692,14 @@ var kotlinSqlStringConcatRule = createRule({
37690
37692
  const lines = source.split("\n");
37691
37693
  for (let i = 0; i < lines.length; i++) {
37692
37694
  const line = lines[i];
37693
- if (!SQL_KEYWORD_REGEX2.test(line)) continue;
37695
+ const keywordMatch = SQL_KEYWORD_REGEX2.exec(line);
37696
+ if (!keywordMatch) continue;
37697
+ const keywordIdx = keywordMatch.index;
37698
+ let j = keywordIdx - 1;
37699
+ while (j >= 0 && /\s/.test(line[j])) j--;
37700
+ if (j < 0) continue;
37701
+ const prevChar = line[j];
37702
+ if (prevChar !== '"' && prevChar !== "'" && prevChar !== "=") continue;
37694
37703
  if (!UNSAFE_REGEX2.test(line)) continue;
37695
37704
  if (SAFE_REGEX2.test(line)) continue;
37696
37705
  issues.push({
@@ -37701,7 +37710,7 @@ var kotlinSqlStringConcatRule = createRule({
37701
37710
  message: `SQL query built via string concat/template at line ${i + 1}`,
37702
37711
  line: i + 1,
37703
37712
  column: 1,
37704
- advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.29 (OWASP A03:2021).'
37713
+ advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.34.8 (refined: require SQL keyword to start a string literal; fixed SAFE_REGEX bug).'
37705
37714
  });
37706
37715
  }
37707
37716
  return issues;
@@ -40606,7 +40615,7 @@ var swiftImplicitlyUnwrappedOptionalRule = createRule({
40606
40615
  // src/rules/swift/print-debug.ts
40607
40616
  var PRINT_REGEX = /\bprint\s*\(/g;
40608
40617
  var DEFAULT_THRESHOLD2 = 1;
40609
- var TEST_FILE_REGEX2 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
40618
+ var TEST_FILE_REGEX3 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
40610
40619
  var swiftPrintDebugRule = createRule({
40611
40620
  id: "swift/print-debug",
40612
40621
  category: "typo",
@@ -40621,7 +40630,7 @@ var swiftPrintDebugRule = createRule({
40621
40630
  const source = facts.v2?._source;
40622
40631
  if (!source) return issues;
40623
40632
  if (!/\.swift$/i.test(facts.filePath)) return issues;
40624
- if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
40633
+ if (TEST_FILE_REGEX3.test(facts.filePath)) return issues;
40625
40634
  const matches = [];
40626
40635
  let m;
40627
40636
  PRINT_REGEX.lastIndex = 0;
@@ -45509,7 +45518,7 @@ var signal_strength_default = {
45509
45518
  precision: 0.0556,
45510
45519
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45511
45520
  verdict: "DORMANT",
45512
- _calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.75 (DORMANT). 18 fires total \u2014 modern Kotlin mostly uses ORMs (Room/Exposed) so SQL string concat is rare in both arms. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: rule is loadable but invisible by default until v0.30 re-calibration with a larger pos arm.",
45521
+ _calibrationNote: 'v0.34.8: REFINED \u2014 rule now requires the SQL keyword to be the start of a string literal (preceded by `"`, `\'`, or `=`), and fixed a bug in the SAFE_REGEX (the `:??` was malformed, should be `:?`). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): 1 TP file, 17 FP files, ratio=0.75 (DORMANT). 18 total fires. The refinement is expected to push precision from 5.6% to 25%+ by removing lines where SELECT/INSERT appears in string values (e.g. `val msg = "Selected 1 row: \\$count"`). Also fixed the bug where `:??` in SAFE_REGEX didn\'t match Java/Kotlin named parameters (`:id`). The full v9 re-calibration is part of the broader v0.34.8/v0.34.9 pipeline. INSUFFICIENT_DATA: pos arm 213 files (below 10k floor).',
45513
45522
  aiSpecific: false,
45514
45523
  _v9Verdict: "DORMANT",
45515
45524
  _v9Lift: 0.75,
@@ -45853,7 +45862,7 @@ var signal_strength_default = {
45853
45862
  precision: 0.4407,
45854
45863
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45855
45864
  verdict: "OK",
45856
- _calibrationNote: "v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 156 TP files, 198 FP files, ratio=2.43 (\u22651.5) \u2014 fourth positive-signal rule in v9 history (and the strongest ratio so far). precision=44.07% (just below 50% USEFUL threshold); verdict=OK. Total fires: 182 TP, 198 FP. The signal is real: post-2024 C++ (mostly AI-generated demos in llama.cpp, whisper.cpp, openai-cpp) uses printf/cout for output; pre-2022 production C++ (folly, protobuf, abseil) uses spdlog / glog / AbslLog. Same direction as kotlin/println-as-log (1.84), java/system-out-println (1.73 refined), swift/print-debug (1.13). INSUFFICIENT_DATA: pos arm 1655 files (below 10k floor).",
45865
+ _calibrationNote: "v0.34.7: REFINED \u2014 rule now skips test files (gtest, catch2, doctest conventions: *_test.cpp, *_test.cc, /tests/ dir, *Test.cpp, *Test.cc, *_unittest.cpp). Per-file unique v9 C++ calibration (5107 neg, 1655 pos): 156 TP files, 198 FP files, ratio=2.43 (OK, fourth positive-signal in v9 history). precision=44.07% (below 50% USEFUL threshold); verdict=OK. The refinement is expected to push precision from 44% to 50%+ by removing gtest test output fires (which were a significant portion of FPs in the v0.33.0 measurement). The full v9 re-calibration is part of the broader v0.34.5/v0.34.7 pipeline; see v0.34.1's Four-language validation section. Same direction as kotlin/println-as-log (1.84), java/system-out-println (1.73 refined, 3.29 unrefined), swift/print-debug (1.13). INSUFFICIENT_DATA: pos arm 1655 files (below 10k floor).",
45857
45866
  aiSpecific: true,
45858
45867
  _v7Verdict: "DORMANT",
45859
45868
  _v7Lift: 1,
package/dist/index.cjs CHANGED
@@ -36,7 +36,7 @@ var VERSION;
36
36
  var init_header = __esm({
37
37
  "src/types/_header.ts"() {
38
38
  "use strict";
39
- VERSION = "0.34.6";
39
+ VERSION = "0.34.8";
40
40
  }
41
41
  });
42
42
 
@@ -27810,7 +27810,7 @@ var init_magic_numbers = __esm({
27810
27810
  });
27811
27811
 
27812
27812
  // src/rules/cpp/printf-debug.ts
27813
- var PRINTF_FAMILY_REGEX, COUT_LITERAL_REGEX, STD_COUT_BARE_REGEX, THRESHOLD_DEFAULT, cppPrintfDebugRule;
27813
+ var PRINTF_FAMILY_REGEX, COUT_LITERAL_REGEX, STD_COUT_BARE_REGEX, THRESHOLD_DEFAULT, TEST_FILE_REGEX, cppPrintfDebugRule;
27814
27814
  var init_printf_debug = __esm({
27815
27815
  "src/rules/cpp/printf-debug.ts"() {
27816
27816
  "use strict";
@@ -27819,6 +27819,7 @@ var init_printf_debug = __esm({
27819
27819
  COUT_LITERAL_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*"[^"]*"/g;
27820
27820
  STD_COUT_BARE_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*'[^']*'/g;
27821
27821
  THRESHOLD_DEFAULT = 1;
27822
+ TEST_FILE_REGEX = /(?:\/tests?\/|_test\.cc|_test\.cpp|Test\.cc|Test\.cpp|Tests\.cc|Tests\.cpp|_unittest\.cc|_unittest\.cpp)/;
27822
27823
  cppPrintfDebugRule = createRule({
27823
27824
  id: "cpp/printf-debug",
27824
27825
  category: "typo",
@@ -27833,6 +27834,7 @@ var init_printf_debug = __esm({
27833
27834
  const source = facts.v2?._source;
27834
27835
  if (!source) return issues;
27835
27836
  if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
27837
+ if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
27836
27838
  const printfCount = (source.match(PRINTF_FAMILY_REGEX) ?? []).length;
27837
27839
  let viaPrintf = false;
27838
27840
  if (printfCount > context.threshold) {
@@ -27849,7 +27851,7 @@ var init_printf_debug = __esm({
27849
27851
  message: viaPrintf ? `${printfCount} printf-family calls \u2014 use spdlog / glog / AbslLog` : "std::cout/cerr/clog with a string literal \u2014 use spdlog / glog / AbslLog",
27850
27852
  line: 1,
27851
27853
  column: 1,
27852
- advice: "Replace with `spdlog::info(...)`, `LOG(INFO) << ...` (glog), or `ABSL_LOG(INFO) << ...` (Abseil). All of these are level-aware and have a configurable sink, and they route to stderr by default. `printf` / `std::cout` have no levels, no redaction, no sink routing, and can't be silenced in release builds. AI agents reach for these because their training data has countless C++ textbook examples with them. Reference: cpp/printf-debug v0.24."
27854
+ advice: "Replace with `spdlog::info(...)`, `LOG(INFO) << ...` (glog), or `ABSL_LOG(INFO) << ...` (Abseil). All of these are level-aware and have a configurable sink, and they route to stderr by default. `printf` / `std::cout` have no levels, no redaction, no sink routing, and can't be silenced in release builds. AI agents reach for these because their training data has countless C++ textbook examples with them. Reference: cpp/printf-debug v0.34.7 (refined to skip test files for higher precision)."
27853
27855
  });
27854
27856
  return issues;
27855
27857
  }
@@ -31295,14 +31297,14 @@ var init_object_singleton_misuse = __esm({
31295
31297
  });
31296
31298
 
31297
31299
  // src/rules/kotlin/println-as-log.ts
31298
- var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX2, TEST_FILE_REGEX, kotlinPrintlnAsLogRule;
31300
+ var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX2, TEST_FILE_REGEX2, kotlinPrintlnAsLogRule;
31299
31301
  var init_println_as_log = __esm({
31300
31302
  "src/rules/kotlin/println-as-log.ts"() {
31301
31303
  "use strict";
31302
31304
  init_rule();
31303
31305
  PRINTLN_REGEX = /\bprintln\s*\(/g;
31304
31306
  REAL_LOGGING_IMPORT_REGEX2 = /\bimport\s+(?:android\.util\.Log|org\.slf4j\.|io\.github\.oshai\.kotlinlogging|kotlin\.logging|co\.touchlab\.kermit|com\.github\.ajalt\.timber|org\.apache\.logging\.log4j)/;
31305
- TEST_FILE_REGEX = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
31307
+ TEST_FILE_REGEX2 = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
31306
31308
  kotlinPrintlnAsLogRule = createRule({
31307
31309
  id: "kotlin/println-as-log",
31308
31310
  category: "logic",
@@ -31317,7 +31319,7 @@ var init_println_as_log = __esm({
31317
31319
  const source = facts.v2?._source;
31318
31320
  if (!source) return issues;
31319
31321
  if (!/\.kts?$/i.test(facts.filePath)) return issues;
31320
- if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
31322
+ if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
31321
31323
  if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
31322
31324
  let m;
31323
31325
  PRINTLN_REGEX.lastIndex = 0;
@@ -31439,9 +31441,9 @@ var init_sql_string_concat2 = __esm({
31439
31441
  "src/rules/kotlin/sql-string-concat.ts"() {
31440
31442
  "use strict";
31441
31443
  init_rule();
31442
- SQL_KEYWORD_REGEX2 = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
31444
+ SQL_KEYWORD_REGEX2 = /\b(SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
31443
31445
  UNSAFE_REGEX2 = /(?:\+|\$\{)/;
31444
- SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\\?\\?|\?\\s*,)/;
31446
+ SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\?|\?)/;
31445
31447
  kotlinSqlStringConcatRule = createRule({
31446
31448
  id: "kotlin/sql-string-concat",
31447
31449
  category: "security",
@@ -31459,7 +31461,14 @@ var init_sql_string_concat2 = __esm({
31459
31461
  const lines = source.split("\n");
31460
31462
  for (let i = 0; i < lines.length; i++) {
31461
31463
  const line = lines[i];
31462
- if (!SQL_KEYWORD_REGEX2.test(line)) continue;
31464
+ const keywordMatch = SQL_KEYWORD_REGEX2.exec(line);
31465
+ if (!keywordMatch) continue;
31466
+ const keywordIdx = keywordMatch.index;
31467
+ let j = keywordIdx - 1;
31468
+ while (j >= 0 && /\s/.test(line[j])) j--;
31469
+ if (j < 0) continue;
31470
+ const prevChar = line[j];
31471
+ if (prevChar !== '"' && prevChar !== "'" && prevChar !== "=") continue;
31463
31472
  if (!UNSAFE_REGEX2.test(line)) continue;
31464
31473
  if (SAFE_REGEX2.test(line)) continue;
31465
31474
  issues.push({
@@ -31470,7 +31479,7 @@ var init_sql_string_concat2 = __esm({
31470
31479
  message: `SQL query built via string concat/template at line ${i + 1}`,
31471
31480
  line: i + 1,
31472
31481
  column: 1,
31473
- advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.29 (OWASP A03:2021).'
31482
+ advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.34.8 (refined: require SQL keyword to start a string literal; fixed SAFE_REGEX bug).'
31474
31483
  });
31475
31484
  }
31476
31485
  return issues;
@@ -41176,14 +41185,14 @@ var init_implicitly_unwrapped_optional = __esm({
41176
41185
  });
41177
41186
 
41178
41187
  // src/rules/swift/print-debug.ts
41179
- var PRINT_REGEX, DEFAULT_THRESHOLD2, TEST_FILE_REGEX2, swiftPrintDebugRule;
41188
+ var PRINT_REGEX, DEFAULT_THRESHOLD2, TEST_FILE_REGEX3, swiftPrintDebugRule;
41180
41189
  var init_print_debug = __esm({
41181
41190
  "src/rules/swift/print-debug.ts"() {
41182
41191
  "use strict";
41183
41192
  init_rule();
41184
41193
  PRINT_REGEX = /\bprint\s*\(/g;
41185
41194
  DEFAULT_THRESHOLD2 = 1;
41186
- TEST_FILE_REGEX2 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
41195
+ TEST_FILE_REGEX3 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
41187
41196
  swiftPrintDebugRule = createRule({
41188
41197
  id: "swift/print-debug",
41189
41198
  category: "typo",
@@ -41198,7 +41207,7 @@ var init_print_debug = __esm({
41198
41207
  const source = facts.v2?._source;
41199
41208
  if (!source) return issues;
41200
41209
  if (!/\.swift$/i.test(facts.filePath)) return issues;
41201
- if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
41210
+ if (TEST_FILE_REGEX3.test(facts.filePath)) return issues;
41202
41211
  const matches = [];
41203
41212
  let m;
41204
41213
  PRINT_REGEX.lastIndex = 0;
@@ -51625,7 +51634,7 @@ var init_signal_strength = __esm({
51625
51634
  precision: 0.0556,
51626
51635
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51627
51636
  verdict: "DORMANT",
51628
- _calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.75 (DORMANT). 18 fires total \u2014 modern Kotlin mostly uses ORMs (Room/Exposed) so SQL string concat is rare in both arms. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: rule is loadable but invisible by default until v0.30 re-calibration with a larger pos arm.",
51637
+ _calibrationNote: 'v0.34.8: REFINED \u2014 rule now requires the SQL keyword to be the start of a string literal (preceded by `"`, `\'`, or `=`), and fixed a bug in the SAFE_REGEX (the `:??` was malformed, should be `:?`). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): 1 TP file, 17 FP files, ratio=0.75 (DORMANT). 18 total fires. The refinement is expected to push precision from 5.6% to 25%+ by removing lines where SELECT/INSERT appears in string values (e.g. `val msg = "Selected 1 row: \\$count"`). Also fixed the bug where `:??` in SAFE_REGEX didn\'t match Java/Kotlin named parameters (`:id`). The full v9 re-calibration is part of the broader v0.34.8/v0.34.9 pipeline. INSUFFICIENT_DATA: pos arm 213 files (below 10k floor).',
51629
51638
  aiSpecific: false,
51630
51639
  _v9Verdict: "DORMANT",
51631
51640
  _v9Lift: 0.75,
@@ -51969,7 +51978,7 @@ var init_signal_strength = __esm({
51969
51978
  precision: 0.4407,
51970
51979
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51971
51980
  verdict: "OK",
51972
- _calibrationNote: "v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 156 TP files, 198 FP files, ratio=2.43 (\u22651.5) \u2014 fourth positive-signal rule in v9 history (and the strongest ratio so far). precision=44.07% (just below 50% USEFUL threshold); verdict=OK. Total fires: 182 TP, 198 FP. The signal is real: post-2024 C++ (mostly AI-generated demos in llama.cpp, whisper.cpp, openai-cpp) uses printf/cout for output; pre-2022 production C++ (folly, protobuf, abseil) uses spdlog / glog / AbslLog. Same direction as kotlin/println-as-log (1.84), java/system-out-println (1.73 refined), swift/print-debug (1.13). INSUFFICIENT_DATA: pos arm 1655 files (below 10k floor).",
51981
+ _calibrationNote: "v0.34.7: REFINED \u2014 rule now skips test files (gtest, catch2, doctest conventions: *_test.cpp, *_test.cc, /tests/ dir, *Test.cpp, *Test.cc, *_unittest.cpp). Per-file unique v9 C++ calibration (5107 neg, 1655 pos): 156 TP files, 198 FP files, ratio=2.43 (OK, fourth positive-signal in v9 history). precision=44.07% (below 50% USEFUL threshold); verdict=OK. The refinement is expected to push precision from 44% to 50%+ by removing gtest test output fires (which were a significant portion of FPs in the v0.33.0 measurement). The full v9 re-calibration is part of the broader v0.34.5/v0.34.7 pipeline; see v0.34.1's Four-language validation section. Same direction as kotlin/println-as-log (1.84), java/system-out-println (1.73 refined, 3.29 unrefined), swift/print-debug (1.13). INSUFFICIENT_DATA: pos arm 1655 files (below 10k floor).",
51973
51982
  aiSpecific: true,
51974
51983
  _v7Verdict: "DORMANT",
51975
51984
  _v7Lift: 1,
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ var VERSION;
19
19
  var init_header = __esm({
20
20
  "src/types/_header.ts"() {
21
21
  "use strict";
22
- VERSION = "0.34.6";
22
+ VERSION = "0.34.8";
23
23
  }
24
24
  });
25
25
 
@@ -27792,7 +27792,7 @@ var init_magic_numbers = __esm({
27792
27792
  });
27793
27793
 
27794
27794
  // src/rules/cpp/printf-debug.ts
27795
- var PRINTF_FAMILY_REGEX, COUT_LITERAL_REGEX, STD_COUT_BARE_REGEX, THRESHOLD_DEFAULT, cppPrintfDebugRule;
27795
+ var PRINTF_FAMILY_REGEX, COUT_LITERAL_REGEX, STD_COUT_BARE_REGEX, THRESHOLD_DEFAULT, TEST_FILE_REGEX, cppPrintfDebugRule;
27796
27796
  var init_printf_debug = __esm({
27797
27797
  "src/rules/cpp/printf-debug.ts"() {
27798
27798
  "use strict";
@@ -27801,6 +27801,7 @@ var init_printf_debug = __esm({
27801
27801
  COUT_LITERAL_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*"[^"]*"/g;
27802
27802
  STD_COUT_BARE_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*'[^']*'/g;
27803
27803
  THRESHOLD_DEFAULT = 1;
27804
+ TEST_FILE_REGEX = /(?:\/tests?\/|_test\.cc|_test\.cpp|Test\.cc|Test\.cpp|Tests\.cc|Tests\.cpp|_unittest\.cc|_unittest\.cpp)/;
27804
27805
  cppPrintfDebugRule = createRule({
27805
27806
  id: "cpp/printf-debug",
27806
27807
  category: "typo",
@@ -27815,6 +27816,7 @@ var init_printf_debug = __esm({
27815
27816
  const source = facts.v2?._source;
27816
27817
  if (!source) return issues;
27817
27818
  if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
27819
+ if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
27818
27820
  const printfCount = (source.match(PRINTF_FAMILY_REGEX) ?? []).length;
27819
27821
  let viaPrintf = false;
27820
27822
  if (printfCount > context.threshold) {
@@ -27831,7 +27833,7 @@ var init_printf_debug = __esm({
27831
27833
  message: viaPrintf ? `${printfCount} printf-family calls \u2014 use spdlog / glog / AbslLog` : "std::cout/cerr/clog with a string literal \u2014 use spdlog / glog / AbslLog",
27832
27834
  line: 1,
27833
27835
  column: 1,
27834
- advice: "Replace with `spdlog::info(...)`, `LOG(INFO) << ...` (glog), or `ABSL_LOG(INFO) << ...` (Abseil). All of these are level-aware and have a configurable sink, and they route to stderr by default. `printf` / `std::cout` have no levels, no redaction, no sink routing, and can't be silenced in release builds. AI agents reach for these because their training data has countless C++ textbook examples with them. Reference: cpp/printf-debug v0.24."
27836
+ advice: "Replace with `spdlog::info(...)`, `LOG(INFO) << ...` (glog), or `ABSL_LOG(INFO) << ...` (Abseil). All of these are level-aware and have a configurable sink, and they route to stderr by default. `printf` / `std::cout` have no levels, no redaction, no sink routing, and can't be silenced in release builds. AI agents reach for these because their training data has countless C++ textbook examples with them. Reference: cpp/printf-debug v0.34.7 (refined to skip test files for higher precision)."
27835
27837
  });
27836
27838
  return issues;
27837
27839
  }
@@ -31276,14 +31278,14 @@ var init_object_singleton_misuse = __esm({
31276
31278
  });
31277
31279
 
31278
31280
  // src/rules/kotlin/println-as-log.ts
31279
- var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX2, TEST_FILE_REGEX, kotlinPrintlnAsLogRule;
31281
+ var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX2, TEST_FILE_REGEX2, kotlinPrintlnAsLogRule;
31280
31282
  var init_println_as_log = __esm({
31281
31283
  "src/rules/kotlin/println-as-log.ts"() {
31282
31284
  "use strict";
31283
31285
  init_rule();
31284
31286
  PRINTLN_REGEX = /\bprintln\s*\(/g;
31285
31287
  REAL_LOGGING_IMPORT_REGEX2 = /\bimport\s+(?:android\.util\.Log|org\.slf4j\.|io\.github\.oshai\.kotlinlogging|kotlin\.logging|co\.touchlab\.kermit|com\.github\.ajalt\.timber|org\.apache\.logging\.log4j)/;
31286
- TEST_FILE_REGEX = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
31288
+ TEST_FILE_REGEX2 = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
31287
31289
  kotlinPrintlnAsLogRule = createRule({
31288
31290
  id: "kotlin/println-as-log",
31289
31291
  category: "logic",
@@ -31298,7 +31300,7 @@ var init_println_as_log = __esm({
31298
31300
  const source = facts.v2?._source;
31299
31301
  if (!source) return issues;
31300
31302
  if (!/\.kts?$/i.test(facts.filePath)) return issues;
31301
- if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
31303
+ if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
31302
31304
  if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
31303
31305
  let m;
31304
31306
  PRINTLN_REGEX.lastIndex = 0;
@@ -31420,9 +31422,9 @@ var init_sql_string_concat2 = __esm({
31420
31422
  "src/rules/kotlin/sql-string-concat.ts"() {
31421
31423
  "use strict";
31422
31424
  init_rule();
31423
- SQL_KEYWORD_REGEX2 = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
31425
+ SQL_KEYWORD_REGEX2 = /\b(SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
31424
31426
  UNSAFE_REGEX2 = /(?:\+|\$\{)/;
31425
- SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\\?\\?|\?\\s*,)/;
31427
+ SAFE_REGEX2 = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\?|\?)/;
31426
31428
  kotlinSqlStringConcatRule = createRule({
31427
31429
  id: "kotlin/sql-string-concat",
31428
31430
  category: "security",
@@ -31440,7 +31442,14 @@ var init_sql_string_concat2 = __esm({
31440
31442
  const lines = source.split("\n");
31441
31443
  for (let i = 0; i < lines.length; i++) {
31442
31444
  const line = lines[i];
31443
- if (!SQL_KEYWORD_REGEX2.test(line)) continue;
31445
+ const keywordMatch = SQL_KEYWORD_REGEX2.exec(line);
31446
+ if (!keywordMatch) continue;
31447
+ const keywordIdx = keywordMatch.index;
31448
+ let j = keywordIdx - 1;
31449
+ while (j >= 0 && /\s/.test(line[j])) j--;
31450
+ if (j < 0) continue;
31451
+ const prevChar = line[j];
31452
+ if (prevChar !== '"' && prevChar !== "'" && prevChar !== "=") continue;
31444
31453
  if (!UNSAFE_REGEX2.test(line)) continue;
31445
31454
  if (SAFE_REGEX2.test(line)) continue;
31446
31455
  issues.push({
@@ -31451,7 +31460,7 @@ var init_sql_string_concat2 = __esm({
31451
31460
  message: `SQL query built via string concat/template at line ${i + 1}`,
31452
31461
  line: i + 1,
31453
31462
  column: 1,
31454
- advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.29 (OWASP A03:2021).'
31463
+ advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.34.8 (refined: require SQL keyword to start a string literal; fixed SAFE_REGEX bug).'
31455
31464
  });
31456
31465
  }
31457
31466
  return issues;
@@ -41157,14 +41166,14 @@ var init_implicitly_unwrapped_optional = __esm({
41157
41166
  });
41158
41167
 
41159
41168
  // src/rules/swift/print-debug.ts
41160
- var PRINT_REGEX, DEFAULT_THRESHOLD2, TEST_FILE_REGEX2, swiftPrintDebugRule;
41169
+ var PRINT_REGEX, DEFAULT_THRESHOLD2, TEST_FILE_REGEX3, swiftPrintDebugRule;
41161
41170
  var init_print_debug = __esm({
41162
41171
  "src/rules/swift/print-debug.ts"() {
41163
41172
  "use strict";
41164
41173
  init_rule();
41165
41174
  PRINT_REGEX = /\bprint\s*\(/g;
41166
41175
  DEFAULT_THRESHOLD2 = 1;
41167
- TEST_FILE_REGEX2 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
41176
+ TEST_FILE_REGEX3 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
41168
41177
  swiftPrintDebugRule = createRule({
41169
41178
  id: "swift/print-debug",
41170
41179
  category: "typo",
@@ -41179,7 +41188,7 @@ var init_print_debug = __esm({
41179
41188
  const source = facts.v2?._source;
41180
41189
  if (!source) return issues;
41181
41190
  if (!/\.swift$/i.test(facts.filePath)) return issues;
41182
- if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
41191
+ if (TEST_FILE_REGEX3.test(facts.filePath)) return issues;
41183
41192
  const matches = [];
41184
41193
  let m;
41185
41194
  PRINT_REGEX.lastIndex = 0;
@@ -51603,7 +51612,7 @@ var init_signal_strength = __esm({
51603
51612
  precision: 0.0556,
51604
51613
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51605
51614
  verdict: "DORMANT",
51606
- _calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.75 (DORMANT). 18 fires total \u2014 modern Kotlin mostly uses ORMs (Room/Exposed) so SQL string concat is rare in both arms. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: rule is loadable but invisible by default until v0.30 re-calibration with a larger pos arm.",
51615
+ _calibrationNote: 'v0.34.8: REFINED \u2014 rule now requires the SQL keyword to be the start of a string literal (preceded by `"`, `\'`, or `=`), and fixed a bug in the SAFE_REGEX (the `:??` was malformed, should be `:?`). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): 1 TP file, 17 FP files, ratio=0.75 (DORMANT). 18 total fires. The refinement is expected to push precision from 5.6% to 25%+ by removing lines where SELECT/INSERT appears in string values (e.g. `val msg = "Selected 1 row: \\$count"`). Also fixed the bug where `:??` in SAFE_REGEX didn\'t match Java/Kotlin named parameters (`:id`). The full v9 re-calibration is part of the broader v0.34.8/v0.34.9 pipeline. INSUFFICIENT_DATA: pos arm 213 files (below 10k floor).',
51607
51616
  aiSpecific: false,
51608
51617
  _v9Verdict: "DORMANT",
51609
51618
  _v9Lift: 0.75,
@@ -51947,7 +51956,7 @@ var init_signal_strength = __esm({
51947
51956
  precision: 0.4407,
51948
51957
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51949
51958
  verdict: "OK",
51950
- _calibrationNote: "v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 156 TP files, 198 FP files, ratio=2.43 (\u22651.5) \u2014 fourth positive-signal rule in v9 history (and the strongest ratio so far). precision=44.07% (just below 50% USEFUL threshold); verdict=OK. Total fires: 182 TP, 198 FP. The signal is real: post-2024 C++ (mostly AI-generated demos in llama.cpp, whisper.cpp, openai-cpp) uses printf/cout for output; pre-2022 production C++ (folly, protobuf, abseil) uses spdlog / glog / AbslLog. Same direction as kotlin/println-as-log (1.84), java/system-out-println (1.73 refined), swift/print-debug (1.13). INSUFFICIENT_DATA: pos arm 1655 files (below 10k floor).",
51959
+ _calibrationNote: "v0.34.7: REFINED \u2014 rule now skips test files (gtest, catch2, doctest conventions: *_test.cpp, *_test.cc, /tests/ dir, *Test.cpp, *Test.cc, *_unittest.cpp). Per-file unique v9 C++ calibration (5107 neg, 1655 pos): 156 TP files, 198 FP files, ratio=2.43 (OK, fourth positive-signal in v9 history). precision=44.07% (below 50% USEFUL threshold); verdict=OK. The refinement is expected to push precision from 44% to 50%+ by removing gtest test output fires (which were a significant portion of FPs in the v0.33.0 measurement). The full v9 re-calibration is part of the broader v0.34.5/v0.34.7 pipeline; see v0.34.1's Four-language validation section. Same direction as kotlin/println-as-log (1.84), java/system-out-println (1.73 refined, 3.29 unrefined), swift/print-debug (1.13). INSUFFICIENT_DATA: pos arm 1655 files (below 10k floor).",
51951
51960
  aiSpecific: true,
51952
51961
  _v7Verdict: "DORMANT",
51953
51962
  _v7Lift: 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slopbrick",
3
- "version": "0.34.6",
3
+ "version": "0.34.8",
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": {