slopbrick 0.27.0 → 0.28.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.
@@ -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 (!/\.kt$/i.test(facts.filePath)) return issues;
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 (!/\.kt$/i.test(facts.filePath)) return issues;
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) {
@@ -37098,7 +37107,7 @@ var kotlinObjectSingletonMisuseRule = createRule({
37098
37107
  const issues = [];
37099
37108
  const source = facts.v2?._source;
37100
37109
  if (!source) return issues;
37101
- if (!/\.kt$/i.test(facts.filePath)) return issues;
37110
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
37102
37111
  let m;
37103
37112
  OBJECT_DECL_REGEX.lastIndex = 0;
37104
37113
  while ((m = OBJECT_DECL_REGEX.exec(source)) !== null) {
@@ -37149,7 +37158,7 @@ var kotlinPrintlnDebugRule = createRule({
37149
37158
  const issues = [];
37150
37159
  const source = facts.v2?._source;
37151
37160
  if (!source) return issues;
37152
- if (!/\.kt$/i.test(facts.filePath)) return issues;
37161
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
37153
37162
  const matches = [];
37154
37163
  let m;
37155
37164
  PRINTLN_REGEX.lastIndex = 0;
@@ -37191,7 +37200,7 @@ var kotlinStringConcatLoopRule = createRule({
37191
37200
  const issues = [];
37192
37201
  const source = facts.v2?._source;
37193
37202
  if (!source) return issues;
37194
- if (!/\.kt$/i.test(facts.filePath)) return issues;
37203
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
37195
37204
  if (!/\b(?:for|while|repeat|forEach)\b/.test(source)) return issues;
37196
37205
  let m;
37197
37206
  STRING_CONCAT_REGEX.lastIndex = 0;
@@ -44844,13 +44853,13 @@ var signal_strength_default = {
44844
44853
  defaultOff: true
44845
44854
  },
44846
44855
  "kotlin/data-class-defaults-overuse": {
44847
- recall: 0,
44848
- fpRate: 0,
44849
- ratio: 1,
44850
- precision: 0,
44851
- lastCalibratedAt: "2026-07-02T00:00:00Z",
44856
+ recall: 469e-5,
44857
+ fpRate: 222e-5,
44858
+ ratio: 2.11,
44859
+ precision: 0.1429,
44860
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
44852
44861
  verdict: "DORMANT",
44853
- _calibrationNote: "v0.24: new rule (data class with 3+ literal defaults). DORMANT until v9 Kotlin corpus calibration.",
44862
+ _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
44863
  aiSpecific: true,
44855
44864
  _v7Verdict: "DORMANT",
44856
44865
  _v7Lift: 1,
@@ -44859,16 +44868,21 @@ var signal_strength_default = {
44859
44868
  _v7Precision: 0,
44860
44869
  _v8Verdict: "DORMANT",
44861
44870
  _v8Lift: 1,
44871
+ _v9Verdict: "DORMANT",
44872
+ _v9Lift: 2.11,
44873
+ _v9Recall: 469e-5,
44874
+ _v9FpRate: 222e-5,
44875
+ _v9Precision: 0.1429,
44862
44876
  defaultOff: true
44863
44877
  },
44864
44878
  "kotlin/coroutine-global-scope": {
44865
- recall: 0,
44866
- fpRate: 0,
44867
- ratio: 1,
44868
- precision: 0,
44869
- lastCalibratedAt: "2026-07-02T00:00:00Z",
44879
+ recall: 469e-5,
44880
+ fpRate: 0.05004,
44881
+ ratio: 0.09,
44882
+ precision: 735e-5,
44883
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
44870
44884
  verdict: "DORMANT",
44871
- _calibrationNote: "v0.24: new rule (GlobalScope.launch/async/runBlocking \u2014 bypasses structured concurrency). DORMANT until v9 Kotlin corpus calibration.",
44885
+ _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
44886
  aiSpecific: true,
44873
44887
  _v7Verdict: "DORMANT",
44874
44888
  _v7Lift: 1,
@@ -44877,16 +44891,21 @@ var signal_strength_default = {
44877
44891
  _v7Precision: 0,
44878
44892
  _v8Verdict: "DORMANT",
44879
44893
  _v8Lift: 1,
44894
+ _v9Verdict: "DORMANT",
44895
+ _v9Lift: 0.09,
44896
+ _v9Recall: 469e-5,
44897
+ _v9FpRate: 0.05004,
44898
+ _v9Precision: 735e-5,
44880
44899
  defaultOff: true
44881
44900
  },
44882
44901
  "kotlin/println-debug": {
44883
- recall: 0,
44884
- fpRate: 0,
44885
- ratio: 1,
44886
- precision: 0,
44887
- lastCalibratedAt: "2026-07-02T00:00:00Z",
44902
+ recall: 0.07042,
44903
+ fpRate: 0.11379,
44904
+ ratio: 0.62,
44905
+ precision: 0.04659,
44906
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
44888
44907
  verdict: "DORMANT",
44889
- _calibrationNote: "v0.24: new rule (multi-println in Kotlin source \u2014 use Timber / android.util.Log). DORMANT until v9 Kotlin corpus calibration.",
44908
+ _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
44909
  aiSpecific: true,
44891
44910
  _v7Verdict: "DORMANT",
44892
44911
  _v7Lift: 1,
@@ -44895,16 +44914,21 @@ var signal_strength_default = {
44895
44914
  _v7Precision: 0,
44896
44915
  _v8Verdict: "DORMANT",
44897
44916
  _v8Lift: 1,
44917
+ _v9Verdict: "DORMANT",
44918
+ _v9Lift: 0.62,
44919
+ _v9Recall: 0.07042,
44920
+ _v9FpRate: 0.11379,
44921
+ _v9Precision: 0.04659,
44898
44922
  defaultOff: true
44899
44923
  },
44900
44924
  "kotlin/object-singleton-misuse": {
44901
- recall: 0,
44902
- fpRate: 0,
44903
- ratio: 1,
44904
- precision: 0,
44905
- lastCalibratedAt: "2026-07-02T00:00:00Z",
44925
+ recall: 469e-5,
44926
+ fpRate: 371e-5,
44927
+ ratio: 1.27,
44928
+ precision: 0.09091,
44929
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
44906
44930
  verdict: "DORMANT",
44907
- _calibrationNote: "v0.24: new rule (Kotlin `object` singleton holding `var` \u2014 hidden shared state). DORMANT until v9 Kotlin corpus calibration.",
44931
+ _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
44932
  aiSpecific: true,
44909
44933
  _v7Verdict: "DORMANT",
44910
44934
  _v7Lift: 1,
@@ -44913,16 +44937,21 @@ var signal_strength_default = {
44913
44937
  _v7Precision: 0,
44914
44938
  _v8Verdict: "DORMANT",
44915
44939
  _v8Lift: 1,
44940
+ _v9Verdict: "DORMANT",
44941
+ _v9Lift: 1.27,
44942
+ _v9Recall: 469e-5,
44943
+ _v9FpRate: 371e-5,
44944
+ _v9Precision: 0.09091,
44916
44945
  defaultOff: true
44917
44946
  },
44918
44947
  "kotlin/string-concat-loop": {
44919
- recall: 0,
44920
- fpRate: 0,
44921
- ratio: 1,
44922
- precision: 0,
44923
- lastCalibratedAt: "2026-07-02T00:00:00Z",
44948
+ recall: 939e-5,
44949
+ fpRate: 519e-5,
44950
+ ratio: 1.81,
44951
+ precision: 0.125,
44952
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
44924
44953
  verdict: "DORMANT",
44925
- _calibrationNote: "v0.24: new rule (string concat inside a Kotlin loop \u2014 use buildString { ... }). DORMANT until v9 Kotlin corpus calibration.",
44954
+ _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
44955
  aiSpecific: true,
44927
44956
  _v7Verdict: "DORMANT",
44928
44957
  _v7Lift: 1,
@@ -44931,6 +44960,11 @@ var signal_strength_default = {
44931
44960
  _v7Precision: 0,
44932
44961
  _v8Verdict: "DORMANT",
44933
44962
  _v8Lift: 1,
44963
+ _v9Verdict: "DORMANT",
44964
+ _v9Lift: 1.81,
44965
+ _v9Recall: 939e-5,
44966
+ _v9FpRate: 519e-5,
44967
+ _v9Precision: 0.125,
44934
44968
  defaultOff: true
44935
44969
  },
44936
44970
  "swift/force-unwrap": {
@@ -45163,8 +45197,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
45163
45197
  const ext = (0, import_node_path9.extname)(filePath).toLowerCase();
45164
45198
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
45165
45199
  ".swift",
45166
- ".kt",
45167
- ".kts",
45168
45200
  ".dart",
45169
45201
  ".cpp",
45170
45202
  ".cc",
@@ -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 (!/\.kt$/i.test(facts.filePath)) return issues;
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 (!/\.kt$/i.test(facts.filePath)) return issues;
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) {
@@ -37069,7 +37078,7 @@ var kotlinObjectSingletonMisuseRule = createRule({
37069
37078
  const issues = [];
37070
37079
  const source = facts.v2?._source;
37071
37080
  if (!source) return issues;
37072
- if (!/\.kt$/i.test(facts.filePath)) return issues;
37081
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
37073
37082
  let m;
37074
37083
  OBJECT_DECL_REGEX.lastIndex = 0;
37075
37084
  while ((m = OBJECT_DECL_REGEX.exec(source)) !== null) {
@@ -37120,7 +37129,7 @@ var kotlinPrintlnDebugRule = createRule({
37120
37129
  const issues = [];
37121
37130
  const source = facts.v2?._source;
37122
37131
  if (!source) return issues;
37123
- if (!/\.kt$/i.test(facts.filePath)) return issues;
37132
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
37124
37133
  const matches = [];
37125
37134
  let m;
37126
37135
  PRINTLN_REGEX.lastIndex = 0;
@@ -37162,7 +37171,7 @@ var kotlinStringConcatLoopRule = createRule({
37162
37171
  const issues = [];
37163
37172
  const source = facts.v2?._source;
37164
37173
  if (!source) return issues;
37165
- if (!/\.kt$/i.test(facts.filePath)) return issues;
37174
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
37166
37175
  if (!/\b(?:for|while|repeat|forEach)\b/.test(source)) return issues;
37167
37176
  let m;
37168
37177
  STRING_CONCAT_REGEX.lastIndex = 0;
@@ -44815,13 +44824,13 @@ var signal_strength_default = {
44815
44824
  defaultOff: true
44816
44825
  },
44817
44826
  "kotlin/data-class-defaults-overuse": {
44818
- recall: 0,
44819
- fpRate: 0,
44820
- ratio: 1,
44821
- precision: 0,
44822
- lastCalibratedAt: "2026-07-02T00:00:00Z",
44827
+ recall: 469e-5,
44828
+ fpRate: 222e-5,
44829
+ ratio: 2.11,
44830
+ precision: 0.1429,
44831
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
44823
44832
  verdict: "DORMANT",
44824
- _calibrationNote: "v0.24: new rule (data class with 3+ literal defaults). DORMANT until v9 Kotlin corpus calibration.",
44833
+ _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
44834
  aiSpecific: true,
44826
44835
  _v7Verdict: "DORMANT",
44827
44836
  _v7Lift: 1,
@@ -44830,16 +44839,21 @@ var signal_strength_default = {
44830
44839
  _v7Precision: 0,
44831
44840
  _v8Verdict: "DORMANT",
44832
44841
  _v8Lift: 1,
44842
+ _v9Verdict: "DORMANT",
44843
+ _v9Lift: 2.11,
44844
+ _v9Recall: 469e-5,
44845
+ _v9FpRate: 222e-5,
44846
+ _v9Precision: 0.1429,
44833
44847
  defaultOff: true
44834
44848
  },
44835
44849
  "kotlin/coroutine-global-scope": {
44836
- recall: 0,
44837
- fpRate: 0,
44838
- ratio: 1,
44839
- precision: 0,
44840
- lastCalibratedAt: "2026-07-02T00:00:00Z",
44850
+ recall: 469e-5,
44851
+ fpRate: 0.05004,
44852
+ ratio: 0.09,
44853
+ precision: 735e-5,
44854
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
44841
44855
  verdict: "DORMANT",
44842
- _calibrationNote: "v0.24: new rule (GlobalScope.launch/async/runBlocking \u2014 bypasses structured concurrency). DORMANT until v9 Kotlin corpus calibration.",
44856
+ _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
44857
  aiSpecific: true,
44844
44858
  _v7Verdict: "DORMANT",
44845
44859
  _v7Lift: 1,
@@ -44848,16 +44862,21 @@ var signal_strength_default = {
44848
44862
  _v7Precision: 0,
44849
44863
  _v8Verdict: "DORMANT",
44850
44864
  _v8Lift: 1,
44865
+ _v9Verdict: "DORMANT",
44866
+ _v9Lift: 0.09,
44867
+ _v9Recall: 469e-5,
44868
+ _v9FpRate: 0.05004,
44869
+ _v9Precision: 735e-5,
44851
44870
  defaultOff: true
44852
44871
  },
44853
44872
  "kotlin/println-debug": {
44854
- recall: 0,
44855
- fpRate: 0,
44856
- ratio: 1,
44857
- precision: 0,
44858
- lastCalibratedAt: "2026-07-02T00:00:00Z",
44873
+ recall: 0.07042,
44874
+ fpRate: 0.11379,
44875
+ ratio: 0.62,
44876
+ precision: 0.04659,
44877
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
44859
44878
  verdict: "DORMANT",
44860
- _calibrationNote: "v0.24: new rule (multi-println in Kotlin source \u2014 use Timber / android.util.Log). DORMANT until v9 Kotlin corpus calibration.",
44879
+ _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
44880
  aiSpecific: true,
44862
44881
  _v7Verdict: "DORMANT",
44863
44882
  _v7Lift: 1,
@@ -44866,16 +44885,21 @@ var signal_strength_default = {
44866
44885
  _v7Precision: 0,
44867
44886
  _v8Verdict: "DORMANT",
44868
44887
  _v8Lift: 1,
44888
+ _v9Verdict: "DORMANT",
44889
+ _v9Lift: 0.62,
44890
+ _v9Recall: 0.07042,
44891
+ _v9FpRate: 0.11379,
44892
+ _v9Precision: 0.04659,
44869
44893
  defaultOff: true
44870
44894
  },
44871
44895
  "kotlin/object-singleton-misuse": {
44872
- recall: 0,
44873
- fpRate: 0,
44874
- ratio: 1,
44875
- precision: 0,
44876
- lastCalibratedAt: "2026-07-02T00:00:00Z",
44896
+ recall: 469e-5,
44897
+ fpRate: 371e-5,
44898
+ ratio: 1.27,
44899
+ precision: 0.09091,
44900
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
44877
44901
  verdict: "DORMANT",
44878
- _calibrationNote: "v0.24: new rule (Kotlin `object` singleton holding `var` \u2014 hidden shared state). DORMANT until v9 Kotlin corpus calibration.",
44902
+ _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
44903
  aiSpecific: true,
44880
44904
  _v7Verdict: "DORMANT",
44881
44905
  _v7Lift: 1,
@@ -44884,16 +44908,21 @@ var signal_strength_default = {
44884
44908
  _v7Precision: 0,
44885
44909
  _v8Verdict: "DORMANT",
44886
44910
  _v8Lift: 1,
44911
+ _v9Verdict: "DORMANT",
44912
+ _v9Lift: 1.27,
44913
+ _v9Recall: 469e-5,
44914
+ _v9FpRate: 371e-5,
44915
+ _v9Precision: 0.09091,
44887
44916
  defaultOff: true
44888
44917
  },
44889
44918
  "kotlin/string-concat-loop": {
44890
- recall: 0,
44891
- fpRate: 0,
44892
- ratio: 1,
44893
- precision: 0,
44894
- lastCalibratedAt: "2026-07-02T00:00:00Z",
44919
+ recall: 939e-5,
44920
+ fpRate: 519e-5,
44921
+ ratio: 1.81,
44922
+ precision: 0.125,
44923
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
44895
44924
  verdict: "DORMANT",
44896
- _calibrationNote: "v0.24: new rule (string concat inside a Kotlin loop \u2014 use buildString { ... }). DORMANT until v9 Kotlin corpus calibration.",
44925
+ _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
44926
  aiSpecific: true,
44898
44927
  _v7Verdict: "DORMANT",
44899
44928
  _v7Lift: 1,
@@ -44902,6 +44931,11 @@ var signal_strength_default = {
44902
44931
  _v7Precision: 0,
44903
44932
  _v8Verdict: "DORMANT",
44904
44933
  _v8Lift: 1,
44934
+ _v9Verdict: "DORMANT",
44935
+ _v9Lift: 1.81,
44936
+ _v9Recall: 939e-5,
44937
+ _v9FpRate: 519e-5,
44938
+ _v9Precision: 0.125,
44905
44939
  defaultOff: true
44906
44940
  },
44907
44941
  "swift/force-unwrap": {
@@ -45134,8 +45168,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
45134
45168
  const ext = extname4(filePath).toLowerCase();
45135
45169
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
45136
45170
  ".swift",
45137
- ".kt",
45138
- ".kts",
45139
45171
  ".dart",
45140
45172
  ".cpp",
45141
45173
  ".cc",
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.27.0";
39
+ VERSION = "0.28.0";
40
40
  }
41
41
  });
42
42
 
@@ -30682,7 +30682,7 @@ var init_coroutine_global_scope = __esm({
30682
30682
  const issues = [];
30683
30683
  const source = facts.v2?._source;
30684
30684
  if (!source) return issues;
30685
- if (!/\.kt$/i.test(facts.filePath)) return issues;
30685
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
30686
30686
  let m;
30687
30687
  GLOBAL_SCOPE_REGEX.lastIndex = 0;
30688
30688
  while ((m = GLOBAL_SCOPE_REGEX.exec(source)) !== null) {
@@ -30726,7 +30726,7 @@ var init_data_class_defaults_overuse = __esm({
30726
30726
  const issues = [];
30727
30727
  const source = facts.v2?._source;
30728
30728
  if (!source) return issues;
30729
- if (!/\.kt$/i.test(facts.filePath)) return issues;
30729
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
30730
30730
  let m;
30731
30731
  DATA_CLASS_HEAD_REGEX.lastIndex = 0;
30732
30732
  while ((m = DATA_CLASS_HEAD_REGEX.exec(source)) !== null) {
@@ -30788,7 +30788,7 @@ var init_object_singleton_misuse = __esm({
30788
30788
  const issues = [];
30789
30789
  const source = facts.v2?._source;
30790
30790
  if (!source) return issues;
30791
- if (!/\.kt$/i.test(facts.filePath)) return issues;
30791
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
30792
30792
  let m;
30793
30793
  OBJECT_DECL_REGEX.lastIndex = 0;
30794
30794
  while ((m = OBJECT_DECL_REGEX.exec(source)) !== null) {
@@ -30846,7 +30846,7 @@ var init_println_debug = __esm({
30846
30846
  const issues = [];
30847
30847
  const source = facts.v2?._source;
30848
30848
  if (!source) return issues;
30849
- if (!/\.kt$/i.test(facts.filePath)) return issues;
30849
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
30850
30850
  const matches = [];
30851
30851
  let m;
30852
30852
  PRINTLN_REGEX.lastIndex = 0;
@@ -30895,7 +30895,7 @@ var init_string_concat_loop = __esm({
30895
30895
  const issues = [];
30896
30896
  const source = facts.v2?._source;
30897
30897
  if (!source) return issues;
30898
- if (!/\.kt$/i.test(facts.filePath)) return issues;
30898
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
30899
30899
  if (!/\b(?:for|while|repeat|forEach)\b/.test(source)) return issues;
30900
30900
  let m;
30901
30901
  STRING_CONCAT_REGEX.lastIndex = 0;
@@ -36117,6 +36117,15 @@ function parseSource(source, filePath) {
36117
36117
  case "go":
36118
36118
  case "rs":
36119
36119
  case "java":
36120
+ // v0.28.0: Kotlin files get the same parseBlankModule path as
36121
+ // Java (v0.24.5). All 5 `kotlin/*` rules are regex-based and
36122
+ // gate themselves on `/\.kts?$/i.test(filePath)` inside their
36123
+ // `analyze()`. The tree-sitter Kotlin integration is a larger
36124
+ // lift; the v0.27.0 methodology paper confirmed era-confounding
36125
+ // is the dominant signal anyway, so a regex-only Kotlin pass is
36126
+ // sufficient for the v9 calibration goal.
36127
+ case "kt":
36128
+ case "kts":
36120
36129
  return parseBlankModule(source);
36121
36130
  default:
36122
36131
  return parseWithSwc(source, filePath);
@@ -50851,13 +50860,13 @@ var init_signal_strength = __esm({
50851
50860
  defaultOff: true
50852
50861
  },
50853
50862
  "kotlin/data-class-defaults-overuse": {
50854
- recall: 0,
50855
- fpRate: 0,
50856
- ratio: 1,
50857
- precision: 0,
50858
- lastCalibratedAt: "2026-07-02T00:00:00Z",
50863
+ recall: 469e-5,
50864
+ fpRate: 222e-5,
50865
+ ratio: 2.11,
50866
+ precision: 0.1429,
50867
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
50859
50868
  verdict: "DORMANT",
50860
- _calibrationNote: "v0.24: new rule (data class with 3+ literal defaults). DORMANT until v9 Kotlin corpus calibration.",
50869
+ _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.",
50861
50870
  aiSpecific: true,
50862
50871
  _v7Verdict: "DORMANT",
50863
50872
  _v7Lift: 1,
@@ -50866,16 +50875,21 @@ var init_signal_strength = __esm({
50866
50875
  _v7Precision: 0,
50867
50876
  _v8Verdict: "DORMANT",
50868
50877
  _v8Lift: 1,
50878
+ _v9Verdict: "DORMANT",
50879
+ _v9Lift: 2.11,
50880
+ _v9Recall: 469e-5,
50881
+ _v9FpRate: 222e-5,
50882
+ _v9Precision: 0.1429,
50869
50883
  defaultOff: true
50870
50884
  },
50871
50885
  "kotlin/coroutine-global-scope": {
50872
- recall: 0,
50873
- fpRate: 0,
50874
- ratio: 1,
50875
- precision: 0,
50876
- lastCalibratedAt: "2026-07-02T00:00:00Z",
50886
+ recall: 469e-5,
50887
+ fpRate: 0.05004,
50888
+ ratio: 0.09,
50889
+ precision: 735e-5,
50890
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
50877
50891
  verdict: "DORMANT",
50878
- _calibrationNote: "v0.24: new rule (GlobalScope.launch/async/runBlocking \u2014 bypasses structured concurrency). DORMANT until v9 Kotlin corpus calibration.",
50892
+ _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.",
50879
50893
  aiSpecific: true,
50880
50894
  _v7Verdict: "DORMANT",
50881
50895
  _v7Lift: 1,
@@ -50884,16 +50898,21 @@ var init_signal_strength = __esm({
50884
50898
  _v7Precision: 0,
50885
50899
  _v8Verdict: "DORMANT",
50886
50900
  _v8Lift: 1,
50901
+ _v9Verdict: "DORMANT",
50902
+ _v9Lift: 0.09,
50903
+ _v9Recall: 469e-5,
50904
+ _v9FpRate: 0.05004,
50905
+ _v9Precision: 735e-5,
50887
50906
  defaultOff: true
50888
50907
  },
50889
50908
  "kotlin/println-debug": {
50890
- recall: 0,
50891
- fpRate: 0,
50892
- ratio: 1,
50893
- precision: 0,
50894
- lastCalibratedAt: "2026-07-02T00:00:00Z",
50909
+ recall: 0.07042,
50910
+ fpRate: 0.11379,
50911
+ ratio: 0.62,
50912
+ precision: 0.04659,
50913
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
50895
50914
  verdict: "DORMANT",
50896
- _calibrationNote: "v0.24: new rule (multi-println in Kotlin source \u2014 use Timber / android.util.Log). DORMANT until v9 Kotlin corpus calibration.",
50915
+ _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.",
50897
50916
  aiSpecific: true,
50898
50917
  _v7Verdict: "DORMANT",
50899
50918
  _v7Lift: 1,
@@ -50902,16 +50921,21 @@ var init_signal_strength = __esm({
50902
50921
  _v7Precision: 0,
50903
50922
  _v8Verdict: "DORMANT",
50904
50923
  _v8Lift: 1,
50924
+ _v9Verdict: "DORMANT",
50925
+ _v9Lift: 0.62,
50926
+ _v9Recall: 0.07042,
50927
+ _v9FpRate: 0.11379,
50928
+ _v9Precision: 0.04659,
50905
50929
  defaultOff: true
50906
50930
  },
50907
50931
  "kotlin/object-singleton-misuse": {
50908
- recall: 0,
50909
- fpRate: 0,
50910
- ratio: 1,
50911
- precision: 0,
50912
- lastCalibratedAt: "2026-07-02T00:00:00Z",
50932
+ recall: 469e-5,
50933
+ fpRate: 371e-5,
50934
+ ratio: 1.27,
50935
+ precision: 0.09091,
50936
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
50913
50937
  verdict: "DORMANT",
50914
- _calibrationNote: "v0.24: new rule (Kotlin `object` singleton holding `var` \u2014 hidden shared state). DORMANT until v9 Kotlin corpus calibration.",
50938
+ _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).",
50915
50939
  aiSpecific: true,
50916
50940
  _v7Verdict: "DORMANT",
50917
50941
  _v7Lift: 1,
@@ -50920,16 +50944,21 @@ var init_signal_strength = __esm({
50920
50944
  _v7Precision: 0,
50921
50945
  _v8Verdict: "DORMANT",
50922
50946
  _v8Lift: 1,
50947
+ _v9Verdict: "DORMANT",
50948
+ _v9Lift: 1.27,
50949
+ _v9Recall: 469e-5,
50950
+ _v9FpRate: 371e-5,
50951
+ _v9Precision: 0.09091,
50923
50952
  defaultOff: true
50924
50953
  },
50925
50954
  "kotlin/string-concat-loop": {
50926
- recall: 0,
50927
- fpRate: 0,
50928
- ratio: 1,
50929
- precision: 0,
50930
- lastCalibratedAt: "2026-07-02T00:00:00Z",
50955
+ recall: 939e-5,
50956
+ fpRate: 519e-5,
50957
+ ratio: 1.81,
50958
+ precision: 0.125,
50959
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
50931
50960
  verdict: "DORMANT",
50932
- _calibrationNote: "v0.24: new rule (string concat inside a Kotlin loop \u2014 use buildString { ... }). DORMANT until v9 Kotlin corpus calibration.",
50961
+ _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.",
50933
50962
  aiSpecific: true,
50934
50963
  _v7Verdict: "DORMANT",
50935
50964
  _v7Lift: 1,
@@ -50938,6 +50967,11 @@ var init_signal_strength = __esm({
50938
50967
  _v7Precision: 0,
50939
50968
  _v8Verdict: "DORMANT",
50940
50969
  _v8Lift: 1,
50970
+ _v9Verdict: "DORMANT",
50971
+ _v9Lift: 1.81,
50972
+ _v9Recall: 939e-5,
50973
+ _v9FpRate: 519e-5,
50974
+ _v9Precision: 0.125,
50941
50975
  defaultOff: true
50942
50976
  },
50943
50977
  "swift/force-unwrap": {
@@ -51198,8 +51232,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
51198
51232
  const ext = (0, import_node_path12.extname)(filePath).toLowerCase();
51199
51233
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
51200
51234
  ".swift",
51201
- ".kt",
51202
- ".kts",
51203
51235
  ".dart",
51204
51236
  ".cpp",
51205
51237
  ".cc",
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.27.0";
22
+ VERSION = "0.28.0";
23
23
  }
24
24
  });
25
25
 
@@ -30663,7 +30663,7 @@ var init_coroutine_global_scope = __esm({
30663
30663
  const issues = [];
30664
30664
  const source = facts.v2?._source;
30665
30665
  if (!source) return issues;
30666
- if (!/\.kt$/i.test(facts.filePath)) return issues;
30666
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
30667
30667
  let m;
30668
30668
  GLOBAL_SCOPE_REGEX.lastIndex = 0;
30669
30669
  while ((m = GLOBAL_SCOPE_REGEX.exec(source)) !== null) {
@@ -30707,7 +30707,7 @@ var init_data_class_defaults_overuse = __esm({
30707
30707
  const issues = [];
30708
30708
  const source = facts.v2?._source;
30709
30709
  if (!source) return issues;
30710
- if (!/\.kt$/i.test(facts.filePath)) return issues;
30710
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
30711
30711
  let m;
30712
30712
  DATA_CLASS_HEAD_REGEX.lastIndex = 0;
30713
30713
  while ((m = DATA_CLASS_HEAD_REGEX.exec(source)) !== null) {
@@ -30769,7 +30769,7 @@ var init_object_singleton_misuse = __esm({
30769
30769
  const issues = [];
30770
30770
  const source = facts.v2?._source;
30771
30771
  if (!source) return issues;
30772
- if (!/\.kt$/i.test(facts.filePath)) return issues;
30772
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
30773
30773
  let m;
30774
30774
  OBJECT_DECL_REGEX.lastIndex = 0;
30775
30775
  while ((m = OBJECT_DECL_REGEX.exec(source)) !== null) {
@@ -30827,7 +30827,7 @@ var init_println_debug = __esm({
30827
30827
  const issues = [];
30828
30828
  const source = facts.v2?._source;
30829
30829
  if (!source) return issues;
30830
- if (!/\.kt$/i.test(facts.filePath)) return issues;
30830
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
30831
30831
  const matches = [];
30832
30832
  let m;
30833
30833
  PRINTLN_REGEX.lastIndex = 0;
@@ -30876,7 +30876,7 @@ var init_string_concat_loop = __esm({
30876
30876
  const issues = [];
30877
30877
  const source = facts.v2?._source;
30878
30878
  if (!source) return issues;
30879
- if (!/\.kt$/i.test(facts.filePath)) return issues;
30879
+ if (!/\.kts?$/i.test(facts.filePath)) return issues;
30880
30880
  if (!/\b(?:for|while|repeat|forEach)\b/.test(source)) return issues;
30881
30881
  let m;
30882
30882
  STRING_CONCAT_REGEX.lastIndex = 0;
@@ -36107,6 +36107,15 @@ function parseSource(source, filePath) {
36107
36107
  case "go":
36108
36108
  case "rs":
36109
36109
  case "java":
36110
+ // v0.28.0: Kotlin files get the same parseBlankModule path as
36111
+ // Java (v0.24.5). All 5 `kotlin/*` rules are regex-based and
36112
+ // gate themselves on `/\.kts?$/i.test(filePath)` inside their
36113
+ // `analyze()`. The tree-sitter Kotlin integration is a larger
36114
+ // lift; the v0.27.0 methodology paper confirmed era-confounding
36115
+ // is the dominant signal anyway, so a regex-only Kotlin pass is
36116
+ // sufficient for the v9 calibration goal.
36117
+ case "kt":
36118
+ case "kts":
36110
36119
  return parseBlankModule(source);
36111
36120
  default:
36112
36121
  return parseWithSwc(source, filePath);
@@ -50829,13 +50838,13 @@ var init_signal_strength = __esm({
50829
50838
  defaultOff: true
50830
50839
  },
50831
50840
  "kotlin/data-class-defaults-overuse": {
50832
- recall: 0,
50833
- fpRate: 0,
50834
- ratio: 1,
50835
- precision: 0,
50836
- lastCalibratedAt: "2026-07-02T00:00:00Z",
50841
+ recall: 469e-5,
50842
+ fpRate: 222e-5,
50843
+ ratio: 2.11,
50844
+ precision: 0.1429,
50845
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
50837
50846
  verdict: "DORMANT",
50838
- _calibrationNote: "v0.24: new rule (data class with 3+ literal defaults). DORMANT until v9 Kotlin corpus calibration.",
50847
+ _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.",
50839
50848
  aiSpecific: true,
50840
50849
  _v7Verdict: "DORMANT",
50841
50850
  _v7Lift: 1,
@@ -50844,16 +50853,21 @@ var init_signal_strength = __esm({
50844
50853
  _v7Precision: 0,
50845
50854
  _v8Verdict: "DORMANT",
50846
50855
  _v8Lift: 1,
50856
+ _v9Verdict: "DORMANT",
50857
+ _v9Lift: 2.11,
50858
+ _v9Recall: 469e-5,
50859
+ _v9FpRate: 222e-5,
50860
+ _v9Precision: 0.1429,
50847
50861
  defaultOff: true
50848
50862
  },
50849
50863
  "kotlin/coroutine-global-scope": {
50850
- recall: 0,
50851
- fpRate: 0,
50852
- ratio: 1,
50853
- precision: 0,
50854
- lastCalibratedAt: "2026-07-02T00:00:00Z",
50864
+ recall: 469e-5,
50865
+ fpRate: 0.05004,
50866
+ ratio: 0.09,
50867
+ precision: 735e-5,
50868
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
50855
50869
  verdict: "DORMANT",
50856
- _calibrationNote: "v0.24: new rule (GlobalScope.launch/async/runBlocking \u2014 bypasses structured concurrency). DORMANT until v9 Kotlin corpus calibration.",
50870
+ _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.",
50857
50871
  aiSpecific: true,
50858
50872
  _v7Verdict: "DORMANT",
50859
50873
  _v7Lift: 1,
@@ -50862,16 +50876,21 @@ var init_signal_strength = __esm({
50862
50876
  _v7Precision: 0,
50863
50877
  _v8Verdict: "DORMANT",
50864
50878
  _v8Lift: 1,
50879
+ _v9Verdict: "DORMANT",
50880
+ _v9Lift: 0.09,
50881
+ _v9Recall: 469e-5,
50882
+ _v9FpRate: 0.05004,
50883
+ _v9Precision: 735e-5,
50865
50884
  defaultOff: true
50866
50885
  },
50867
50886
  "kotlin/println-debug": {
50868
- recall: 0,
50869
- fpRate: 0,
50870
- ratio: 1,
50871
- precision: 0,
50872
- lastCalibratedAt: "2026-07-02T00:00:00Z",
50887
+ recall: 0.07042,
50888
+ fpRate: 0.11379,
50889
+ ratio: 0.62,
50890
+ precision: 0.04659,
50891
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
50873
50892
  verdict: "DORMANT",
50874
- _calibrationNote: "v0.24: new rule (multi-println in Kotlin source \u2014 use Timber / android.util.Log). DORMANT until v9 Kotlin corpus calibration.",
50893
+ _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.",
50875
50894
  aiSpecific: true,
50876
50895
  _v7Verdict: "DORMANT",
50877
50896
  _v7Lift: 1,
@@ -50880,16 +50899,21 @@ var init_signal_strength = __esm({
50880
50899
  _v7Precision: 0,
50881
50900
  _v8Verdict: "DORMANT",
50882
50901
  _v8Lift: 1,
50902
+ _v9Verdict: "DORMANT",
50903
+ _v9Lift: 0.62,
50904
+ _v9Recall: 0.07042,
50905
+ _v9FpRate: 0.11379,
50906
+ _v9Precision: 0.04659,
50883
50907
  defaultOff: true
50884
50908
  },
50885
50909
  "kotlin/object-singleton-misuse": {
50886
- recall: 0,
50887
- fpRate: 0,
50888
- ratio: 1,
50889
- precision: 0,
50890
- lastCalibratedAt: "2026-07-02T00:00:00Z",
50910
+ recall: 469e-5,
50911
+ fpRate: 371e-5,
50912
+ ratio: 1.27,
50913
+ precision: 0.09091,
50914
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
50891
50915
  verdict: "DORMANT",
50892
- _calibrationNote: "v0.24: new rule (Kotlin `object` singleton holding `var` \u2014 hidden shared state). DORMANT until v9 Kotlin corpus calibration.",
50916
+ _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).",
50893
50917
  aiSpecific: true,
50894
50918
  _v7Verdict: "DORMANT",
50895
50919
  _v7Lift: 1,
@@ -50898,16 +50922,21 @@ var init_signal_strength = __esm({
50898
50922
  _v7Precision: 0,
50899
50923
  _v8Verdict: "DORMANT",
50900
50924
  _v8Lift: 1,
50925
+ _v9Verdict: "DORMANT",
50926
+ _v9Lift: 1.27,
50927
+ _v9Recall: 469e-5,
50928
+ _v9FpRate: 371e-5,
50929
+ _v9Precision: 0.09091,
50901
50930
  defaultOff: true
50902
50931
  },
50903
50932
  "kotlin/string-concat-loop": {
50904
- recall: 0,
50905
- fpRate: 0,
50906
- ratio: 1,
50907
- precision: 0,
50908
- lastCalibratedAt: "2026-07-02T00:00:00Z",
50933
+ recall: 939e-5,
50934
+ fpRate: 519e-5,
50935
+ ratio: 1.81,
50936
+ precision: 0.125,
50937
+ lastCalibratedAt: "2026-07-03T00:00:00Z",
50909
50938
  verdict: "DORMANT",
50910
- _calibrationNote: "v0.24: new rule (string concat inside a Kotlin loop \u2014 use buildString { ... }). DORMANT until v9 Kotlin corpus calibration.",
50939
+ _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.",
50911
50940
  aiSpecific: true,
50912
50941
  _v7Verdict: "DORMANT",
50913
50942
  _v7Lift: 1,
@@ -50916,6 +50945,11 @@ var init_signal_strength = __esm({
50916
50945
  _v7Precision: 0,
50917
50946
  _v8Verdict: "DORMANT",
50918
50947
  _v8Lift: 1,
50948
+ _v9Verdict: "DORMANT",
50949
+ _v9Lift: 1.81,
50950
+ _v9Recall: 939e-5,
50951
+ _v9FpRate: 519e-5,
50952
+ _v9Precision: 0.125,
50919
50953
  defaultOff: true
50920
50954
  },
50921
50955
  "swift/force-unwrap": {
@@ -51180,8 +51214,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
51180
51214
  const ext = extname5(filePath).toLowerCase();
51181
51215
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
51182
51216
  ".swift",
51183
- ".kt",
51184
- ".kts",
51185
51217
  ".dart",
51186
51218
  ".cpp",
51187
51219
  ".cc",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slopbrick",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
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": {