slopbrick 0.18.9 → 0.19.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 +614 -180
- package/dist/engine/worker.js +616 -182
- package/dist/index.cjs +701 -123
- package/dist/index.js +705 -127
- package/package.json +2 -2
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.
|
|
39
|
+
VERSION = "0.19.0";
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
|
|
@@ -31353,6 +31353,246 @@ var init_broken_link = __esm({
|
|
|
31353
31353
|
}
|
|
31354
31354
|
});
|
|
31355
31355
|
|
|
31356
|
+
// src/rules/dup/identical-block.ts
|
|
31357
|
+
function normalizeAndHash(lines) {
|
|
31358
|
+
const normalized = lines.map(
|
|
31359
|
+
(line) => line.replace(/\/\/.*$/, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").trim()
|
|
31360
|
+
).filter((line) => line.length > 0).join("\n");
|
|
31361
|
+
if (normalized.length < MIN_NORMALIZED_LENGTH) return void 0;
|
|
31362
|
+
return crypto.createHash("sha1").update(normalized).digest("hex").slice(0, HASH_PREFIX_LENGTH);
|
|
31363
|
+
}
|
|
31364
|
+
var crypto, WINDOW_SIZE, MIN_NORMALIZED_LENGTH, HASH_PREFIX_LENGTH, DEDUP_CACHE, dupIdenticalBlockRule;
|
|
31365
|
+
var init_identical_block = __esm({
|
|
31366
|
+
"src/rules/dup/identical-block.ts"() {
|
|
31367
|
+
"use strict";
|
|
31368
|
+
crypto = __toESM(require("crypto"), 1);
|
|
31369
|
+
init_rule();
|
|
31370
|
+
WINDOW_SIZE = 10;
|
|
31371
|
+
MIN_NORMALIZED_LENGTH = 40;
|
|
31372
|
+
HASH_PREFIX_LENGTH = 16;
|
|
31373
|
+
DEDUP_CACHE = /* @__PURE__ */ new Map();
|
|
31374
|
+
dupIdenticalBlockRule = createRule({
|
|
31375
|
+
id: "dup/identical-block",
|
|
31376
|
+
category: "logic",
|
|
31377
|
+
severity: "medium",
|
|
31378
|
+
aiSpecific: false,
|
|
31379
|
+
description: "Block of >=10 lines is identical across >=2 files (Type-1 clone detector)",
|
|
31380
|
+
create(_context) {
|
|
31381
|
+
return {};
|
|
31382
|
+
},
|
|
31383
|
+
analyze(_context, facts) {
|
|
31384
|
+
const issues = [];
|
|
31385
|
+
const source = facts.v2?._source;
|
|
31386
|
+
if (!source) return issues;
|
|
31387
|
+
const filePath = facts.filePath;
|
|
31388
|
+
const lines = source.split("\n");
|
|
31389
|
+
for (let i = 0; i <= lines.length - WINDOW_SIZE; i++) {
|
|
31390
|
+
const window = lines.slice(i, i + WINDOW_SIZE);
|
|
31391
|
+
const hash = normalizeAndHash(window);
|
|
31392
|
+
if (!hash) continue;
|
|
31393
|
+
const existing = DEDUP_CACHE.get(hash) ?? [];
|
|
31394
|
+
const matches = existing.filter((m) => m.file !== filePath);
|
|
31395
|
+
for (const match of matches) {
|
|
31396
|
+
issues.push({
|
|
31397
|
+
ruleId: "dup/identical-block",
|
|
31398
|
+
category: "logic",
|
|
31399
|
+
severity: "medium",
|
|
31400
|
+
aiSpecific: false,
|
|
31401
|
+
message: `Identical ${WINDOW_SIZE}-line block at line ${i + 1} also appears in ${match.file}:${match.line + 1}`,
|
|
31402
|
+
line: i + 1,
|
|
31403
|
+
column: 0,
|
|
31404
|
+
advice: "Refactor to a shared helper. This is a Type-1 clone (byte-for-byte identical after normalization). Common in AI-generated code that copy-pastes from training data.",
|
|
31405
|
+
extras: {
|
|
31406
|
+
duplicateOf: {
|
|
31407
|
+
file: match.file,
|
|
31408
|
+
line: match.line + 1,
|
|
31409
|
+
hash
|
|
31410
|
+
}
|
|
31411
|
+
}
|
|
31412
|
+
});
|
|
31413
|
+
}
|
|
31414
|
+
existing.push({ file: filePath, line: i });
|
|
31415
|
+
DEDUP_CACHE.set(hash, existing);
|
|
31416
|
+
}
|
|
31417
|
+
return issues;
|
|
31418
|
+
}
|
|
31419
|
+
});
|
|
31420
|
+
}
|
|
31421
|
+
});
|
|
31422
|
+
|
|
31423
|
+
// src/rules/go/error-wrap-without-context.ts
|
|
31424
|
+
var ERR_WRAP_REGEX, GENERIC_PREFIX_REGEX, goErrorWrapWithoutContextRule;
|
|
31425
|
+
var init_error_wrap_without_context = __esm({
|
|
31426
|
+
"src/rules/go/error-wrap-without-context.ts"() {
|
|
31427
|
+
"use strict";
|
|
31428
|
+
init_rule();
|
|
31429
|
+
ERR_WRAP_REGEX = /fmt\.Errorf\(\s*"((?:[^"\\]|\\.)*)"\s*,\s*([^)]+)\)/g;
|
|
31430
|
+
GENERIC_PREFIX_REGEX = /^\s*(?:error|err|failed|wrap(?:ping)?|invalid)\s*(?::\s*)?%w\b/i;
|
|
31431
|
+
goErrorWrapWithoutContextRule = createRule({
|
|
31432
|
+
id: "go/error-wrap-without-context",
|
|
31433
|
+
category: "typo",
|
|
31434
|
+
severity: "low",
|
|
31435
|
+
aiSpecific: true,
|
|
31436
|
+
description: 'fmt.Errorf wrap without operation context \u2014 AI defaults to generic "error: %w"',
|
|
31437
|
+
create(_context) {
|
|
31438
|
+
return {};
|
|
31439
|
+
},
|
|
31440
|
+
analyze(_context, facts) {
|
|
31441
|
+
const issues = [];
|
|
31442
|
+
const source = facts.v2?._source;
|
|
31443
|
+
if (!source) return issues;
|
|
31444
|
+
let match;
|
|
31445
|
+
ERR_WRAP_REGEX.lastIndex = 0;
|
|
31446
|
+
while ((match = ERR_WRAP_REGEX.exec(source)) !== null) {
|
|
31447
|
+
const formatString = match[1];
|
|
31448
|
+
if (!formatString.includes("%w")) continue;
|
|
31449
|
+
if (formatString.length >= 30) continue;
|
|
31450
|
+
if (!GENERIC_PREFIX_REGEX.test(formatString)) continue;
|
|
31451
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
31452
|
+
issues.push({
|
|
31453
|
+
ruleId: "go/error-wrap-without-context",
|
|
31454
|
+
category: "typo",
|
|
31455
|
+
severity: "low",
|
|
31456
|
+
aiSpecific: true,
|
|
31457
|
+
message: `fmt.Errorf wrap with generic message "${formatString}" \u2014 include the failing operation`,
|
|
31458
|
+
line,
|
|
31459
|
+
column: match[0].indexOf("fmt") + 1,
|
|
31460
|
+
advice: 'Real Go errors include the failing operation: `fmt.Errorf("opening config: %w", err)`. Generic messages ("error: %w", "failed: %w") tell the reader nothing about what failed. Reference: go/error-wrap-without-context v0.19. See: https://github.com/golang/go/wiki/CodeReviewComments#error-strings'
|
|
31461
|
+
});
|
|
31462
|
+
}
|
|
31463
|
+
return issues;
|
|
31464
|
+
}
|
|
31465
|
+
});
|
|
31466
|
+
}
|
|
31467
|
+
});
|
|
31468
|
+
|
|
31469
|
+
// src/rules/go/nil-slice-vs-empty.ts
|
|
31470
|
+
var NIL_SLICE_DECL_REGEX, EMPTY_SLICE_ASSIGN_REGEX, goNilSliceVsEmptyRule;
|
|
31471
|
+
var init_nil_slice_vs_empty = __esm({
|
|
31472
|
+
"src/rules/go/nil-slice-vs-empty.ts"() {
|
|
31473
|
+
"use strict";
|
|
31474
|
+
init_rule();
|
|
31475
|
+
NIL_SLICE_DECL_REGEX = /^[\t ]*var\s+([A-Za-z_][A-Za-z0-9_]*)\s+\[\][\w.*]+\b/gm;
|
|
31476
|
+
EMPTY_SLICE_ASSIGN_REGEX = /^[\t ]*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:\[\][\w.*]*\{\}|make\(\[\][\w.*]*)/gm;
|
|
31477
|
+
goNilSliceVsEmptyRule = createRule({
|
|
31478
|
+
id: "go/nil-slice-vs-empty",
|
|
31479
|
+
category: "typo",
|
|
31480
|
+
severity: "low",
|
|
31481
|
+
aiSpecific: true,
|
|
31482
|
+
description: "Variable declared `var x []int` but later assigned `x = []int{}` or `make([]int, n)` \u2014 pick one form",
|
|
31483
|
+
create(_context) {
|
|
31484
|
+
return {};
|
|
31485
|
+
},
|
|
31486
|
+
analyze(_context, facts) {
|
|
31487
|
+
const issues = [];
|
|
31488
|
+
const source = facts.v2?._source;
|
|
31489
|
+
if (!source) return issues;
|
|
31490
|
+
const nilDecls = /* @__PURE__ */ new Map();
|
|
31491
|
+
let m;
|
|
31492
|
+
NIL_SLICE_DECL_REGEX.lastIndex = 0;
|
|
31493
|
+
while ((m = NIL_SLICE_DECL_REGEX.exec(source)) !== null) {
|
|
31494
|
+
nilDecls.set(m[1], source.slice(0, m.index).split("\n").length);
|
|
31495
|
+
}
|
|
31496
|
+
if (nilDecls.size === 0) return issues;
|
|
31497
|
+
EMPTY_SLICE_ASSIGN_REGEX.lastIndex = 0;
|
|
31498
|
+
while ((m = EMPTY_SLICE_ASSIGN_REGEX.exec(source)) !== null) {
|
|
31499
|
+
const name = m[1];
|
|
31500
|
+
if (!nilDecls.has(name)) continue;
|
|
31501
|
+
const declLine = nilDecls.get(name);
|
|
31502
|
+
const assignLine = source.slice(0, m.index).split("\n").length;
|
|
31503
|
+
if (assignLine <= declLine) continue;
|
|
31504
|
+
issues.push({
|
|
31505
|
+
ruleId: "go/nil-slice-vs-empty",
|
|
31506
|
+
category: "typo",
|
|
31507
|
+
severity: "low",
|
|
31508
|
+
aiSpecific: true,
|
|
31509
|
+
message: `Variable '${name}' declared as nil slice (line ${declLine}) but assigned an empty slice (line ${assignLine}) \u2014 pick one form`,
|
|
31510
|
+
line: declLine,
|
|
31511
|
+
column: 1,
|
|
31512
|
+
advice: "Either declare as `var " + name + " = []int{}` or assign with `make([]int, 0)`. The nil/empty inconsistency is an AI signal \u2014 real code picks one form and sticks with it. Reference: go/nil-slice-vs-empty v0.19."
|
|
31513
|
+
});
|
|
31514
|
+
}
|
|
31515
|
+
return issues;
|
|
31516
|
+
}
|
|
31517
|
+
});
|
|
31518
|
+
}
|
|
31519
|
+
});
|
|
31520
|
+
|
|
31521
|
+
// src/rules/go/struct-tag-inconsistency.ts
|
|
31522
|
+
var JSON_TAG_REGEX, goStructTagInconsistencyRule;
|
|
31523
|
+
var init_struct_tag_inconsistency = __esm({
|
|
31524
|
+
"src/rules/go/struct-tag-inconsistency.ts"() {
|
|
31525
|
+
"use strict";
|
|
31526
|
+
init_rule();
|
|
31527
|
+
JSON_TAG_REGEX = /`json:"([^",]+)(?:,([^"]+))?"`/g;
|
|
31528
|
+
goStructTagInconsistencyRule = createRule({
|
|
31529
|
+
id: "go/struct-tag-inconsistency",
|
|
31530
|
+
category: "typo",
|
|
31531
|
+
severity: "low",
|
|
31532
|
+
aiSpecific: true,
|
|
31533
|
+
description: 'Struct fields mix json:"foo" and json:"foo,omitempty" \u2014 pick one convention per struct',
|
|
31534
|
+
create(_context) {
|
|
31535
|
+
return {};
|
|
31536
|
+
},
|
|
31537
|
+
analyze(_context, facts) {
|
|
31538
|
+
const issues = [];
|
|
31539
|
+
const source = facts.v2?._source;
|
|
31540
|
+
if (!source) return issues;
|
|
31541
|
+
const structRegex = /type\s+[A-Z][A-Za-z0-9_]*\s+struct\s*\{/g;
|
|
31542
|
+
let structMatch;
|
|
31543
|
+
while ((structMatch = structRegex.exec(source)) !== null) {
|
|
31544
|
+
const startIdx = structMatch.index;
|
|
31545
|
+
const openBrace = source.indexOf("{", startIdx);
|
|
31546
|
+
if (openBrace < 0) continue;
|
|
31547
|
+
let depth = 1;
|
|
31548
|
+
let i = openBrace + 1;
|
|
31549
|
+
while (i < source.length && depth > 0) {
|
|
31550
|
+
const ch = source[i];
|
|
31551
|
+
if (ch === "{") depth++;
|
|
31552
|
+
else if (ch === "}") depth--;
|
|
31553
|
+
i++;
|
|
31554
|
+
}
|
|
31555
|
+
const structBody = source.slice(openBrace, i);
|
|
31556
|
+
const structLine = source.slice(0, startIdx).split("\n").length;
|
|
31557
|
+
const styleCount = {};
|
|
31558
|
+
const tagMatches = [];
|
|
31559
|
+
let m;
|
|
31560
|
+
JSON_TAG_REGEX.lastIndex = 0;
|
|
31561
|
+
while ((m = JSON_TAG_REGEX.exec(structBody)) !== null) {
|
|
31562
|
+
const tag = m[1];
|
|
31563
|
+
const options = m[2] ?? "";
|
|
31564
|
+
const style = options ? "with-options" : "no-options";
|
|
31565
|
+
styleCount[style] = (styleCount[style] ?? 0) + 1;
|
|
31566
|
+
tagMatches.push({ tag, style, idx: openBrace + m.index });
|
|
31567
|
+
}
|
|
31568
|
+
const styles = Object.keys(styleCount);
|
|
31569
|
+
if (styles.length < 2 || tagMatches.length < 2) continue;
|
|
31570
|
+
const dominant = styles.reduce(
|
|
31571
|
+
(a, b) => (styleCount[a] ?? 0) >= (styleCount[b] ?? 0) ? a : b
|
|
31572
|
+
);
|
|
31573
|
+
const minority = tagMatches.filter((t) => t.style !== dominant);
|
|
31574
|
+
if (minority.length === 0) continue;
|
|
31575
|
+
for (const m2 of minority) {
|
|
31576
|
+
const line = source.slice(0, m2.idx).split("\n").length;
|
|
31577
|
+
issues.push({
|
|
31578
|
+
ruleId: "go/struct-tag-inconsistency",
|
|
31579
|
+
category: "typo",
|
|
31580
|
+
severity: "low",
|
|
31581
|
+
aiSpecific: true,
|
|
31582
|
+
message: `Struct mixes json tag styles \u2014 this field uses "json:"${m2.tag}${m2.style === "with-options" ? ",..." : ""}"" but the dominant style is ${dominant === "with-options" ? "with options (e.g. omitempty)" : "no options"}`,
|
|
31583
|
+
line,
|
|
31584
|
+
column: 1,
|
|
31585
|
+
advice: 'Pick one tag style per struct. If most fields are `json:"foo"`, this field should be too. Real Go code maintains consistency within a struct (or within a package). Reference: go/struct-tag-inconsistency v0.19.'
|
|
31586
|
+
});
|
|
31587
|
+
}
|
|
31588
|
+
if (issues.length > 0) break;
|
|
31589
|
+
}
|
|
31590
|
+
return issues;
|
|
31591
|
+
}
|
|
31592
|
+
});
|
|
31593
|
+
}
|
|
31594
|
+
});
|
|
31595
|
+
|
|
31356
31596
|
// src/rules/layout/gap-monopoly.ts
|
|
31357
31597
|
var GAP_RE, gapMonopolyRule;
|
|
31358
31598
|
var init_gap_monopoly = __esm({
|
|
@@ -38641,91 +38881,6 @@ var init_key_prop_missing = __esm({
|
|
|
38641
38881
|
}
|
|
38642
38882
|
});
|
|
38643
38883
|
|
|
38644
|
-
// src/rules/logic/ks-distribution-shift.ts
|
|
38645
|
-
function extractFileFeatures(source) {
|
|
38646
|
-
const lines = source.split("\n");
|
|
38647
|
-
const lineLengths = lines.map((l) => l.length);
|
|
38648
|
-
const identifierLengths = [];
|
|
38649
|
-
const idRe = /[A-Za-z_$][A-Za-z0-9_$]*/g;
|
|
38650
|
-
let m;
|
|
38651
|
-
while ((m = idRe.exec(source)) !== null) {
|
|
38652
|
-
identifierLengths.push(m[0].length);
|
|
38653
|
-
}
|
|
38654
|
-
const commentDensity = lines.map((l) => {
|
|
38655
|
-
const trimmed = l.trim();
|
|
38656
|
-
if (trimmed.length === 0) return 0;
|
|
38657
|
-
const commentChars = (trimmed.match(/^\/\/.*$/)?.[0]?.length ?? 0) + (trimmed.match(/^\s*\/\*.*?\*\/\s*$/)?.at(0)?.length ?? 0);
|
|
38658
|
-
return commentChars / trimmed.length;
|
|
38659
|
-
});
|
|
38660
|
-
return { lineLengths, identifierLengths, commentDensity };
|
|
38661
|
-
}
|
|
38662
|
-
var MIN_SAMPLES_PER_FEATURE, ksDistributionShiftRule;
|
|
38663
|
-
var init_ks_distribution_shift = __esm({
|
|
38664
|
-
"src/rules/logic/ks-distribution-shift.ts"() {
|
|
38665
|
-
"use strict";
|
|
38666
|
-
init_rule();
|
|
38667
|
-
init_dist2();
|
|
38668
|
-
init_corpus_baselines2();
|
|
38669
|
-
MIN_SAMPLES_PER_FEATURE = 20;
|
|
38670
|
-
ksDistributionShiftRule = createRule({
|
|
38671
|
-
id: "logic/ks-distribution-shift",
|
|
38672
|
-
category: "logic",
|
|
38673
|
-
severity: "medium",
|
|
38674
|
-
aiSpecific: false,
|
|
38675
|
-
description: "Multi-feature Kolmogorov\u2013Smirnov distribution-shift vs corpus baseline (Bonferroni-corrected). Peer-reviewed ML distribution-shift detector (arXiv:2510.15996, Oct 2025).",
|
|
38676
|
-
create(context) {
|
|
38677
|
-
return context;
|
|
38678
|
-
},
|
|
38679
|
-
analyze(_context, facts) {
|
|
38680
|
-
const issues = [];
|
|
38681
|
-
if (!facts.v2) return issues;
|
|
38682
|
-
const source = facts.v2._source ?? "";
|
|
38683
|
-
if (source.length < 200) return issues;
|
|
38684
|
-
const features = extractFileFeatures(source);
|
|
38685
|
-
const samples = /* @__PURE__ */ new Map([
|
|
38686
|
-
["lineLengths", features.lineLengths],
|
|
38687
|
-
["identifierLengths", features.identifierLengths],
|
|
38688
|
-
["commentDensity", features.commentDensity]
|
|
38689
|
-
]);
|
|
38690
|
-
const baselines = getCorpusBaselines();
|
|
38691
|
-
const baselinesMap = /* @__PURE__ */ new Map();
|
|
38692
|
-
if (baselines) {
|
|
38693
|
-
baselinesMap.set("lineLengths", baselines.features.lineLengths.sample);
|
|
38694
|
-
baselinesMap.set("identifierLengths", baselines.features.identifierLengths.sample);
|
|
38695
|
-
baselinesMap.set("commentDensity", baselines.features.commentDensity.sample);
|
|
38696
|
-
} else {
|
|
38697
|
-
baselinesMap.set("lineLengths", [20, 25, 30, 32, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100]);
|
|
38698
|
-
baselinesMap.set("identifierLengths", [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 24, 28]);
|
|
38699
|
-
baselinesMap.set("commentDensity", [0, 0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5]);
|
|
38700
|
-
}
|
|
38701
|
-
for (const [name, vals] of samples) {
|
|
38702
|
-
if (vals.length < MIN_SAMPLES_PER_FEATURE) samples.delete(name);
|
|
38703
|
-
}
|
|
38704
|
-
if (samples.size === 0) return issues;
|
|
38705
|
-
const result = multiFeatureKsTest(samples, baselinesMap, 0.05);
|
|
38706
|
-
if (!result.anySignificant) return issues;
|
|
38707
|
-
const shifted = result.significantFeatures.join(", ");
|
|
38708
|
-
const details = result.significantFeatures.map((name) => {
|
|
38709
|
-
const r = result.perFeature.get(name);
|
|
38710
|
-
if (!r) return name;
|
|
38711
|
-
return `${name} (D=${r.statistic.toFixed(3)}, p=${r.pValue.toExponential(2)})`;
|
|
38712
|
-
}).join("; ");
|
|
38713
|
-
issues.push({
|
|
38714
|
-
ruleId: "logic/ks-distribution-shift",
|
|
38715
|
-
category: "logic",
|
|
38716
|
-
severity: "medium",
|
|
38717
|
-
aiSpecific: true,
|
|
38718
|
-
message: `Distribution shift detected on ${result.significantFeatures.length} of ${result.perFeature.size} features (Bonferroni \u03B1=${result.bonferroniAlpha.toExponential(2)}). Features: ${shifted}. Detail: ${details}.`,
|
|
38719
|
-
line: 1,
|
|
38720
|
-
column: 1,
|
|
38721
|
-
advice: "Inspect the shifted features. KS detects both AI anomalies and production-rot anomalies (it is symmetric); combine with Heaps/Zipf for AI-specific signal."
|
|
38722
|
-
});
|
|
38723
|
-
return issues;
|
|
38724
|
-
}
|
|
38725
|
-
});
|
|
38726
|
-
}
|
|
38727
|
-
});
|
|
38728
|
-
|
|
38729
38884
|
// src/rules/logic/math-any-density.ts
|
|
38730
38885
|
var ANY_PER_100_LINES, MIN_ABSOLUTE, COLON, ANY_KEYWORD, ANY_TYPE_RE, MSG_FRAGMENT, RULE_DESCRIPTION, RULE_MESSAGE, RULE_ADVICE, mathAnyDensityRule;
|
|
38731
38886
|
var init_math_any_density = __esm({
|
|
@@ -38795,13 +38950,13 @@ var init_math_any_density = __esm({
|
|
|
38795
38950
|
});
|
|
38796
38951
|
|
|
38797
38952
|
// src/rules/logic/math-console-log-storm.ts
|
|
38798
|
-
var
|
|
38953
|
+
var WINDOW_SIZE2, STORM_THRESHOLD, CONSOLE_LOG_RE, mathConsoleLogStormRule;
|
|
38799
38954
|
var init_math_console_log_storm = __esm({
|
|
38800
38955
|
"src/rules/logic/math-console-log-storm.ts"() {
|
|
38801
38956
|
"use strict";
|
|
38802
38957
|
init_rule();
|
|
38803
38958
|
init_utils();
|
|
38804
|
-
|
|
38959
|
+
WINDOW_SIZE2 = 30;
|
|
38805
38960
|
STORM_THRESHOLD = 5;
|
|
38806
38961
|
CONSOLE_LOG_RE = /\bconsole\.log\s*\(/g;
|
|
38807
38962
|
mathConsoleLogStormRule = createRule({
|
|
@@ -38832,7 +38987,7 @@ var init_math_console_log_storm = __esm({
|
|
|
38832
38987
|
let maxEndLine = 0;
|
|
38833
38988
|
let i = 0;
|
|
38834
38989
|
for (let j = 0; j < lines.length; j++) {
|
|
38835
|
-
while (lines[j] - lines[i] >
|
|
38990
|
+
while (lines[j] - lines[i] > WINDOW_SIZE2) i++;
|
|
38836
38991
|
const count = j - i + 1;
|
|
38837
38992
|
if (count > maxCount) {
|
|
38838
38993
|
maxCount = count;
|
|
@@ -38846,7 +39001,7 @@ var init_math_console_log_storm = __esm({
|
|
|
38846
39001
|
category: "logic",
|
|
38847
39002
|
severity: "high",
|
|
38848
39003
|
aiSpecific: true,
|
|
38849
|
-
message: `${maxCount} console.log calls clustered in a ${
|
|
39004
|
+
message: `${maxCount} console.log calls clustered in a ${WINDOW_SIZE2}-line window ending at line ${maxEndLine}. AI debug-sprays logs in a single function; humans use one strategic log.`,
|
|
38850
39005
|
line: firstIdx >= 0 ? lines[firstIdx] : 1,
|
|
38851
39006
|
column: firstIdx >= 0 ? columns[firstIdx] : 1,
|
|
38852
39007
|
advice: "Replace debug logs with a proper debugger or logger.debug() \u2014 remove all console.log before shipping."
|
|
@@ -41670,6 +41825,251 @@ var init_weak_assertion = __esm({
|
|
|
41670
41825
|
}
|
|
41671
41826
|
});
|
|
41672
41827
|
|
|
41828
|
+
// src/rules/ts/enum-vs-as-const.ts
|
|
41829
|
+
var ENUM_DECL_REGEX, tsEnumVsAsConstRule;
|
|
41830
|
+
var init_enum_vs_as_const = __esm({
|
|
41831
|
+
"src/rules/ts/enum-vs-as-const.ts"() {
|
|
41832
|
+
"use strict";
|
|
41833
|
+
init_rule();
|
|
41834
|
+
ENUM_DECL_REGEX = /^[ \t]*(?:export\s+)?(?:const\s+)?enum\s+[A-Z_][A-Za-z0-9_]*\s*\{/gm;
|
|
41835
|
+
tsEnumVsAsConstRule = createRule({
|
|
41836
|
+
id: "ts/enum-vs-as-const",
|
|
41837
|
+
category: "typo",
|
|
41838
|
+
severity: "low",
|
|
41839
|
+
aiSpecific: true,
|
|
41840
|
+
description: "Uses `enum` \u2014 modern TS prefers `as const` objects",
|
|
41841
|
+
create(_context) {
|
|
41842
|
+
return {};
|
|
41843
|
+
},
|
|
41844
|
+
analyze(_context, facts) {
|
|
41845
|
+
const issues = [];
|
|
41846
|
+
const source = facts.v2?._source;
|
|
41847
|
+
if (!source) return issues;
|
|
41848
|
+
let match;
|
|
41849
|
+
ENUM_DECL_REGEX.lastIndex = 0;
|
|
41850
|
+
while ((match = ENUM_DECL_REGEX.exec(source)) !== null) {
|
|
41851
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
41852
|
+
issues.push({
|
|
41853
|
+
ruleId: "ts/enum-vs-as-const",
|
|
41854
|
+
category: "typo",
|
|
41855
|
+
severity: "low",
|
|
41856
|
+
aiSpecific: true,
|
|
41857
|
+
message: `'enum' is an AI / older-TS pattern \u2014 prefer 'as const' for a frozen object literal`,
|
|
41858
|
+
line,
|
|
41859
|
+
column: match[0].indexOf("enum") + 1,
|
|
41860
|
+
advice: 'Replace `enum Foo { A, B }` with `const Foo = { A: "A", B: "B" } as const` (or `const Foo = ["A", "B"] as const`). Modern TS style guides (Google, TS-eslint) prefer `as const` because enums have surprising runtime semantics. Reference: ts/enum-vs-as-const v0.19.'
|
|
41861
|
+
});
|
|
41862
|
+
}
|
|
41863
|
+
return issues;
|
|
41864
|
+
}
|
|
41865
|
+
});
|
|
41866
|
+
}
|
|
41867
|
+
});
|
|
41868
|
+
|
|
41869
|
+
// src/rules/ts/excessive-type-assertion.ts
|
|
41870
|
+
var DEFAULT_MAX, FN_DECL_REGEX, AS_REGEX, tsExcessiveTypeAssertionRule;
|
|
41871
|
+
var init_excessive_type_assertion = __esm({
|
|
41872
|
+
"src/rules/ts/excessive-type-assertion.ts"() {
|
|
41873
|
+
"use strict";
|
|
41874
|
+
init_rule();
|
|
41875
|
+
DEFAULT_MAX = 3;
|
|
41876
|
+
FN_DECL_REGEX = /^[ \t]*(?:export\s+)?(?:async\s+)?function\s+[A-Za-z_$][\w$]*\s*\([^)]*\)\s*[^{]*\{|^\s*(?:export\s+)?(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*[^=]+)?\s*=>\s*\{|^\s*(?:public|private|protected|static|async|abstract|readonly|\s)*\s*[A-Za-z_$][\w$]*\s*\([^)]*\)\s*:\s*[^{]*\{/gm;
|
|
41877
|
+
AS_REGEX = /(?<![\w$])as\s+(?!const)([A-Z][\w$.,<>\[\]|&]*)/g;
|
|
41878
|
+
tsExcessiveTypeAssertionRule = createRule({
|
|
41879
|
+
id: "ts/excessive-type-assertion",
|
|
41880
|
+
category: "typo",
|
|
41881
|
+
severity: "low",
|
|
41882
|
+
aiSpecific: true,
|
|
41883
|
+
description: "Function with >3 `as` type assertions \u2014 likely AI fighting the type system",
|
|
41884
|
+
create(_context) {
|
|
41885
|
+
return { maxAssertionsPerFunction: DEFAULT_MAX };
|
|
41886
|
+
},
|
|
41887
|
+
analyze(context, facts) {
|
|
41888
|
+
const issues = [];
|
|
41889
|
+
const source = facts.v2?._source;
|
|
41890
|
+
if (!source) return issues;
|
|
41891
|
+
let match;
|
|
41892
|
+
FN_DECL_REGEX.lastIndex = 0;
|
|
41893
|
+
while ((match = FN_DECL_REGEX.exec(source)) !== null) {
|
|
41894
|
+
const startIdx = match.index;
|
|
41895
|
+
const openBraceIdx = source.indexOf("{", startIdx);
|
|
41896
|
+
if (openBraceIdx < 0) continue;
|
|
41897
|
+
let depth = 1;
|
|
41898
|
+
let i = openBraceIdx + 1;
|
|
41899
|
+
while (i < source.length && depth > 0) {
|
|
41900
|
+
const ch = source[i];
|
|
41901
|
+
if (ch === "{") depth++;
|
|
41902
|
+
else if (ch === "}") depth--;
|
|
41903
|
+
i++;
|
|
41904
|
+
}
|
|
41905
|
+
const body = source.slice(openBraceIdx, i);
|
|
41906
|
+
const line = source.slice(0, startIdx).split("\n").length;
|
|
41907
|
+
let asCount = 0;
|
|
41908
|
+
const seen = /* @__PURE__ */ new Set();
|
|
41909
|
+
let asMatch;
|
|
41910
|
+
AS_REGEX.lastIndex = 0;
|
|
41911
|
+
while ((asMatch = AS_REGEX.exec(body)) !== null) {
|
|
41912
|
+
const captured = asMatch[1];
|
|
41913
|
+
if (seen.has(captured)) continue;
|
|
41914
|
+
seen.add(captured);
|
|
41915
|
+
asCount++;
|
|
41916
|
+
}
|
|
41917
|
+
if (asCount > context.maxAssertionsPerFunction) {
|
|
41918
|
+
issues.push({
|
|
41919
|
+
ruleId: "ts/excessive-type-assertion",
|
|
41920
|
+
category: "typo",
|
|
41921
|
+
severity: "low",
|
|
41922
|
+
aiSpecific: true,
|
|
41923
|
+
message: `Function has ${asCount} 'as' assertions (max ${context.maxAssertionsPerFunction}) \u2014 likely AI fighting the type system`,
|
|
41924
|
+
line,
|
|
41925
|
+
column: 1,
|
|
41926
|
+
advice: "More than 3 `as` assertions in a function is a strong signal that the type is wrong, not the code. Fix the type definition (or use a type guard) instead of bypassing the type system. Reference: ts/excessive-type-assertion v0.19."
|
|
41927
|
+
});
|
|
41928
|
+
}
|
|
41929
|
+
}
|
|
41930
|
+
return issues;
|
|
41931
|
+
}
|
|
41932
|
+
});
|
|
41933
|
+
}
|
|
41934
|
+
});
|
|
41935
|
+
|
|
41936
|
+
// src/rules/ts/import-type-misuse.ts
|
|
41937
|
+
var INLINE_TYPE_IMPORT_REGEX, tsImportTypeMisuseRule;
|
|
41938
|
+
var init_import_type_misuse = __esm({
|
|
41939
|
+
"src/rules/ts/import-type-misuse.ts"() {
|
|
41940
|
+
"use strict";
|
|
41941
|
+
init_rule();
|
|
41942
|
+
INLINE_TYPE_IMPORT_REGEX = /^[ \t]*import\s*\{[^}]*\btype\s+[A-Za-z_]/gm;
|
|
41943
|
+
tsImportTypeMisuseRule = createRule({
|
|
41944
|
+
id: "ts/import-type-misuse",
|
|
41945
|
+
category: "typo",
|
|
41946
|
+
severity: "low",
|
|
41947
|
+
aiSpecific: true,
|
|
41948
|
+
description: "Inline `import { type X }` \u2014 prefer `import type { X }` for clarity",
|
|
41949
|
+
create(_context) {
|
|
41950
|
+
return {};
|
|
41951
|
+
},
|
|
41952
|
+
analyze(_context, facts) {
|
|
41953
|
+
const issues = [];
|
|
41954
|
+
const source = facts.v2?._source;
|
|
41955
|
+
if (!source) return issues;
|
|
41956
|
+
let match;
|
|
41957
|
+
INLINE_TYPE_IMPORT_REGEX.lastIndex = 0;
|
|
41958
|
+
while ((match = INLINE_TYPE_IMPORT_REGEX.exec(source)) !== null) {
|
|
41959
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
41960
|
+
issues.push({
|
|
41961
|
+
ruleId: "ts/import-type-misuse",
|
|
41962
|
+
category: "typo",
|
|
41963
|
+
severity: "low",
|
|
41964
|
+
aiSpecific: true,
|
|
41965
|
+
message: "Inline `type` in a value import \u2014 split into a separate `import type` statement",
|
|
41966
|
+
line,
|
|
41967
|
+
column: match[0].indexOf("type") + 1,
|
|
41968
|
+
advice: 'Use `import type { X } from "..."` instead of `import { type X } from "..."`. The inline form is valid but the split form is more common in real codebases and makes the type-only intent unambiguous. Reference: ts/import-type-misuse v0.19.'
|
|
41969
|
+
});
|
|
41970
|
+
}
|
|
41971
|
+
return issues;
|
|
41972
|
+
}
|
|
41973
|
+
});
|
|
41974
|
+
}
|
|
41975
|
+
});
|
|
41976
|
+
|
|
41977
|
+
// src/rules/ts/never-vs-unknown.ts
|
|
41978
|
+
var NEVER_RETURN_REGEX, THROW_OR_LOOP_REGEX, tsNeverVsUnknownRule;
|
|
41979
|
+
var init_never_vs_unknown = __esm({
|
|
41980
|
+
"src/rules/ts/never-vs-unknown.ts"() {
|
|
41981
|
+
"use strict";
|
|
41982
|
+
init_rule();
|
|
41983
|
+
NEVER_RETURN_REGEX = /^[ \t]*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)[^{]*:\s*(?:Promise<)?never\b[^{]*\{/gm;
|
|
41984
|
+
THROW_OR_LOOP_REGEX = /\b(?:throw\b|while\s*\(|for\s*\(|process\.exit\b|System\.exit\b)/;
|
|
41985
|
+
tsNeverVsUnknownRule = createRule({
|
|
41986
|
+
id: "ts/never-vs-unknown",
|
|
41987
|
+
category: "typo",
|
|
41988
|
+
severity: "low",
|
|
41989
|
+
aiSpecific: true,
|
|
41990
|
+
description: "Return type `: never` but body has no throw / loop / exit \u2014 likely AI misuse",
|
|
41991
|
+
create(_context) {
|
|
41992
|
+
return {};
|
|
41993
|
+
},
|
|
41994
|
+
analyze(_context, facts) {
|
|
41995
|
+
const issues = [];
|
|
41996
|
+
const source = facts.v2?._source;
|
|
41997
|
+
if (!source) return issues;
|
|
41998
|
+
let match;
|
|
41999
|
+
NEVER_RETURN_REGEX.lastIndex = 0;
|
|
42000
|
+
while ((match = NEVER_RETURN_REGEX.exec(source)) !== null) {
|
|
42001
|
+
const fnName = match[1];
|
|
42002
|
+
const startIdx = match.index;
|
|
42003
|
+
const line = source.slice(0, startIdx).split("\n").length;
|
|
42004
|
+
const openBraceIdx = source.indexOf("{", startIdx);
|
|
42005
|
+
if (openBraceIdx < 0) continue;
|
|
42006
|
+
let depth = 1;
|
|
42007
|
+
let i = openBraceIdx + 1;
|
|
42008
|
+
while (i < source.length && depth > 0) {
|
|
42009
|
+
const ch = source[i];
|
|
42010
|
+
if (ch === "{") depth++;
|
|
42011
|
+
else if (ch === "}") depth--;
|
|
42012
|
+
i++;
|
|
42013
|
+
}
|
|
42014
|
+
const body = source.slice(openBraceIdx, i);
|
|
42015
|
+
if (THROW_OR_LOOP_REGEX.test(body)) continue;
|
|
42016
|
+
issues.push({
|
|
42017
|
+
ruleId: "ts/never-vs-unknown",
|
|
42018
|
+
category: "typo",
|
|
42019
|
+
severity: "low",
|
|
42020
|
+
aiSpecific: true,
|
|
42021
|
+
message: `Function '${fnName}' returns 'never' but its body has no throw, loop, or exit \u2014 likely AI misuse`,
|
|
42022
|
+
line,
|
|
42023
|
+
column: match[0].indexOf("never") + 1,
|
|
42024
|
+
advice: 'The `never` return type means "this function never returns". Reserve it for functions that always throw, always loop, or always exit. For "impossible" branches, use a concrete type (`void`, `Error`, `unknown`) and an exhaustive check. Reference: ts/never-vs-unknown v0.19.'
|
|
42025
|
+
});
|
|
42026
|
+
}
|
|
42027
|
+
return issues;
|
|
42028
|
+
}
|
|
42029
|
+
});
|
|
42030
|
+
}
|
|
42031
|
+
});
|
|
42032
|
+
|
|
42033
|
+
// src/rules/ts/optional-chain-overuse.ts
|
|
42034
|
+
var DEFAULT_MIN_CHAIN_LENGTH, tsOptionalChainOveruseRule;
|
|
42035
|
+
var init_optional_chain_overuse = __esm({
|
|
42036
|
+
"src/rules/ts/optional-chain-overuse.ts"() {
|
|
42037
|
+
"use strict";
|
|
42038
|
+
init_rule();
|
|
42039
|
+
DEFAULT_MIN_CHAIN_LENGTH = 5;
|
|
42040
|
+
tsOptionalChainOveruseRule = createRule({
|
|
42041
|
+
id: "ts/optional-chain-overuse",
|
|
42042
|
+
category: "logic",
|
|
42043
|
+
severity: "low",
|
|
42044
|
+
aiSpecific: true,
|
|
42045
|
+
description: "Optional chaining (?.) used 5+ times in a single chain \u2014 AI tends to chain rather than narrow",
|
|
42046
|
+
create(_context) {
|
|
42047
|
+
return { minChainLength: DEFAULT_MIN_CHAIN_LENGTH };
|
|
42048
|
+
},
|
|
42049
|
+
analyze(context, facts) {
|
|
42050
|
+
const issues = [];
|
|
42051
|
+
const expressions = facts.v2.logic?.logicalExpressions;
|
|
42052
|
+
if (!expressions) return issues;
|
|
42053
|
+
for (const expression of expressions) {
|
|
42054
|
+
if (expression.depth >= context.minChainLength && expression.isOptionalChainLike) {
|
|
42055
|
+
issues.push({
|
|
42056
|
+
ruleId: "ts/optional-chain-overuse",
|
|
42057
|
+
category: "logic",
|
|
42058
|
+
severity: "low",
|
|
42059
|
+
aiSpecific: true,
|
|
42060
|
+
message: `Optional chain depth ${expression.depth} \u2014 break with an intermediate variable or guard clause`,
|
|
42061
|
+
line: expression.line,
|
|
42062
|
+
column: expression.column,
|
|
42063
|
+
advice: "Long optional chains are an AI pattern. Use a guard clause (`if (!value) return`) or intermediate variables to make the narrowing explicit. Reference: ts/optional-chain-overuse v0.19."
|
|
42064
|
+
});
|
|
42065
|
+
}
|
|
42066
|
+
}
|
|
42067
|
+
return issues;
|
|
42068
|
+
}
|
|
42069
|
+
});
|
|
42070
|
+
}
|
|
42071
|
+
});
|
|
42072
|
+
|
|
41673
42073
|
// src/rules/typo/calc-fontsize.ts
|
|
41674
42074
|
var FONT_SIZE_RE, calcFontsizeRule;
|
|
41675
42075
|
var init_calc_fontsize = __esm({
|
|
@@ -45811,6 +46211,10 @@ var init_builtins = __esm({
|
|
|
45811
46211
|
init_expired_code_example();
|
|
45812
46212
|
init_stale_function_reference();
|
|
45813
46213
|
init_stale_package_reference();
|
|
46214
|
+
init_identical_block();
|
|
46215
|
+
init_error_wrap_without_context();
|
|
46216
|
+
init_nil_slice_vs_empty();
|
|
46217
|
+
init_struct_tag_inconsistency();
|
|
45814
46218
|
init_gap_monopoly();
|
|
45815
46219
|
init_math_element_uniformity();
|
|
45816
46220
|
init_math_grid_uniformity();
|
|
@@ -45820,7 +46224,6 @@ var init_builtins = __esm({
|
|
|
45820
46224
|
init_ghost_defensive();
|
|
45821
46225
|
init_heaps_deviation();
|
|
45822
46226
|
init_key_prop_missing();
|
|
45823
|
-
init_ks_distribution_shift();
|
|
45824
46227
|
init_math_any_density();
|
|
45825
46228
|
init_math_console_log_storm();
|
|
45826
46229
|
init_math_gini_class_usage();
|
|
@@ -45854,6 +46257,11 @@ var init_builtins = __esm({
|
|
|
45854
46257
|
init_fake_placeholder();
|
|
45855
46258
|
init_missing_edge_case();
|
|
45856
46259
|
init_weak_assertion();
|
|
46260
|
+
init_enum_vs_as_const();
|
|
46261
|
+
init_excessive_type_assertion();
|
|
46262
|
+
init_import_type_misuse();
|
|
46263
|
+
init_never_vs_unknown();
|
|
46264
|
+
init_optional_chain_overuse();
|
|
45857
46265
|
init_calc_fontsize();
|
|
45858
46266
|
init_calc_raw_px();
|
|
45859
46267
|
init_clamp_offscale();
|
|
@@ -45916,6 +46324,10 @@ var init_builtins = __esm({
|
|
|
45916
46324
|
expiredCodeExampleRule,
|
|
45917
46325
|
staleFunctionReferenceRule,
|
|
45918
46326
|
stalePackageReferenceRule,
|
|
46327
|
+
dupIdenticalBlockRule,
|
|
46328
|
+
goErrorWrapWithoutContextRule,
|
|
46329
|
+
goNilSliceVsEmptyRule,
|
|
46330
|
+
goStructTagInconsistencyRule,
|
|
45919
46331
|
gapMonopolyRule,
|
|
45920
46332
|
mathElementUniformityRule,
|
|
45921
46333
|
mathGridUniformityRule,
|
|
@@ -45925,7 +46337,6 @@ var init_builtins = __esm({
|
|
|
45925
46337
|
ghostDefensiveRule,
|
|
45926
46338
|
heapsDeviationRule,
|
|
45927
46339
|
keyPropMissingRule,
|
|
45928
|
-
ksDistributionShiftRule,
|
|
45929
46340
|
mathAnyDensityRule,
|
|
45930
46341
|
mathConsoleLogStormRule,
|
|
45931
46342
|
mathGiniClassUsageRule,
|
|
@@ -45959,6 +46370,11 @@ var init_builtins = __esm({
|
|
|
45959
46370
|
fakePlaceholderRule,
|
|
45960
46371
|
missingEdgeCaseRule,
|
|
45961
46372
|
weakAssertionRule,
|
|
46373
|
+
tsEnumVsAsConstRule,
|
|
46374
|
+
tsExcessiveTypeAssertionRule,
|
|
46375
|
+
tsImportTypeMisuseRule,
|
|
46376
|
+
tsNeverVsUnknownRule,
|
|
46377
|
+
tsOptionalChainOveruseRule,
|
|
45962
46378
|
calcFontsizeRule,
|
|
45963
46379
|
calcRawPxRule,
|
|
45964
46380
|
clampOffscaleRule,
|
|
@@ -48040,7 +48456,7 @@ var init_signal_strength = __esm({
|
|
|
48040
48456
|
precision: 0.9966,
|
|
48041
48457
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
48042
48458
|
verdict: "USEFUL",
|
|
48043
|
-
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=294, FP=1, P=99.7%, FPR=0.00%, lift=251225.49. v7 was USEFUL (TP=231, FP=1, lift=182622.43). v8 was USEFUL (TP=63, FP=0).",
|
|
48459
|
+
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=294, FP=1, P=99.7%, FPR=0.00%, lift=251225.49. v7 was USEFUL (TP=231, FP=1, lift=182622.43). v8 was USEFUL (TP=63, FP=0). v0.19 default-on (explicit defaultOff: false): P 99.7% / 251k lift \u2014 core AI fingerprint.",
|
|
48044
48460
|
aiSpecific: true,
|
|
48045
48461
|
_v7Verdict: "USEFUL",
|
|
48046
48462
|
_v7Lift: 182622.43,
|
|
@@ -48048,7 +48464,8 @@ var init_signal_strength = __esm({
|
|
|
48048
48464
|
_v7FpRate: 0,
|
|
48049
48465
|
_v7Precision: 0.9957,
|
|
48050
48466
|
_v8Verdict: "USEFUL",
|
|
48051
|
-
_v8Lift: 99999
|
|
48467
|
+
_v8Lift: 99999,
|
|
48468
|
+
defaultOff: false
|
|
48052
48469
|
},
|
|
48053
48470
|
"ai/errors-near-eof": {
|
|
48054
48471
|
recall: 0.0948,
|
|
@@ -48297,7 +48714,7 @@ var init_signal_strength = __esm({
|
|
|
48297
48714
|
precision: 0.9512,
|
|
48298
48715
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
48299
48716
|
verdict: "USEFUL",
|
|
48300
|
-
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=468, FP=24, P=95.1%, FPR=0.01%, lift=9990.98. v7 was USEFUL (TP=314, FP=24, lift=7099.57). v8 was USEFUL (TP=154, FP=0).",
|
|
48717
|
+
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=468, FP=24, P=95.1%, FPR=0.01%, lift=9990.98. v7 was USEFUL (TP=314, FP=24, lift=7099.57). v8 was USEFUL (TP=154, FP=0). v0.19 default-on (explicit defaultOff: false): P 95.1% / 9990x lift \u2014 UI bug.",
|
|
48301
48718
|
aiSpecific: true,
|
|
48302
48719
|
_v7Verdict: "USEFUL",
|
|
48303
48720
|
_v7Lift: 7099.57,
|
|
@@ -48305,7 +48722,8 @@ var init_signal_strength = __esm({
|
|
|
48305
48722
|
_v7FpRate: 1e-4,
|
|
48306
48723
|
_v7Precision: 0.929,
|
|
48307
48724
|
_v8Verdict: "USEFUL",
|
|
48308
|
-
_v8Lift: 99999
|
|
48725
|
+
_v8Lift: 99999,
|
|
48726
|
+
defaultOff: false
|
|
48309
48727
|
},
|
|
48310
48728
|
"context/import-path-mismatch": {
|
|
48311
48729
|
recall: 0.0681,
|
|
@@ -48489,7 +48907,7 @@ var init_signal_strength = __esm({
|
|
|
48489
48907
|
precision: 0.8834,
|
|
48490
48908
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
48491
48909
|
verdict: "USEFUL",
|
|
48492
|
-
_calibrationNote: "v8.5 (v0.18.9): v85 verdict=USEFUL, v7 was DORMANT, v8 was USEFUL. v0.18.8 v8a first measurement (1000 files): see docs/research/v0.18.8-dead-rules-measurement.md.",
|
|
48910
|
+
_calibrationNote: "v8.5 (v0.18.9): v85 verdict=USEFUL, v7 was DORMANT, v8 was USEFUL. v0.18.8 v8a first measurement (1000 files): see docs/research/v0.18.8-dead-rules-measurement.md. v0.19 default-on (explicit defaultOff: false): P 88.3% / FPR 0.74% / 120x lift \u2014 code hygiene.",
|
|
48493
48911
|
aiSpecific: true,
|
|
48494
48912
|
_v7Verdict: "DORMANT",
|
|
48495
48913
|
_v7Lift: 1,
|
|
@@ -48497,7 +48915,8 @@ var init_signal_strength = __esm({
|
|
|
48497
48915
|
_v7FpRate: 0,
|
|
48498
48916
|
_v7Precision: 0,
|
|
48499
48917
|
_v8Verdict: "USEFUL",
|
|
48500
|
-
_v8Lift: 32.74
|
|
48918
|
+
_v8Lift: 32.74,
|
|
48919
|
+
defaultOff: false
|
|
48501
48920
|
},
|
|
48502
48921
|
"dead/unused-parameter": {
|
|
48503
48922
|
recall: 8e-4,
|
|
@@ -48585,6 +49004,78 @@ var init_signal_strength = __esm({
|
|
|
48585
49004
|
_v8Verdict: "OK",
|
|
48586
49005
|
_v8Lift: 255.9
|
|
48587
49006
|
},
|
|
49007
|
+
"dup/identical-block": {
|
|
49008
|
+
recall: 0,
|
|
49009
|
+
fpRate: 0,
|
|
49010
|
+
ratio: 1,
|
|
49011
|
+
precision: 0,
|
|
49012
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49013
|
+
verdict: "DORMANT",
|
|
49014
|
+
_calibrationNote: "v0.19: new rule, not yet calibrated. v8.5 calibration does not run dup/* rules. Scheduled for v0.20 calibration on near-dup corpus.",
|
|
49015
|
+
aiSpecific: false,
|
|
49016
|
+
_v7Verdict: "DORMANT",
|
|
49017
|
+
_v7Lift: 1,
|
|
49018
|
+
_v7Recall: 0,
|
|
49019
|
+
_v7FpRate: 0,
|
|
49020
|
+
_v7Precision: 0,
|
|
49021
|
+
_v8Verdict: "DORMANT",
|
|
49022
|
+
_v8Lift: 1,
|
|
49023
|
+
defaultOff: true
|
|
49024
|
+
},
|
|
49025
|
+
"go/error-wrap-without-context": {
|
|
49026
|
+
recall: 0,
|
|
49027
|
+
fpRate: 0,
|
|
49028
|
+
ratio: 1,
|
|
49029
|
+
precision: 0,
|
|
49030
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49031
|
+
verdict: "DORMANT",
|
|
49032
|
+
_calibrationNote: "v0.19: new rule (fmt.Errorf wrap with generic message \u2014 needs operation context). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49033
|
+
aiSpecific: true,
|
|
49034
|
+
_v7Verdict: "DORMANT",
|
|
49035
|
+
_v7Lift: 1,
|
|
49036
|
+
_v7Recall: 0,
|
|
49037
|
+
_v7FpRate: 0,
|
|
49038
|
+
_v7Precision: 0,
|
|
49039
|
+
_v8Verdict: "DORMANT",
|
|
49040
|
+
_v8Lift: 1,
|
|
49041
|
+
defaultOff: true
|
|
49042
|
+
},
|
|
49043
|
+
"go/nil-slice-vs-empty": {
|
|
49044
|
+
recall: 0,
|
|
49045
|
+
fpRate: 0,
|
|
49046
|
+
ratio: 1,
|
|
49047
|
+
precision: 0,
|
|
49048
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49049
|
+
verdict: "DORMANT",
|
|
49050
|
+
_calibrationNote: "v0.19: new rule (Variable declared nil slice but assigned empty slice). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49051
|
+
aiSpecific: true,
|
|
49052
|
+
_v7Verdict: "DORMANT",
|
|
49053
|
+
_v7Lift: 1,
|
|
49054
|
+
_v7Recall: 0,
|
|
49055
|
+
_v7FpRate: 0,
|
|
49056
|
+
_v7Precision: 0,
|
|
49057
|
+
_v8Verdict: "DORMANT",
|
|
49058
|
+
_v8Lift: 1,
|
|
49059
|
+
defaultOff: true
|
|
49060
|
+
},
|
|
49061
|
+
"go/struct-tag-inconsistency": {
|
|
49062
|
+
recall: 0,
|
|
49063
|
+
fpRate: 0,
|
|
49064
|
+
ratio: 1,
|
|
49065
|
+
precision: 0,
|
|
49066
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49067
|
+
verdict: "DORMANT",
|
|
49068
|
+
_calibrationNote: "v0.19: new rule (Struct fields mix json tag styles). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49069
|
+
aiSpecific: true,
|
|
49070
|
+
_v7Verdict: "DORMANT",
|
|
49071
|
+
_v7Lift: 1,
|
|
49072
|
+
_v7Recall: 0,
|
|
49073
|
+
_v7FpRate: 0,
|
|
49074
|
+
_v7Precision: 0,
|
|
49075
|
+
_v8Verdict: "DORMANT",
|
|
49076
|
+
_v8Lift: 1,
|
|
49077
|
+
defaultOff: true
|
|
49078
|
+
},
|
|
48588
49079
|
"layout/forced-layout": {
|
|
48589
49080
|
recall: 0,
|
|
48590
49081
|
fpRate: 0,
|
|
@@ -48713,7 +49204,7 @@ var init_signal_strength = __esm({
|
|
|
48713
49204
|
precision: 0.8889,
|
|
48714
49205
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
48715
49206
|
verdict: "USEFUL",
|
|
48716
|
-
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=16, FP=2, P=88.9%, FPR=0.00%, lift=112035.56. v7 was USEFUL (TP=15, FP=2, lift=80917.50). v8 was USEFUL (TP=1, FP=0).",
|
|
49207
|
+
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=16, FP=2, P=88.9%, FPR=0.00%, lift=112035.56. v7 was USEFUL (TP=15, FP=2, lift=80917.50). v8 was USEFUL (TP=1, FP=0). v0.19 default-on (explicit defaultOff: false): P 88.9% / 112k lift \u2014 code smell.",
|
|
48717
49208
|
aiSpecific: true,
|
|
48718
49209
|
_v7Verdict: "USEFUL",
|
|
48719
49210
|
_v7Lift: 80917.5,
|
|
@@ -48721,7 +49212,8 @@ var init_signal_strength = __esm({
|
|
|
48721
49212
|
_v7FpRate: 0,
|
|
48722
49213
|
_v7Precision: 0.8824,
|
|
48723
49214
|
_v8Verdict: "USEFUL",
|
|
48724
|
-
_v8Lift: 99999
|
|
49215
|
+
_v8Lift: 99999,
|
|
49216
|
+
defaultOff: false
|
|
48725
49217
|
},
|
|
48726
49218
|
"logic/heaps-deviation": {
|
|
48727
49219
|
recall: 0.0126,
|
|
@@ -48757,24 +49249,6 @@ var init_signal_strength = __esm({
|
|
|
48757
49249
|
_v8Verdict: "USEFUL",
|
|
48758
49250
|
_v8Lift: 1760.69
|
|
48759
49251
|
},
|
|
48760
|
-
"logic/ks-distribution-shift": {
|
|
48761
|
-
recall: 0.6889,
|
|
48762
|
-
fpRate: 0.4411,
|
|
48763
|
-
ratio: 1.46,
|
|
48764
|
-
precision: 0.6457,
|
|
48765
|
-
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
48766
|
-
verdict: "NOISY",
|
|
48767
|
-
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=202658, FP=111193, P=64.6%, FPR=44.11%, lift=1.46. v7 was USEFUL (TP=152391, FP=62358, lift=2.09). v8 was INVERTED (TP=50267, FP=48835).",
|
|
48768
|
-
aiSpecific: false,
|
|
48769
|
-
_v7Verdict: "USEFUL",
|
|
48770
|
-
_v7Lift: 2.09,
|
|
48771
|
-
_v7Recall: 0.6431,
|
|
48772
|
-
_v7FpRate: 0.34,
|
|
48773
|
-
_v7Precision: 0.7096,
|
|
48774
|
-
_v8Verdict: "INVERTED",
|
|
48775
|
-
_v8Lift: 0.71,
|
|
48776
|
-
defaultOff: true
|
|
48777
|
-
},
|
|
48778
49252
|
"logic/math-any-density": {
|
|
48779
49253
|
recall: 17e-4,
|
|
48780
49254
|
fpRate: 13e-4,
|
|
@@ -49140,7 +49614,7 @@ var init_signal_strength = __esm({
|
|
|
49140
49614
|
precision: 1,
|
|
49141
49615
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49142
49616
|
verdict: "USEFUL",
|
|
49143
|
-
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=1, FP=0, P=100.0%, FPR=0.00%, lift=inf. v7 was USEFUL (TP=1, FP=0, lift=inf). v8 was DORMANT (TP=0, FP=0).",
|
|
49617
|
+
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=1, FP=0, P=100.0%, FPR=0.00%, lift=inf. v7 was USEFUL (TP=1, FP=0, lift=inf). v8 was DORMANT (TP=0, FP=0). v0.19 default-on (explicit defaultOff: false): P 100% / inf lift \u2014 security must always be on.",
|
|
49144
49618
|
aiSpecific: true,
|
|
49145
49619
|
_v7Verdict: "USEFUL",
|
|
49146
49620
|
_v7Lift: 99999,
|
|
@@ -49148,7 +49622,8 @@ var init_signal_strength = __esm({
|
|
|
49148
49622
|
_v7FpRate: 0,
|
|
49149
49623
|
_v7Precision: 1,
|
|
49150
49624
|
_v8Verdict: "DORMANT",
|
|
49151
|
-
_v8Lift: 1
|
|
49625
|
+
_v8Lift: 1,
|
|
49626
|
+
defaultOff: false
|
|
49152
49627
|
},
|
|
49153
49628
|
"security/hardcoded-secret": {
|
|
49154
49629
|
recall: 14e-4,
|
|
@@ -49338,6 +49813,96 @@ var init_signal_strength = __esm({
|
|
|
49338
49813
|
_v8Verdict: "USEFUL",
|
|
49339
49814
|
_v8Lift: 651.94
|
|
49340
49815
|
},
|
|
49816
|
+
"ts/enum-vs-as-const": {
|
|
49817
|
+
recall: 0,
|
|
49818
|
+
fpRate: 0,
|
|
49819
|
+
ratio: 1,
|
|
49820
|
+
precision: 0,
|
|
49821
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49822
|
+
verdict: "DORMANT",
|
|
49823
|
+
_calibrationNote: "v0.19: new rule (Uses `enum` \u2014 modern TS prefers `as const`). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49824
|
+
aiSpecific: true,
|
|
49825
|
+
_v7Verdict: "DORMANT",
|
|
49826
|
+
_v7Lift: 1,
|
|
49827
|
+
_v7Recall: 0,
|
|
49828
|
+
_v7FpRate: 0,
|
|
49829
|
+
_v7Precision: 0,
|
|
49830
|
+
_v8Verdict: "DORMANT",
|
|
49831
|
+
_v8Lift: 1,
|
|
49832
|
+
defaultOff: true
|
|
49833
|
+
},
|
|
49834
|
+
"ts/excessive-type-assertion": {
|
|
49835
|
+
recall: 0,
|
|
49836
|
+
fpRate: 0,
|
|
49837
|
+
ratio: 1,
|
|
49838
|
+
precision: 0,
|
|
49839
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49840
|
+
verdict: "DORMANT",
|
|
49841
|
+
_calibrationNote: "v0.19: new rule (Function with >3 `as` assertions \u2014 AI fighting the type system). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49842
|
+
aiSpecific: true,
|
|
49843
|
+
_v7Verdict: "DORMANT",
|
|
49844
|
+
_v7Lift: 1,
|
|
49845
|
+
_v7Recall: 0,
|
|
49846
|
+
_v7FpRate: 0,
|
|
49847
|
+
_v7Precision: 0,
|
|
49848
|
+
_v8Verdict: "DORMANT",
|
|
49849
|
+
_v8Lift: 1,
|
|
49850
|
+
defaultOff: true
|
|
49851
|
+
},
|
|
49852
|
+
"ts/import-type-misuse": {
|
|
49853
|
+
recall: 0,
|
|
49854
|
+
fpRate: 0,
|
|
49855
|
+
ratio: 1,
|
|
49856
|
+
precision: 0,
|
|
49857
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49858
|
+
verdict: "DORMANT",
|
|
49859
|
+
_calibrationNote: "v0.19: new rule (Inline `import { type X }` \u2014 prefer separate `import type`). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49860
|
+
aiSpecific: true,
|
|
49861
|
+
_v7Verdict: "DORMANT",
|
|
49862
|
+
_v7Lift: 1,
|
|
49863
|
+
_v7Recall: 0,
|
|
49864
|
+
_v7FpRate: 0,
|
|
49865
|
+
_v7Precision: 0,
|
|
49866
|
+
_v8Verdict: "DORMANT",
|
|
49867
|
+
_v8Lift: 1,
|
|
49868
|
+
defaultOff: true
|
|
49869
|
+
},
|
|
49870
|
+
"ts/never-vs-unknown": {
|
|
49871
|
+
recall: 0,
|
|
49872
|
+
fpRate: 0,
|
|
49873
|
+
ratio: 1,
|
|
49874
|
+
precision: 0,
|
|
49875
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49876
|
+
verdict: "DORMANT",
|
|
49877
|
+
_calibrationNote: "v0.19: new rule (Return type `never` but body has no throw/loop/exit). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49878
|
+
aiSpecific: true,
|
|
49879
|
+
_v7Verdict: "DORMANT",
|
|
49880
|
+
_v7Lift: 1,
|
|
49881
|
+
_v7Recall: 0,
|
|
49882
|
+
_v7FpRate: 0,
|
|
49883
|
+
_v7Precision: 0,
|
|
49884
|
+
_v8Verdict: "DORMANT",
|
|
49885
|
+
_v8Lift: 1,
|
|
49886
|
+
defaultOff: true
|
|
49887
|
+
},
|
|
49888
|
+
"ts/optional-chain-overuse": {
|
|
49889
|
+
recall: 0,
|
|
49890
|
+
fpRate: 0,
|
|
49891
|
+
ratio: 1,
|
|
49892
|
+
precision: 0,
|
|
49893
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49894
|
+
verdict: "DORMANT",
|
|
49895
|
+
_calibrationNote: "v0.19: new rule (Optional chain depth >= 5 \u2014 AI chains ?. rather than narrowing). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49896
|
+
aiSpecific: true,
|
|
49897
|
+
_v7Verdict: "DORMANT",
|
|
49898
|
+
_v7Lift: 1,
|
|
49899
|
+
_v7Recall: 0,
|
|
49900
|
+
_v7FpRate: 0,
|
|
49901
|
+
_v7Precision: 0,
|
|
49902
|
+
_v8Verdict: "DORMANT",
|
|
49903
|
+
_v8Lift: 1,
|
|
49904
|
+
defaultOff: true
|
|
49905
|
+
},
|
|
49341
49906
|
"typo/calc-fontsize": {
|
|
49342
49907
|
recall: 0,
|
|
49343
49908
|
fpRate: 0,
|
|
@@ -49640,7 +50205,7 @@ var init_signal_strength = __esm({
|
|
|
49640
50205
|
precision: 0.9774,
|
|
49641
50206
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49642
50207
|
verdict: "USEFUL",
|
|
49643
|
-
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=130, FP=3, P=97.7%, FPR=0.00%, lift=82131.33. v7 was USEFUL (TP=96, FP=3, lift=59285.01). v8 was USEFUL (TP=34, FP=0).",
|
|
50208
|
+
_calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=130, FP=3, P=97.7%, FPR=0.00%, lift=82131.33. v7 was USEFUL (TP=96, FP=3, lift=59285.01). v8 was USEFUL (TP=34, FP=0). v0.19 default-on (explicit defaultOff: false): P 97.7% / 82k lift \u2014 UI consistency.",
|
|
49644
50209
|
aiSpecific: false,
|
|
49645
50210
|
_v7Verdict: "USEFUL",
|
|
49646
50211
|
_v7Lift: 59285.01,
|
|
@@ -49648,7 +50213,8 @@ var init_signal_strength = __esm({
|
|
|
49648
50213
|
_v7FpRate: 0,
|
|
49649
50214
|
_v7Precision: 0.9697,
|
|
49650
50215
|
_v8Verdict: "USEFUL",
|
|
49651
|
-
_v8Lift: 99999
|
|
50216
|
+
_v8Lift: 99999,
|
|
50217
|
+
defaultOff: false
|
|
49652
50218
|
},
|
|
49653
50219
|
"visual/spacing-scale-violation": {
|
|
49654
50220
|
recall: 86e-4,
|
|
@@ -56730,7 +57296,19 @@ var RULE_HINTS = {
|
|
|
56730
57296
|
"rust/unused-pub-fn": "Remove the function or call it. Rust's compiler doesn't warn on `pub fn` references unless `#![warn(dead_code)]` is set at the crate root. AI agents leave these behind during iterative refactors.",
|
|
56731
57297
|
"rust/unwrap-in-production": "Replace `.unwrap()` / `.expect()` with `?` (early-return on Err), `.map_err(...)` for error conversion, or an explicit `match`. They panic in production; reserve them for `#[cfg(test)]` or `#[test]` scopes.",
|
|
56732
57298
|
"rust/todo-macro": "Implement the function body or remove the stub. `todo!()` / `unimplemented!()` both expand to `panic!()`, so a hit at runtime crashes the program. Move them into a `#[cfg(test)] mod tests` block to suppress.",
|
|
56733
|
-
"rust/stringly-typed": 'Replace the `String` / `&str` parameter with the typed enum that already exists in the file. Stringly-typed APIs lose type information at the boundary \u2014 typos (`"Click"` vs `"click"`) only fail at runtime.'
|
|
57299
|
+
"rust/stringly-typed": 'Replace the `String` / `&str` parameter with the typed enum that already exists in the file. Stringly-typed APIs lose type information at the boundary \u2014 typos (`"Click"` vs `"click"`) only fail at runtime.',
|
|
57300
|
+
// v0.19.0 — TypeScript-specific rules
|
|
57301
|
+
"ts/optional-chain-overuse": "Break long `?.` chains with an intermediate variable or guard clause (`if (!value) return;`). Long chains hide the type-narrowing that should be explicit. AI agents chain `?.` to avoid null checks.",
|
|
57302
|
+
"ts/enum-vs-as-const": "Replace `enum Foo { A, B }` with `const Foo = { A: 'A', B: 'B' } as const` (or `const Foo = ['A', 'B'] as const`). Modern TS style guides (Google, TS-eslint) prefer `as const` because enums have surprising runtime semantics.",
|
|
57303
|
+
"ts/import-type-misuse": "Use `import type { X } from '...'` instead of `import { type X } from '...'`. The split form is more common in real codebases and makes the type-only intent unambiguous.",
|
|
57304
|
+
"ts/never-vs-unknown": "The `never` return type means 'this function never returns'. Reserve it for functions that always throw, always loop, or always exit. For 'impossible' branches, use a concrete type (`void`, `Error`, `unknown`) and an exhaustive check.",
|
|
57305
|
+
"ts/excessive-type-assertion": "More than 3 `as` assertions in one function is a strong signal that the type is wrong, not the code. Fix the type definition (or use a type guard) instead of bypassing the type system.",
|
|
57306
|
+
// v0.19.0 — Go-specific rules
|
|
57307
|
+
"go/error-wrap-without-context": 'Use `fmt.Errorf("opening config: %w", err)` not `fmt.Errorf("error: %w", err)`. Real Go errors include the failing operation. See golang.org/wiki/CodeReviewComments#error-strings.',
|
|
57308
|
+
"go/struct-tag-inconsistency": 'Pick one tag style per struct. If most fields are `json:"foo"`, this field should be too. Real Go code maintains consistency within a struct (or within a package).',
|
|
57309
|
+
"go/nil-slice-vs-empty": "Either declare as `var x = []int{}` or assign with `make([]int, 0)`. The nil/empty inconsistency is an AI signal \u2014 real code picks one form and sticks with it.",
|
|
57310
|
+
// v0.19.0 — Duplication detector
|
|
57311
|
+
"dup/identical-block": "Refactor to a shared helper. This is a Type-1 clone (byte-for-byte identical after normalization). Common in AI-generated code that copy-pastes from training data."
|
|
56734
57312
|
};
|
|
56735
57313
|
|
|
56736
57314
|
// src/cli/commands/explain.ts
|