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.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.
|
|
22
|
+
VERSION = "0.19.0";
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -31335,6 +31335,246 @@ var init_broken_link = __esm({
|
|
|
31335
31335
|
}
|
|
31336
31336
|
});
|
|
31337
31337
|
|
|
31338
|
+
// src/rules/dup/identical-block.ts
|
|
31339
|
+
import * as crypto from "crypto";
|
|
31340
|
+
function normalizeAndHash(lines) {
|
|
31341
|
+
const normalized = lines.map(
|
|
31342
|
+
(line) => line.replace(/\/\/.*$/, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").trim()
|
|
31343
|
+
).filter((line) => line.length > 0).join("\n");
|
|
31344
|
+
if (normalized.length < MIN_NORMALIZED_LENGTH) return void 0;
|
|
31345
|
+
return crypto.createHash("sha1").update(normalized).digest("hex").slice(0, HASH_PREFIX_LENGTH);
|
|
31346
|
+
}
|
|
31347
|
+
var WINDOW_SIZE, MIN_NORMALIZED_LENGTH, HASH_PREFIX_LENGTH, DEDUP_CACHE, dupIdenticalBlockRule;
|
|
31348
|
+
var init_identical_block = __esm({
|
|
31349
|
+
"src/rules/dup/identical-block.ts"() {
|
|
31350
|
+
"use strict";
|
|
31351
|
+
init_rule();
|
|
31352
|
+
WINDOW_SIZE = 10;
|
|
31353
|
+
MIN_NORMALIZED_LENGTH = 40;
|
|
31354
|
+
HASH_PREFIX_LENGTH = 16;
|
|
31355
|
+
DEDUP_CACHE = /* @__PURE__ */ new Map();
|
|
31356
|
+
dupIdenticalBlockRule = createRule({
|
|
31357
|
+
id: "dup/identical-block",
|
|
31358
|
+
category: "logic",
|
|
31359
|
+
severity: "medium",
|
|
31360
|
+
aiSpecific: false,
|
|
31361
|
+
description: "Block of >=10 lines is identical across >=2 files (Type-1 clone detector)",
|
|
31362
|
+
create(_context) {
|
|
31363
|
+
return {};
|
|
31364
|
+
},
|
|
31365
|
+
analyze(_context, facts) {
|
|
31366
|
+
const issues = [];
|
|
31367
|
+
const source = facts.v2?._source;
|
|
31368
|
+
if (!source) return issues;
|
|
31369
|
+
const filePath = facts.filePath;
|
|
31370
|
+
const lines = source.split("\n");
|
|
31371
|
+
for (let i = 0; i <= lines.length - WINDOW_SIZE; i++) {
|
|
31372
|
+
const window = lines.slice(i, i + WINDOW_SIZE);
|
|
31373
|
+
const hash = normalizeAndHash(window);
|
|
31374
|
+
if (!hash) continue;
|
|
31375
|
+
const existing = DEDUP_CACHE.get(hash) ?? [];
|
|
31376
|
+
const matches = existing.filter((m) => m.file !== filePath);
|
|
31377
|
+
for (const match of matches) {
|
|
31378
|
+
issues.push({
|
|
31379
|
+
ruleId: "dup/identical-block",
|
|
31380
|
+
category: "logic",
|
|
31381
|
+
severity: "medium",
|
|
31382
|
+
aiSpecific: false,
|
|
31383
|
+
message: `Identical ${WINDOW_SIZE}-line block at line ${i + 1} also appears in ${match.file}:${match.line + 1}`,
|
|
31384
|
+
line: i + 1,
|
|
31385
|
+
column: 0,
|
|
31386
|
+
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.",
|
|
31387
|
+
extras: {
|
|
31388
|
+
duplicateOf: {
|
|
31389
|
+
file: match.file,
|
|
31390
|
+
line: match.line + 1,
|
|
31391
|
+
hash
|
|
31392
|
+
}
|
|
31393
|
+
}
|
|
31394
|
+
});
|
|
31395
|
+
}
|
|
31396
|
+
existing.push({ file: filePath, line: i });
|
|
31397
|
+
DEDUP_CACHE.set(hash, existing);
|
|
31398
|
+
}
|
|
31399
|
+
return issues;
|
|
31400
|
+
}
|
|
31401
|
+
});
|
|
31402
|
+
}
|
|
31403
|
+
});
|
|
31404
|
+
|
|
31405
|
+
// src/rules/go/error-wrap-without-context.ts
|
|
31406
|
+
var ERR_WRAP_REGEX, GENERIC_PREFIX_REGEX, goErrorWrapWithoutContextRule;
|
|
31407
|
+
var init_error_wrap_without_context = __esm({
|
|
31408
|
+
"src/rules/go/error-wrap-without-context.ts"() {
|
|
31409
|
+
"use strict";
|
|
31410
|
+
init_rule();
|
|
31411
|
+
ERR_WRAP_REGEX = /fmt\.Errorf\(\s*"((?:[^"\\]|\\.)*)"\s*,\s*([^)]+)\)/g;
|
|
31412
|
+
GENERIC_PREFIX_REGEX = /^\s*(?:error|err|failed|wrap(?:ping)?|invalid)\s*(?::\s*)?%w\b/i;
|
|
31413
|
+
goErrorWrapWithoutContextRule = createRule({
|
|
31414
|
+
id: "go/error-wrap-without-context",
|
|
31415
|
+
category: "typo",
|
|
31416
|
+
severity: "low",
|
|
31417
|
+
aiSpecific: true,
|
|
31418
|
+
description: 'fmt.Errorf wrap without operation context \u2014 AI defaults to generic "error: %w"',
|
|
31419
|
+
create(_context) {
|
|
31420
|
+
return {};
|
|
31421
|
+
},
|
|
31422
|
+
analyze(_context, facts) {
|
|
31423
|
+
const issues = [];
|
|
31424
|
+
const source = facts.v2?._source;
|
|
31425
|
+
if (!source) return issues;
|
|
31426
|
+
let match;
|
|
31427
|
+
ERR_WRAP_REGEX.lastIndex = 0;
|
|
31428
|
+
while ((match = ERR_WRAP_REGEX.exec(source)) !== null) {
|
|
31429
|
+
const formatString = match[1];
|
|
31430
|
+
if (!formatString.includes("%w")) continue;
|
|
31431
|
+
if (formatString.length >= 30) continue;
|
|
31432
|
+
if (!GENERIC_PREFIX_REGEX.test(formatString)) continue;
|
|
31433
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
31434
|
+
issues.push({
|
|
31435
|
+
ruleId: "go/error-wrap-without-context",
|
|
31436
|
+
category: "typo",
|
|
31437
|
+
severity: "low",
|
|
31438
|
+
aiSpecific: true,
|
|
31439
|
+
message: `fmt.Errorf wrap with generic message "${formatString}" \u2014 include the failing operation`,
|
|
31440
|
+
line,
|
|
31441
|
+
column: match[0].indexOf("fmt") + 1,
|
|
31442
|
+
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'
|
|
31443
|
+
});
|
|
31444
|
+
}
|
|
31445
|
+
return issues;
|
|
31446
|
+
}
|
|
31447
|
+
});
|
|
31448
|
+
}
|
|
31449
|
+
});
|
|
31450
|
+
|
|
31451
|
+
// src/rules/go/nil-slice-vs-empty.ts
|
|
31452
|
+
var NIL_SLICE_DECL_REGEX, EMPTY_SLICE_ASSIGN_REGEX, goNilSliceVsEmptyRule;
|
|
31453
|
+
var init_nil_slice_vs_empty = __esm({
|
|
31454
|
+
"src/rules/go/nil-slice-vs-empty.ts"() {
|
|
31455
|
+
"use strict";
|
|
31456
|
+
init_rule();
|
|
31457
|
+
NIL_SLICE_DECL_REGEX = /^[\t ]*var\s+([A-Za-z_][A-Za-z0-9_]*)\s+\[\][\w.*]+\b/gm;
|
|
31458
|
+
EMPTY_SLICE_ASSIGN_REGEX = /^[\t ]*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:\[\][\w.*]*\{\}|make\(\[\][\w.*]*)/gm;
|
|
31459
|
+
goNilSliceVsEmptyRule = createRule({
|
|
31460
|
+
id: "go/nil-slice-vs-empty",
|
|
31461
|
+
category: "typo",
|
|
31462
|
+
severity: "low",
|
|
31463
|
+
aiSpecific: true,
|
|
31464
|
+
description: "Variable declared `var x []int` but later assigned `x = []int{}` or `make([]int, n)` \u2014 pick one form",
|
|
31465
|
+
create(_context) {
|
|
31466
|
+
return {};
|
|
31467
|
+
},
|
|
31468
|
+
analyze(_context, facts) {
|
|
31469
|
+
const issues = [];
|
|
31470
|
+
const source = facts.v2?._source;
|
|
31471
|
+
if (!source) return issues;
|
|
31472
|
+
const nilDecls = /* @__PURE__ */ new Map();
|
|
31473
|
+
let m;
|
|
31474
|
+
NIL_SLICE_DECL_REGEX.lastIndex = 0;
|
|
31475
|
+
while ((m = NIL_SLICE_DECL_REGEX.exec(source)) !== null) {
|
|
31476
|
+
nilDecls.set(m[1], source.slice(0, m.index).split("\n").length);
|
|
31477
|
+
}
|
|
31478
|
+
if (nilDecls.size === 0) return issues;
|
|
31479
|
+
EMPTY_SLICE_ASSIGN_REGEX.lastIndex = 0;
|
|
31480
|
+
while ((m = EMPTY_SLICE_ASSIGN_REGEX.exec(source)) !== null) {
|
|
31481
|
+
const name = m[1];
|
|
31482
|
+
if (!nilDecls.has(name)) continue;
|
|
31483
|
+
const declLine = nilDecls.get(name);
|
|
31484
|
+
const assignLine = source.slice(0, m.index).split("\n").length;
|
|
31485
|
+
if (assignLine <= declLine) continue;
|
|
31486
|
+
issues.push({
|
|
31487
|
+
ruleId: "go/nil-slice-vs-empty",
|
|
31488
|
+
category: "typo",
|
|
31489
|
+
severity: "low",
|
|
31490
|
+
aiSpecific: true,
|
|
31491
|
+
message: `Variable '${name}' declared as nil slice (line ${declLine}) but assigned an empty slice (line ${assignLine}) \u2014 pick one form`,
|
|
31492
|
+
line: declLine,
|
|
31493
|
+
column: 1,
|
|
31494
|
+
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."
|
|
31495
|
+
});
|
|
31496
|
+
}
|
|
31497
|
+
return issues;
|
|
31498
|
+
}
|
|
31499
|
+
});
|
|
31500
|
+
}
|
|
31501
|
+
});
|
|
31502
|
+
|
|
31503
|
+
// src/rules/go/struct-tag-inconsistency.ts
|
|
31504
|
+
var JSON_TAG_REGEX, goStructTagInconsistencyRule;
|
|
31505
|
+
var init_struct_tag_inconsistency = __esm({
|
|
31506
|
+
"src/rules/go/struct-tag-inconsistency.ts"() {
|
|
31507
|
+
"use strict";
|
|
31508
|
+
init_rule();
|
|
31509
|
+
JSON_TAG_REGEX = /`json:"([^",]+)(?:,([^"]+))?"`/g;
|
|
31510
|
+
goStructTagInconsistencyRule = createRule({
|
|
31511
|
+
id: "go/struct-tag-inconsistency",
|
|
31512
|
+
category: "typo",
|
|
31513
|
+
severity: "low",
|
|
31514
|
+
aiSpecific: true,
|
|
31515
|
+
description: 'Struct fields mix json:"foo" and json:"foo,omitempty" \u2014 pick one convention per struct',
|
|
31516
|
+
create(_context) {
|
|
31517
|
+
return {};
|
|
31518
|
+
},
|
|
31519
|
+
analyze(_context, facts) {
|
|
31520
|
+
const issues = [];
|
|
31521
|
+
const source = facts.v2?._source;
|
|
31522
|
+
if (!source) return issues;
|
|
31523
|
+
const structRegex = /type\s+[A-Z][A-Za-z0-9_]*\s+struct\s*\{/g;
|
|
31524
|
+
let structMatch;
|
|
31525
|
+
while ((structMatch = structRegex.exec(source)) !== null) {
|
|
31526
|
+
const startIdx = structMatch.index;
|
|
31527
|
+
const openBrace = source.indexOf("{", startIdx);
|
|
31528
|
+
if (openBrace < 0) continue;
|
|
31529
|
+
let depth = 1;
|
|
31530
|
+
let i = openBrace + 1;
|
|
31531
|
+
while (i < source.length && depth > 0) {
|
|
31532
|
+
const ch = source[i];
|
|
31533
|
+
if (ch === "{") depth++;
|
|
31534
|
+
else if (ch === "}") depth--;
|
|
31535
|
+
i++;
|
|
31536
|
+
}
|
|
31537
|
+
const structBody = source.slice(openBrace, i);
|
|
31538
|
+
const structLine = source.slice(0, startIdx).split("\n").length;
|
|
31539
|
+
const styleCount = {};
|
|
31540
|
+
const tagMatches = [];
|
|
31541
|
+
let m;
|
|
31542
|
+
JSON_TAG_REGEX.lastIndex = 0;
|
|
31543
|
+
while ((m = JSON_TAG_REGEX.exec(structBody)) !== null) {
|
|
31544
|
+
const tag = m[1];
|
|
31545
|
+
const options = m[2] ?? "";
|
|
31546
|
+
const style = options ? "with-options" : "no-options";
|
|
31547
|
+
styleCount[style] = (styleCount[style] ?? 0) + 1;
|
|
31548
|
+
tagMatches.push({ tag, style, idx: openBrace + m.index });
|
|
31549
|
+
}
|
|
31550
|
+
const styles = Object.keys(styleCount);
|
|
31551
|
+
if (styles.length < 2 || tagMatches.length < 2) continue;
|
|
31552
|
+
const dominant = styles.reduce(
|
|
31553
|
+
(a, b) => (styleCount[a] ?? 0) >= (styleCount[b] ?? 0) ? a : b
|
|
31554
|
+
);
|
|
31555
|
+
const minority = tagMatches.filter((t) => t.style !== dominant);
|
|
31556
|
+
if (minority.length === 0) continue;
|
|
31557
|
+
for (const m2 of minority) {
|
|
31558
|
+
const line = source.slice(0, m2.idx).split("\n").length;
|
|
31559
|
+
issues.push({
|
|
31560
|
+
ruleId: "go/struct-tag-inconsistency",
|
|
31561
|
+
category: "typo",
|
|
31562
|
+
severity: "low",
|
|
31563
|
+
aiSpecific: true,
|
|
31564
|
+
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"}`,
|
|
31565
|
+
line,
|
|
31566
|
+
column: 1,
|
|
31567
|
+
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.'
|
|
31568
|
+
});
|
|
31569
|
+
}
|
|
31570
|
+
if (issues.length > 0) break;
|
|
31571
|
+
}
|
|
31572
|
+
return issues;
|
|
31573
|
+
}
|
|
31574
|
+
});
|
|
31575
|
+
}
|
|
31576
|
+
});
|
|
31577
|
+
|
|
31338
31578
|
// src/rules/layout/gap-monopoly.ts
|
|
31339
31579
|
var GAP_RE, gapMonopolyRule;
|
|
31340
31580
|
var init_gap_monopoly = __esm({
|
|
@@ -36225,11 +36465,11 @@ __export(dist_exports2, {
|
|
|
36225
36465
|
tokenizeIdentifiers: () => tokenizeIdentifiers
|
|
36226
36466
|
});
|
|
36227
36467
|
import { readFile, writeFile, mkdir, access } from "fs/promises";
|
|
36228
|
-
import { createHash } from "crypto";
|
|
36468
|
+
import { createHash as createHash2 } from "crypto";
|
|
36229
36469
|
import { join as join10, dirname as dirname6 } from "path";
|
|
36230
36470
|
import { parseSync } from "@swc/core";
|
|
36231
36471
|
import { join as join22 } from "path";
|
|
36232
|
-
import { createHash as
|
|
36472
|
+
import { createHash as createHash22 } from "crypto";
|
|
36233
36473
|
import { createHash as createHash3 } from "crypto";
|
|
36234
36474
|
import { readFile as readFile2 } from "fs/promises";
|
|
36235
36475
|
import { relative as relative3 } from "path";
|
|
@@ -36483,7 +36723,7 @@ function legacyCacheRoot() {
|
|
|
36483
36723
|
return join10(process.cwd(), ".slopbrick", "cache", "ast");
|
|
36484
36724
|
}
|
|
36485
36725
|
function hashContent(content) {
|
|
36486
|
-
return
|
|
36726
|
+
return createHash2("md5").update(content, "utf-8").digest("hex");
|
|
36487
36727
|
}
|
|
36488
36728
|
function cachePathWithRoot(content, root) {
|
|
36489
36729
|
return join10(root, `${hashContent(content)}.json`);
|
|
@@ -36681,7 +36921,7 @@ function fingerprintFor(component) {
|
|
|
36681
36921
|
const hooks = component.hookCalls.map((h) => h.name).sort();
|
|
36682
36922
|
const props = component.props.map((p) => p.name).sort();
|
|
36683
36923
|
const payload = `${component.name}|${hooks.join(",")}|${props.join(",")}`;
|
|
36684
|
-
return
|
|
36924
|
+
return createHash22("sha256").update(payload).digest("hex").slice(0, 16);
|
|
36685
36925
|
}
|
|
36686
36926
|
function buildComponentFingerprints(results) {
|
|
36687
36927
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -38623,91 +38863,6 @@ var init_key_prop_missing = __esm({
|
|
|
38623
38863
|
}
|
|
38624
38864
|
});
|
|
38625
38865
|
|
|
38626
|
-
// src/rules/logic/ks-distribution-shift.ts
|
|
38627
|
-
function extractFileFeatures(source) {
|
|
38628
|
-
const lines = source.split("\n");
|
|
38629
|
-
const lineLengths = lines.map((l) => l.length);
|
|
38630
|
-
const identifierLengths = [];
|
|
38631
|
-
const idRe = /[A-Za-z_$][A-Za-z0-9_$]*/g;
|
|
38632
|
-
let m;
|
|
38633
|
-
while ((m = idRe.exec(source)) !== null) {
|
|
38634
|
-
identifierLengths.push(m[0].length);
|
|
38635
|
-
}
|
|
38636
|
-
const commentDensity = lines.map((l) => {
|
|
38637
|
-
const trimmed = l.trim();
|
|
38638
|
-
if (trimmed.length === 0) return 0;
|
|
38639
|
-
const commentChars = (trimmed.match(/^\/\/.*$/)?.[0]?.length ?? 0) + (trimmed.match(/^\s*\/\*.*?\*\/\s*$/)?.at(0)?.length ?? 0);
|
|
38640
|
-
return commentChars / trimmed.length;
|
|
38641
|
-
});
|
|
38642
|
-
return { lineLengths, identifierLengths, commentDensity };
|
|
38643
|
-
}
|
|
38644
|
-
var MIN_SAMPLES_PER_FEATURE, ksDistributionShiftRule;
|
|
38645
|
-
var init_ks_distribution_shift = __esm({
|
|
38646
|
-
"src/rules/logic/ks-distribution-shift.ts"() {
|
|
38647
|
-
"use strict";
|
|
38648
|
-
init_rule();
|
|
38649
|
-
init_dist2();
|
|
38650
|
-
init_corpus_baselines2();
|
|
38651
|
-
MIN_SAMPLES_PER_FEATURE = 20;
|
|
38652
|
-
ksDistributionShiftRule = createRule({
|
|
38653
|
-
id: "logic/ks-distribution-shift",
|
|
38654
|
-
category: "logic",
|
|
38655
|
-
severity: "medium",
|
|
38656
|
-
aiSpecific: false,
|
|
38657
|
-
description: "Multi-feature Kolmogorov\u2013Smirnov distribution-shift vs corpus baseline (Bonferroni-corrected). Peer-reviewed ML distribution-shift detector (arXiv:2510.15996, Oct 2025).",
|
|
38658
|
-
create(context) {
|
|
38659
|
-
return context;
|
|
38660
|
-
},
|
|
38661
|
-
analyze(_context, facts) {
|
|
38662
|
-
const issues = [];
|
|
38663
|
-
if (!facts.v2) return issues;
|
|
38664
|
-
const source = facts.v2._source ?? "";
|
|
38665
|
-
if (source.length < 200) return issues;
|
|
38666
|
-
const features = extractFileFeatures(source);
|
|
38667
|
-
const samples = /* @__PURE__ */ new Map([
|
|
38668
|
-
["lineLengths", features.lineLengths],
|
|
38669
|
-
["identifierLengths", features.identifierLengths],
|
|
38670
|
-
["commentDensity", features.commentDensity]
|
|
38671
|
-
]);
|
|
38672
|
-
const baselines = getCorpusBaselines();
|
|
38673
|
-
const baselinesMap = /* @__PURE__ */ new Map();
|
|
38674
|
-
if (baselines) {
|
|
38675
|
-
baselinesMap.set("lineLengths", baselines.features.lineLengths.sample);
|
|
38676
|
-
baselinesMap.set("identifierLengths", baselines.features.identifierLengths.sample);
|
|
38677
|
-
baselinesMap.set("commentDensity", baselines.features.commentDensity.sample);
|
|
38678
|
-
} else {
|
|
38679
|
-
baselinesMap.set("lineLengths", [20, 25, 30, 32, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100]);
|
|
38680
|
-
baselinesMap.set("identifierLengths", [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 24, 28]);
|
|
38681
|
-
baselinesMap.set("commentDensity", [0, 0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5]);
|
|
38682
|
-
}
|
|
38683
|
-
for (const [name, vals] of samples) {
|
|
38684
|
-
if (vals.length < MIN_SAMPLES_PER_FEATURE) samples.delete(name);
|
|
38685
|
-
}
|
|
38686
|
-
if (samples.size === 0) return issues;
|
|
38687
|
-
const result = multiFeatureKsTest(samples, baselinesMap, 0.05);
|
|
38688
|
-
if (!result.anySignificant) return issues;
|
|
38689
|
-
const shifted = result.significantFeatures.join(", ");
|
|
38690
|
-
const details = result.significantFeatures.map((name) => {
|
|
38691
|
-
const r = result.perFeature.get(name);
|
|
38692
|
-
if (!r) return name;
|
|
38693
|
-
return `${name} (D=${r.statistic.toFixed(3)}, p=${r.pValue.toExponential(2)})`;
|
|
38694
|
-
}).join("; ");
|
|
38695
|
-
issues.push({
|
|
38696
|
-
ruleId: "logic/ks-distribution-shift",
|
|
38697
|
-
category: "logic",
|
|
38698
|
-
severity: "medium",
|
|
38699
|
-
aiSpecific: true,
|
|
38700
|
-
message: `Distribution shift detected on ${result.significantFeatures.length} of ${result.perFeature.size} features (Bonferroni \u03B1=${result.bonferroniAlpha.toExponential(2)}). Features: ${shifted}. Detail: ${details}.`,
|
|
38701
|
-
line: 1,
|
|
38702
|
-
column: 1,
|
|
38703
|
-
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."
|
|
38704
|
-
});
|
|
38705
|
-
return issues;
|
|
38706
|
-
}
|
|
38707
|
-
});
|
|
38708
|
-
}
|
|
38709
|
-
});
|
|
38710
|
-
|
|
38711
38866
|
// src/rules/logic/math-any-density.ts
|
|
38712
38867
|
var ANY_PER_100_LINES, MIN_ABSOLUTE, COLON, ANY_KEYWORD, ANY_TYPE_RE, MSG_FRAGMENT, RULE_DESCRIPTION, RULE_MESSAGE, RULE_ADVICE, mathAnyDensityRule;
|
|
38713
38868
|
var init_math_any_density = __esm({
|
|
@@ -38777,13 +38932,13 @@ var init_math_any_density = __esm({
|
|
|
38777
38932
|
});
|
|
38778
38933
|
|
|
38779
38934
|
// src/rules/logic/math-console-log-storm.ts
|
|
38780
|
-
var
|
|
38935
|
+
var WINDOW_SIZE2, STORM_THRESHOLD, CONSOLE_LOG_RE, mathConsoleLogStormRule;
|
|
38781
38936
|
var init_math_console_log_storm = __esm({
|
|
38782
38937
|
"src/rules/logic/math-console-log-storm.ts"() {
|
|
38783
38938
|
"use strict";
|
|
38784
38939
|
init_rule();
|
|
38785
38940
|
init_utils();
|
|
38786
|
-
|
|
38941
|
+
WINDOW_SIZE2 = 30;
|
|
38787
38942
|
STORM_THRESHOLD = 5;
|
|
38788
38943
|
CONSOLE_LOG_RE = /\bconsole\.log\s*\(/g;
|
|
38789
38944
|
mathConsoleLogStormRule = createRule({
|
|
@@ -38814,7 +38969,7 @@ var init_math_console_log_storm = __esm({
|
|
|
38814
38969
|
let maxEndLine = 0;
|
|
38815
38970
|
let i = 0;
|
|
38816
38971
|
for (let j = 0; j < lines.length; j++) {
|
|
38817
|
-
while (lines[j] - lines[i] >
|
|
38972
|
+
while (lines[j] - lines[i] > WINDOW_SIZE2) i++;
|
|
38818
38973
|
const count = j - i + 1;
|
|
38819
38974
|
if (count > maxCount) {
|
|
38820
38975
|
maxCount = count;
|
|
@@ -38828,7 +38983,7 @@ var init_math_console_log_storm = __esm({
|
|
|
38828
38983
|
category: "logic",
|
|
38829
38984
|
severity: "high",
|
|
38830
38985
|
aiSpecific: true,
|
|
38831
|
-
message: `${maxCount} console.log calls clustered in a ${
|
|
38986
|
+
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.`,
|
|
38832
38987
|
line: firstIdx >= 0 ? lines[firstIdx] : 1,
|
|
38833
38988
|
column: firstIdx >= 0 ? columns[firstIdx] : 1,
|
|
38834
38989
|
advice: "Replace debug logs with a proper debugger or logger.debug() \u2014 remove all console.log before shipping."
|
|
@@ -41652,6 +41807,251 @@ var init_weak_assertion = __esm({
|
|
|
41652
41807
|
}
|
|
41653
41808
|
});
|
|
41654
41809
|
|
|
41810
|
+
// src/rules/ts/enum-vs-as-const.ts
|
|
41811
|
+
var ENUM_DECL_REGEX, tsEnumVsAsConstRule;
|
|
41812
|
+
var init_enum_vs_as_const = __esm({
|
|
41813
|
+
"src/rules/ts/enum-vs-as-const.ts"() {
|
|
41814
|
+
"use strict";
|
|
41815
|
+
init_rule();
|
|
41816
|
+
ENUM_DECL_REGEX = /^[ \t]*(?:export\s+)?(?:const\s+)?enum\s+[A-Z_][A-Za-z0-9_]*\s*\{/gm;
|
|
41817
|
+
tsEnumVsAsConstRule = createRule({
|
|
41818
|
+
id: "ts/enum-vs-as-const",
|
|
41819
|
+
category: "typo",
|
|
41820
|
+
severity: "low",
|
|
41821
|
+
aiSpecific: true,
|
|
41822
|
+
description: "Uses `enum` \u2014 modern TS prefers `as const` objects",
|
|
41823
|
+
create(_context) {
|
|
41824
|
+
return {};
|
|
41825
|
+
},
|
|
41826
|
+
analyze(_context, facts) {
|
|
41827
|
+
const issues = [];
|
|
41828
|
+
const source = facts.v2?._source;
|
|
41829
|
+
if (!source) return issues;
|
|
41830
|
+
let match;
|
|
41831
|
+
ENUM_DECL_REGEX.lastIndex = 0;
|
|
41832
|
+
while ((match = ENUM_DECL_REGEX.exec(source)) !== null) {
|
|
41833
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
41834
|
+
issues.push({
|
|
41835
|
+
ruleId: "ts/enum-vs-as-const",
|
|
41836
|
+
category: "typo",
|
|
41837
|
+
severity: "low",
|
|
41838
|
+
aiSpecific: true,
|
|
41839
|
+
message: `'enum' is an AI / older-TS pattern \u2014 prefer 'as const' for a frozen object literal`,
|
|
41840
|
+
line,
|
|
41841
|
+
column: match[0].indexOf("enum") + 1,
|
|
41842
|
+
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.'
|
|
41843
|
+
});
|
|
41844
|
+
}
|
|
41845
|
+
return issues;
|
|
41846
|
+
}
|
|
41847
|
+
});
|
|
41848
|
+
}
|
|
41849
|
+
});
|
|
41850
|
+
|
|
41851
|
+
// src/rules/ts/excessive-type-assertion.ts
|
|
41852
|
+
var DEFAULT_MAX, FN_DECL_REGEX, AS_REGEX, tsExcessiveTypeAssertionRule;
|
|
41853
|
+
var init_excessive_type_assertion = __esm({
|
|
41854
|
+
"src/rules/ts/excessive-type-assertion.ts"() {
|
|
41855
|
+
"use strict";
|
|
41856
|
+
init_rule();
|
|
41857
|
+
DEFAULT_MAX = 3;
|
|
41858
|
+
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;
|
|
41859
|
+
AS_REGEX = /(?<![\w$])as\s+(?!const)([A-Z][\w$.,<>\[\]|&]*)/g;
|
|
41860
|
+
tsExcessiveTypeAssertionRule = createRule({
|
|
41861
|
+
id: "ts/excessive-type-assertion",
|
|
41862
|
+
category: "typo",
|
|
41863
|
+
severity: "low",
|
|
41864
|
+
aiSpecific: true,
|
|
41865
|
+
description: "Function with >3 `as` type assertions \u2014 likely AI fighting the type system",
|
|
41866
|
+
create(_context) {
|
|
41867
|
+
return { maxAssertionsPerFunction: DEFAULT_MAX };
|
|
41868
|
+
},
|
|
41869
|
+
analyze(context, facts) {
|
|
41870
|
+
const issues = [];
|
|
41871
|
+
const source = facts.v2?._source;
|
|
41872
|
+
if (!source) return issues;
|
|
41873
|
+
let match;
|
|
41874
|
+
FN_DECL_REGEX.lastIndex = 0;
|
|
41875
|
+
while ((match = FN_DECL_REGEX.exec(source)) !== null) {
|
|
41876
|
+
const startIdx = match.index;
|
|
41877
|
+
const openBraceIdx = source.indexOf("{", startIdx);
|
|
41878
|
+
if (openBraceIdx < 0) continue;
|
|
41879
|
+
let depth = 1;
|
|
41880
|
+
let i = openBraceIdx + 1;
|
|
41881
|
+
while (i < source.length && depth > 0) {
|
|
41882
|
+
const ch = source[i];
|
|
41883
|
+
if (ch === "{") depth++;
|
|
41884
|
+
else if (ch === "}") depth--;
|
|
41885
|
+
i++;
|
|
41886
|
+
}
|
|
41887
|
+
const body = source.slice(openBraceIdx, i);
|
|
41888
|
+
const line = source.slice(0, startIdx).split("\n").length;
|
|
41889
|
+
let asCount = 0;
|
|
41890
|
+
const seen = /* @__PURE__ */ new Set();
|
|
41891
|
+
let asMatch;
|
|
41892
|
+
AS_REGEX.lastIndex = 0;
|
|
41893
|
+
while ((asMatch = AS_REGEX.exec(body)) !== null) {
|
|
41894
|
+
const captured = asMatch[1];
|
|
41895
|
+
if (seen.has(captured)) continue;
|
|
41896
|
+
seen.add(captured);
|
|
41897
|
+
asCount++;
|
|
41898
|
+
}
|
|
41899
|
+
if (asCount > context.maxAssertionsPerFunction) {
|
|
41900
|
+
issues.push({
|
|
41901
|
+
ruleId: "ts/excessive-type-assertion",
|
|
41902
|
+
category: "typo",
|
|
41903
|
+
severity: "low",
|
|
41904
|
+
aiSpecific: true,
|
|
41905
|
+
message: `Function has ${asCount} 'as' assertions (max ${context.maxAssertionsPerFunction}) \u2014 likely AI fighting the type system`,
|
|
41906
|
+
line,
|
|
41907
|
+
column: 1,
|
|
41908
|
+
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."
|
|
41909
|
+
});
|
|
41910
|
+
}
|
|
41911
|
+
}
|
|
41912
|
+
return issues;
|
|
41913
|
+
}
|
|
41914
|
+
});
|
|
41915
|
+
}
|
|
41916
|
+
});
|
|
41917
|
+
|
|
41918
|
+
// src/rules/ts/import-type-misuse.ts
|
|
41919
|
+
var INLINE_TYPE_IMPORT_REGEX, tsImportTypeMisuseRule;
|
|
41920
|
+
var init_import_type_misuse = __esm({
|
|
41921
|
+
"src/rules/ts/import-type-misuse.ts"() {
|
|
41922
|
+
"use strict";
|
|
41923
|
+
init_rule();
|
|
41924
|
+
INLINE_TYPE_IMPORT_REGEX = /^[ \t]*import\s*\{[^}]*\btype\s+[A-Za-z_]/gm;
|
|
41925
|
+
tsImportTypeMisuseRule = createRule({
|
|
41926
|
+
id: "ts/import-type-misuse",
|
|
41927
|
+
category: "typo",
|
|
41928
|
+
severity: "low",
|
|
41929
|
+
aiSpecific: true,
|
|
41930
|
+
description: "Inline `import { type X }` \u2014 prefer `import type { X }` for clarity",
|
|
41931
|
+
create(_context) {
|
|
41932
|
+
return {};
|
|
41933
|
+
},
|
|
41934
|
+
analyze(_context, facts) {
|
|
41935
|
+
const issues = [];
|
|
41936
|
+
const source = facts.v2?._source;
|
|
41937
|
+
if (!source) return issues;
|
|
41938
|
+
let match;
|
|
41939
|
+
INLINE_TYPE_IMPORT_REGEX.lastIndex = 0;
|
|
41940
|
+
while ((match = INLINE_TYPE_IMPORT_REGEX.exec(source)) !== null) {
|
|
41941
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
41942
|
+
issues.push({
|
|
41943
|
+
ruleId: "ts/import-type-misuse",
|
|
41944
|
+
category: "typo",
|
|
41945
|
+
severity: "low",
|
|
41946
|
+
aiSpecific: true,
|
|
41947
|
+
message: "Inline `type` in a value import \u2014 split into a separate `import type` statement",
|
|
41948
|
+
line,
|
|
41949
|
+
column: match[0].indexOf("type") + 1,
|
|
41950
|
+
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.'
|
|
41951
|
+
});
|
|
41952
|
+
}
|
|
41953
|
+
return issues;
|
|
41954
|
+
}
|
|
41955
|
+
});
|
|
41956
|
+
}
|
|
41957
|
+
});
|
|
41958
|
+
|
|
41959
|
+
// src/rules/ts/never-vs-unknown.ts
|
|
41960
|
+
var NEVER_RETURN_REGEX, THROW_OR_LOOP_REGEX, tsNeverVsUnknownRule;
|
|
41961
|
+
var init_never_vs_unknown = __esm({
|
|
41962
|
+
"src/rules/ts/never-vs-unknown.ts"() {
|
|
41963
|
+
"use strict";
|
|
41964
|
+
init_rule();
|
|
41965
|
+
NEVER_RETURN_REGEX = /^[ \t]*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)[^{]*:\s*(?:Promise<)?never\b[^{]*\{/gm;
|
|
41966
|
+
THROW_OR_LOOP_REGEX = /\b(?:throw\b|while\s*\(|for\s*\(|process\.exit\b|System\.exit\b)/;
|
|
41967
|
+
tsNeverVsUnknownRule = createRule({
|
|
41968
|
+
id: "ts/never-vs-unknown",
|
|
41969
|
+
category: "typo",
|
|
41970
|
+
severity: "low",
|
|
41971
|
+
aiSpecific: true,
|
|
41972
|
+
description: "Return type `: never` but body has no throw / loop / exit \u2014 likely AI misuse",
|
|
41973
|
+
create(_context) {
|
|
41974
|
+
return {};
|
|
41975
|
+
},
|
|
41976
|
+
analyze(_context, facts) {
|
|
41977
|
+
const issues = [];
|
|
41978
|
+
const source = facts.v2?._source;
|
|
41979
|
+
if (!source) return issues;
|
|
41980
|
+
let match;
|
|
41981
|
+
NEVER_RETURN_REGEX.lastIndex = 0;
|
|
41982
|
+
while ((match = NEVER_RETURN_REGEX.exec(source)) !== null) {
|
|
41983
|
+
const fnName = match[1];
|
|
41984
|
+
const startIdx = match.index;
|
|
41985
|
+
const line = source.slice(0, startIdx).split("\n").length;
|
|
41986
|
+
const openBraceIdx = source.indexOf("{", startIdx);
|
|
41987
|
+
if (openBraceIdx < 0) continue;
|
|
41988
|
+
let depth = 1;
|
|
41989
|
+
let i = openBraceIdx + 1;
|
|
41990
|
+
while (i < source.length && depth > 0) {
|
|
41991
|
+
const ch = source[i];
|
|
41992
|
+
if (ch === "{") depth++;
|
|
41993
|
+
else if (ch === "}") depth--;
|
|
41994
|
+
i++;
|
|
41995
|
+
}
|
|
41996
|
+
const body = source.slice(openBraceIdx, i);
|
|
41997
|
+
if (THROW_OR_LOOP_REGEX.test(body)) continue;
|
|
41998
|
+
issues.push({
|
|
41999
|
+
ruleId: "ts/never-vs-unknown",
|
|
42000
|
+
category: "typo",
|
|
42001
|
+
severity: "low",
|
|
42002
|
+
aiSpecific: true,
|
|
42003
|
+
message: `Function '${fnName}' returns 'never' but its body has no throw, loop, or exit \u2014 likely AI misuse`,
|
|
42004
|
+
line,
|
|
42005
|
+
column: match[0].indexOf("never") + 1,
|
|
42006
|
+
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.'
|
|
42007
|
+
});
|
|
42008
|
+
}
|
|
42009
|
+
return issues;
|
|
42010
|
+
}
|
|
42011
|
+
});
|
|
42012
|
+
}
|
|
42013
|
+
});
|
|
42014
|
+
|
|
42015
|
+
// src/rules/ts/optional-chain-overuse.ts
|
|
42016
|
+
var DEFAULT_MIN_CHAIN_LENGTH, tsOptionalChainOveruseRule;
|
|
42017
|
+
var init_optional_chain_overuse = __esm({
|
|
42018
|
+
"src/rules/ts/optional-chain-overuse.ts"() {
|
|
42019
|
+
"use strict";
|
|
42020
|
+
init_rule();
|
|
42021
|
+
DEFAULT_MIN_CHAIN_LENGTH = 5;
|
|
42022
|
+
tsOptionalChainOveruseRule = createRule({
|
|
42023
|
+
id: "ts/optional-chain-overuse",
|
|
42024
|
+
category: "logic",
|
|
42025
|
+
severity: "low",
|
|
42026
|
+
aiSpecific: true,
|
|
42027
|
+
description: "Optional chaining (?.) used 5+ times in a single chain \u2014 AI tends to chain rather than narrow",
|
|
42028
|
+
create(_context) {
|
|
42029
|
+
return { minChainLength: DEFAULT_MIN_CHAIN_LENGTH };
|
|
42030
|
+
},
|
|
42031
|
+
analyze(context, facts) {
|
|
42032
|
+
const issues = [];
|
|
42033
|
+
const expressions = facts.v2.logic?.logicalExpressions;
|
|
42034
|
+
if (!expressions) return issues;
|
|
42035
|
+
for (const expression of expressions) {
|
|
42036
|
+
if (expression.depth >= context.minChainLength && expression.isOptionalChainLike) {
|
|
42037
|
+
issues.push({
|
|
42038
|
+
ruleId: "ts/optional-chain-overuse",
|
|
42039
|
+
category: "logic",
|
|
42040
|
+
severity: "low",
|
|
42041
|
+
aiSpecific: true,
|
|
42042
|
+
message: `Optional chain depth ${expression.depth} \u2014 break with an intermediate variable or guard clause`,
|
|
42043
|
+
line: expression.line,
|
|
42044
|
+
column: expression.column,
|
|
42045
|
+
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."
|
|
42046
|
+
});
|
|
42047
|
+
}
|
|
42048
|
+
}
|
|
42049
|
+
return issues;
|
|
42050
|
+
}
|
|
42051
|
+
});
|
|
42052
|
+
}
|
|
42053
|
+
});
|
|
42054
|
+
|
|
41655
42055
|
// src/rules/typo/calc-fontsize.ts
|
|
41656
42056
|
var FONT_SIZE_RE, calcFontsizeRule;
|
|
41657
42057
|
var init_calc_fontsize = __esm({
|
|
@@ -45793,6 +46193,10 @@ var init_builtins = __esm({
|
|
|
45793
46193
|
init_expired_code_example();
|
|
45794
46194
|
init_stale_function_reference();
|
|
45795
46195
|
init_stale_package_reference();
|
|
46196
|
+
init_identical_block();
|
|
46197
|
+
init_error_wrap_without_context();
|
|
46198
|
+
init_nil_slice_vs_empty();
|
|
46199
|
+
init_struct_tag_inconsistency();
|
|
45796
46200
|
init_gap_monopoly();
|
|
45797
46201
|
init_math_element_uniformity();
|
|
45798
46202
|
init_math_grid_uniformity();
|
|
@@ -45802,7 +46206,6 @@ var init_builtins = __esm({
|
|
|
45802
46206
|
init_ghost_defensive();
|
|
45803
46207
|
init_heaps_deviation();
|
|
45804
46208
|
init_key_prop_missing();
|
|
45805
|
-
init_ks_distribution_shift();
|
|
45806
46209
|
init_math_any_density();
|
|
45807
46210
|
init_math_console_log_storm();
|
|
45808
46211
|
init_math_gini_class_usage();
|
|
@@ -45836,6 +46239,11 @@ var init_builtins = __esm({
|
|
|
45836
46239
|
init_fake_placeholder();
|
|
45837
46240
|
init_missing_edge_case();
|
|
45838
46241
|
init_weak_assertion();
|
|
46242
|
+
init_enum_vs_as_const();
|
|
46243
|
+
init_excessive_type_assertion();
|
|
46244
|
+
init_import_type_misuse();
|
|
46245
|
+
init_never_vs_unknown();
|
|
46246
|
+
init_optional_chain_overuse();
|
|
45839
46247
|
init_calc_fontsize();
|
|
45840
46248
|
init_calc_raw_px();
|
|
45841
46249
|
init_clamp_offscale();
|
|
@@ -45898,6 +46306,10 @@ var init_builtins = __esm({
|
|
|
45898
46306
|
expiredCodeExampleRule,
|
|
45899
46307
|
staleFunctionReferenceRule,
|
|
45900
46308
|
stalePackageReferenceRule,
|
|
46309
|
+
dupIdenticalBlockRule,
|
|
46310
|
+
goErrorWrapWithoutContextRule,
|
|
46311
|
+
goNilSliceVsEmptyRule,
|
|
46312
|
+
goStructTagInconsistencyRule,
|
|
45901
46313
|
gapMonopolyRule,
|
|
45902
46314
|
mathElementUniformityRule,
|
|
45903
46315
|
mathGridUniformityRule,
|
|
@@ -45907,7 +46319,6 @@ var init_builtins = __esm({
|
|
|
45907
46319
|
ghostDefensiveRule,
|
|
45908
46320
|
heapsDeviationRule,
|
|
45909
46321
|
keyPropMissingRule,
|
|
45910
|
-
ksDistributionShiftRule,
|
|
45911
46322
|
mathAnyDensityRule,
|
|
45912
46323
|
mathConsoleLogStormRule,
|
|
45913
46324
|
mathGiniClassUsageRule,
|
|
@@ -45941,6 +46352,11 @@ var init_builtins = __esm({
|
|
|
45941
46352
|
fakePlaceholderRule,
|
|
45942
46353
|
missingEdgeCaseRule,
|
|
45943
46354
|
weakAssertionRule,
|
|
46355
|
+
tsEnumVsAsConstRule,
|
|
46356
|
+
tsExcessiveTypeAssertionRule,
|
|
46357
|
+
tsImportTypeMisuseRule,
|
|
46358
|
+
tsNeverVsUnknownRule,
|
|
46359
|
+
tsOptionalChainOveruseRule,
|
|
45944
46360
|
calcFontsizeRule,
|
|
45945
46361
|
calcRawPxRule,
|
|
45946
46362
|
clampOffscaleRule,
|
|
@@ -48019,7 +48435,7 @@ var init_signal_strength = __esm({
|
|
|
48019
48435
|
precision: 0.9966,
|
|
48020
48436
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
48021
48437
|
verdict: "USEFUL",
|
|
48022
|
-
_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).",
|
|
48438
|
+
_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.",
|
|
48023
48439
|
aiSpecific: true,
|
|
48024
48440
|
_v7Verdict: "USEFUL",
|
|
48025
48441
|
_v7Lift: 182622.43,
|
|
@@ -48027,7 +48443,8 @@ var init_signal_strength = __esm({
|
|
|
48027
48443
|
_v7FpRate: 0,
|
|
48028
48444
|
_v7Precision: 0.9957,
|
|
48029
48445
|
_v8Verdict: "USEFUL",
|
|
48030
|
-
_v8Lift: 99999
|
|
48446
|
+
_v8Lift: 99999,
|
|
48447
|
+
defaultOff: false
|
|
48031
48448
|
},
|
|
48032
48449
|
"ai/errors-near-eof": {
|
|
48033
48450
|
recall: 0.0948,
|
|
@@ -48276,7 +48693,7 @@ var init_signal_strength = __esm({
|
|
|
48276
48693
|
precision: 0.9512,
|
|
48277
48694
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
48278
48695
|
verdict: "USEFUL",
|
|
48279
|
-
_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).",
|
|
48696
|
+
_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.",
|
|
48280
48697
|
aiSpecific: true,
|
|
48281
48698
|
_v7Verdict: "USEFUL",
|
|
48282
48699
|
_v7Lift: 7099.57,
|
|
@@ -48284,7 +48701,8 @@ var init_signal_strength = __esm({
|
|
|
48284
48701
|
_v7FpRate: 1e-4,
|
|
48285
48702
|
_v7Precision: 0.929,
|
|
48286
48703
|
_v8Verdict: "USEFUL",
|
|
48287
|
-
_v8Lift: 99999
|
|
48704
|
+
_v8Lift: 99999,
|
|
48705
|
+
defaultOff: false
|
|
48288
48706
|
},
|
|
48289
48707
|
"context/import-path-mismatch": {
|
|
48290
48708
|
recall: 0.0681,
|
|
@@ -48468,7 +48886,7 @@ var init_signal_strength = __esm({
|
|
|
48468
48886
|
precision: 0.8834,
|
|
48469
48887
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
48470
48888
|
verdict: "USEFUL",
|
|
48471
|
-
_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.",
|
|
48889
|
+
_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.",
|
|
48472
48890
|
aiSpecific: true,
|
|
48473
48891
|
_v7Verdict: "DORMANT",
|
|
48474
48892
|
_v7Lift: 1,
|
|
@@ -48476,7 +48894,8 @@ var init_signal_strength = __esm({
|
|
|
48476
48894
|
_v7FpRate: 0,
|
|
48477
48895
|
_v7Precision: 0,
|
|
48478
48896
|
_v8Verdict: "USEFUL",
|
|
48479
|
-
_v8Lift: 32.74
|
|
48897
|
+
_v8Lift: 32.74,
|
|
48898
|
+
defaultOff: false
|
|
48480
48899
|
},
|
|
48481
48900
|
"dead/unused-parameter": {
|
|
48482
48901
|
recall: 8e-4,
|
|
@@ -48564,6 +48983,78 @@ var init_signal_strength = __esm({
|
|
|
48564
48983
|
_v8Verdict: "OK",
|
|
48565
48984
|
_v8Lift: 255.9
|
|
48566
48985
|
},
|
|
48986
|
+
"dup/identical-block": {
|
|
48987
|
+
recall: 0,
|
|
48988
|
+
fpRate: 0,
|
|
48989
|
+
ratio: 1,
|
|
48990
|
+
precision: 0,
|
|
48991
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
48992
|
+
verdict: "DORMANT",
|
|
48993
|
+
_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.",
|
|
48994
|
+
aiSpecific: false,
|
|
48995
|
+
_v7Verdict: "DORMANT",
|
|
48996
|
+
_v7Lift: 1,
|
|
48997
|
+
_v7Recall: 0,
|
|
48998
|
+
_v7FpRate: 0,
|
|
48999
|
+
_v7Precision: 0,
|
|
49000
|
+
_v8Verdict: "DORMANT",
|
|
49001
|
+
_v8Lift: 1,
|
|
49002
|
+
defaultOff: true
|
|
49003
|
+
},
|
|
49004
|
+
"go/error-wrap-without-context": {
|
|
49005
|
+
recall: 0,
|
|
49006
|
+
fpRate: 0,
|
|
49007
|
+
ratio: 1,
|
|
49008
|
+
precision: 0,
|
|
49009
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49010
|
+
verdict: "DORMANT",
|
|
49011
|
+
_calibrationNote: "v0.19: new rule (fmt.Errorf wrap with generic message \u2014 needs operation context). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49012
|
+
aiSpecific: true,
|
|
49013
|
+
_v7Verdict: "DORMANT",
|
|
49014
|
+
_v7Lift: 1,
|
|
49015
|
+
_v7Recall: 0,
|
|
49016
|
+
_v7FpRate: 0,
|
|
49017
|
+
_v7Precision: 0,
|
|
49018
|
+
_v8Verdict: "DORMANT",
|
|
49019
|
+
_v8Lift: 1,
|
|
49020
|
+
defaultOff: true
|
|
49021
|
+
},
|
|
49022
|
+
"go/nil-slice-vs-empty": {
|
|
49023
|
+
recall: 0,
|
|
49024
|
+
fpRate: 0,
|
|
49025
|
+
ratio: 1,
|
|
49026
|
+
precision: 0,
|
|
49027
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49028
|
+
verdict: "DORMANT",
|
|
49029
|
+
_calibrationNote: "v0.19: new rule (Variable declared nil slice but assigned empty slice). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49030
|
+
aiSpecific: true,
|
|
49031
|
+
_v7Verdict: "DORMANT",
|
|
49032
|
+
_v7Lift: 1,
|
|
49033
|
+
_v7Recall: 0,
|
|
49034
|
+
_v7FpRate: 0,
|
|
49035
|
+
_v7Precision: 0,
|
|
49036
|
+
_v8Verdict: "DORMANT",
|
|
49037
|
+
_v8Lift: 1,
|
|
49038
|
+
defaultOff: true
|
|
49039
|
+
},
|
|
49040
|
+
"go/struct-tag-inconsistency": {
|
|
49041
|
+
recall: 0,
|
|
49042
|
+
fpRate: 0,
|
|
49043
|
+
ratio: 1,
|
|
49044
|
+
precision: 0,
|
|
49045
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49046
|
+
verdict: "DORMANT",
|
|
49047
|
+
_calibrationNote: "v0.19: new rule (Struct fields mix json tag styles). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49048
|
+
aiSpecific: true,
|
|
49049
|
+
_v7Verdict: "DORMANT",
|
|
49050
|
+
_v7Lift: 1,
|
|
49051
|
+
_v7Recall: 0,
|
|
49052
|
+
_v7FpRate: 0,
|
|
49053
|
+
_v7Precision: 0,
|
|
49054
|
+
_v8Verdict: "DORMANT",
|
|
49055
|
+
_v8Lift: 1,
|
|
49056
|
+
defaultOff: true
|
|
49057
|
+
},
|
|
48567
49058
|
"layout/forced-layout": {
|
|
48568
49059
|
recall: 0,
|
|
48569
49060
|
fpRate: 0,
|
|
@@ -48692,7 +49183,7 @@ var init_signal_strength = __esm({
|
|
|
48692
49183
|
precision: 0.8889,
|
|
48693
49184
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
48694
49185
|
verdict: "USEFUL",
|
|
48695
|
-
_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).",
|
|
49186
|
+
_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.",
|
|
48696
49187
|
aiSpecific: true,
|
|
48697
49188
|
_v7Verdict: "USEFUL",
|
|
48698
49189
|
_v7Lift: 80917.5,
|
|
@@ -48700,7 +49191,8 @@ var init_signal_strength = __esm({
|
|
|
48700
49191
|
_v7FpRate: 0,
|
|
48701
49192
|
_v7Precision: 0.8824,
|
|
48702
49193
|
_v8Verdict: "USEFUL",
|
|
48703
|
-
_v8Lift: 99999
|
|
49194
|
+
_v8Lift: 99999,
|
|
49195
|
+
defaultOff: false
|
|
48704
49196
|
},
|
|
48705
49197
|
"logic/heaps-deviation": {
|
|
48706
49198
|
recall: 0.0126,
|
|
@@ -48736,24 +49228,6 @@ var init_signal_strength = __esm({
|
|
|
48736
49228
|
_v8Verdict: "USEFUL",
|
|
48737
49229
|
_v8Lift: 1760.69
|
|
48738
49230
|
},
|
|
48739
|
-
"logic/ks-distribution-shift": {
|
|
48740
|
-
recall: 0.6889,
|
|
48741
|
-
fpRate: 0.4411,
|
|
48742
|
-
ratio: 1.46,
|
|
48743
|
-
precision: 0.6457,
|
|
48744
|
-
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
48745
|
-
verdict: "NOISY",
|
|
48746
|
-
_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).",
|
|
48747
|
-
aiSpecific: false,
|
|
48748
|
-
_v7Verdict: "USEFUL",
|
|
48749
|
-
_v7Lift: 2.09,
|
|
48750
|
-
_v7Recall: 0.6431,
|
|
48751
|
-
_v7FpRate: 0.34,
|
|
48752
|
-
_v7Precision: 0.7096,
|
|
48753
|
-
_v8Verdict: "INVERTED",
|
|
48754
|
-
_v8Lift: 0.71,
|
|
48755
|
-
defaultOff: true
|
|
48756
|
-
},
|
|
48757
49231
|
"logic/math-any-density": {
|
|
48758
49232
|
recall: 17e-4,
|
|
48759
49233
|
fpRate: 13e-4,
|
|
@@ -49119,7 +49593,7 @@ var init_signal_strength = __esm({
|
|
|
49119
49593
|
precision: 1,
|
|
49120
49594
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49121
49595
|
verdict: "USEFUL",
|
|
49122
|
-
_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).",
|
|
49596
|
+
_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.",
|
|
49123
49597
|
aiSpecific: true,
|
|
49124
49598
|
_v7Verdict: "USEFUL",
|
|
49125
49599
|
_v7Lift: 99999,
|
|
@@ -49127,7 +49601,8 @@ var init_signal_strength = __esm({
|
|
|
49127
49601
|
_v7FpRate: 0,
|
|
49128
49602
|
_v7Precision: 1,
|
|
49129
49603
|
_v8Verdict: "DORMANT",
|
|
49130
|
-
_v8Lift: 1
|
|
49604
|
+
_v8Lift: 1,
|
|
49605
|
+
defaultOff: false
|
|
49131
49606
|
},
|
|
49132
49607
|
"security/hardcoded-secret": {
|
|
49133
49608
|
recall: 14e-4,
|
|
@@ -49317,6 +49792,96 @@ var init_signal_strength = __esm({
|
|
|
49317
49792
|
_v8Verdict: "USEFUL",
|
|
49318
49793
|
_v8Lift: 651.94
|
|
49319
49794
|
},
|
|
49795
|
+
"ts/enum-vs-as-const": {
|
|
49796
|
+
recall: 0,
|
|
49797
|
+
fpRate: 0,
|
|
49798
|
+
ratio: 1,
|
|
49799
|
+
precision: 0,
|
|
49800
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49801
|
+
verdict: "DORMANT",
|
|
49802
|
+
_calibrationNote: "v0.19: new rule (Uses `enum` \u2014 modern TS prefers `as const`). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49803
|
+
aiSpecific: true,
|
|
49804
|
+
_v7Verdict: "DORMANT",
|
|
49805
|
+
_v7Lift: 1,
|
|
49806
|
+
_v7Recall: 0,
|
|
49807
|
+
_v7FpRate: 0,
|
|
49808
|
+
_v7Precision: 0,
|
|
49809
|
+
_v8Verdict: "DORMANT",
|
|
49810
|
+
_v8Lift: 1,
|
|
49811
|
+
defaultOff: true
|
|
49812
|
+
},
|
|
49813
|
+
"ts/excessive-type-assertion": {
|
|
49814
|
+
recall: 0,
|
|
49815
|
+
fpRate: 0,
|
|
49816
|
+
ratio: 1,
|
|
49817
|
+
precision: 0,
|
|
49818
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49819
|
+
verdict: "DORMANT",
|
|
49820
|
+
_calibrationNote: "v0.19: new rule (Function with >3 `as` assertions \u2014 AI fighting the type system). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49821
|
+
aiSpecific: true,
|
|
49822
|
+
_v7Verdict: "DORMANT",
|
|
49823
|
+
_v7Lift: 1,
|
|
49824
|
+
_v7Recall: 0,
|
|
49825
|
+
_v7FpRate: 0,
|
|
49826
|
+
_v7Precision: 0,
|
|
49827
|
+
_v8Verdict: "DORMANT",
|
|
49828
|
+
_v8Lift: 1,
|
|
49829
|
+
defaultOff: true
|
|
49830
|
+
},
|
|
49831
|
+
"ts/import-type-misuse": {
|
|
49832
|
+
recall: 0,
|
|
49833
|
+
fpRate: 0,
|
|
49834
|
+
ratio: 1,
|
|
49835
|
+
precision: 0,
|
|
49836
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49837
|
+
verdict: "DORMANT",
|
|
49838
|
+
_calibrationNote: "v0.19: new rule (Inline `import { type X }` \u2014 prefer separate `import type`). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49839
|
+
aiSpecific: true,
|
|
49840
|
+
_v7Verdict: "DORMANT",
|
|
49841
|
+
_v7Lift: 1,
|
|
49842
|
+
_v7Recall: 0,
|
|
49843
|
+
_v7FpRate: 0,
|
|
49844
|
+
_v7Precision: 0,
|
|
49845
|
+
_v8Verdict: "DORMANT",
|
|
49846
|
+
_v8Lift: 1,
|
|
49847
|
+
defaultOff: true
|
|
49848
|
+
},
|
|
49849
|
+
"ts/never-vs-unknown": {
|
|
49850
|
+
recall: 0,
|
|
49851
|
+
fpRate: 0,
|
|
49852
|
+
ratio: 1,
|
|
49853
|
+
precision: 0,
|
|
49854
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49855
|
+
verdict: "DORMANT",
|
|
49856
|
+
_calibrationNote: "v0.19: new rule (Return type `never` but body has no throw/loop/exit). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49857
|
+
aiSpecific: true,
|
|
49858
|
+
_v7Verdict: "DORMANT",
|
|
49859
|
+
_v7Lift: 1,
|
|
49860
|
+
_v7Recall: 0,
|
|
49861
|
+
_v7FpRate: 0,
|
|
49862
|
+
_v7Precision: 0,
|
|
49863
|
+
_v8Verdict: "DORMANT",
|
|
49864
|
+
_v8Lift: 1,
|
|
49865
|
+
defaultOff: true
|
|
49866
|
+
},
|
|
49867
|
+
"ts/optional-chain-overuse": {
|
|
49868
|
+
recall: 0,
|
|
49869
|
+
fpRate: 0,
|
|
49870
|
+
ratio: 1,
|
|
49871
|
+
precision: 0,
|
|
49872
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49873
|
+
verdict: "DORMANT",
|
|
49874
|
+
_calibrationNote: "v0.19: new rule (Optional chain depth >= 5 \u2014 AI chains ?. rather than narrowing). Not yet calibrated. Scheduled for v9 calibration.",
|
|
49875
|
+
aiSpecific: true,
|
|
49876
|
+
_v7Verdict: "DORMANT",
|
|
49877
|
+
_v7Lift: 1,
|
|
49878
|
+
_v7Recall: 0,
|
|
49879
|
+
_v7FpRate: 0,
|
|
49880
|
+
_v7Precision: 0,
|
|
49881
|
+
_v8Verdict: "DORMANT",
|
|
49882
|
+
_v8Lift: 1,
|
|
49883
|
+
defaultOff: true
|
|
49884
|
+
},
|
|
49320
49885
|
"typo/calc-fontsize": {
|
|
49321
49886
|
recall: 0,
|
|
49322
49887
|
fpRate: 0,
|
|
@@ -49619,7 +50184,7 @@ var init_signal_strength = __esm({
|
|
|
49619
50184
|
precision: 0.9774,
|
|
49620
50185
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
49621
50186
|
verdict: "USEFUL",
|
|
49622
|
-
_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).",
|
|
50187
|
+
_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.",
|
|
49623
50188
|
aiSpecific: false,
|
|
49624
50189
|
_v7Verdict: "USEFUL",
|
|
49625
50190
|
_v7Lift: 59285.01,
|
|
@@ -49627,7 +50192,8 @@ var init_signal_strength = __esm({
|
|
|
49627
50192
|
_v7FpRate: 0,
|
|
49628
50193
|
_v7Precision: 0.9697,
|
|
49629
50194
|
_v8Verdict: "USEFUL",
|
|
49630
|
-
_v8Lift: 99999
|
|
50195
|
+
_v8Lift: 99999,
|
|
50196
|
+
defaultOff: false
|
|
49631
50197
|
},
|
|
49632
50198
|
"visual/spacing-scale-violation": {
|
|
49633
50199
|
recall: 86e-4,
|
|
@@ -56633,7 +57199,19 @@ var RULE_HINTS = {
|
|
|
56633
57199
|
"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.",
|
|
56634
57200
|
"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.",
|
|
56635
57201
|
"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.",
|
|
56636
|
-
"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.'
|
|
57202
|
+
"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.',
|
|
57203
|
+
// v0.19.0 — TypeScript-specific rules
|
|
57204
|
+
"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.",
|
|
57205
|
+
"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.",
|
|
57206
|
+
"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.",
|
|
57207
|
+
"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.",
|
|
57208
|
+
"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.",
|
|
57209
|
+
// v0.19.0 — Go-specific rules
|
|
57210
|
+
"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.',
|
|
57211
|
+
"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).',
|
|
57212
|
+
"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.",
|
|
57213
|
+
// v0.19.0 — Duplication detector
|
|
57214
|
+
"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."
|
|
56637
57215
|
};
|
|
56638
57216
|
|
|
56639
57217
|
// src/cli/commands/explain.ts
|