slopbrick 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/worker.cjs +365 -40
- package/dist/engine/worker.js +365 -40
- package/dist/index.cjs +415 -44
- package/dist/index.js +415 -44
- package/package.json +1 -1
package/dist/engine/worker.js
CHANGED
|
@@ -4337,6 +4337,15 @@ function parseSource(source, filePath) {
|
|
|
4337
4337
|
case "go":
|
|
4338
4338
|
case "rs":
|
|
4339
4339
|
case "java":
|
|
4340
|
+
// v0.28.0: Kotlin files get the same parseBlankModule path as
|
|
4341
|
+
// Java (v0.24.5). All 5 `kotlin/*` rules are regex-based and
|
|
4342
|
+
// gate themselves on `/\.kts?$/i.test(filePath)` inside their
|
|
4343
|
+
// `analyze()`. The tree-sitter Kotlin integration is a larger
|
|
4344
|
+
// lift; the v0.27.0 methodology paper confirmed era-confounding
|
|
4345
|
+
// is the dominant signal anyway, so a regex-only Kotlin pass is
|
|
4346
|
+
// sufficient for the v9 calibration goal.
|
|
4347
|
+
case "kt":
|
|
4348
|
+
case "kts":
|
|
4340
4349
|
return parseBlankModule(source);
|
|
4341
4350
|
default:
|
|
4342
4351
|
return parseWithSwc(source, filePath);
|
|
@@ -36977,7 +36986,7 @@ var kotlinCoroutineGlobalScopeRule = createRule({
|
|
|
36977
36986
|
const issues = [];
|
|
36978
36987
|
const source = facts.v2?._source;
|
|
36979
36988
|
if (!source) return issues;
|
|
36980
|
-
if (!/\.
|
|
36989
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
36981
36990
|
let m;
|
|
36982
36991
|
GLOBAL_SCOPE_REGEX.lastIndex = 0;
|
|
36983
36992
|
while ((m = GLOBAL_SCOPE_REGEX.exec(source)) !== null) {
|
|
@@ -37014,7 +37023,7 @@ var kotlinDataClassDefaultsOveruseRule = createRule({
|
|
|
37014
37023
|
const issues = [];
|
|
37015
37024
|
const source = facts.v2?._source;
|
|
37016
37025
|
if (!source) return issues;
|
|
37017
|
-
if (!/\.
|
|
37026
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37018
37027
|
let m;
|
|
37019
37028
|
DATA_CLASS_HEAD_REGEX.lastIndex = 0;
|
|
37020
37029
|
while ((m = DATA_CLASS_HEAD_REGEX.exec(source)) !== null) {
|
|
@@ -37054,6 +37063,101 @@ var kotlinDataClassDefaultsOveruseRule = createRule({
|
|
|
37054
37063
|
}
|
|
37055
37064
|
});
|
|
37056
37065
|
|
|
37066
|
+
// src/rules/kotlin/force-unwrap.ts
|
|
37067
|
+
var FORCE_UNWRAP_REGEX = /!!(?=\s*[.\)}\};,\n\[])/g;
|
|
37068
|
+
var kotlinForceUnwrapRule = createRule({
|
|
37069
|
+
id: "kotlin/force-unwrap",
|
|
37070
|
+
category: "logic",
|
|
37071
|
+
severity: "medium",
|
|
37072
|
+
aiSpecific: false,
|
|
37073
|
+
description: "!! force-unwrap \u2014 use ?. (safe call) or a proper null check",
|
|
37074
|
+
create(_context) {
|
|
37075
|
+
return {};
|
|
37076
|
+
},
|
|
37077
|
+
analyze(_context, facts) {
|
|
37078
|
+
const issues = [];
|
|
37079
|
+
const source = facts.v2?._source;
|
|
37080
|
+
if (!source) return issues;
|
|
37081
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37082
|
+
let m;
|
|
37083
|
+
FORCE_UNWRAP_REGEX.lastIndex = 0;
|
|
37084
|
+
while ((m = FORCE_UNWRAP_REGEX.exec(source)) !== null) {
|
|
37085
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
37086
|
+
const lineStart = source.lastIndexOf("\n", m.index) + 1;
|
|
37087
|
+
const lineText = source.slice(lineStart, m.index);
|
|
37088
|
+
const quoteCount = (lineText.match(/"/g) || []).length;
|
|
37089
|
+
if (quoteCount % 2 === 1) continue;
|
|
37090
|
+
issues.push({
|
|
37091
|
+
ruleId: "kotlin/force-unwrap",
|
|
37092
|
+
category: "logic",
|
|
37093
|
+
severity: "medium",
|
|
37094
|
+
aiSpecific: false,
|
|
37095
|
+
message: `!! force-unwrap at line ${line}`,
|
|
37096
|
+
line,
|
|
37097
|
+
column: m.index - lineStart + 1,
|
|
37098
|
+
advice: "Use ?. (safe call) with ?: (Elvis) for a default, or a proper when/if check. !! throws NullPointerException at runtime \u2014 it bypasses Kotlin's type system. Reference: kotlin/force-unwrap v0.29."
|
|
37099
|
+
});
|
|
37100
|
+
}
|
|
37101
|
+
return issues;
|
|
37102
|
+
}
|
|
37103
|
+
});
|
|
37104
|
+
|
|
37105
|
+
// src/rules/kotlin/hardcoded-credential.ts
|
|
37106
|
+
var KEY_REGEX = /\b(api[_-]?key|apikey|secret|token|password|auth|access[_-]?key|client[_-]?secret|private[_-]?key)\b\s*[=:]/i;
|
|
37107
|
+
var VALUE_REGEX = /["']([A-Za-z0-9_\-+/=.@*!]{16,})["']/;
|
|
37108
|
+
var FALSE_POSITIVES = /* @__PURE__ */ new Set([
|
|
37109
|
+
"passwordless",
|
|
37110
|
+
"tokenize",
|
|
37111
|
+
"tokenizer",
|
|
37112
|
+
"authorizationrequired",
|
|
37113
|
+
"authenticated",
|
|
37114
|
+
"authenticatortoken",
|
|
37115
|
+
"authtoken",
|
|
37116
|
+
"placeholder",
|
|
37117
|
+
// common in test fixtures
|
|
37118
|
+
"changeme"
|
|
37119
|
+
// placeholder, but still suspicious — kept for review
|
|
37120
|
+
]);
|
|
37121
|
+
var kotlinHardcodedCredentialRule = createRule({
|
|
37122
|
+
id: "kotlin/hardcoded-credential",
|
|
37123
|
+
category: "security",
|
|
37124
|
+
severity: "high",
|
|
37125
|
+
aiSpecific: false,
|
|
37126
|
+
description: "Hardcoded credential literal \u2014 use env vars or a secrets manager",
|
|
37127
|
+
create(_context) {
|
|
37128
|
+
return {};
|
|
37129
|
+
},
|
|
37130
|
+
analyze(_context, facts) {
|
|
37131
|
+
const issues = [];
|
|
37132
|
+
const source = facts.v2?._source;
|
|
37133
|
+
if (!source) return issues;
|
|
37134
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37135
|
+
const lines = source.split("\n");
|
|
37136
|
+
for (let i = 0; i < lines.length; i++) {
|
|
37137
|
+
const line = lines[i];
|
|
37138
|
+
if (!KEY_REGEX.test(line)) continue;
|
|
37139
|
+
const valueMatch = VALUE_REGEX.exec(line);
|
|
37140
|
+
if (!valueMatch) continue;
|
|
37141
|
+
const value = valueMatch[1];
|
|
37142
|
+
if (FALSE_POSITIVES.has(value.toLowerCase())) continue;
|
|
37143
|
+
if (!/[a-zA-Z]/.test(value) || !/[0-9]/.test(value)) continue;
|
|
37144
|
+
if (value.startsWith("$")) continue;
|
|
37145
|
+
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) continue;
|
|
37146
|
+
issues.push({
|
|
37147
|
+
ruleId: "kotlin/hardcoded-credential",
|
|
37148
|
+
category: "security",
|
|
37149
|
+
severity: "high",
|
|
37150
|
+
aiSpecific: false,
|
|
37151
|
+
message: `Hardcoded credential at line ${i + 1}`,
|
|
37152
|
+
line: i + 1,
|
|
37153
|
+
column: 1,
|
|
37154
|
+
advice: "Move the credential to an environment variable, a .env file that is .gitignore'd, or a secrets manager (Vault, AWS Secrets Manager, GCP Secret Manager). Hardcoded credentials are the #1 source of secret leaks on GitHub. Reference: kotlin/hardcoded-credential v0.29 (OWASP A07:2021 \u2014 Identification and Authentication Failures)."
|
|
37155
|
+
});
|
|
37156
|
+
}
|
|
37157
|
+
return issues;
|
|
37158
|
+
}
|
|
37159
|
+
});
|
|
37160
|
+
|
|
37057
37161
|
// src/rules/kotlin/object-singleton-misuse.ts
|
|
37058
37162
|
var OBJECT_DECL_REGEX = /\bobject\s+(?!\w*Companion\b)(\w+)\s*\{/g;
|
|
37059
37163
|
var kotlinObjectSingletonMisuseRule = createRule({
|
|
@@ -37069,7 +37173,7 @@ var kotlinObjectSingletonMisuseRule = createRule({
|
|
|
37069
37173
|
const issues = [];
|
|
37070
37174
|
const source = facts.v2?._source;
|
|
37071
37175
|
if (!source) return issues;
|
|
37072
|
-
if (!/\.
|
|
37176
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37073
37177
|
let m;
|
|
37074
37178
|
OBJECT_DECL_REGEX.lastIndex = 0;
|
|
37075
37179
|
while ((m = OBJECT_DECL_REGEX.exec(source)) !== null) {
|
|
@@ -37104,8 +37208,46 @@ var kotlinObjectSingletonMisuseRule = createRule({
|
|
|
37104
37208
|
}
|
|
37105
37209
|
});
|
|
37106
37210
|
|
|
37211
|
+
// src/rules/kotlin/println-as-log.ts
|
|
37212
|
+
var PRINTLN_REGEX = /\bprintln\s*\(/g;
|
|
37213
|
+
var REAL_LOGGING_IMPORT_REGEX = /\bimport\s+(?:android\.util\.Log|org\.slf4j\.|io\.github\.oshai\.kotlinlogging|kotlin\.logging|co\.touchlab\.kermit|com\.github\.ajalt\.timber|org\.apache\.logging\.log4j)/;
|
|
37214
|
+
var kotlinPrintlnAsLogRule = createRule({
|
|
37215
|
+
id: "kotlin/println-as-log",
|
|
37216
|
+
category: "logic",
|
|
37217
|
+
severity: "low",
|
|
37218
|
+
aiSpecific: false,
|
|
37219
|
+
description: "println() used for logging \u2014 use slf4j, kermit, or android.util.Log",
|
|
37220
|
+
create(_context) {
|
|
37221
|
+
return {};
|
|
37222
|
+
},
|
|
37223
|
+
analyze(_context, facts) {
|
|
37224
|
+
const issues = [];
|
|
37225
|
+
const source = facts.v2?._source;
|
|
37226
|
+
if (!source) return issues;
|
|
37227
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37228
|
+
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) return issues;
|
|
37229
|
+
if (REAL_LOGGING_IMPORT_REGEX.test(source)) return issues;
|
|
37230
|
+
let m;
|
|
37231
|
+
PRINTLN_REGEX.lastIndex = 0;
|
|
37232
|
+
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
37233
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
37234
|
+
issues.push({
|
|
37235
|
+
ruleId: "kotlin/println-as-log",
|
|
37236
|
+
category: "logic",
|
|
37237
|
+
severity: "low",
|
|
37238
|
+
aiSpecific: false,
|
|
37239
|
+
message: `println() as logger at line ${line}`,
|
|
37240
|
+
line,
|
|
37241
|
+
column: 1,
|
|
37242
|
+
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."
|
|
37243
|
+
});
|
|
37244
|
+
}
|
|
37245
|
+
return issues;
|
|
37246
|
+
}
|
|
37247
|
+
});
|
|
37248
|
+
|
|
37107
37249
|
// src/rules/kotlin/println-debug.ts
|
|
37108
|
-
var
|
|
37250
|
+
var PRINTLN_REGEX2 = /^\s*println\s*\(/gm;
|
|
37109
37251
|
var DEFAULT_THRESHOLD = 1;
|
|
37110
37252
|
var kotlinPrintlnDebugRule = createRule({
|
|
37111
37253
|
id: "kotlin/println-debug",
|
|
@@ -37120,11 +37262,11 @@ var kotlinPrintlnDebugRule = createRule({
|
|
|
37120
37262
|
const issues = [];
|
|
37121
37263
|
const source = facts.v2?._source;
|
|
37122
37264
|
if (!source) return issues;
|
|
37123
|
-
if (!/\.
|
|
37265
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37124
37266
|
const matches = [];
|
|
37125
37267
|
let m;
|
|
37126
|
-
|
|
37127
|
-
while ((m =
|
|
37268
|
+
PRINTLN_REGEX2.lastIndex = 0;
|
|
37269
|
+
while ((m = PRINTLN_REGEX2.exec(source)) !== null) {
|
|
37128
37270
|
matches.push(m.index);
|
|
37129
37271
|
}
|
|
37130
37272
|
if (matches.length <= context.threshold) return issues;
|
|
@@ -37147,6 +37289,81 @@ var kotlinPrintlnDebugRule = createRule({
|
|
|
37147
37289
|
}
|
|
37148
37290
|
});
|
|
37149
37291
|
|
|
37292
|
+
// src/rules/kotlin/runblocking-misuse.ts
|
|
37293
|
+
var RUN_BLOCKING_REGEX = /\brunBlocking\s*[({]/g;
|
|
37294
|
+
var kotlinRunBlockingMisuseRule = createRule({
|
|
37295
|
+
id: "kotlin/runblocking-misuse",
|
|
37296
|
+
category: "perf",
|
|
37297
|
+
severity: "medium",
|
|
37298
|
+
aiSpecific: false,
|
|
37299
|
+
description: "runBlocking { ... } \u2014 blocks the calling thread; use coroutineScope {} or call suspend fun directly",
|
|
37300
|
+
create(_context) {
|
|
37301
|
+
return {};
|
|
37302
|
+
},
|
|
37303
|
+
analyze(_context, facts) {
|
|
37304
|
+
const issues = [];
|
|
37305
|
+
const source = facts.v2?._source;
|
|
37306
|
+
if (!source) return issues;
|
|
37307
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37308
|
+
if (/\bfun\s+main\s*\(/.test(source)) return issues;
|
|
37309
|
+
let m;
|
|
37310
|
+
RUN_BLOCKING_REGEX.lastIndex = 0;
|
|
37311
|
+
while ((m = RUN_BLOCKING_REGEX.exec(source)) !== null) {
|
|
37312
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
37313
|
+
issues.push({
|
|
37314
|
+
ruleId: "kotlin/runblocking-misuse",
|
|
37315
|
+
category: "perf",
|
|
37316
|
+
severity: "medium",
|
|
37317
|
+
aiSpecific: false,
|
|
37318
|
+
message: `runBlocking { ... } at line ${line}`,
|
|
37319
|
+
line,
|
|
37320
|
+
column: 1,
|
|
37321
|
+
advice: 'runBlocking blocks the calling thread, defeating the purpose of coroutines. Use coroutineScope { } for structured concurrency, or call the suspend function directly. The Kotlin coroutines docs: runBlocking "should rarely (if ever) be used outside of main()". Reference: kotlin/runblocking-misuse v0.29.'
|
|
37322
|
+
});
|
|
37323
|
+
}
|
|
37324
|
+
return issues;
|
|
37325
|
+
}
|
|
37326
|
+
});
|
|
37327
|
+
|
|
37328
|
+
// src/rules/kotlin/sql-string-concat.ts
|
|
37329
|
+
var SQL_KEYWORD_REGEX = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
|
|
37330
|
+
var UNSAFE_REGEX = /(?:\+|\$\{)/;
|
|
37331
|
+
var SAFE_REGEX = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\\?\\?|\?\\s*,)/;
|
|
37332
|
+
var kotlinSqlStringConcatRule = createRule({
|
|
37333
|
+
id: "kotlin/sql-string-concat",
|
|
37334
|
+
category: "security",
|
|
37335
|
+
severity: "high",
|
|
37336
|
+
aiSpecific: false,
|
|
37337
|
+
description: "SQL query built via string concat / template \u2014 use PreparedStatement or setParameter()",
|
|
37338
|
+
create(_context) {
|
|
37339
|
+
return {};
|
|
37340
|
+
},
|
|
37341
|
+
analyze(_context, facts) {
|
|
37342
|
+
const issues = [];
|
|
37343
|
+
const source = facts.v2?._source;
|
|
37344
|
+
if (!source) return issues;
|
|
37345
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37346
|
+
const lines = source.split("\n");
|
|
37347
|
+
for (let i = 0; i < lines.length; i++) {
|
|
37348
|
+
const line = lines[i];
|
|
37349
|
+
if (!SQL_KEYWORD_REGEX.test(line)) continue;
|
|
37350
|
+
if (!UNSAFE_REGEX.test(line)) continue;
|
|
37351
|
+
if (SAFE_REGEX.test(line)) continue;
|
|
37352
|
+
issues.push({
|
|
37353
|
+
ruleId: "kotlin/sql-string-concat",
|
|
37354
|
+
category: "security",
|
|
37355
|
+
severity: "high",
|
|
37356
|
+
aiSpecific: false,
|
|
37357
|
+
message: `SQL query built via string concat/template at line ${i + 1}`,
|
|
37358
|
+
line: i + 1,
|
|
37359
|
+
column: 1,
|
|
37360
|
+
advice: 'Use a PreparedStatement (JDBC), setParameter() (Exposed), or an ORM (Room, jOOQ). String concatenation or template interpolation into a SQL query is the canonical SQL-injection pattern \u2014 even "trusted" inputs (signed JWT, internal config) can be influenced by an attacker. Reference: kotlin/sql-string-concat v0.29 (OWASP A03:2021).'
|
|
37361
|
+
});
|
|
37362
|
+
}
|
|
37363
|
+
return issues;
|
|
37364
|
+
}
|
|
37365
|
+
});
|
|
37366
|
+
|
|
37150
37367
|
// src/rules/kotlin/string-concat-loop.ts
|
|
37151
37368
|
var STRING_CONCAT_REGEX = /\b(\w+)\s*=\s*\1\s*\+\s*[^;}]+[;}\n]/g;
|
|
37152
37369
|
var kotlinStringConcatLoopRule = createRule({
|
|
@@ -37162,7 +37379,7 @@ var kotlinStringConcatLoopRule = createRule({
|
|
|
37162
37379
|
const issues = [];
|
|
37163
37380
|
const source = facts.v2?._source;
|
|
37164
37381
|
if (!source) return issues;
|
|
37165
|
-
if (!/\.
|
|
37382
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37166
37383
|
if (!/\b(?:for|while|repeat|forEach)\b/.test(source)) return issues;
|
|
37167
37384
|
let m;
|
|
37168
37385
|
STRING_CONCAT_REGEX.lastIndex = 0;
|
|
@@ -42709,8 +42926,13 @@ var builtinRules = [
|
|
|
42709
42926
|
goStructTagInconsistencyRule,
|
|
42710
42927
|
kotlinCoroutineGlobalScopeRule,
|
|
42711
42928
|
kotlinDataClassDefaultsOveruseRule,
|
|
42929
|
+
kotlinForceUnwrapRule,
|
|
42930
|
+
kotlinHardcodedCredentialRule,
|
|
42712
42931
|
kotlinObjectSingletonMisuseRule,
|
|
42932
|
+
kotlinPrintlnAsLogRule,
|
|
42713
42933
|
kotlinPrintlnDebugRule,
|
|
42934
|
+
kotlinRunBlockingMisuseRule,
|
|
42935
|
+
kotlinSqlStringConcatRule,
|
|
42714
42936
|
kotlinStringConcatLoopRule,
|
|
42715
42937
|
gapMonopolyRule,
|
|
42716
42938
|
mathElementUniformityRule,
|
|
@@ -44815,13 +45037,13 @@ var signal_strength_default = {
|
|
|
44815
45037
|
defaultOff: true
|
|
44816
45038
|
},
|
|
44817
45039
|
"kotlin/data-class-defaults-overuse": {
|
|
44818
|
-
recall:
|
|
44819
|
-
fpRate:
|
|
44820
|
-
ratio:
|
|
44821
|
-
precision: 0,
|
|
44822
|
-
lastCalibratedAt: "2026-07-
|
|
45040
|
+
recall: 469e-5,
|
|
45041
|
+
fpRate: 222e-5,
|
|
45042
|
+
ratio: 2.11,
|
|
45043
|
+
precision: 0.1429,
|
|
45044
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
44823
45045
|
verdict: "DORMANT",
|
|
44824
|
-
_calibrationNote: "v0.
|
|
45046
|
+
_calibrationNote: "v0.28: v9 Kotlin calibration (2698 neg, 213 pos). ratio=2.11 (\u22651.5) but precision=14.3% (<50%) \u2014 verdict DORMANT. Era-confounding: pre-2022 Kotlin is sparse; the rule fires on the rare .kt file with multiple default values regardless of authorship.",
|
|
44825
45047
|
aiSpecific: true,
|
|
44826
45048
|
_v7Verdict: "DORMANT",
|
|
44827
45049
|
_v7Lift: 1,
|
|
@@ -44830,16 +45052,21 @@ var signal_strength_default = {
|
|
|
44830
45052
|
_v7Precision: 0,
|
|
44831
45053
|
_v8Verdict: "DORMANT",
|
|
44832
45054
|
_v8Lift: 1,
|
|
45055
|
+
_v9Verdict: "DORMANT",
|
|
45056
|
+
_v9Lift: 2.11,
|
|
45057
|
+
_v9Recall: 469e-5,
|
|
45058
|
+
_v9FpRate: 222e-5,
|
|
45059
|
+
_v9Precision: 0.1429,
|
|
44833
45060
|
defaultOff: true
|
|
44834
45061
|
},
|
|
44835
45062
|
"kotlin/coroutine-global-scope": {
|
|
44836
|
-
recall:
|
|
44837
|
-
fpRate: 0,
|
|
44838
|
-
ratio:
|
|
44839
|
-
precision:
|
|
44840
|
-
lastCalibratedAt: "2026-07-
|
|
45063
|
+
recall: 469e-5,
|
|
45064
|
+
fpRate: 0.05004,
|
|
45065
|
+
ratio: 0.09,
|
|
45066
|
+
precision: 735e-5,
|
|
45067
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
44841
45068
|
verdict: "DORMANT",
|
|
44842
|
-
_calibrationNote: "v0.
|
|
45069
|
+
_calibrationNote: "v0.28: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.09 (FAR <1.0) \u2014 fires 135x more on neg than pos. Classic era-confound: pre-2022 Kotlin coroutines had GlobalScope as the default; modern Kotlin uses viewModelScope / lifecycleScope. The rule detects era, not AI authorship.",
|
|
44843
45070
|
aiSpecific: true,
|
|
44844
45071
|
_v7Verdict: "DORMANT",
|
|
44845
45072
|
_v7Lift: 1,
|
|
@@ -44848,16 +45075,21 @@ var signal_strength_default = {
|
|
|
44848
45075
|
_v7Precision: 0,
|
|
44849
45076
|
_v8Verdict: "DORMANT",
|
|
44850
45077
|
_v8Lift: 1,
|
|
45078
|
+
_v9Verdict: "DORMANT",
|
|
45079
|
+
_v9Lift: 0.09,
|
|
45080
|
+
_v9Recall: 469e-5,
|
|
45081
|
+
_v9FpRate: 0.05004,
|
|
45082
|
+
_v9Precision: 735e-5,
|
|
44851
45083
|
defaultOff: true
|
|
44852
45084
|
},
|
|
44853
45085
|
"kotlin/println-debug": {
|
|
44854
|
-
recall: 0,
|
|
44855
|
-
fpRate: 0,
|
|
44856
|
-
ratio:
|
|
44857
|
-
precision: 0,
|
|
44858
|
-
lastCalibratedAt: "2026-07-
|
|
45086
|
+
recall: 0.07042,
|
|
45087
|
+
fpRate: 0.11379,
|
|
45088
|
+
ratio: 0.62,
|
|
45089
|
+
precision: 0.04659,
|
|
45090
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
44859
45091
|
verdict: "DORMANT",
|
|
44860
|
-
_calibrationNote: "v0.
|
|
45092
|
+
_calibrationNote: "v0.28: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.62 (\u226A1.0) \u2014 fires 20x more on neg than pos. Classic era-confound: pre-2022 Kotlin code uses println() for debug; modern Kotlin uses Timber / android.util.Log / kermit.",
|
|
44861
45093
|
aiSpecific: true,
|
|
44862
45094
|
_v7Verdict: "DORMANT",
|
|
44863
45095
|
_v7Lift: 1,
|
|
@@ -44866,16 +45098,21 @@ var signal_strength_default = {
|
|
|
44866
45098
|
_v7Precision: 0,
|
|
44867
45099
|
_v8Verdict: "DORMANT",
|
|
44868
45100
|
_v8Lift: 1,
|
|
45101
|
+
_v9Verdict: "DORMANT",
|
|
45102
|
+
_v9Lift: 0.62,
|
|
45103
|
+
_v9Recall: 0.07042,
|
|
45104
|
+
_v9FpRate: 0.11379,
|
|
45105
|
+
_v9Precision: 0.04659,
|
|
44869
45106
|
defaultOff: true
|
|
44870
45107
|
},
|
|
44871
45108
|
"kotlin/object-singleton-misuse": {
|
|
44872
|
-
recall:
|
|
44873
|
-
fpRate:
|
|
44874
|
-
ratio: 1,
|
|
44875
|
-
precision: 0,
|
|
44876
|
-
lastCalibratedAt: "2026-07-
|
|
45109
|
+
recall: 469e-5,
|
|
45110
|
+
fpRate: 371e-5,
|
|
45111
|
+
ratio: 1.27,
|
|
45112
|
+
precision: 0.09091,
|
|
45113
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
44877
45114
|
verdict: "DORMANT",
|
|
44878
|
-
_calibrationNote: "v0.
|
|
45115
|
+
_calibrationNote: "v0.28: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.27 (\u22651.0) but <1.5; precision=9.1% \u2014 verdict DORMANT. INSUFFICIENT_DATA: pos arm only 213 files (under 10k minimum).",
|
|
44879
45116
|
aiSpecific: true,
|
|
44880
45117
|
_v7Verdict: "DORMANT",
|
|
44881
45118
|
_v7Lift: 1,
|
|
@@ -44884,16 +45121,21 @@ var signal_strength_default = {
|
|
|
44884
45121
|
_v7Precision: 0,
|
|
44885
45122
|
_v8Verdict: "DORMANT",
|
|
44886
45123
|
_v8Lift: 1,
|
|
45124
|
+
_v9Verdict: "DORMANT",
|
|
45125
|
+
_v9Lift: 1.27,
|
|
45126
|
+
_v9Recall: 469e-5,
|
|
45127
|
+
_v9FpRate: 371e-5,
|
|
45128
|
+
_v9Precision: 0.09091,
|
|
44887
45129
|
defaultOff: true
|
|
44888
45130
|
},
|
|
44889
45131
|
"kotlin/string-concat-loop": {
|
|
44890
|
-
recall:
|
|
44891
|
-
fpRate:
|
|
44892
|
-
ratio: 1,
|
|
44893
|
-
precision: 0,
|
|
44894
|
-
lastCalibratedAt: "2026-07-
|
|
45132
|
+
recall: 939e-5,
|
|
45133
|
+
fpRate: 519e-5,
|
|
45134
|
+
ratio: 1.81,
|
|
45135
|
+
precision: 0.125,
|
|
45136
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
44895
45137
|
verdict: "DORMANT",
|
|
44896
|
-
_calibrationNote: "v0.
|
|
45138
|
+
_calibrationNote: "v0.28: v9 Kotlin calibration (2698 neg, 213 pos). ratio=1.81 (\u22651.5) but precision=12.5% (<50%) \u2014 verdict DORMANT. INSUFFICIENT_DATA: pos arm only 213 files.",
|
|
44897
45139
|
aiSpecific: true,
|
|
44898
45140
|
_v7Verdict: "DORMANT",
|
|
44899
45141
|
_v7Lift: 1,
|
|
@@ -44902,6 +45144,91 @@ var signal_strength_default = {
|
|
|
44902
45144
|
_v7Precision: 0,
|
|
44903
45145
|
_v8Verdict: "DORMANT",
|
|
44904
45146
|
_v8Lift: 1,
|
|
45147
|
+
_v9Verdict: "DORMANT",
|
|
45148
|
+
_v9Lift: 1.81,
|
|
45149
|
+
_v9Recall: 939e-5,
|
|
45150
|
+
_v9FpRate: 519e-5,
|
|
45151
|
+
_v9Precision: 0.125,
|
|
45152
|
+
defaultOff: true
|
|
45153
|
+
},
|
|
45154
|
+
"kotlin/sql-string-concat": {
|
|
45155
|
+
recall: 469e-5,
|
|
45156
|
+
fpRate: 63e-4,
|
|
45157
|
+
ratio: 0.75,
|
|
45158
|
+
precision: 0.0556,
|
|
45159
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45160
|
+
verdict: "DORMANT",
|
|
45161
|
+
_calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.75 (DORMANT). 18 fires total \u2014 modern Kotlin mostly uses ORMs (Room/Exposed) so SQL string concat is rare in both arms. INSUFFICIENT_DATA: pos arm only 213 files. defaultOff: rule is loadable but invisible by default until v0.30 re-calibration with a larger pos arm.",
|
|
45162
|
+
aiSpecific: false,
|
|
45163
|
+
_v9Verdict: "DORMANT",
|
|
45164
|
+
_v9Lift: 0.75,
|
|
45165
|
+
_v9Recall: 469e-5,
|
|
45166
|
+
_v9FpRate: 63e-4,
|
|
45167
|
+
_v9Precision: 0.0556,
|
|
45168
|
+
defaultOff: true
|
|
45169
|
+
},
|
|
45170
|
+
"kotlin/hardcoded-credential": {
|
|
45171
|
+
recall: 0,
|
|
45172
|
+
fpRate: 0,
|
|
45173
|
+
ratio: 0,
|
|
45174
|
+
precision: 0,
|
|
45175
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45176
|
+
verdict: "DORMANT",
|
|
45177
|
+
_calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). 0 fires. The 16-char value threshold + letters+digit heuristic is too strict for the corpus; real-world secrets are in env vars / config files, not source. INSUFFICIENT_DATA: needs different corpus (CI configs, .env samples). defaultOff: loadable but invisible by default.",
|
|
45178
|
+
aiSpecific: false,
|
|
45179
|
+
_v9Verdict: "DORMANT",
|
|
45180
|
+
_v9Lift: 0,
|
|
45181
|
+
_v9Recall: 0,
|
|
45182
|
+
_v9FpRate: 0,
|
|
45183
|
+
_v9Precision: 0,
|
|
45184
|
+
defaultOff: true
|
|
45185
|
+
},
|
|
45186
|
+
"kotlin/runblocking-misuse": {
|
|
45187
|
+
recall: 0.10798,
|
|
45188
|
+
fpRate: 0.21534,
|
|
45189
|
+
ratio: 0.5,
|
|
45190
|
+
precision: 0.0381,
|
|
45191
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45192
|
+
verdict: "DORMANT",
|
|
45193
|
+
_calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.50 \u2014 fires 2x more on pre-2022 (neg) than post-2024 (pos). Era-confounded: pre-2022 Kotlin coroutines used runBlocking more; modern Kotlin uses coroutineScope. Same direction as kotlin/coroutine-global-scope (0.09). defaultOff: loadable but invisible by default until v0.30 re-calibration.",
|
|
45194
|
+
aiSpecific: false,
|
|
45195
|
+
_v9Verdict: "DORMANT",
|
|
45196
|
+
_v9Lift: 0.5,
|
|
45197
|
+
_v9Recall: 0.10798,
|
|
45198
|
+
_v9FpRate: 0.21534,
|
|
45199
|
+
_v9Precision: 0.0381,
|
|
45200
|
+
defaultOff: true
|
|
45201
|
+
},
|
|
45202
|
+
"kotlin/println-as-log": {
|
|
45203
|
+
recall: 0.08451,
|
|
45204
|
+
fpRate: 0.04596,
|
|
45205
|
+
ratio: 1.84,
|
|
45206
|
+
precision: 0.1268,
|
|
45207
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45208
|
+
verdict: "OK",
|
|
45209
|
+
_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).",
|
|
45210
|
+
aiSpecific: false,
|
|
45211
|
+
_v9Verdict: "OK",
|
|
45212
|
+
_v9Lift: 1.84,
|
|
45213
|
+
_v9Recall: 0.08451,
|
|
45214
|
+
_v9FpRate: 0.04596,
|
|
45215
|
+
_v9Precision: 0.1268,
|
|
45216
|
+
defaultOff: true
|
|
45217
|
+
},
|
|
45218
|
+
"kotlin/force-unwrap": {
|
|
45219
|
+
recall: 0.11737,
|
|
45220
|
+
fpRate: 0.33284,
|
|
45221
|
+
ratio: 0.35,
|
|
45222
|
+
precision: 0.0271,
|
|
45223
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45224
|
+
verdict: "DORMANT",
|
|
45225
|
+
_calibrationNote: "v0.29: v9 Kotlin calibration (2698 neg, 213 pos). ratio=0.35 \u2014 fires 3x more on pre-2022 (neg) than post-2024 (pos). Era-confounded: pre-2022 Kotlin code uses !! freely; modern Kotlin relies on ?./?: and Result<T> wrappers. Stronger era signal than runblocking-misuse. defaultOff: loadable but invisible by default.",
|
|
45226
|
+
aiSpecific: false,
|
|
45227
|
+
_v9Verdict: "DORMANT",
|
|
45228
|
+
_v9Lift: 0.35,
|
|
45229
|
+
_v9Recall: 0.11737,
|
|
45230
|
+
_v9FpRate: 0.33284,
|
|
45231
|
+
_v9Precision: 0.0271,
|
|
44905
45232
|
defaultOff: true
|
|
44906
45233
|
},
|
|
44907
45234
|
"swift/force-unwrap": {
|
|
@@ -45134,8 +45461,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
|
|
|
45134
45461
|
const ext = extname4(filePath).toLowerCase();
|
|
45135
45462
|
const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
|
|
45136
45463
|
".swift",
|
|
45137
|
-
".kt",
|
|
45138
|
-
".kts",
|
|
45139
45464
|
".dart",
|
|
45140
45465
|
".cpp",
|
|
45141
45466
|
".cc",
|