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.cjs
CHANGED
|
@@ -4366,6 +4366,15 @@ function parseSource(source, filePath) {
|
|
|
4366
4366
|
case "go":
|
|
4367
4367
|
case "rs":
|
|
4368
4368
|
case "java":
|
|
4369
|
+
// v0.28.0: Kotlin files get the same parseBlankModule path as
|
|
4370
|
+
// Java (v0.24.5). All 5 `kotlin/*` rules are regex-based and
|
|
4371
|
+
// gate themselves on `/\.kts?$/i.test(filePath)` inside their
|
|
4372
|
+
// `analyze()`. The tree-sitter Kotlin integration is a larger
|
|
4373
|
+
// lift; the v0.27.0 methodology paper confirmed era-confounding
|
|
4374
|
+
// is the dominant signal anyway, so a regex-only Kotlin pass is
|
|
4375
|
+
// sufficient for the v9 calibration goal.
|
|
4376
|
+
case "kt":
|
|
4377
|
+
case "kts":
|
|
4369
4378
|
return parseBlankModule(source);
|
|
4370
4379
|
default:
|
|
4371
4380
|
return parseWithSwc(source, filePath);
|
|
@@ -37006,7 +37015,7 @@ var kotlinCoroutineGlobalScopeRule = createRule({
|
|
|
37006
37015
|
const issues = [];
|
|
37007
37016
|
const source = facts.v2?._source;
|
|
37008
37017
|
if (!source) return issues;
|
|
37009
|
-
if (!/\.
|
|
37018
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37010
37019
|
let m;
|
|
37011
37020
|
GLOBAL_SCOPE_REGEX.lastIndex = 0;
|
|
37012
37021
|
while ((m = GLOBAL_SCOPE_REGEX.exec(source)) !== null) {
|
|
@@ -37043,7 +37052,7 @@ var kotlinDataClassDefaultsOveruseRule = createRule({
|
|
|
37043
37052
|
const issues = [];
|
|
37044
37053
|
const source = facts.v2?._source;
|
|
37045
37054
|
if (!source) return issues;
|
|
37046
|
-
if (!/\.
|
|
37055
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37047
37056
|
let m;
|
|
37048
37057
|
DATA_CLASS_HEAD_REGEX.lastIndex = 0;
|
|
37049
37058
|
while ((m = DATA_CLASS_HEAD_REGEX.exec(source)) !== null) {
|
|
@@ -37083,6 +37092,101 @@ var kotlinDataClassDefaultsOveruseRule = createRule({
|
|
|
37083
37092
|
}
|
|
37084
37093
|
});
|
|
37085
37094
|
|
|
37095
|
+
// src/rules/kotlin/force-unwrap.ts
|
|
37096
|
+
var FORCE_UNWRAP_REGEX = /!!(?=\s*[.\)}\};,\n\[])/g;
|
|
37097
|
+
var kotlinForceUnwrapRule = createRule({
|
|
37098
|
+
id: "kotlin/force-unwrap",
|
|
37099
|
+
category: "logic",
|
|
37100
|
+
severity: "medium",
|
|
37101
|
+
aiSpecific: false,
|
|
37102
|
+
description: "!! force-unwrap \u2014 use ?. (safe call) or a proper null check",
|
|
37103
|
+
create(_context) {
|
|
37104
|
+
return {};
|
|
37105
|
+
},
|
|
37106
|
+
analyze(_context, facts) {
|
|
37107
|
+
const issues = [];
|
|
37108
|
+
const source = facts.v2?._source;
|
|
37109
|
+
if (!source) return issues;
|
|
37110
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37111
|
+
let m;
|
|
37112
|
+
FORCE_UNWRAP_REGEX.lastIndex = 0;
|
|
37113
|
+
while ((m = FORCE_UNWRAP_REGEX.exec(source)) !== null) {
|
|
37114
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
37115
|
+
const lineStart = source.lastIndexOf("\n", m.index) + 1;
|
|
37116
|
+
const lineText = source.slice(lineStart, m.index);
|
|
37117
|
+
const quoteCount = (lineText.match(/"/g) || []).length;
|
|
37118
|
+
if (quoteCount % 2 === 1) continue;
|
|
37119
|
+
issues.push({
|
|
37120
|
+
ruleId: "kotlin/force-unwrap",
|
|
37121
|
+
category: "logic",
|
|
37122
|
+
severity: "medium",
|
|
37123
|
+
aiSpecific: false,
|
|
37124
|
+
message: `!! force-unwrap at line ${line}`,
|
|
37125
|
+
line,
|
|
37126
|
+
column: m.index - lineStart + 1,
|
|
37127
|
+
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."
|
|
37128
|
+
});
|
|
37129
|
+
}
|
|
37130
|
+
return issues;
|
|
37131
|
+
}
|
|
37132
|
+
});
|
|
37133
|
+
|
|
37134
|
+
// src/rules/kotlin/hardcoded-credential.ts
|
|
37135
|
+
var KEY_REGEX = /\b(api[_-]?key|apikey|secret|token|password|auth|access[_-]?key|client[_-]?secret|private[_-]?key)\b\s*[=:]/i;
|
|
37136
|
+
var VALUE_REGEX = /["']([A-Za-z0-9_\-+/=.@*!]{16,})["']/;
|
|
37137
|
+
var FALSE_POSITIVES = /* @__PURE__ */ new Set([
|
|
37138
|
+
"passwordless",
|
|
37139
|
+
"tokenize",
|
|
37140
|
+
"tokenizer",
|
|
37141
|
+
"authorizationrequired",
|
|
37142
|
+
"authenticated",
|
|
37143
|
+
"authenticatortoken",
|
|
37144
|
+
"authtoken",
|
|
37145
|
+
"placeholder",
|
|
37146
|
+
// common in test fixtures
|
|
37147
|
+
"changeme"
|
|
37148
|
+
// placeholder, but still suspicious — kept for review
|
|
37149
|
+
]);
|
|
37150
|
+
var kotlinHardcodedCredentialRule = createRule({
|
|
37151
|
+
id: "kotlin/hardcoded-credential",
|
|
37152
|
+
category: "security",
|
|
37153
|
+
severity: "high",
|
|
37154
|
+
aiSpecific: false,
|
|
37155
|
+
description: "Hardcoded credential literal \u2014 use env vars or a secrets manager",
|
|
37156
|
+
create(_context) {
|
|
37157
|
+
return {};
|
|
37158
|
+
},
|
|
37159
|
+
analyze(_context, facts) {
|
|
37160
|
+
const issues = [];
|
|
37161
|
+
const source = facts.v2?._source;
|
|
37162
|
+
if (!source) return issues;
|
|
37163
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37164
|
+
const lines = source.split("\n");
|
|
37165
|
+
for (let i = 0; i < lines.length; i++) {
|
|
37166
|
+
const line = lines[i];
|
|
37167
|
+
if (!KEY_REGEX.test(line)) continue;
|
|
37168
|
+
const valueMatch = VALUE_REGEX.exec(line);
|
|
37169
|
+
if (!valueMatch) continue;
|
|
37170
|
+
const value = valueMatch[1];
|
|
37171
|
+
if (FALSE_POSITIVES.has(value.toLowerCase())) continue;
|
|
37172
|
+
if (!/[a-zA-Z]/.test(value) || !/[0-9]/.test(value)) continue;
|
|
37173
|
+
if (value.startsWith("$")) continue;
|
|
37174
|
+
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) continue;
|
|
37175
|
+
issues.push({
|
|
37176
|
+
ruleId: "kotlin/hardcoded-credential",
|
|
37177
|
+
category: "security",
|
|
37178
|
+
severity: "high",
|
|
37179
|
+
aiSpecific: false,
|
|
37180
|
+
message: `Hardcoded credential at line ${i + 1}`,
|
|
37181
|
+
line: i + 1,
|
|
37182
|
+
column: 1,
|
|
37183
|
+
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)."
|
|
37184
|
+
});
|
|
37185
|
+
}
|
|
37186
|
+
return issues;
|
|
37187
|
+
}
|
|
37188
|
+
});
|
|
37189
|
+
|
|
37086
37190
|
// src/rules/kotlin/object-singleton-misuse.ts
|
|
37087
37191
|
var OBJECT_DECL_REGEX = /\bobject\s+(?!\w*Companion\b)(\w+)\s*\{/g;
|
|
37088
37192
|
var kotlinObjectSingletonMisuseRule = createRule({
|
|
@@ -37098,7 +37202,7 @@ var kotlinObjectSingletonMisuseRule = createRule({
|
|
|
37098
37202
|
const issues = [];
|
|
37099
37203
|
const source = facts.v2?._source;
|
|
37100
37204
|
if (!source) return issues;
|
|
37101
|
-
if (!/\.
|
|
37205
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37102
37206
|
let m;
|
|
37103
37207
|
OBJECT_DECL_REGEX.lastIndex = 0;
|
|
37104
37208
|
while ((m = OBJECT_DECL_REGEX.exec(source)) !== null) {
|
|
@@ -37133,8 +37237,46 @@ var kotlinObjectSingletonMisuseRule = createRule({
|
|
|
37133
37237
|
}
|
|
37134
37238
|
});
|
|
37135
37239
|
|
|
37240
|
+
// src/rules/kotlin/println-as-log.ts
|
|
37241
|
+
var PRINTLN_REGEX = /\bprintln\s*\(/g;
|
|
37242
|
+
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)/;
|
|
37243
|
+
var kotlinPrintlnAsLogRule = createRule({
|
|
37244
|
+
id: "kotlin/println-as-log",
|
|
37245
|
+
category: "logic",
|
|
37246
|
+
severity: "low",
|
|
37247
|
+
aiSpecific: false,
|
|
37248
|
+
description: "println() used for logging \u2014 use slf4j, kermit, or android.util.Log",
|
|
37249
|
+
create(_context) {
|
|
37250
|
+
return {};
|
|
37251
|
+
},
|
|
37252
|
+
analyze(_context, facts) {
|
|
37253
|
+
const issues = [];
|
|
37254
|
+
const source = facts.v2?._source;
|
|
37255
|
+
if (!source) return issues;
|
|
37256
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37257
|
+
if (/\/test\//i.test(facts.filePath) || /\.test\.kts?$/i.test(facts.filePath)) return issues;
|
|
37258
|
+
if (REAL_LOGGING_IMPORT_REGEX.test(source)) return issues;
|
|
37259
|
+
let m;
|
|
37260
|
+
PRINTLN_REGEX.lastIndex = 0;
|
|
37261
|
+
while ((m = PRINTLN_REGEX.exec(source)) !== null) {
|
|
37262
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
37263
|
+
issues.push({
|
|
37264
|
+
ruleId: "kotlin/println-as-log",
|
|
37265
|
+
category: "logic",
|
|
37266
|
+
severity: "low",
|
|
37267
|
+
aiSpecific: false,
|
|
37268
|
+
message: `println() as logger at line ${line}`,
|
|
37269
|
+
line,
|
|
37270
|
+
column: 1,
|
|
37271
|
+
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."
|
|
37272
|
+
});
|
|
37273
|
+
}
|
|
37274
|
+
return issues;
|
|
37275
|
+
}
|
|
37276
|
+
});
|
|
37277
|
+
|
|
37136
37278
|
// src/rules/kotlin/println-debug.ts
|
|
37137
|
-
var
|
|
37279
|
+
var PRINTLN_REGEX2 = /^\s*println\s*\(/gm;
|
|
37138
37280
|
var DEFAULT_THRESHOLD = 1;
|
|
37139
37281
|
var kotlinPrintlnDebugRule = createRule({
|
|
37140
37282
|
id: "kotlin/println-debug",
|
|
@@ -37149,11 +37291,11 @@ var kotlinPrintlnDebugRule = createRule({
|
|
|
37149
37291
|
const issues = [];
|
|
37150
37292
|
const source = facts.v2?._source;
|
|
37151
37293
|
if (!source) return issues;
|
|
37152
|
-
if (!/\.
|
|
37294
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37153
37295
|
const matches = [];
|
|
37154
37296
|
let m;
|
|
37155
|
-
|
|
37156
|
-
while ((m =
|
|
37297
|
+
PRINTLN_REGEX2.lastIndex = 0;
|
|
37298
|
+
while ((m = PRINTLN_REGEX2.exec(source)) !== null) {
|
|
37157
37299
|
matches.push(m.index);
|
|
37158
37300
|
}
|
|
37159
37301
|
if (matches.length <= context.threshold) return issues;
|
|
@@ -37176,6 +37318,81 @@ var kotlinPrintlnDebugRule = createRule({
|
|
|
37176
37318
|
}
|
|
37177
37319
|
});
|
|
37178
37320
|
|
|
37321
|
+
// src/rules/kotlin/runblocking-misuse.ts
|
|
37322
|
+
var RUN_BLOCKING_REGEX = /\brunBlocking\s*[({]/g;
|
|
37323
|
+
var kotlinRunBlockingMisuseRule = createRule({
|
|
37324
|
+
id: "kotlin/runblocking-misuse",
|
|
37325
|
+
category: "perf",
|
|
37326
|
+
severity: "medium",
|
|
37327
|
+
aiSpecific: false,
|
|
37328
|
+
description: "runBlocking { ... } \u2014 blocks the calling thread; use coroutineScope {} or call suspend fun directly",
|
|
37329
|
+
create(_context) {
|
|
37330
|
+
return {};
|
|
37331
|
+
},
|
|
37332
|
+
analyze(_context, facts) {
|
|
37333
|
+
const issues = [];
|
|
37334
|
+
const source = facts.v2?._source;
|
|
37335
|
+
if (!source) return issues;
|
|
37336
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37337
|
+
if (/\bfun\s+main\s*\(/.test(source)) return issues;
|
|
37338
|
+
let m;
|
|
37339
|
+
RUN_BLOCKING_REGEX.lastIndex = 0;
|
|
37340
|
+
while ((m = RUN_BLOCKING_REGEX.exec(source)) !== null) {
|
|
37341
|
+
const line = source.slice(0, m.index).split("\n").length;
|
|
37342
|
+
issues.push({
|
|
37343
|
+
ruleId: "kotlin/runblocking-misuse",
|
|
37344
|
+
category: "perf",
|
|
37345
|
+
severity: "medium",
|
|
37346
|
+
aiSpecific: false,
|
|
37347
|
+
message: `runBlocking { ... } at line ${line}`,
|
|
37348
|
+
line,
|
|
37349
|
+
column: 1,
|
|
37350
|
+
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.'
|
|
37351
|
+
});
|
|
37352
|
+
}
|
|
37353
|
+
return issues;
|
|
37354
|
+
}
|
|
37355
|
+
});
|
|
37356
|
+
|
|
37357
|
+
// src/rules/kotlin/sql-string-concat.ts
|
|
37358
|
+
var SQL_KEYWORD_REGEX = /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|CREATE\s+TABLE|DROP\s+TABLE|ALTER\s+TABLE)\b/i;
|
|
37359
|
+
var UNSAFE_REGEX = /(?:\+|\$\{)/;
|
|
37360
|
+
var SAFE_REGEX = /(?:PreparedStatement|setParameter|setString|setInt|setLong|bind|:name|:\\?\\?|\?\\s*,)/;
|
|
37361
|
+
var kotlinSqlStringConcatRule = createRule({
|
|
37362
|
+
id: "kotlin/sql-string-concat",
|
|
37363
|
+
category: "security",
|
|
37364
|
+
severity: "high",
|
|
37365
|
+
aiSpecific: false,
|
|
37366
|
+
description: "SQL query built via string concat / template \u2014 use PreparedStatement or setParameter()",
|
|
37367
|
+
create(_context) {
|
|
37368
|
+
return {};
|
|
37369
|
+
},
|
|
37370
|
+
analyze(_context, facts) {
|
|
37371
|
+
const issues = [];
|
|
37372
|
+
const source = facts.v2?._source;
|
|
37373
|
+
if (!source) return issues;
|
|
37374
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37375
|
+
const lines = source.split("\n");
|
|
37376
|
+
for (let i = 0; i < lines.length; i++) {
|
|
37377
|
+
const line = lines[i];
|
|
37378
|
+
if (!SQL_KEYWORD_REGEX.test(line)) continue;
|
|
37379
|
+
if (!UNSAFE_REGEX.test(line)) continue;
|
|
37380
|
+
if (SAFE_REGEX.test(line)) continue;
|
|
37381
|
+
issues.push({
|
|
37382
|
+
ruleId: "kotlin/sql-string-concat",
|
|
37383
|
+
category: "security",
|
|
37384
|
+
severity: "high",
|
|
37385
|
+
aiSpecific: false,
|
|
37386
|
+
message: `SQL query built via string concat/template at line ${i + 1}`,
|
|
37387
|
+
line: i + 1,
|
|
37388
|
+
column: 1,
|
|
37389
|
+
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).'
|
|
37390
|
+
});
|
|
37391
|
+
}
|
|
37392
|
+
return issues;
|
|
37393
|
+
}
|
|
37394
|
+
});
|
|
37395
|
+
|
|
37179
37396
|
// src/rules/kotlin/string-concat-loop.ts
|
|
37180
37397
|
var STRING_CONCAT_REGEX = /\b(\w+)\s*=\s*\1\s*\+\s*[^;}]+[;}\n]/g;
|
|
37181
37398
|
var kotlinStringConcatLoopRule = createRule({
|
|
@@ -37191,7 +37408,7 @@ var kotlinStringConcatLoopRule = createRule({
|
|
|
37191
37408
|
const issues = [];
|
|
37192
37409
|
const source = facts.v2?._source;
|
|
37193
37410
|
if (!source) return issues;
|
|
37194
|
-
if (!/\.
|
|
37411
|
+
if (!/\.kts?$/i.test(facts.filePath)) return issues;
|
|
37195
37412
|
if (!/\b(?:for|while|repeat|forEach)\b/.test(source)) return issues;
|
|
37196
37413
|
let m;
|
|
37197
37414
|
STRING_CONCAT_REGEX.lastIndex = 0;
|
|
@@ -42738,8 +42955,13 @@ var builtinRules = [
|
|
|
42738
42955
|
goStructTagInconsistencyRule,
|
|
42739
42956
|
kotlinCoroutineGlobalScopeRule,
|
|
42740
42957
|
kotlinDataClassDefaultsOveruseRule,
|
|
42958
|
+
kotlinForceUnwrapRule,
|
|
42959
|
+
kotlinHardcodedCredentialRule,
|
|
42741
42960
|
kotlinObjectSingletonMisuseRule,
|
|
42961
|
+
kotlinPrintlnAsLogRule,
|
|
42742
42962
|
kotlinPrintlnDebugRule,
|
|
42963
|
+
kotlinRunBlockingMisuseRule,
|
|
42964
|
+
kotlinSqlStringConcatRule,
|
|
42743
42965
|
kotlinStringConcatLoopRule,
|
|
42744
42966
|
gapMonopolyRule,
|
|
42745
42967
|
mathElementUniformityRule,
|
|
@@ -44844,13 +45066,13 @@ var signal_strength_default = {
|
|
|
44844
45066
|
defaultOff: true
|
|
44845
45067
|
},
|
|
44846
45068
|
"kotlin/data-class-defaults-overuse": {
|
|
44847
|
-
recall:
|
|
44848
|
-
fpRate:
|
|
44849
|
-
ratio:
|
|
44850
|
-
precision: 0,
|
|
44851
|
-
lastCalibratedAt: "2026-07-
|
|
45069
|
+
recall: 469e-5,
|
|
45070
|
+
fpRate: 222e-5,
|
|
45071
|
+
ratio: 2.11,
|
|
45072
|
+
precision: 0.1429,
|
|
45073
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
44852
45074
|
verdict: "DORMANT",
|
|
44853
|
-
_calibrationNote: "v0.
|
|
45075
|
+
_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.",
|
|
44854
45076
|
aiSpecific: true,
|
|
44855
45077
|
_v7Verdict: "DORMANT",
|
|
44856
45078
|
_v7Lift: 1,
|
|
@@ -44859,16 +45081,21 @@ var signal_strength_default = {
|
|
|
44859
45081
|
_v7Precision: 0,
|
|
44860
45082
|
_v8Verdict: "DORMANT",
|
|
44861
45083
|
_v8Lift: 1,
|
|
45084
|
+
_v9Verdict: "DORMANT",
|
|
45085
|
+
_v9Lift: 2.11,
|
|
45086
|
+
_v9Recall: 469e-5,
|
|
45087
|
+
_v9FpRate: 222e-5,
|
|
45088
|
+
_v9Precision: 0.1429,
|
|
44862
45089
|
defaultOff: true
|
|
44863
45090
|
},
|
|
44864
45091
|
"kotlin/coroutine-global-scope": {
|
|
44865
|
-
recall:
|
|
44866
|
-
fpRate: 0,
|
|
44867
|
-
ratio:
|
|
44868
|
-
precision:
|
|
44869
|
-
lastCalibratedAt: "2026-07-
|
|
45092
|
+
recall: 469e-5,
|
|
45093
|
+
fpRate: 0.05004,
|
|
45094
|
+
ratio: 0.09,
|
|
45095
|
+
precision: 735e-5,
|
|
45096
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
44870
45097
|
verdict: "DORMANT",
|
|
44871
|
-
_calibrationNote: "v0.
|
|
45098
|
+
_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.",
|
|
44872
45099
|
aiSpecific: true,
|
|
44873
45100
|
_v7Verdict: "DORMANT",
|
|
44874
45101
|
_v7Lift: 1,
|
|
@@ -44877,16 +45104,21 @@ var signal_strength_default = {
|
|
|
44877
45104
|
_v7Precision: 0,
|
|
44878
45105
|
_v8Verdict: "DORMANT",
|
|
44879
45106
|
_v8Lift: 1,
|
|
45107
|
+
_v9Verdict: "DORMANT",
|
|
45108
|
+
_v9Lift: 0.09,
|
|
45109
|
+
_v9Recall: 469e-5,
|
|
45110
|
+
_v9FpRate: 0.05004,
|
|
45111
|
+
_v9Precision: 735e-5,
|
|
44880
45112
|
defaultOff: true
|
|
44881
45113
|
},
|
|
44882
45114
|
"kotlin/println-debug": {
|
|
44883
|
-
recall: 0,
|
|
44884
|
-
fpRate: 0,
|
|
44885
|
-
ratio:
|
|
44886
|
-
precision: 0,
|
|
44887
|
-
lastCalibratedAt: "2026-07-
|
|
45115
|
+
recall: 0.07042,
|
|
45116
|
+
fpRate: 0.11379,
|
|
45117
|
+
ratio: 0.62,
|
|
45118
|
+
precision: 0.04659,
|
|
45119
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
44888
45120
|
verdict: "DORMANT",
|
|
44889
|
-
_calibrationNote: "v0.
|
|
45121
|
+
_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.",
|
|
44890
45122
|
aiSpecific: true,
|
|
44891
45123
|
_v7Verdict: "DORMANT",
|
|
44892
45124
|
_v7Lift: 1,
|
|
@@ -44895,16 +45127,21 @@ var signal_strength_default = {
|
|
|
44895
45127
|
_v7Precision: 0,
|
|
44896
45128
|
_v8Verdict: "DORMANT",
|
|
44897
45129
|
_v8Lift: 1,
|
|
45130
|
+
_v9Verdict: "DORMANT",
|
|
45131
|
+
_v9Lift: 0.62,
|
|
45132
|
+
_v9Recall: 0.07042,
|
|
45133
|
+
_v9FpRate: 0.11379,
|
|
45134
|
+
_v9Precision: 0.04659,
|
|
44898
45135
|
defaultOff: true
|
|
44899
45136
|
},
|
|
44900
45137
|
"kotlin/object-singleton-misuse": {
|
|
44901
|
-
recall:
|
|
44902
|
-
fpRate:
|
|
44903
|
-
ratio: 1,
|
|
44904
|
-
precision: 0,
|
|
44905
|
-
lastCalibratedAt: "2026-07-
|
|
45138
|
+
recall: 469e-5,
|
|
45139
|
+
fpRate: 371e-5,
|
|
45140
|
+
ratio: 1.27,
|
|
45141
|
+
precision: 0.09091,
|
|
45142
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
44906
45143
|
verdict: "DORMANT",
|
|
44907
|
-
_calibrationNote: "v0.
|
|
45144
|
+
_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).",
|
|
44908
45145
|
aiSpecific: true,
|
|
44909
45146
|
_v7Verdict: "DORMANT",
|
|
44910
45147
|
_v7Lift: 1,
|
|
@@ -44913,16 +45150,21 @@ var signal_strength_default = {
|
|
|
44913
45150
|
_v7Precision: 0,
|
|
44914
45151
|
_v8Verdict: "DORMANT",
|
|
44915
45152
|
_v8Lift: 1,
|
|
45153
|
+
_v9Verdict: "DORMANT",
|
|
45154
|
+
_v9Lift: 1.27,
|
|
45155
|
+
_v9Recall: 469e-5,
|
|
45156
|
+
_v9FpRate: 371e-5,
|
|
45157
|
+
_v9Precision: 0.09091,
|
|
44916
45158
|
defaultOff: true
|
|
44917
45159
|
},
|
|
44918
45160
|
"kotlin/string-concat-loop": {
|
|
44919
|
-
recall:
|
|
44920
|
-
fpRate:
|
|
44921
|
-
ratio: 1,
|
|
44922
|
-
precision: 0,
|
|
44923
|
-
lastCalibratedAt: "2026-07-
|
|
45161
|
+
recall: 939e-5,
|
|
45162
|
+
fpRate: 519e-5,
|
|
45163
|
+
ratio: 1.81,
|
|
45164
|
+
precision: 0.125,
|
|
45165
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
44924
45166
|
verdict: "DORMANT",
|
|
44925
|
-
_calibrationNote: "v0.
|
|
45167
|
+
_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.",
|
|
44926
45168
|
aiSpecific: true,
|
|
44927
45169
|
_v7Verdict: "DORMANT",
|
|
44928
45170
|
_v7Lift: 1,
|
|
@@ -44931,6 +45173,91 @@ var signal_strength_default = {
|
|
|
44931
45173
|
_v7Precision: 0,
|
|
44932
45174
|
_v8Verdict: "DORMANT",
|
|
44933
45175
|
_v8Lift: 1,
|
|
45176
|
+
_v9Verdict: "DORMANT",
|
|
45177
|
+
_v9Lift: 1.81,
|
|
45178
|
+
_v9Recall: 939e-5,
|
|
45179
|
+
_v9FpRate: 519e-5,
|
|
45180
|
+
_v9Precision: 0.125,
|
|
45181
|
+
defaultOff: true
|
|
45182
|
+
},
|
|
45183
|
+
"kotlin/sql-string-concat": {
|
|
45184
|
+
recall: 469e-5,
|
|
45185
|
+
fpRate: 63e-4,
|
|
45186
|
+
ratio: 0.75,
|
|
45187
|
+
precision: 0.0556,
|
|
45188
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45189
|
+
verdict: "DORMANT",
|
|
45190
|
+
_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.",
|
|
45191
|
+
aiSpecific: false,
|
|
45192
|
+
_v9Verdict: "DORMANT",
|
|
45193
|
+
_v9Lift: 0.75,
|
|
45194
|
+
_v9Recall: 469e-5,
|
|
45195
|
+
_v9FpRate: 63e-4,
|
|
45196
|
+
_v9Precision: 0.0556,
|
|
45197
|
+
defaultOff: true
|
|
45198
|
+
},
|
|
45199
|
+
"kotlin/hardcoded-credential": {
|
|
45200
|
+
recall: 0,
|
|
45201
|
+
fpRate: 0,
|
|
45202
|
+
ratio: 0,
|
|
45203
|
+
precision: 0,
|
|
45204
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45205
|
+
verdict: "DORMANT",
|
|
45206
|
+
_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.",
|
|
45207
|
+
aiSpecific: false,
|
|
45208
|
+
_v9Verdict: "DORMANT",
|
|
45209
|
+
_v9Lift: 0,
|
|
45210
|
+
_v9Recall: 0,
|
|
45211
|
+
_v9FpRate: 0,
|
|
45212
|
+
_v9Precision: 0,
|
|
45213
|
+
defaultOff: true
|
|
45214
|
+
},
|
|
45215
|
+
"kotlin/runblocking-misuse": {
|
|
45216
|
+
recall: 0.10798,
|
|
45217
|
+
fpRate: 0.21534,
|
|
45218
|
+
ratio: 0.5,
|
|
45219
|
+
precision: 0.0381,
|
|
45220
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45221
|
+
verdict: "DORMANT",
|
|
45222
|
+
_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.",
|
|
45223
|
+
aiSpecific: false,
|
|
45224
|
+
_v9Verdict: "DORMANT",
|
|
45225
|
+
_v9Lift: 0.5,
|
|
45226
|
+
_v9Recall: 0.10798,
|
|
45227
|
+
_v9FpRate: 0.21534,
|
|
45228
|
+
_v9Precision: 0.0381,
|
|
45229
|
+
defaultOff: true
|
|
45230
|
+
},
|
|
45231
|
+
"kotlin/println-as-log": {
|
|
45232
|
+
recall: 0.08451,
|
|
45233
|
+
fpRate: 0.04596,
|
|
45234
|
+
ratio: 1.84,
|
|
45235
|
+
precision: 0.1268,
|
|
45236
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45237
|
+
verdict: "OK",
|
|
45238
|
+
_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).",
|
|
45239
|
+
aiSpecific: false,
|
|
45240
|
+
_v9Verdict: "OK",
|
|
45241
|
+
_v9Lift: 1.84,
|
|
45242
|
+
_v9Recall: 0.08451,
|
|
45243
|
+
_v9FpRate: 0.04596,
|
|
45244
|
+
_v9Precision: 0.1268,
|
|
45245
|
+
defaultOff: true
|
|
45246
|
+
},
|
|
45247
|
+
"kotlin/force-unwrap": {
|
|
45248
|
+
recall: 0.11737,
|
|
45249
|
+
fpRate: 0.33284,
|
|
45250
|
+
ratio: 0.35,
|
|
45251
|
+
precision: 0.0271,
|
|
45252
|
+
lastCalibratedAt: "2026-07-03T00:00:00Z",
|
|
45253
|
+
verdict: "DORMANT",
|
|
45254
|
+
_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.",
|
|
45255
|
+
aiSpecific: false,
|
|
45256
|
+
_v9Verdict: "DORMANT",
|
|
45257
|
+
_v9Lift: 0.35,
|
|
45258
|
+
_v9Recall: 0.11737,
|
|
45259
|
+
_v9FpRate: 0.33284,
|
|
45260
|
+
_v9Precision: 0.0271,
|
|
44934
45261
|
defaultOff: true
|
|
44935
45262
|
},
|
|
44936
45263
|
"swift/force-unwrap": {
|
|
@@ -45163,8 +45490,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
|
|
|
45163
45490
|
const ext = (0, import_node_path9.extname)(filePath).toLowerCase();
|
|
45164
45491
|
const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
|
|
45165
45492
|
".swift",
|
|
45166
|
-
".kt",
|
|
45167
|
-
".kts",
|
|
45168
45493
|
".dart",
|
|
45169
45494
|
".cpp",
|
|
45170
45495
|
".cc",
|