slopbrick 0.34.3 → 0.34.7

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.
@@ -34497,6 +34497,7 @@ var cppMagicNumbersRule = createRule({
34497
34497
  if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
34498
34498
  const lines = source.split("\n");
34499
34499
  const allowSet = /* @__PURE__ */ new Set([
34500
+ // v0.24 originals
34500
34501
  "1024",
34501
34502
  "65535",
34502
34503
  "65536",
@@ -34515,17 +34516,42 @@ var cppMagicNumbersRule = createRule({
34515
34516
  "2",
34516
34517
  "3",
34517
34518
  "4",
34518
- "5"
34519
+ "5",
34520
+ // v0.34.4 additions
34521
+ "-1",
34522
+ // sentinel value for "not found" / "all bits set"
34523
+ "100",
34524
+ // percent literal, very common
34525
+ "0.0",
34526
+ "0.5",
34527
+ "1.0",
34528
+ "2.0",
34529
+ // common probability / ratio
34530
+ "4096",
34531
+ // page size, hash bucket count
34532
+ "2048",
34533
+ "512",
34534
+ // power-of-2 sizes
34535
+ "32",
34536
+ "64",
34537
+ "128",
34538
+ // bit widths, byte sizes
34539
+ "16",
34540
+ "8",
34541
+ // common small constants
34542
+ "50"
34543
+ // percentile literal
34519
34544
  ]);
34520
34545
  for (let i = 0; i < lines.length; i++) {
34521
34546
  const line = lines[i] ?? "";
34522
34547
  if (!COMPARE_OR_RETURN_REGEX.test(line)) continue;
34548
+ const codeLine = line.replace(/\/\/.*$/, "").replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
34523
34549
  let m;
34524
34550
  MAGIC_NUMBER_REGEX.lastIndex = 0;
34525
- while ((m = MAGIC_NUMBER_REGEX.exec(line)) !== null) {
34551
+ while ((m = MAGIC_NUMBER_REGEX.exec(codeLine)) !== null) {
34526
34552
  const literal = m[0] ?? "";
34527
34553
  if (allowSet.has(literal)) continue;
34528
- if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(line.slice(0, m.index).trimEnd())) continue;
34554
+ if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(codeLine.slice(0, m.index).trimEnd())) continue;
34529
34555
  const prevLine = i > 0 ? lines[i - 1] ?? "" : "";
34530
34556
  if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(prevLine.trim())) continue;
34531
34557
  issues.push({
@@ -34536,7 +34562,7 @@ var cppMagicNumbersRule = createRule({
34536
34562
  message: `magic number ${literal} at line ${i + 1} \u2014 name it: constexpr int MAX = ${literal};`,
34537
34563
  line: i + 1,
34538
34564
  column: 1,
34539
- advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.24.'
34565
+ advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.34.4 (expanded allowSet + string/comment exclusion).'
34540
34566
  });
34541
34567
  }
34542
34568
  }
@@ -34549,6 +34575,7 @@ var PRINTF_FAMILY_REGEX = /\b(?:printf|fprintf|sprintf|snprintf)\s*\(/g;
34549
34575
  var COUT_LITERAL_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*"[^"]*"/g;
34550
34576
  var STD_COUT_BARE_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*'[^']*'/g;
34551
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)/;
34552
34579
  var cppPrintfDebugRule = createRule({
34553
34580
  id: "cpp/printf-debug",
34554
34581
  category: "typo",
@@ -34563,6 +34590,7 @@ var cppPrintfDebugRule = createRule({
34563
34590
  const source = facts.v2?._source;
34564
34591
  if (!source) return issues;
34565
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;
34566
34594
  const printfCount = (source.match(PRINTF_FAMILY_REGEX) ?? []).length;
34567
34595
  let viaPrintf = false;
34568
34596
  if (printfCount > context.threshold) {
@@ -34579,7 +34607,7 @@ var cppPrintfDebugRule = createRule({
34579
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",
34580
34608
  line: 1,
34581
34609
  column: 1,
34582
- 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)."
34583
34611
  });
34584
34612
  return issues;
34585
34613
  }
@@ -37189,7 +37217,6 @@ var javaSystemOutPrintlnRule = createRule({
37189
37217
  });
37190
37218
 
37191
37219
  // src/rules/java/thread-sleep-in-loop.ts
37192
- var THREAD_SLEEP_REGEX = /\bThread\.sleep\s*\(/g;
37193
37220
  var javaThreadSleepInLoopRule = createRule({
37194
37221
  id: "java/thread-sleep-in-loop",
37195
37222
  category: "perf",
@@ -37205,11 +37232,104 @@ var javaThreadSleepInLoopRule = createRule({
37205
37232
  if (!source) return issues;
37206
37233
  if (!/\.java$/i.test(facts.filePath)) return issues;
37207
37234
  if (!/\bThread\.sleep\s*\(/.test(source)) return issues;
37208
- if (!/\b(?:for|while|do)\b/.test(source)) return issues;
37209
- let m;
37210
- THREAD_SLEEP_REGEX.lastIndex = 0;
37211
- while ((m = THREAD_SLEEP_REGEX.exec(source)) !== null) {
37212
- const line = source.slice(0, m.index).split("\n").length;
37235
+ const braceStack = [];
37236
+ const loopSet = /* @__PURE__ */ new Set();
37237
+ let pendingLoopKeyword = null;
37238
+ let parenDepth = 0;
37239
+ const sleepEvents = [];
37240
+ let inString = false;
37241
+ let inLineComment = false;
37242
+ let inBlockComment = false;
37243
+ for (let i = 0; i < source.length; i++) {
37244
+ const c = source[i] ?? "";
37245
+ const next = source[i + 1] ?? "";
37246
+ const prev = source[i - 1] ?? "";
37247
+ if (inLineComment) {
37248
+ if (c === "\n") inLineComment = false;
37249
+ continue;
37250
+ }
37251
+ if (inBlockComment) {
37252
+ if (c === "*" && next === "/") {
37253
+ inBlockComment = false;
37254
+ i++;
37255
+ }
37256
+ continue;
37257
+ }
37258
+ if (inString) {
37259
+ if (c === "\\") {
37260
+ i++;
37261
+ continue;
37262
+ }
37263
+ if (c === inString) inString = false;
37264
+ continue;
37265
+ }
37266
+ if (c === "/" && next === "/") {
37267
+ inLineComment = true;
37268
+ i++;
37269
+ continue;
37270
+ }
37271
+ if (c === "/" && next === "*") {
37272
+ inBlockComment = true;
37273
+ i++;
37274
+ continue;
37275
+ }
37276
+ if (c === '"' || c === "'") {
37277
+ inString = c;
37278
+ continue;
37279
+ }
37280
+ if (c === "(") {
37281
+ parenDepth++;
37282
+ continue;
37283
+ }
37284
+ if (c === ")") {
37285
+ parenDepth = Math.max(0, parenDepth - 1);
37286
+ continue;
37287
+ }
37288
+ if (/[A-Za-z_]/.test(c) && !/[A-Za-z0-9_]/.test(prev)) {
37289
+ const next3 = source.slice(i, i + 3);
37290
+ const next5 = source.slice(i, i + 5);
37291
+ const next2 = source.slice(i, i + 2);
37292
+ const after3 = source[i + 3] ?? "";
37293
+ const after5 = source[i + 5] ?? "";
37294
+ const after2 = source[i + 2] ?? "";
37295
+ if (next3 === "for" && !/[A-Za-z0-9_]/.test(after3)) {
37296
+ pendingLoopKeyword = { kind: "for", idx: i };
37297
+ i += 2;
37298
+ continue;
37299
+ }
37300
+ if (next5 === "while" && !/[A-Za-z0-9_]/.test(after5)) {
37301
+ pendingLoopKeyword = { kind: "while", idx: i };
37302
+ i += 4;
37303
+ continue;
37304
+ }
37305
+ if (next2 === "do" && !/[A-Za-z0-9_]/.test(after2)) {
37306
+ pendingLoopKeyword = { kind: "do", idx: i };
37307
+ i += 1;
37308
+ continue;
37309
+ }
37310
+ }
37311
+ if (c === "{") {
37312
+ braceStack.push(i);
37313
+ if (pendingLoopKeyword && parenDepth === 0) {
37314
+ loopSet.add(i);
37315
+ pendingLoopKeyword = null;
37316
+ }
37317
+ } else if (c === "}") {
37318
+ const popped = braceStack.pop();
37319
+ if (popped !== void 0 && loopSet.has(popped)) {
37320
+ loopSet.delete(popped);
37321
+ }
37322
+ if (pendingLoopKeyword) {
37323
+ pendingLoopKeyword = null;
37324
+ }
37325
+ } else if (c === "T" && source.slice(i, i + 13) === "Thread.sleep(") {
37326
+ sleepEvents.push({ idx: i, loopDepth: loopSet.size });
37327
+ i += 12;
37328
+ }
37329
+ }
37330
+ for (const ev of sleepEvents) {
37331
+ if (ev.loopDepth === 0) continue;
37332
+ const line = source.slice(0, ev.idx).split("\n").length;
37213
37333
  issues.push({
37214
37334
  ruleId: "java/thread-sleep-in-loop",
37215
37335
  category: "perf",
@@ -37218,7 +37338,7 @@ var javaThreadSleepInLoopRule = createRule({
37218
37338
  message: `Thread.sleep() at line ${line}`,
37219
37339
  line,
37220
37340
  column: 1,
37221
- advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.30.'
37341
+ advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.34.6 (refined to require Thread.sleep inside the loop block, not just in the file).'
37222
37342
  });
37223
37343
  }
37224
37344
  return issues;
@@ -37465,6 +37585,7 @@ var kotlinObjectSingletonMisuseRule = createRule({
37465
37585
  // src/rules/kotlin/println-as-log.ts
37466
37586
  var PRINTLN_REGEX = /\bprintln\s*\(/g;
37467
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)/;
37588
+ var TEST_FILE_REGEX2 = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
37468
37589
  var kotlinPrintlnAsLogRule = createRule({
37469
37590
  id: "kotlin/println-as-log",
37470
37591
  category: "logic",
@@ -37479,7 +37600,7 @@ var kotlinPrintlnAsLogRule = createRule({
37479
37600
  const source = facts.v2?._source;
37480
37601
  if (!source) return issues;
37481
37602
  if (!/\.kts?$/i.test(facts.filePath)) return issues;
37482
- if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) return issues;
37603
+ if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
37483
37604
  if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
37484
37605
  let m;
37485
37606
  PRINTLN_REGEX.lastIndex = 0;
@@ -37493,7 +37614,7 @@ var kotlinPrintlnAsLogRule = createRule({
37493
37614
  message: `println() as logger at line ${line}`,
37494
37615
  line,
37495
37616
  column: 1,
37496
- advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.29."
37617
+ advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.34.5 (refined to skip test files for higher precision)."
37497
37618
  });
37498
37619
  }
37499
37620
  return issues;
@@ -40516,7 +40637,7 @@ var swiftImplicitlyUnwrappedOptionalRule = createRule({
40516
40637
  // src/rules/swift/print-debug.ts
40517
40638
  var PRINT_REGEX = /\bprint\s*\(/g;
40518
40639
  var DEFAULT_THRESHOLD2 = 1;
40519
- var TEST_FILE_REGEX = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
40640
+ var TEST_FILE_REGEX3 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
40520
40641
  var swiftPrintDebugRule = createRule({
40521
40642
  id: "swift/print-debug",
40522
40643
  category: "typo",
@@ -40531,7 +40652,7 @@ var swiftPrintDebugRule = createRule({
40531
40652
  const source = facts.v2?._source;
40532
40653
  if (!source) return issues;
40533
40654
  if (!/\.swift$/i.test(facts.filePath)) return issues;
40534
- if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
40655
+ if (TEST_FILE_REGEX3.test(facts.filePath)) return issues;
40535
40656
  const matches = [];
40536
40657
  let m;
40537
40658
  PRINT_REGEX.lastIndex = 0;
@@ -45467,7 +45588,7 @@ var signal_strength_default = {
45467
45588
  precision: 0.1268,
45468
45589
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45469
45590
  verdict: "OK",
45470
- _calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
45591
+ _calibrationNote: "v0.34.5: REFINED \u2014 rule now skips test files (JUnit4/5 + Android: *Tests.kt, *Test.kt, src/test/, test/ dirs). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): ratio=1.84 (\u22651.5, OK). The refinement mirrors v0.34.2's swift/print-debug fix: the previous exclusion only matched `\\/test\\/` and `.test.kts?$`, missing JUnit5's `FooTests.kt` naming. Expected post-refinement precision: 12.7% \u2192 25%+ by removing the JUnit assertion / debug-output fires that were ~50% of the FPs. Same direction as java/system-out-println (1.73 refined) and the v0.34.2 swift/print-debug pipeline. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
45471
45592
  aiSpecific: false,
45472
45593
  _v9Verdict: "OK",
45473
45594
  _v9Lift: 1.84,
@@ -45531,7 +45652,7 @@ var signal_strength_default = {
45531
45652
  precision: 0.1091,
45532
45653
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45533
45654
  verdict: "DORMANT",
45534
- _calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
45655
+ _calibrationNote: "v0.34.6: REFINED \u2014 rule now requires Thread.sleep to be INSIDE the loop block (verified via brace-counting), not just in a file that happens to contain a for/while/do keyword. v0.30 baseline: 2154 total fires, ratio 0.97 (DORMANT). The refinement targets: (1) Thread.sleep in `main()` no longer fires if a different method has a `for` loop (the v0.30 heuristic fired on every Thread.sleep in the file); (2) Thread.sleep before/after a loop block (not inside it) no longer fires; (3) string literals containing `Thread.sleep(...)` are skipped via the string-state-machine. Expected post-refinement ratio: 1.5+ (positive-signal OK verdict) by removing the cross-method over-fires. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
45535
45656
  aiSpecific: false,
45536
45657
  _v9Verdict: "DORMANT",
45537
45658
  _v9Lift: 0.97,
@@ -45763,7 +45884,7 @@ var signal_strength_default = {
45763
45884
  precision: 0.4407,
45764
45885
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45765
45886
  verdict: "OK",
45766
- _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).",
45887
+ _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).",
45767
45888
  aiSpecific: true,
45768
45889
  _v7Verdict: "DORMANT",
45769
45890
  _v7Lift: 1,
@@ -45786,7 +45907,7 @@ var signal_strength_default = {
45786
45907
  precision: 0.2187,
45787
45908
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45788
45909
  verdict: "DORMANT",
45789
- _calibrationNote: "v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
45910
+ _calibrationNote: "v0.34.4: REFINED \u2014 expanded allowSet with common constants (-1 sentinel, 100 percent literal, 0.5/1.0/2.0 ratios, 4096/2048/512/128/64/32/16/8 power-of-2 sizes, 50 percentile). Added string-literal and `//` comment stripping so substrings like `\"got 42 errors\"` and `// ticket #4242` no longer fire. v0.33 baseline: 220 TP / 786 FP per-file (ratio 0.86, DORMANT). The refinement targets: (1) sentinel `-1` literals are skipped because `1` is allowlisted and the `-` doesn't match MAGIC_NUMBER_REGEX anyway; (2) hex literals (`0xFF`) are skipped because the digit-only regex requires a word-boundary after digits, and `0x...` has none; (3) literals inside string/comment contexts no longer fire. Expected post-refinement ratio: 1.0-1.2 (still DORMANT but better-targeted). Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
45790
45911
  aiSpecific: true,
45791
45912
  _v7Verdict: "DORMANT",
45792
45913
  _v7Lift: 1,
@@ -34468,6 +34468,7 @@ var cppMagicNumbersRule = createRule({
34468
34468
  if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
34469
34469
  const lines = source.split("\n");
34470
34470
  const allowSet = /* @__PURE__ */ new Set([
34471
+ // v0.24 originals
34471
34472
  "1024",
34472
34473
  "65535",
34473
34474
  "65536",
@@ -34486,17 +34487,42 @@ var cppMagicNumbersRule = createRule({
34486
34487
  "2",
34487
34488
  "3",
34488
34489
  "4",
34489
- "5"
34490
+ "5",
34491
+ // v0.34.4 additions
34492
+ "-1",
34493
+ // sentinel value for "not found" / "all bits set"
34494
+ "100",
34495
+ // percent literal, very common
34496
+ "0.0",
34497
+ "0.5",
34498
+ "1.0",
34499
+ "2.0",
34500
+ // common probability / ratio
34501
+ "4096",
34502
+ // page size, hash bucket count
34503
+ "2048",
34504
+ "512",
34505
+ // power-of-2 sizes
34506
+ "32",
34507
+ "64",
34508
+ "128",
34509
+ // bit widths, byte sizes
34510
+ "16",
34511
+ "8",
34512
+ // common small constants
34513
+ "50"
34514
+ // percentile literal
34490
34515
  ]);
34491
34516
  for (let i = 0; i < lines.length; i++) {
34492
34517
  const line = lines[i] ?? "";
34493
34518
  if (!COMPARE_OR_RETURN_REGEX.test(line)) continue;
34519
+ const codeLine = line.replace(/\/\/.*$/, "").replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
34494
34520
  let m;
34495
34521
  MAGIC_NUMBER_REGEX.lastIndex = 0;
34496
- while ((m = MAGIC_NUMBER_REGEX.exec(line)) !== null) {
34522
+ while ((m = MAGIC_NUMBER_REGEX.exec(codeLine)) !== null) {
34497
34523
  const literal = m[0] ?? "";
34498
34524
  if (allowSet.has(literal)) continue;
34499
- if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(line.slice(0, m.index).trimEnd())) continue;
34525
+ if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(codeLine.slice(0, m.index).trimEnd())) continue;
34500
34526
  const prevLine = i > 0 ? lines[i - 1] ?? "" : "";
34501
34527
  if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(prevLine.trim())) continue;
34502
34528
  issues.push({
@@ -34507,7 +34533,7 @@ var cppMagicNumbersRule = createRule({
34507
34533
  message: `magic number ${literal} at line ${i + 1} \u2014 name it: constexpr int MAX = ${literal};`,
34508
34534
  line: i + 1,
34509
34535
  column: 1,
34510
- advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.24.'
34536
+ advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.34.4 (expanded allowSet + string/comment exclusion).'
34511
34537
  });
34512
34538
  }
34513
34539
  }
@@ -34520,6 +34546,7 @@ var PRINTF_FAMILY_REGEX = /\b(?:printf|fprintf|sprintf|snprintf)\s*\(/g;
34520
34546
  var COUT_LITERAL_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*"[^"]*"/g;
34521
34547
  var STD_COUT_BARE_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*'[^']*'/g;
34522
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)/;
34523
34550
  var cppPrintfDebugRule = createRule({
34524
34551
  id: "cpp/printf-debug",
34525
34552
  category: "typo",
@@ -34534,6 +34561,7 @@ var cppPrintfDebugRule = createRule({
34534
34561
  const source = facts.v2?._source;
34535
34562
  if (!source) return issues;
34536
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;
34537
34565
  const printfCount = (source.match(PRINTF_FAMILY_REGEX) ?? []).length;
34538
34566
  let viaPrintf = false;
34539
34567
  if (printfCount > context.threshold) {
@@ -34550,7 +34578,7 @@ var cppPrintfDebugRule = createRule({
34550
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",
34551
34579
  line: 1,
34552
34580
  column: 1,
34553
- 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)."
34554
34582
  });
34555
34583
  return issues;
34556
34584
  }
@@ -37160,7 +37188,6 @@ var javaSystemOutPrintlnRule = createRule({
37160
37188
  });
37161
37189
 
37162
37190
  // src/rules/java/thread-sleep-in-loop.ts
37163
- var THREAD_SLEEP_REGEX = /\bThread\.sleep\s*\(/g;
37164
37191
  var javaThreadSleepInLoopRule = createRule({
37165
37192
  id: "java/thread-sleep-in-loop",
37166
37193
  category: "perf",
@@ -37176,11 +37203,104 @@ var javaThreadSleepInLoopRule = createRule({
37176
37203
  if (!source) return issues;
37177
37204
  if (!/\.java$/i.test(facts.filePath)) return issues;
37178
37205
  if (!/\bThread\.sleep\s*\(/.test(source)) return issues;
37179
- if (!/\b(?:for|while|do)\b/.test(source)) return issues;
37180
- let m;
37181
- THREAD_SLEEP_REGEX.lastIndex = 0;
37182
- while ((m = THREAD_SLEEP_REGEX.exec(source)) !== null) {
37183
- const line = source.slice(0, m.index).split("\n").length;
37206
+ const braceStack = [];
37207
+ const loopSet = /* @__PURE__ */ new Set();
37208
+ let pendingLoopKeyword = null;
37209
+ let parenDepth = 0;
37210
+ const sleepEvents = [];
37211
+ let inString = false;
37212
+ let inLineComment = false;
37213
+ let inBlockComment = false;
37214
+ for (let i = 0; i < source.length; i++) {
37215
+ const c = source[i] ?? "";
37216
+ const next = source[i + 1] ?? "";
37217
+ const prev = source[i - 1] ?? "";
37218
+ if (inLineComment) {
37219
+ if (c === "\n") inLineComment = false;
37220
+ continue;
37221
+ }
37222
+ if (inBlockComment) {
37223
+ if (c === "*" && next === "/") {
37224
+ inBlockComment = false;
37225
+ i++;
37226
+ }
37227
+ continue;
37228
+ }
37229
+ if (inString) {
37230
+ if (c === "\\") {
37231
+ i++;
37232
+ continue;
37233
+ }
37234
+ if (c === inString) inString = false;
37235
+ continue;
37236
+ }
37237
+ if (c === "/" && next === "/") {
37238
+ inLineComment = true;
37239
+ i++;
37240
+ continue;
37241
+ }
37242
+ if (c === "/" && next === "*") {
37243
+ inBlockComment = true;
37244
+ i++;
37245
+ continue;
37246
+ }
37247
+ if (c === '"' || c === "'") {
37248
+ inString = c;
37249
+ continue;
37250
+ }
37251
+ if (c === "(") {
37252
+ parenDepth++;
37253
+ continue;
37254
+ }
37255
+ if (c === ")") {
37256
+ parenDepth = Math.max(0, parenDepth - 1);
37257
+ continue;
37258
+ }
37259
+ if (/[A-Za-z_]/.test(c) && !/[A-Za-z0-9_]/.test(prev)) {
37260
+ const next3 = source.slice(i, i + 3);
37261
+ const next5 = source.slice(i, i + 5);
37262
+ const next2 = source.slice(i, i + 2);
37263
+ const after3 = source[i + 3] ?? "";
37264
+ const after5 = source[i + 5] ?? "";
37265
+ const after2 = source[i + 2] ?? "";
37266
+ if (next3 === "for" && !/[A-Za-z0-9_]/.test(after3)) {
37267
+ pendingLoopKeyword = { kind: "for", idx: i };
37268
+ i += 2;
37269
+ continue;
37270
+ }
37271
+ if (next5 === "while" && !/[A-Za-z0-9_]/.test(after5)) {
37272
+ pendingLoopKeyword = { kind: "while", idx: i };
37273
+ i += 4;
37274
+ continue;
37275
+ }
37276
+ if (next2 === "do" && !/[A-Za-z0-9_]/.test(after2)) {
37277
+ pendingLoopKeyword = { kind: "do", idx: i };
37278
+ i += 1;
37279
+ continue;
37280
+ }
37281
+ }
37282
+ if (c === "{") {
37283
+ braceStack.push(i);
37284
+ if (pendingLoopKeyword && parenDepth === 0) {
37285
+ loopSet.add(i);
37286
+ pendingLoopKeyword = null;
37287
+ }
37288
+ } else if (c === "}") {
37289
+ const popped = braceStack.pop();
37290
+ if (popped !== void 0 && loopSet.has(popped)) {
37291
+ loopSet.delete(popped);
37292
+ }
37293
+ if (pendingLoopKeyword) {
37294
+ pendingLoopKeyword = null;
37295
+ }
37296
+ } else if (c === "T" && source.slice(i, i + 13) === "Thread.sleep(") {
37297
+ sleepEvents.push({ idx: i, loopDepth: loopSet.size });
37298
+ i += 12;
37299
+ }
37300
+ }
37301
+ for (const ev of sleepEvents) {
37302
+ if (ev.loopDepth === 0) continue;
37303
+ const line = source.slice(0, ev.idx).split("\n").length;
37184
37304
  issues.push({
37185
37305
  ruleId: "java/thread-sleep-in-loop",
37186
37306
  category: "perf",
@@ -37189,7 +37309,7 @@ var javaThreadSleepInLoopRule = createRule({
37189
37309
  message: `Thread.sleep() at line ${line}`,
37190
37310
  line,
37191
37311
  column: 1,
37192
- advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.30.'
37312
+ advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.34.6 (refined to require Thread.sleep inside the loop block, not just in the file).'
37193
37313
  });
37194
37314
  }
37195
37315
  return issues;
@@ -37436,6 +37556,7 @@ var kotlinObjectSingletonMisuseRule = createRule({
37436
37556
  // src/rules/kotlin/println-as-log.ts
37437
37557
  var PRINTLN_REGEX = /\bprintln\s*\(/g;
37438
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)/;
37559
+ var TEST_FILE_REGEX2 = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
37439
37560
  var kotlinPrintlnAsLogRule = createRule({
37440
37561
  id: "kotlin/println-as-log",
37441
37562
  category: "logic",
@@ -37450,7 +37571,7 @@ var kotlinPrintlnAsLogRule = createRule({
37450
37571
  const source = facts.v2?._source;
37451
37572
  if (!source) return issues;
37452
37573
  if (!/\.kts?$/i.test(facts.filePath)) return issues;
37453
- if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) return issues;
37574
+ if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
37454
37575
  if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
37455
37576
  let m;
37456
37577
  PRINTLN_REGEX.lastIndex = 0;
@@ -37464,7 +37585,7 @@ var kotlinPrintlnAsLogRule = createRule({
37464
37585
  message: `println() as logger at line ${line}`,
37465
37586
  line,
37466
37587
  column: 1,
37467
- advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.29."
37588
+ advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.34.5 (refined to skip test files for higher precision)."
37468
37589
  });
37469
37590
  }
37470
37591
  return issues;
@@ -40487,7 +40608,7 @@ var swiftImplicitlyUnwrappedOptionalRule = createRule({
40487
40608
  // src/rules/swift/print-debug.ts
40488
40609
  var PRINT_REGEX = /\bprint\s*\(/g;
40489
40610
  var DEFAULT_THRESHOLD2 = 1;
40490
- var TEST_FILE_REGEX = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
40611
+ var TEST_FILE_REGEX3 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
40491
40612
  var swiftPrintDebugRule = createRule({
40492
40613
  id: "swift/print-debug",
40493
40614
  category: "typo",
@@ -40502,7 +40623,7 @@ var swiftPrintDebugRule = createRule({
40502
40623
  const source = facts.v2?._source;
40503
40624
  if (!source) return issues;
40504
40625
  if (!/\.swift$/i.test(facts.filePath)) return issues;
40505
- if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
40626
+ if (TEST_FILE_REGEX3.test(facts.filePath)) return issues;
40506
40627
  const matches = [];
40507
40628
  let m;
40508
40629
  PRINT_REGEX.lastIndex = 0;
@@ -45438,7 +45559,7 @@ var signal_strength_default = {
45438
45559
  precision: 0.1268,
45439
45560
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45440
45561
  verdict: "OK",
45441
- _calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
45562
+ _calibrationNote: "v0.34.5: REFINED \u2014 rule now skips test files (JUnit4/5 + Android: *Tests.kt, *Test.kt, src/test/, test/ dirs). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): ratio=1.84 (\u22651.5, OK). The refinement mirrors v0.34.2's swift/print-debug fix: the previous exclusion only matched `\\/test\\/` and `.test.kts?$`, missing JUnit5's `FooTests.kt` naming. Expected post-refinement precision: 12.7% \u2192 25%+ by removing the JUnit assertion / debug-output fires that were ~50% of the FPs. Same direction as java/system-out-println (1.73 refined) and the v0.34.2 swift/print-debug pipeline. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
45442
45563
  aiSpecific: false,
45443
45564
  _v9Verdict: "OK",
45444
45565
  _v9Lift: 1.84,
@@ -45502,7 +45623,7 @@ var signal_strength_default = {
45502
45623
  precision: 0.1091,
45503
45624
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45504
45625
  verdict: "DORMANT",
45505
- _calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
45626
+ _calibrationNote: "v0.34.6: REFINED \u2014 rule now requires Thread.sleep to be INSIDE the loop block (verified via brace-counting), not just in a file that happens to contain a for/while/do keyword. v0.30 baseline: 2154 total fires, ratio 0.97 (DORMANT). The refinement targets: (1) Thread.sleep in `main()` no longer fires if a different method has a `for` loop (the v0.30 heuristic fired on every Thread.sleep in the file); (2) Thread.sleep before/after a loop block (not inside it) no longer fires; (3) string literals containing `Thread.sleep(...)` are skipped via the string-state-machine. Expected post-refinement ratio: 1.5+ (positive-signal OK verdict) by removing the cross-method over-fires. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
45506
45627
  aiSpecific: false,
45507
45628
  _v9Verdict: "DORMANT",
45508
45629
  _v9Lift: 0.97,
@@ -45734,7 +45855,7 @@ var signal_strength_default = {
45734
45855
  precision: 0.4407,
45735
45856
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45736
45857
  verdict: "OK",
45737
- _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).",
45858
+ _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).",
45738
45859
  aiSpecific: true,
45739
45860
  _v7Verdict: "DORMANT",
45740
45861
  _v7Lift: 1,
@@ -45757,7 +45878,7 @@ var signal_strength_default = {
45757
45878
  precision: 0.2187,
45758
45879
  lastCalibratedAt: "2026-07-03T00:00:00Z",
45759
45880
  verdict: "DORMANT",
45760
- _calibrationNote: "v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
45881
+ _calibrationNote: "v0.34.4: REFINED \u2014 expanded allowSet with common constants (-1 sentinel, 100 percent literal, 0.5/1.0/2.0 ratios, 4096/2048/512/128/64/32/16/8 power-of-2 sizes, 50 percentile). Added string-literal and `//` comment stripping so substrings like `\"got 42 errors\"` and `// ticket #4242` no longer fire. v0.33 baseline: 220 TP / 786 FP per-file (ratio 0.86, DORMANT). The refinement targets: (1) sentinel `-1` literals are skipped because `1` is allowlisted and the `-` doesn't match MAGIC_NUMBER_REGEX anyway; (2) hex literals (`0xFF`) are skipped because the digit-only regex requires a word-boundary after digits, and `0x...` has none; (3) literals inside string/comment contexts no longer fire. Expected post-refinement ratio: 1.0-1.2 (still DORMANT but better-targeted). Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
45761
45882
  aiSpecific: true,
45762
45883
  _v7Verdict: "DORMANT",
45763
45884
  _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.3";
39
+ VERSION = "0.34.7";
40
40
  }
41
41
  });
42
42
 
@@ -27734,6 +27734,7 @@ var init_magic_numbers = __esm({
27734
27734
  if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
27735
27735
  const lines = source.split("\n");
27736
27736
  const allowSet = /* @__PURE__ */ new Set([
27737
+ // v0.24 originals
27737
27738
  "1024",
27738
27739
  "65535",
27739
27740
  "65536",
@@ -27752,17 +27753,42 @@ var init_magic_numbers = __esm({
27752
27753
  "2",
27753
27754
  "3",
27754
27755
  "4",
27755
- "5"
27756
+ "5",
27757
+ // v0.34.4 additions
27758
+ "-1",
27759
+ // sentinel value for "not found" / "all bits set"
27760
+ "100",
27761
+ // percent literal, very common
27762
+ "0.0",
27763
+ "0.5",
27764
+ "1.0",
27765
+ "2.0",
27766
+ // common probability / ratio
27767
+ "4096",
27768
+ // page size, hash bucket count
27769
+ "2048",
27770
+ "512",
27771
+ // power-of-2 sizes
27772
+ "32",
27773
+ "64",
27774
+ "128",
27775
+ // bit widths, byte sizes
27776
+ "16",
27777
+ "8",
27778
+ // common small constants
27779
+ "50"
27780
+ // percentile literal
27756
27781
  ]);
27757
27782
  for (let i = 0; i < lines.length; i++) {
27758
27783
  const line = lines[i] ?? "";
27759
27784
  if (!COMPARE_OR_RETURN_REGEX.test(line)) continue;
27785
+ const codeLine = line.replace(/\/\/.*$/, "").replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
27760
27786
  let m;
27761
27787
  MAGIC_NUMBER_REGEX.lastIndex = 0;
27762
- while ((m = MAGIC_NUMBER_REGEX.exec(line)) !== null) {
27788
+ while ((m = MAGIC_NUMBER_REGEX.exec(codeLine)) !== null) {
27763
27789
  const literal = m[0] ?? "";
27764
27790
  if (allowSet.has(literal)) continue;
27765
- if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(line.slice(0, m.index).trimEnd())) continue;
27791
+ if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(codeLine.slice(0, m.index).trimEnd())) continue;
27766
27792
  const prevLine = i > 0 ? lines[i - 1] ?? "" : "";
27767
27793
  if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(prevLine.trim())) continue;
27768
27794
  issues.push({
@@ -27773,7 +27799,7 @@ var init_magic_numbers = __esm({
27773
27799
  message: `magic number ${literal} at line ${i + 1} \u2014 name it: constexpr int MAX = ${literal};`,
27774
27800
  line: i + 1,
27775
27801
  column: 1,
27776
- advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.24.'
27802
+ advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.34.4 (expanded allowSet + string/comment exclusion).'
27777
27803
  });
27778
27804
  }
27779
27805
  }
@@ -27784,7 +27810,7 @@ var init_magic_numbers = __esm({
27784
27810
  });
27785
27811
 
27786
27812
  // src/rules/cpp/printf-debug.ts
27787
- 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;
27788
27814
  var init_printf_debug = __esm({
27789
27815
  "src/rules/cpp/printf-debug.ts"() {
27790
27816
  "use strict";
@@ -27793,6 +27819,7 @@ var init_printf_debug = __esm({
27793
27819
  COUT_LITERAL_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*"[^"]*"/g;
27794
27820
  STD_COUT_BARE_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*'[^']*'/g;
27795
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)/;
27796
27823
  cppPrintfDebugRule = createRule({
27797
27824
  id: "cpp/printf-debug",
27798
27825
  category: "typo",
@@ -27807,6 +27834,7 @@ var init_printf_debug = __esm({
27807
27834
  const source = facts.v2?._source;
27808
27835
  if (!source) return issues;
27809
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;
27810
27838
  const printfCount = (source.match(PRINTF_FAMILY_REGEX) ?? []).length;
27811
27839
  let viaPrintf = false;
27812
27840
  if (printfCount > context.threshold) {
@@ -27823,7 +27851,7 @@ var init_printf_debug = __esm({
27823
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",
27824
27852
  line: 1,
27825
27853
  column: 1,
27826
- 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)."
27827
27855
  });
27828
27856
  return issues;
27829
27857
  }
@@ -30861,12 +30889,11 @@ var init_system_out_println = __esm({
30861
30889
  });
30862
30890
 
30863
30891
  // src/rules/java/thread-sleep-in-loop.ts
30864
- var THREAD_SLEEP_REGEX, javaThreadSleepInLoopRule;
30892
+ var javaThreadSleepInLoopRule;
30865
30893
  var init_thread_sleep_in_loop = __esm({
30866
30894
  "src/rules/java/thread-sleep-in-loop.ts"() {
30867
30895
  "use strict";
30868
30896
  init_rule();
30869
- THREAD_SLEEP_REGEX = /\bThread\.sleep\s*\(/g;
30870
30897
  javaThreadSleepInLoopRule = createRule({
30871
30898
  id: "java/thread-sleep-in-loop",
30872
30899
  category: "perf",
@@ -30882,11 +30909,104 @@ var init_thread_sleep_in_loop = __esm({
30882
30909
  if (!source) return issues;
30883
30910
  if (!/\.java$/i.test(facts.filePath)) return issues;
30884
30911
  if (!/\bThread\.sleep\s*\(/.test(source)) return issues;
30885
- if (!/\b(?:for|while|do)\b/.test(source)) return issues;
30886
- let m;
30887
- THREAD_SLEEP_REGEX.lastIndex = 0;
30888
- while ((m = THREAD_SLEEP_REGEX.exec(source)) !== null) {
30889
- const line = source.slice(0, m.index).split("\n").length;
30912
+ const braceStack = [];
30913
+ const loopSet = /* @__PURE__ */ new Set();
30914
+ let pendingLoopKeyword = null;
30915
+ let parenDepth = 0;
30916
+ const sleepEvents = [];
30917
+ let inString = false;
30918
+ let inLineComment = false;
30919
+ let inBlockComment = false;
30920
+ for (let i = 0; i < source.length; i++) {
30921
+ const c = source[i] ?? "";
30922
+ const next = source[i + 1] ?? "";
30923
+ const prev = source[i - 1] ?? "";
30924
+ if (inLineComment) {
30925
+ if (c === "\n") inLineComment = false;
30926
+ continue;
30927
+ }
30928
+ if (inBlockComment) {
30929
+ if (c === "*" && next === "/") {
30930
+ inBlockComment = false;
30931
+ i++;
30932
+ }
30933
+ continue;
30934
+ }
30935
+ if (inString) {
30936
+ if (c === "\\") {
30937
+ i++;
30938
+ continue;
30939
+ }
30940
+ if (c === inString) inString = false;
30941
+ continue;
30942
+ }
30943
+ if (c === "/" && next === "/") {
30944
+ inLineComment = true;
30945
+ i++;
30946
+ continue;
30947
+ }
30948
+ if (c === "/" && next === "*") {
30949
+ inBlockComment = true;
30950
+ i++;
30951
+ continue;
30952
+ }
30953
+ if (c === '"' || c === "'") {
30954
+ inString = c;
30955
+ continue;
30956
+ }
30957
+ if (c === "(") {
30958
+ parenDepth++;
30959
+ continue;
30960
+ }
30961
+ if (c === ")") {
30962
+ parenDepth = Math.max(0, parenDepth - 1);
30963
+ continue;
30964
+ }
30965
+ if (/[A-Za-z_]/.test(c) && !/[A-Za-z0-9_]/.test(prev)) {
30966
+ const next3 = source.slice(i, i + 3);
30967
+ const next5 = source.slice(i, i + 5);
30968
+ const next2 = source.slice(i, i + 2);
30969
+ const after3 = source[i + 3] ?? "";
30970
+ const after5 = source[i + 5] ?? "";
30971
+ const after2 = source[i + 2] ?? "";
30972
+ if (next3 === "for" && !/[A-Za-z0-9_]/.test(after3)) {
30973
+ pendingLoopKeyword = { kind: "for", idx: i };
30974
+ i += 2;
30975
+ continue;
30976
+ }
30977
+ if (next5 === "while" && !/[A-Za-z0-9_]/.test(after5)) {
30978
+ pendingLoopKeyword = { kind: "while", idx: i };
30979
+ i += 4;
30980
+ continue;
30981
+ }
30982
+ if (next2 === "do" && !/[A-Za-z0-9_]/.test(after2)) {
30983
+ pendingLoopKeyword = { kind: "do", idx: i };
30984
+ i += 1;
30985
+ continue;
30986
+ }
30987
+ }
30988
+ if (c === "{") {
30989
+ braceStack.push(i);
30990
+ if (pendingLoopKeyword && parenDepth === 0) {
30991
+ loopSet.add(i);
30992
+ pendingLoopKeyword = null;
30993
+ }
30994
+ } else if (c === "}") {
30995
+ const popped = braceStack.pop();
30996
+ if (popped !== void 0 && loopSet.has(popped)) {
30997
+ loopSet.delete(popped);
30998
+ }
30999
+ if (pendingLoopKeyword) {
31000
+ pendingLoopKeyword = null;
31001
+ }
31002
+ } else if (c === "T" && source.slice(i, i + 13) === "Thread.sleep(") {
31003
+ sleepEvents.push({ idx: i, loopDepth: loopSet.size });
31004
+ i += 12;
31005
+ }
31006
+ }
31007
+ for (const ev of sleepEvents) {
31008
+ if (ev.loopDepth === 0) continue;
31009
+ const line = source.slice(0, ev.idx).split("\n").length;
30890
31010
  issues.push({
30891
31011
  ruleId: "java/thread-sleep-in-loop",
30892
31012
  category: "perf",
@@ -30895,7 +31015,7 @@ var init_thread_sleep_in_loop = __esm({
30895
31015
  message: `Thread.sleep() at line ${line}`,
30896
31016
  line,
30897
31017
  column: 1,
30898
- advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.30.'
31018
+ advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.34.6 (refined to require Thread.sleep inside the loop block, not just in the file).'
30899
31019
  });
30900
31020
  }
30901
31021
  return issues;
@@ -31177,13 +31297,14 @@ var init_object_singleton_misuse = __esm({
31177
31297
  });
31178
31298
 
31179
31299
  // src/rules/kotlin/println-as-log.ts
31180
- var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX2, kotlinPrintlnAsLogRule;
31300
+ var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX2, TEST_FILE_REGEX2, kotlinPrintlnAsLogRule;
31181
31301
  var init_println_as_log = __esm({
31182
31302
  "src/rules/kotlin/println-as-log.ts"() {
31183
31303
  "use strict";
31184
31304
  init_rule();
31185
31305
  PRINTLN_REGEX = /\bprintln\s*\(/g;
31186
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)/;
31307
+ TEST_FILE_REGEX2 = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
31187
31308
  kotlinPrintlnAsLogRule = createRule({
31188
31309
  id: "kotlin/println-as-log",
31189
31310
  category: "logic",
@@ -31198,7 +31319,7 @@ var init_println_as_log = __esm({
31198
31319
  const source = facts.v2?._source;
31199
31320
  if (!source) return issues;
31200
31321
  if (!/\.kts?$/i.test(facts.filePath)) return issues;
31201
- if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) return issues;
31322
+ if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
31202
31323
  if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
31203
31324
  let m;
31204
31325
  PRINTLN_REGEX.lastIndex = 0;
@@ -31212,7 +31333,7 @@ var init_println_as_log = __esm({
31212
31333
  message: `println() as logger at line ${line}`,
31213
31334
  line,
31214
31335
  column: 1,
31215
- advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.29."
31336
+ advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.34.5 (refined to skip test files for higher precision)."
31216
31337
  });
31217
31338
  }
31218
31339
  return issues;
@@ -41057,14 +41178,14 @@ var init_implicitly_unwrapped_optional = __esm({
41057
41178
  });
41058
41179
 
41059
41180
  // src/rules/swift/print-debug.ts
41060
- var PRINT_REGEX, DEFAULT_THRESHOLD2, TEST_FILE_REGEX, swiftPrintDebugRule;
41181
+ var PRINT_REGEX, DEFAULT_THRESHOLD2, TEST_FILE_REGEX3, swiftPrintDebugRule;
41061
41182
  var init_print_debug = __esm({
41062
41183
  "src/rules/swift/print-debug.ts"() {
41063
41184
  "use strict";
41064
41185
  init_rule();
41065
41186
  PRINT_REGEX = /\bprint\s*\(/g;
41066
41187
  DEFAULT_THRESHOLD2 = 1;
41067
- TEST_FILE_REGEX = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
41188
+ TEST_FILE_REGEX3 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
41068
41189
  swiftPrintDebugRule = createRule({
41069
41190
  id: "swift/print-debug",
41070
41191
  category: "typo",
@@ -41079,7 +41200,7 @@ var init_print_debug = __esm({
41079
41200
  const source = facts.v2?._source;
41080
41201
  if (!source) return issues;
41081
41202
  if (!/\.swift$/i.test(facts.filePath)) return issues;
41082
- if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
41203
+ if (TEST_FILE_REGEX3.test(facts.filePath)) return issues;
41083
41204
  const matches = [];
41084
41205
  let m;
41085
41206
  PRINT_REGEX.lastIndex = 0;
@@ -51554,7 +51675,7 @@ var init_signal_strength = __esm({
51554
51675
  precision: 0.1268,
51555
51676
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51556
51677
  verdict: "OK",
51557
- _calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
51678
+ _calibrationNote: "v0.34.5: REFINED \u2014 rule now skips test files (JUnit4/5 + Android: *Tests.kt, *Test.kt, src/test/, test/ dirs). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): ratio=1.84 (\u22651.5, OK). The refinement mirrors v0.34.2's swift/print-debug fix: the previous exclusion only matched `\\/test\\/` and `.test.kts?$`, missing JUnit5's `FooTests.kt` naming. Expected post-refinement precision: 12.7% \u2192 25%+ by removing the JUnit assertion / debug-output fires that were ~50% of the FPs. Same direction as java/system-out-println (1.73 refined) and the v0.34.2 swift/print-debug pipeline. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
51558
51679
  aiSpecific: false,
51559
51680
  _v9Verdict: "OK",
51560
51681
  _v9Lift: 1.84,
@@ -51618,7 +51739,7 @@ var init_signal_strength = __esm({
51618
51739
  precision: 0.1091,
51619
51740
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51620
51741
  verdict: "DORMANT",
51621
- _calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
51742
+ _calibrationNote: "v0.34.6: REFINED \u2014 rule now requires Thread.sleep to be INSIDE the loop block (verified via brace-counting), not just in a file that happens to contain a for/while/do keyword. v0.30 baseline: 2154 total fires, ratio 0.97 (DORMANT). The refinement targets: (1) Thread.sleep in `main()` no longer fires if a different method has a `for` loop (the v0.30 heuristic fired on every Thread.sleep in the file); (2) Thread.sleep before/after a loop block (not inside it) no longer fires; (3) string literals containing `Thread.sleep(...)` are skipped via the string-state-machine. Expected post-refinement ratio: 1.5+ (positive-signal OK verdict) by removing the cross-method over-fires. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
51622
51743
  aiSpecific: false,
51623
51744
  _v9Verdict: "DORMANT",
51624
51745
  _v9Lift: 0.97,
@@ -51850,7 +51971,7 @@ var init_signal_strength = __esm({
51850
51971
  precision: 0.4407,
51851
51972
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51852
51973
  verdict: "OK",
51853
- _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).",
51974
+ _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).",
51854
51975
  aiSpecific: true,
51855
51976
  _v7Verdict: "DORMANT",
51856
51977
  _v7Lift: 1,
@@ -51873,7 +51994,7 @@ var init_signal_strength = __esm({
51873
51994
  precision: 0.2187,
51874
51995
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51875
51996
  verdict: "DORMANT",
51876
- _calibrationNote: "v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
51997
+ _calibrationNote: "v0.34.4: REFINED \u2014 expanded allowSet with common constants (-1 sentinel, 100 percent literal, 0.5/1.0/2.0 ratios, 4096/2048/512/128/64/32/16/8 power-of-2 sizes, 50 percentile). Added string-literal and `//` comment stripping so substrings like `\"got 42 errors\"` and `// ticket #4242` no longer fire. v0.33 baseline: 220 TP / 786 FP per-file (ratio 0.86, DORMANT). The refinement targets: (1) sentinel `-1` literals are skipped because `1` is allowlisted and the `-` doesn't match MAGIC_NUMBER_REGEX anyway; (2) hex literals (`0xFF`) are skipped because the digit-only regex requires a word-boundary after digits, and `0x...` has none; (3) literals inside string/comment contexts no longer fire. Expected post-refinement ratio: 1.0-1.2 (still DORMANT but better-targeted). Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
51877
51998
  aiSpecific: true,
51878
51999
  _v7Verdict: "DORMANT",
51879
52000
  _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.3";
22
+ VERSION = "0.34.7";
23
23
  }
24
24
  });
25
25
 
@@ -27716,6 +27716,7 @@ var init_magic_numbers = __esm({
27716
27716
  if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
27717
27717
  const lines = source.split("\n");
27718
27718
  const allowSet = /* @__PURE__ */ new Set([
27719
+ // v0.24 originals
27719
27720
  "1024",
27720
27721
  "65535",
27721
27722
  "65536",
@@ -27734,17 +27735,42 @@ var init_magic_numbers = __esm({
27734
27735
  "2",
27735
27736
  "3",
27736
27737
  "4",
27737
- "5"
27738
+ "5",
27739
+ // v0.34.4 additions
27740
+ "-1",
27741
+ // sentinel value for "not found" / "all bits set"
27742
+ "100",
27743
+ // percent literal, very common
27744
+ "0.0",
27745
+ "0.5",
27746
+ "1.0",
27747
+ "2.0",
27748
+ // common probability / ratio
27749
+ "4096",
27750
+ // page size, hash bucket count
27751
+ "2048",
27752
+ "512",
27753
+ // power-of-2 sizes
27754
+ "32",
27755
+ "64",
27756
+ "128",
27757
+ // bit widths, byte sizes
27758
+ "16",
27759
+ "8",
27760
+ // common small constants
27761
+ "50"
27762
+ // percentile literal
27738
27763
  ]);
27739
27764
  for (let i = 0; i < lines.length; i++) {
27740
27765
  const line = lines[i] ?? "";
27741
27766
  if (!COMPARE_OR_RETURN_REGEX.test(line)) continue;
27767
+ const codeLine = line.replace(/\/\/.*$/, "").replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
27742
27768
  let m;
27743
27769
  MAGIC_NUMBER_REGEX.lastIndex = 0;
27744
- while ((m = MAGIC_NUMBER_REGEX.exec(line)) !== null) {
27770
+ while ((m = MAGIC_NUMBER_REGEX.exec(codeLine)) !== null) {
27745
27771
  const literal = m[0] ?? "";
27746
27772
  if (allowSet.has(literal)) continue;
27747
- if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(line.slice(0, m.index).trimEnd())) continue;
27773
+ if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(codeLine.slice(0, m.index).trimEnd())) continue;
27748
27774
  const prevLine = i > 0 ? lines[i - 1] ?? "" : "";
27749
27775
  if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(prevLine.trim())) continue;
27750
27776
  issues.push({
@@ -27755,7 +27781,7 @@ var init_magic_numbers = __esm({
27755
27781
  message: `magic number ${literal} at line ${i + 1} \u2014 name it: constexpr int MAX = ${literal};`,
27756
27782
  line: i + 1,
27757
27783
  column: 1,
27758
- advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.24.'
27784
+ advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.34.4 (expanded allowSet + string/comment exclusion).'
27759
27785
  });
27760
27786
  }
27761
27787
  }
@@ -27766,7 +27792,7 @@ var init_magic_numbers = __esm({
27766
27792
  });
27767
27793
 
27768
27794
  // src/rules/cpp/printf-debug.ts
27769
- 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;
27770
27796
  var init_printf_debug = __esm({
27771
27797
  "src/rules/cpp/printf-debug.ts"() {
27772
27798
  "use strict";
@@ -27775,6 +27801,7 @@ var init_printf_debug = __esm({
27775
27801
  COUT_LITERAL_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*"[^"]*"/g;
27776
27802
  STD_COUT_BARE_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*'[^']*'/g;
27777
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)/;
27778
27805
  cppPrintfDebugRule = createRule({
27779
27806
  id: "cpp/printf-debug",
27780
27807
  category: "typo",
@@ -27789,6 +27816,7 @@ var init_printf_debug = __esm({
27789
27816
  const source = facts.v2?._source;
27790
27817
  if (!source) return issues;
27791
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;
27792
27820
  const printfCount = (source.match(PRINTF_FAMILY_REGEX) ?? []).length;
27793
27821
  let viaPrintf = false;
27794
27822
  if (printfCount > context.threshold) {
@@ -27805,7 +27833,7 @@ var init_printf_debug = __esm({
27805
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",
27806
27834
  line: 1,
27807
27835
  column: 1,
27808
- 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)."
27809
27837
  });
27810
27838
  return issues;
27811
27839
  }
@@ -30842,12 +30870,11 @@ var init_system_out_println = __esm({
30842
30870
  });
30843
30871
 
30844
30872
  // src/rules/java/thread-sleep-in-loop.ts
30845
- var THREAD_SLEEP_REGEX, javaThreadSleepInLoopRule;
30873
+ var javaThreadSleepInLoopRule;
30846
30874
  var init_thread_sleep_in_loop = __esm({
30847
30875
  "src/rules/java/thread-sleep-in-loop.ts"() {
30848
30876
  "use strict";
30849
30877
  init_rule();
30850
- THREAD_SLEEP_REGEX = /\bThread\.sleep\s*\(/g;
30851
30878
  javaThreadSleepInLoopRule = createRule({
30852
30879
  id: "java/thread-sleep-in-loop",
30853
30880
  category: "perf",
@@ -30863,11 +30890,104 @@ var init_thread_sleep_in_loop = __esm({
30863
30890
  if (!source) return issues;
30864
30891
  if (!/\.java$/i.test(facts.filePath)) return issues;
30865
30892
  if (!/\bThread\.sleep\s*\(/.test(source)) return issues;
30866
- if (!/\b(?:for|while|do)\b/.test(source)) return issues;
30867
- let m;
30868
- THREAD_SLEEP_REGEX.lastIndex = 0;
30869
- while ((m = THREAD_SLEEP_REGEX.exec(source)) !== null) {
30870
- const line = source.slice(0, m.index).split("\n").length;
30893
+ const braceStack = [];
30894
+ const loopSet = /* @__PURE__ */ new Set();
30895
+ let pendingLoopKeyword = null;
30896
+ let parenDepth = 0;
30897
+ const sleepEvents = [];
30898
+ let inString = false;
30899
+ let inLineComment = false;
30900
+ let inBlockComment = false;
30901
+ for (let i = 0; i < source.length; i++) {
30902
+ const c = source[i] ?? "";
30903
+ const next = source[i + 1] ?? "";
30904
+ const prev = source[i - 1] ?? "";
30905
+ if (inLineComment) {
30906
+ if (c === "\n") inLineComment = false;
30907
+ continue;
30908
+ }
30909
+ if (inBlockComment) {
30910
+ if (c === "*" && next === "/") {
30911
+ inBlockComment = false;
30912
+ i++;
30913
+ }
30914
+ continue;
30915
+ }
30916
+ if (inString) {
30917
+ if (c === "\\") {
30918
+ i++;
30919
+ continue;
30920
+ }
30921
+ if (c === inString) inString = false;
30922
+ continue;
30923
+ }
30924
+ if (c === "/" && next === "/") {
30925
+ inLineComment = true;
30926
+ i++;
30927
+ continue;
30928
+ }
30929
+ if (c === "/" && next === "*") {
30930
+ inBlockComment = true;
30931
+ i++;
30932
+ continue;
30933
+ }
30934
+ if (c === '"' || c === "'") {
30935
+ inString = c;
30936
+ continue;
30937
+ }
30938
+ if (c === "(") {
30939
+ parenDepth++;
30940
+ continue;
30941
+ }
30942
+ if (c === ")") {
30943
+ parenDepth = Math.max(0, parenDepth - 1);
30944
+ continue;
30945
+ }
30946
+ if (/[A-Za-z_]/.test(c) && !/[A-Za-z0-9_]/.test(prev)) {
30947
+ const next3 = source.slice(i, i + 3);
30948
+ const next5 = source.slice(i, i + 5);
30949
+ const next2 = source.slice(i, i + 2);
30950
+ const after3 = source[i + 3] ?? "";
30951
+ const after5 = source[i + 5] ?? "";
30952
+ const after2 = source[i + 2] ?? "";
30953
+ if (next3 === "for" && !/[A-Za-z0-9_]/.test(after3)) {
30954
+ pendingLoopKeyword = { kind: "for", idx: i };
30955
+ i += 2;
30956
+ continue;
30957
+ }
30958
+ if (next5 === "while" && !/[A-Za-z0-9_]/.test(after5)) {
30959
+ pendingLoopKeyword = { kind: "while", idx: i };
30960
+ i += 4;
30961
+ continue;
30962
+ }
30963
+ if (next2 === "do" && !/[A-Za-z0-9_]/.test(after2)) {
30964
+ pendingLoopKeyword = { kind: "do", idx: i };
30965
+ i += 1;
30966
+ continue;
30967
+ }
30968
+ }
30969
+ if (c === "{") {
30970
+ braceStack.push(i);
30971
+ if (pendingLoopKeyword && parenDepth === 0) {
30972
+ loopSet.add(i);
30973
+ pendingLoopKeyword = null;
30974
+ }
30975
+ } else if (c === "}") {
30976
+ const popped = braceStack.pop();
30977
+ if (popped !== void 0 && loopSet.has(popped)) {
30978
+ loopSet.delete(popped);
30979
+ }
30980
+ if (pendingLoopKeyword) {
30981
+ pendingLoopKeyword = null;
30982
+ }
30983
+ } else if (c === "T" && source.slice(i, i + 13) === "Thread.sleep(") {
30984
+ sleepEvents.push({ idx: i, loopDepth: loopSet.size });
30985
+ i += 12;
30986
+ }
30987
+ }
30988
+ for (const ev of sleepEvents) {
30989
+ if (ev.loopDepth === 0) continue;
30990
+ const line = source.slice(0, ev.idx).split("\n").length;
30871
30991
  issues.push({
30872
30992
  ruleId: "java/thread-sleep-in-loop",
30873
30993
  category: "perf",
@@ -30876,7 +30996,7 @@ var init_thread_sleep_in_loop = __esm({
30876
30996
  message: `Thread.sleep() at line ${line}`,
30877
30997
  line,
30878
30998
  column: 1,
30879
- advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.30.'
30999
+ advice: 'Use ScheduledExecutorService for periodic work, or BlockingQueue.take() for event-driven work. Thread.sleep in a loop is the classic "polling with sleep" anti-pattern \u2014 the thread blocks for the sleep duration each iteration. In server contexts this ties up Tomcat/Jetty/Netty threads. Reference: java/thread-sleep-in-loop v0.34.6 (refined to require Thread.sleep inside the loop block, not just in the file).'
30880
31000
  });
30881
31001
  }
30882
31002
  return issues;
@@ -31158,13 +31278,14 @@ var init_object_singleton_misuse = __esm({
31158
31278
  });
31159
31279
 
31160
31280
  // src/rules/kotlin/println-as-log.ts
31161
- var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX2, kotlinPrintlnAsLogRule;
31281
+ var PRINTLN_REGEX, REAL_LOGGING_IMPORT_REGEX2, TEST_FILE_REGEX2, kotlinPrintlnAsLogRule;
31162
31282
  var init_println_as_log = __esm({
31163
31283
  "src/rules/kotlin/println-as-log.ts"() {
31164
31284
  "use strict";
31165
31285
  init_rule();
31166
31286
  PRINTLN_REGEX = /\bprintln\s*\(/g;
31167
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)/;
31288
+ TEST_FILE_REGEX2 = /(?:\/src\/test\/|\/test\/|\/Tests\/|\/Test\.kt|\/Tests\.kt|Tests\.kt$|Test\.kt$)/;
31168
31289
  kotlinPrintlnAsLogRule = createRule({
31169
31290
  id: "kotlin/println-as-log",
31170
31291
  category: "logic",
@@ -31179,7 +31300,7 @@ var init_println_as_log = __esm({
31179
31300
  const source = facts.v2?._source;
31180
31301
  if (!source) return issues;
31181
31302
  if (!/\.kts?$/i.test(facts.filePath)) return issues;
31182
- if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) return issues;
31303
+ if (TEST_FILE_REGEX2.test(facts.filePath)) return issues;
31183
31304
  if (REAL_LOGGING_IMPORT_REGEX2.test(source)) return issues;
31184
31305
  let m;
31185
31306
  PRINTLN_REGEX.lastIndex = 0;
@@ -31193,7 +31314,7 @@ var init_println_as_log = __esm({
31193
31314
  message: `println() as logger at line ${line}`,
31194
31315
  line,
31195
31316
  column: 1,
31196
- advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.29."
31317
+ advice: "Use a real logging library: slf4j (JVM), android.util.Log (Android), Timber (Android), kermit (multiplatform), or kotlin-logging. println() has no log level, no timestamp, no correlation ID, and cannot be filtered. Reference: kotlin/println-as-log v0.34.5 (refined to skip test files for higher precision)."
31197
31318
  });
31198
31319
  }
31199
31320
  return issues;
@@ -41038,14 +41159,14 @@ var init_implicitly_unwrapped_optional = __esm({
41038
41159
  });
41039
41160
 
41040
41161
  // src/rules/swift/print-debug.ts
41041
- var PRINT_REGEX, DEFAULT_THRESHOLD2, TEST_FILE_REGEX, swiftPrintDebugRule;
41162
+ var PRINT_REGEX, DEFAULT_THRESHOLD2, TEST_FILE_REGEX3, swiftPrintDebugRule;
41042
41163
  var init_print_debug = __esm({
41043
41164
  "src/rules/swift/print-debug.ts"() {
41044
41165
  "use strict";
41045
41166
  init_rule();
41046
41167
  PRINT_REGEX = /\bprint\s*\(/g;
41047
41168
  DEFAULT_THRESHOLD2 = 1;
41048
- TEST_FILE_REGEX = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
41169
+ TEST_FILE_REGEX3 = /(?:\/Tests\/|\/Tests\.swift|\/Test\.swift|Tests\.swift$|Test\.swift$)/;
41049
41170
  swiftPrintDebugRule = createRule({
41050
41171
  id: "swift/print-debug",
41051
41172
  category: "typo",
@@ -41060,7 +41181,7 @@ var init_print_debug = __esm({
41060
41181
  const source = facts.v2?._source;
41061
41182
  if (!source) return issues;
41062
41183
  if (!/\.swift$/i.test(facts.filePath)) return issues;
41063
- if (TEST_FILE_REGEX.test(facts.filePath)) return issues;
41184
+ if (TEST_FILE_REGEX3.test(facts.filePath)) return issues;
41064
41185
  const matches = [];
41065
41186
  let m;
41066
41187
  PRINT_REGEX.lastIndex = 0;
@@ -51532,7 +51653,7 @@ var init_signal_strength = __esm({
51532
51653
  precision: 0.1268,
51533
51654
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51534
51655
  verdict: "OK",
51535
- _calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
51656
+ _calibrationNote: "v0.34.5: REFINED \u2014 rule now skips test files (JUnit4/5 + Android: *Tests.kt, *Test.kt, src/test/, test/ dirs). Per-file unique v9 Kotlin calibration (2698 neg, 213 pos): ratio=1.84 (\u22651.5, OK). The refinement mirrors v0.34.2's swift/print-debug fix: the previous exclusion only matched `\\/test\\/` and `.test.kts?$`, missing JUnit5's `FooTests.kt` naming. Expected post-refinement precision: 12.7% \u2192 25%+ by removing the JUnit assertion / debug-output fires that were ~50% of the FPs. Same direction as java/system-out-println (1.73 refined) and the v0.34.2 swift/print-debug pipeline. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.84 (\u22651.5) \u2014 first rule in this entire session with a positive direction! Fires 1.84x more on post-2024 AI/demos than pre-2022 production. Precision=12.7% (below 50% USEFUL threshold); verdict=OK. The signal is real: post-2024 Kotlin code (especially AI-generated examples) uses println for output; pre-2022 production code uses slf4j/kermit. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: still set to true (verdict is OK but precision is below 50% \u2014 the guardrail expects OK/USEFUL rules to be defaultOff:false only when calibrated with a meaningful pos arm).",
51536
51657
  aiSpecific: false,
51537
51658
  _v9Verdict: "OK",
51538
51659
  _v9Lift: 1.84,
@@ -51596,7 +51717,7 @@ var init_signal_strength = __esm({
51596
51717
  precision: 0.1091,
51597
51718
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51598
51719
  verdict: "DORMANT",
51599
- _calibrationNote: "v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
51720
+ _calibrationNote: "v0.34.6: REFINED \u2014 rule now requires Thread.sleep to be INSIDE the loop block (verified via brace-counting), not just in a file that happens to contain a for/while/do keyword. v0.30 baseline: 2154 total fires, ratio 0.97 (DORMANT). The refinement targets: (1) Thread.sleep in `main()` no longer fires if a different method has a `for` loop (the v0.30 heuristic fired on every Thread.sleep in the file); (2) Thread.sleep before/after a loop block (not inside it) no longer fires; (3) string literals containing `Thread.sleep(...)` are skipped via the string-state-machine. Expected post-refinement ratio: 1.5+ (positive-signal OK verdict) by removing the cross-method over-fires. Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.30: v9 Java calibration (81891 neg, 10305 pos). ratio=0.97 (just below 1.0) \u2014 fires equally on both arms. Borderline era-confound: pre-2022 Java had more Thread.sleep in loops (less mature concurrency APIs); modern Java uses ScheduledExecutorService. Same direction as kotlin/runblocking-misuse (0.50). 2154 total fires \u2014 high absolute count, meaningful measurement. defaultOff.",
51600
51721
  aiSpecific: false,
51601
51722
  _v9Verdict: "DORMANT",
51602
51723
  _v9Lift: 0.97,
@@ -51828,7 +51949,7 @@ var init_signal_strength = __esm({
51828
51949
  precision: 0.4407,
51829
51950
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51830
51951
  verdict: "OK",
51831
- _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).",
51952
+ _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).",
51832
51953
  aiSpecific: true,
51833
51954
  _v7Verdict: "DORMANT",
51834
51955
  _v7Lift: 1,
@@ -51851,7 +51972,7 @@ var init_signal_strength = __esm({
51851
51972
  precision: 0.2187,
51852
51973
  lastCalibratedAt: "2026-07-03T00:00:00Z",
51853
51974
  verdict: "DORMANT",
51854
- _calibrationNote: "v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
51975
+ _calibrationNote: "v0.34.4: REFINED \u2014 expanded allowSet with common constants (-1 sentinel, 100 percent literal, 0.5/1.0/2.0 ratios, 4096/2048/512/128/64/32/16/8 power-of-2 sizes, 50 percentile). Added string-literal and `//` comment stripping so substrings like `\"got 42 errors\"` and `// ticket #4242` no longer fire. v0.33 baseline: 220 TP / 786 FP per-file (ratio 0.86, DORMANT). The refinement targets: (1) sentinel `-1` literals are skipped because `1` is allowlisted and the `-` doesn't match MAGIC_NUMBER_REGEX anyway; (2) hex literals (`0xFF`) are skipped because the digit-only regex requires a word-boundary after digits, and `0x...` has none; (3) literals inside string/comment contexts no longer fire. Expected post-refinement ratio: 1.0-1.2 (still DORMANT but better-targeted). Full v9 re-calibration is part of the v0.35.0 re-measurement. v0.33: v9 C++ calibration (5107 neg, 1655 pos). Per-file unique: 220 TP, 786 FP, ratio=0.86. Both arms have similar proportions of files with magic numbers \u2014 this is a general C++ anti-pattern that hasn't been eradicated. INSUFFICIENT_DATA: pos arm 1655 files.",
51855
51976
  aiSpecific: true,
51856
51977
  _v7Verdict: "DORMANT",
51857
51978
  _v7Lift: 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slopbrick",
3
- "version": "0.34.3",
3
+ "version": "0.34.7",
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": {