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.
- package/dist/engine/worker.cjs +141 -20
- package/dist/engine/worker.js +141 -20
- package/dist/index.cjs +146 -25
- package/dist/index.js +146 -25
- package/package.json +1 -1
package/dist/engine/worker.cjs
CHANGED
|
@@ -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(
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
37209
|
-
|
|
37210
|
-
|
|
37211
|
-
|
|
37212
|
-
|
|
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.
|
|
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 (
|
|
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.
|
|
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
|
|
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 (
|
|
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.
|
|
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,
|
package/dist/engine/worker.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
37180
|
-
|
|
37181
|
-
|
|
37182
|
-
|
|
37183
|
-
|
|
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.
|
|
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 (
|
|
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.
|
|
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
|
|
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 (
|
|
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.
|
|
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.
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
30886
|
-
|
|
30887
|
-
|
|
30888
|
-
|
|
30889
|
-
|
|
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.
|
|
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 (
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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 (
|
|
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.
|
|
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.
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
30867
|
-
|
|
30868
|
-
|
|
30869
|
-
|
|
30870
|
-
|
|
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.
|
|
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 (
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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 (
|
|
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.
|
|
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
|
+
"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": {
|