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/engine/worker.cjs
CHANGED
|
@@ -5463,76 +5463,6 @@ function tokenizeIdentifiers(source) {
|
|
|
5463
5463
|
return tokens;
|
|
5464
5464
|
}
|
|
5465
5465
|
var SQRT_2_TIMES_LN_2 = Math.sqrt(2 * Math.LN2);
|
|
5466
|
-
function ecdfAt(sortedSamples, x) {
|
|
5467
|
-
let lo = 0;
|
|
5468
|
-
let hi = sortedSamples.length;
|
|
5469
|
-
while (lo < hi) {
|
|
5470
|
-
const mid = lo + hi >>> 1;
|
|
5471
|
-
if (sortedSamples[mid] <= x) lo = mid + 1;
|
|
5472
|
-
else hi = mid;
|
|
5473
|
-
}
|
|
5474
|
-
return lo / sortedSamples.length;
|
|
5475
|
-
}
|
|
5476
|
-
function ksStatistic(sampleA, sampleB) {
|
|
5477
|
-
if (sampleA.length === 0 || sampleB.length === 0) return 1;
|
|
5478
|
-
const sortedA = [...sampleA].sort((a, b) => a - b);
|
|
5479
|
-
const sortedB = [...sampleB].sort((a, b) => a - b);
|
|
5480
|
-
const allPoints = [...sortedA, ...sortedB].sort((a, b) => a - b);
|
|
5481
|
-
let maxDiff = 0;
|
|
5482
|
-
for (const x of allPoints) {
|
|
5483
|
-
const fa = ecdfAt(sortedA, x);
|
|
5484
|
-
const fb = ecdfAt(sortedB, x);
|
|
5485
|
-
const diff = Math.abs(fa - fb);
|
|
5486
|
-
if (diff > maxDiff) maxDiff = diff;
|
|
5487
|
-
}
|
|
5488
|
-
return maxDiff;
|
|
5489
|
-
}
|
|
5490
|
-
function ksPValue(statistic, n, m) {
|
|
5491
|
-
if (n === 0 || m === 0) return 1;
|
|
5492
|
-
if (statistic < 0) return 1;
|
|
5493
|
-
if (statistic > 1) return 0;
|
|
5494
|
-
const lambda = Math.sqrt(n * m / (n + m)) * statistic;
|
|
5495
|
-
if (lambda > 3.6) return 0;
|
|
5496
|
-
let p = 0;
|
|
5497
|
-
for (let j = 1; j < 1e3; j++) {
|
|
5498
|
-
const term = 2 * Math.pow(-1, j - 1) * Math.exp(-2 * j * j * lambda * lambda);
|
|
5499
|
-
p += term;
|
|
5500
|
-
if (Math.abs(term) < 1e-15) break;
|
|
5501
|
-
}
|
|
5502
|
-
return Math.max(0, Math.min(1, p));
|
|
5503
|
-
}
|
|
5504
|
-
function ksTest(sampleA, sampleB, alpha = 0.05) {
|
|
5505
|
-
const statistic = ksStatistic(sampleA, sampleB);
|
|
5506
|
-
const pValue = ksPValue(statistic, sampleA.length, sampleB.length);
|
|
5507
|
-
return {
|
|
5508
|
-
statistic,
|
|
5509
|
-
pValue,
|
|
5510
|
-
significant: pValue < alpha,
|
|
5511
|
-
n: sampleA.length,
|
|
5512
|
-
m: sampleB.length
|
|
5513
|
-
};
|
|
5514
|
-
}
|
|
5515
|
-
function multiFeatureKsTest(features, baselines, alpha = 0.05) {
|
|
5516
|
-
const featureNames = [...features.keys()];
|
|
5517
|
-
const k = featureNames.length;
|
|
5518
|
-
const bonferroniAlpha = k > 0 ? alpha / k : alpha;
|
|
5519
|
-
const perFeature = /* @__PURE__ */ new Map();
|
|
5520
|
-
const significantFeatures = [];
|
|
5521
|
-
for (const name of featureNames) {
|
|
5522
|
-
const sample = features.get(name);
|
|
5523
|
-
const baseline = baselines.get(name);
|
|
5524
|
-
if (!sample || !baseline) continue;
|
|
5525
|
-
const result = ksTest(sample, baseline, bonferroniAlpha);
|
|
5526
|
-
perFeature.set(name, result);
|
|
5527
|
-
if (result.significant) significantFeatures.push(name);
|
|
5528
|
-
}
|
|
5529
|
-
return {
|
|
5530
|
-
perFeature,
|
|
5531
|
-
bonferroniAlpha,
|
|
5532
|
-
anySignificant: significantFeatures.length > 0,
|
|
5533
|
-
significantFeatures
|
|
5534
|
-
};
|
|
5535
|
-
}
|
|
5536
5466
|
|
|
5537
5467
|
// src/engine/visitors/react.ts
|
|
5538
5468
|
function isObject(node) {
|
|
@@ -36262,6 +36192,218 @@ var brokenLinkRule = createRule({
|
|
|
36262
36192
|
}
|
|
36263
36193
|
});
|
|
36264
36194
|
|
|
36195
|
+
// src/rules/dup/identical-block.ts
|
|
36196
|
+
var crypto = __toESM(require("crypto"), 1);
|
|
36197
|
+
var WINDOW_SIZE = 10;
|
|
36198
|
+
var MIN_NORMALIZED_LENGTH = 40;
|
|
36199
|
+
var HASH_PREFIX_LENGTH = 16;
|
|
36200
|
+
var DEDUP_CACHE = /* @__PURE__ */ new Map();
|
|
36201
|
+
function normalizeAndHash(lines) {
|
|
36202
|
+
const normalized = lines.map(
|
|
36203
|
+
(line) => line.replace(/\/\/.*$/, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").trim()
|
|
36204
|
+
).filter((line) => line.length > 0).join("\n");
|
|
36205
|
+
if (normalized.length < MIN_NORMALIZED_LENGTH) return void 0;
|
|
36206
|
+
return crypto.createHash("sha1").update(normalized).digest("hex").slice(0, HASH_PREFIX_LENGTH);
|
|
36207
|
+
}
|
|
36208
|
+
var dupIdenticalBlockRule = createRule({
|
|
36209
|
+
id: "dup/identical-block",
|
|
36210
|
+
category: "logic",
|
|
36211
|
+
severity: "medium",
|
|
36212
|
+
aiSpecific: false,
|
|
36213
|
+
description: "Block of >=10 lines is identical across >=2 files (Type-1 clone detector)",
|
|
36214
|
+
create(_context) {
|
|
36215
|
+
return {};
|
|
36216
|
+
},
|
|
36217
|
+
analyze(_context, facts) {
|
|
36218
|
+
const issues = [];
|
|
36219
|
+
const source = facts.v2?._source;
|
|
36220
|
+
if (!source) return issues;
|
|
36221
|
+
const filePath = facts.filePath;
|
|
36222
|
+
const lines = source.split("\n");
|
|
36223
|
+
for (let i = 0; i <= lines.length - WINDOW_SIZE; i++) {
|
|
36224
|
+
const window = lines.slice(i, i + WINDOW_SIZE);
|
|
36225
|
+
const hash = normalizeAndHash(window);
|
|
36226
|
+
if (!hash) continue;
|
|
36227
|
+
const existing = DEDUP_CACHE.get(hash) ?? [];
|
|
36228
|
+
const matches = existing.filter((m) => m.file !== filePath);
|
|
36229
|
+
for (const match of matches) {
|
|
36230
|
+
issues.push({
|
|
36231
|
+
ruleId: "dup/identical-block",
|
|
36232
|
+
category: "logic",
|
|
36233
|
+
severity: "medium",
|
|
36234
|
+
aiSpecific: false,
|
|
36235
|
+
message: `Identical ${WINDOW_SIZE}-line block at line ${i + 1} also appears in ${match.file}:${match.line + 1}`,
|
|
36236
|
+
line: i + 1,
|
|
36237
|
+
column: 0,
|
|
36238
|
+
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.",
|
|
36239
|
+
extras: {
|
|
36240
|
+
duplicateOf: {
|
|
36241
|
+
file: match.file,
|
|
36242
|
+
line: match.line + 1,
|
|
36243
|
+
hash
|
|
36244
|
+
}
|
|
36245
|
+
}
|
|
36246
|
+
});
|
|
36247
|
+
}
|
|
36248
|
+
existing.push({ file: filePath, line: i });
|
|
36249
|
+
DEDUP_CACHE.set(hash, existing);
|
|
36250
|
+
}
|
|
36251
|
+
return issues;
|
|
36252
|
+
}
|
|
36253
|
+
});
|
|
36254
|
+
|
|
36255
|
+
// src/rules/go/error-wrap-without-context.ts
|
|
36256
|
+
var ERR_WRAP_REGEX = /fmt\.Errorf\(\s*"((?:[^"\\]|\\.)*)"\s*,\s*([^)]+)\)/g;
|
|
36257
|
+
var GENERIC_PREFIX_REGEX = /^\s*(?:error|err|failed|wrap(?:ping)?|invalid)\s*(?::\s*)?%w\b/i;
|
|
36258
|
+
var goErrorWrapWithoutContextRule = createRule({
|
|
36259
|
+
id: "go/error-wrap-without-context",
|
|
36260
|
+
category: "typo",
|
|
36261
|
+
severity: "low",
|
|
36262
|
+
aiSpecific: true,
|
|
36263
|
+
description: 'fmt.Errorf wrap without operation context \u2014 AI defaults to generic "error: %w"',
|
|
36264
|
+
create(_context) {
|
|
36265
|
+
return {};
|
|
36266
|
+
},
|
|
36267
|
+
analyze(_context, facts) {
|
|
36268
|
+
const issues = [];
|
|
36269
|
+
const source = facts.v2?._source;
|
|
36270
|
+
if (!source) return issues;
|
|
36271
|
+
let match;
|
|
36272
|
+
ERR_WRAP_REGEX.lastIndex = 0;
|
|
36273
|
+
while ((match = ERR_WRAP_REGEX.exec(source)) !== null) {
|
|
36274
|
+
const formatString = match[1];
|
|
36275
|
+
if (!formatString.includes("%w")) continue;
|
|
36276
|
+
if (formatString.length >= 30) continue;
|
|
36277
|
+
if (!GENERIC_PREFIX_REGEX.test(formatString)) continue;
|
|
36278
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
36279
|
+
issues.push({
|
|
36280
|
+
ruleId: "go/error-wrap-without-context",
|
|
36281
|
+
category: "typo",
|
|
36282
|
+
severity: "low",
|
|
36283
|
+
aiSpecific: true,
|
|
36284
|
+
message: `fmt.Errorf wrap with generic message "${formatString}" \u2014 include the failing operation`,
|
|
36285
|
+
line,
|
|
36286
|
+
column: match[0].indexOf("fmt") + 1,
|
|
36287
|
+
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'
|
|
36288
|
+
});
|
|
36289
|
+
}
|
|
36290
|
+
return issues;
|
|
36291
|
+
}
|
|
36292
|
+
});
|
|
36293
|
+
|
|
36294
|
+
// src/rules/go/nil-slice-vs-empty.ts
|
|
36295
|
+
var NIL_SLICE_DECL_REGEX = /^[\t ]*var\s+([A-Za-z_][A-Za-z0-9_]*)\s+\[\][\w.*]+\b/gm;
|
|
36296
|
+
var EMPTY_SLICE_ASSIGN_REGEX = /^[\t ]*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:\[\][\w.*]*\{\}|make\(\[\][\w.*]*)/gm;
|
|
36297
|
+
var goNilSliceVsEmptyRule = createRule({
|
|
36298
|
+
id: "go/nil-slice-vs-empty",
|
|
36299
|
+
category: "typo",
|
|
36300
|
+
severity: "low",
|
|
36301
|
+
aiSpecific: true,
|
|
36302
|
+
description: "Variable declared `var x []int` but later assigned `x = []int{}` or `make([]int, n)` \u2014 pick one form",
|
|
36303
|
+
create(_context) {
|
|
36304
|
+
return {};
|
|
36305
|
+
},
|
|
36306
|
+
analyze(_context, facts) {
|
|
36307
|
+
const issues = [];
|
|
36308
|
+
const source = facts.v2?._source;
|
|
36309
|
+
if (!source) return issues;
|
|
36310
|
+
const nilDecls = /* @__PURE__ */ new Map();
|
|
36311
|
+
let m;
|
|
36312
|
+
NIL_SLICE_DECL_REGEX.lastIndex = 0;
|
|
36313
|
+
while ((m = NIL_SLICE_DECL_REGEX.exec(source)) !== null) {
|
|
36314
|
+
nilDecls.set(m[1], source.slice(0, m.index).split("\n").length);
|
|
36315
|
+
}
|
|
36316
|
+
if (nilDecls.size === 0) return issues;
|
|
36317
|
+
EMPTY_SLICE_ASSIGN_REGEX.lastIndex = 0;
|
|
36318
|
+
while ((m = EMPTY_SLICE_ASSIGN_REGEX.exec(source)) !== null) {
|
|
36319
|
+
const name = m[1];
|
|
36320
|
+
if (!nilDecls.has(name)) continue;
|
|
36321
|
+
const declLine = nilDecls.get(name);
|
|
36322
|
+
const assignLine = source.slice(0, m.index).split("\n").length;
|
|
36323
|
+
if (assignLine <= declLine) continue;
|
|
36324
|
+
issues.push({
|
|
36325
|
+
ruleId: "go/nil-slice-vs-empty",
|
|
36326
|
+
category: "typo",
|
|
36327
|
+
severity: "low",
|
|
36328
|
+
aiSpecific: true,
|
|
36329
|
+
message: `Variable '${name}' declared as nil slice (line ${declLine}) but assigned an empty slice (line ${assignLine}) \u2014 pick one form`,
|
|
36330
|
+
line: declLine,
|
|
36331
|
+
column: 1,
|
|
36332
|
+
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."
|
|
36333
|
+
});
|
|
36334
|
+
}
|
|
36335
|
+
return issues;
|
|
36336
|
+
}
|
|
36337
|
+
});
|
|
36338
|
+
|
|
36339
|
+
// src/rules/go/struct-tag-inconsistency.ts
|
|
36340
|
+
var JSON_TAG_REGEX = /`json:"([^",]+)(?:,([^"]+))?"`/g;
|
|
36341
|
+
var goStructTagInconsistencyRule = createRule({
|
|
36342
|
+
id: "go/struct-tag-inconsistency",
|
|
36343
|
+
category: "typo",
|
|
36344
|
+
severity: "low",
|
|
36345
|
+
aiSpecific: true,
|
|
36346
|
+
description: 'Struct fields mix json:"foo" and json:"foo,omitempty" \u2014 pick one convention per struct',
|
|
36347
|
+
create(_context) {
|
|
36348
|
+
return {};
|
|
36349
|
+
},
|
|
36350
|
+
analyze(_context, facts) {
|
|
36351
|
+
const issues = [];
|
|
36352
|
+
const source = facts.v2?._source;
|
|
36353
|
+
if (!source) return issues;
|
|
36354
|
+
const structRegex = /type\s+[A-Z][A-Za-z0-9_]*\s+struct\s*\{/g;
|
|
36355
|
+
let structMatch;
|
|
36356
|
+
while ((structMatch = structRegex.exec(source)) !== null) {
|
|
36357
|
+
const startIdx = structMatch.index;
|
|
36358
|
+
const openBrace = source.indexOf("{", startIdx);
|
|
36359
|
+
if (openBrace < 0) continue;
|
|
36360
|
+
let depth = 1;
|
|
36361
|
+
let i = openBrace + 1;
|
|
36362
|
+
while (i < source.length && depth > 0) {
|
|
36363
|
+
const ch = source[i];
|
|
36364
|
+
if (ch === "{") depth++;
|
|
36365
|
+
else if (ch === "}") depth--;
|
|
36366
|
+
i++;
|
|
36367
|
+
}
|
|
36368
|
+
const structBody = source.slice(openBrace, i);
|
|
36369
|
+
const structLine = source.slice(0, startIdx).split("\n").length;
|
|
36370
|
+
const styleCount = {};
|
|
36371
|
+
const tagMatches = [];
|
|
36372
|
+
let m;
|
|
36373
|
+
JSON_TAG_REGEX.lastIndex = 0;
|
|
36374
|
+
while ((m = JSON_TAG_REGEX.exec(structBody)) !== null) {
|
|
36375
|
+
const tag = m[1];
|
|
36376
|
+
const options = m[2] ?? "";
|
|
36377
|
+
const style = options ? "with-options" : "no-options";
|
|
36378
|
+
styleCount[style] = (styleCount[style] ?? 0) + 1;
|
|
36379
|
+
tagMatches.push({ tag, style, idx: openBrace + m.index });
|
|
36380
|
+
}
|
|
36381
|
+
const styles = Object.keys(styleCount);
|
|
36382
|
+
if (styles.length < 2 || tagMatches.length < 2) continue;
|
|
36383
|
+
const dominant = styles.reduce(
|
|
36384
|
+
(a, b) => (styleCount[a] ?? 0) >= (styleCount[b] ?? 0) ? a : b
|
|
36385
|
+
);
|
|
36386
|
+
const minority = tagMatches.filter((t) => t.style !== dominant);
|
|
36387
|
+
if (minority.length === 0) continue;
|
|
36388
|
+
for (const m2 of minority) {
|
|
36389
|
+
const line = source.slice(0, m2.idx).split("\n").length;
|
|
36390
|
+
issues.push({
|
|
36391
|
+
ruleId: "go/struct-tag-inconsistency",
|
|
36392
|
+
category: "typo",
|
|
36393
|
+
severity: "low",
|
|
36394
|
+
aiSpecific: true,
|
|
36395
|
+
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"}`,
|
|
36396
|
+
line,
|
|
36397
|
+
column: 1,
|
|
36398
|
+
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.'
|
|
36399
|
+
});
|
|
36400
|
+
}
|
|
36401
|
+
if (issues.length > 0) break;
|
|
36402
|
+
}
|
|
36403
|
+
return issues;
|
|
36404
|
+
}
|
|
36405
|
+
});
|
|
36406
|
+
|
|
36265
36407
|
// src/rules/layout/gap-monopoly.ts
|
|
36266
36408
|
var GAP_RE = /\bgap(?:-x|-y)?-(\d+)\b/g;
|
|
36267
36409
|
var gapMonopolyRule = createRule({
|
|
@@ -37049,82 +37191,6 @@ var keyPropMissingRule = createRule({
|
|
|
37049
37191
|
}
|
|
37050
37192
|
});
|
|
37051
37193
|
|
|
37052
|
-
// src/rules/logic/ks-distribution-shift.ts
|
|
37053
|
-
var MIN_SAMPLES_PER_FEATURE = 20;
|
|
37054
|
-
function extractFileFeatures(source) {
|
|
37055
|
-
const lines = source.split("\n");
|
|
37056
|
-
const lineLengths = lines.map((l) => l.length);
|
|
37057
|
-
const identifierLengths = [];
|
|
37058
|
-
const idRe = /[A-Za-z_$][A-Za-z0-9_$]*/g;
|
|
37059
|
-
let m;
|
|
37060
|
-
while ((m = idRe.exec(source)) !== null) {
|
|
37061
|
-
identifierLengths.push(m[0].length);
|
|
37062
|
-
}
|
|
37063
|
-
const commentDensity = lines.map((l) => {
|
|
37064
|
-
const trimmed = l.trim();
|
|
37065
|
-
if (trimmed.length === 0) return 0;
|
|
37066
|
-
const commentChars = (trimmed.match(/^\/\/.*$/)?.[0]?.length ?? 0) + (trimmed.match(/^\s*\/\*.*?\*\/\s*$/)?.at(0)?.length ?? 0);
|
|
37067
|
-
return commentChars / trimmed.length;
|
|
37068
|
-
});
|
|
37069
|
-
return { lineLengths, identifierLengths, commentDensity };
|
|
37070
|
-
}
|
|
37071
|
-
var ksDistributionShiftRule = createRule({
|
|
37072
|
-
id: "logic/ks-distribution-shift",
|
|
37073
|
-
category: "logic",
|
|
37074
|
-
severity: "medium",
|
|
37075
|
-
aiSpecific: false,
|
|
37076
|
-
description: "Multi-feature Kolmogorov\u2013Smirnov distribution-shift vs corpus baseline (Bonferroni-corrected). Peer-reviewed ML distribution-shift detector (arXiv:2510.15996, Oct 2025).",
|
|
37077
|
-
create(context) {
|
|
37078
|
-
return context;
|
|
37079
|
-
},
|
|
37080
|
-
analyze(_context, facts) {
|
|
37081
|
-
const issues = [];
|
|
37082
|
-
if (!facts.v2) return issues;
|
|
37083
|
-
const source = facts.v2._source ?? "";
|
|
37084
|
-
if (source.length < 200) return issues;
|
|
37085
|
-
const features = extractFileFeatures(source);
|
|
37086
|
-
const samples = /* @__PURE__ */ new Map([
|
|
37087
|
-
["lineLengths", features.lineLengths],
|
|
37088
|
-
["identifierLengths", features.identifierLengths],
|
|
37089
|
-
["commentDensity", features.commentDensity]
|
|
37090
|
-
]);
|
|
37091
|
-
const baselines = getCorpusBaselines();
|
|
37092
|
-
const baselinesMap = /* @__PURE__ */ new Map();
|
|
37093
|
-
if (baselines) {
|
|
37094
|
-
baselinesMap.set("lineLengths", baselines.features.lineLengths.sample);
|
|
37095
|
-
baselinesMap.set("identifierLengths", baselines.features.identifierLengths.sample);
|
|
37096
|
-
baselinesMap.set("commentDensity", baselines.features.commentDensity.sample);
|
|
37097
|
-
} else {
|
|
37098
|
-
baselinesMap.set("lineLengths", [20, 25, 30, 32, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100]);
|
|
37099
|
-
baselinesMap.set("identifierLengths", [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 24, 28]);
|
|
37100
|
-
baselinesMap.set("commentDensity", [0, 0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5]);
|
|
37101
|
-
}
|
|
37102
|
-
for (const [name, vals] of samples) {
|
|
37103
|
-
if (vals.length < MIN_SAMPLES_PER_FEATURE) samples.delete(name);
|
|
37104
|
-
}
|
|
37105
|
-
if (samples.size === 0) return issues;
|
|
37106
|
-
const result = multiFeatureKsTest(samples, baselinesMap, 0.05);
|
|
37107
|
-
if (!result.anySignificant) return issues;
|
|
37108
|
-
const shifted = result.significantFeatures.join(", ");
|
|
37109
|
-
const details = result.significantFeatures.map((name) => {
|
|
37110
|
-
const r = result.perFeature.get(name);
|
|
37111
|
-
if (!r) return name;
|
|
37112
|
-
return `${name} (D=${r.statistic.toFixed(3)}, p=${r.pValue.toExponential(2)})`;
|
|
37113
|
-
}).join("; ");
|
|
37114
|
-
issues.push({
|
|
37115
|
-
ruleId: "logic/ks-distribution-shift",
|
|
37116
|
-
category: "logic",
|
|
37117
|
-
severity: "medium",
|
|
37118
|
-
aiSpecific: true,
|
|
37119
|
-
message: `Distribution shift detected on ${result.significantFeatures.length} of ${result.perFeature.size} features (Bonferroni \u03B1=${result.bonferroniAlpha.toExponential(2)}). Features: ${shifted}. Detail: ${details}.`,
|
|
37120
|
-
line: 1,
|
|
37121
|
-
column: 1,
|
|
37122
|
-
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."
|
|
37123
|
-
});
|
|
37124
|
-
return issues;
|
|
37125
|
-
}
|
|
37126
|
-
});
|
|
37127
|
-
|
|
37128
37194
|
// src/rules/logic/math-any-density.ts
|
|
37129
37195
|
var ANY_PER_100_LINES = 5;
|
|
37130
37196
|
var MIN_ABSOLUTE = 6;
|
|
@@ -37186,7 +37252,7 @@ var mathAnyDensityRule = createRule({
|
|
|
37186
37252
|
});
|
|
37187
37253
|
|
|
37188
37254
|
// src/rules/logic/math-console-log-storm.ts
|
|
37189
|
-
var
|
|
37255
|
+
var WINDOW_SIZE2 = 30;
|
|
37190
37256
|
var STORM_THRESHOLD = 5;
|
|
37191
37257
|
var CONSOLE_LOG_RE = /\bconsole\.log\s*\(/g;
|
|
37192
37258
|
var mathConsoleLogStormRule = createRule({
|
|
@@ -37217,7 +37283,7 @@ var mathConsoleLogStormRule = createRule({
|
|
|
37217
37283
|
let maxEndLine = 0;
|
|
37218
37284
|
let i = 0;
|
|
37219
37285
|
for (let j = 0; j < lines.length; j++) {
|
|
37220
|
-
while (lines[j] - lines[i] >
|
|
37286
|
+
while (lines[j] - lines[i] > WINDOW_SIZE2) i++;
|
|
37221
37287
|
const count = j - i + 1;
|
|
37222
37288
|
if (count > maxCount) {
|
|
37223
37289
|
maxCount = count;
|
|
@@ -37231,7 +37297,7 @@ var mathConsoleLogStormRule = createRule({
|
|
|
37231
37297
|
category: "logic",
|
|
37232
37298
|
severity: "high",
|
|
37233
37299
|
aiSpecific: true,
|
|
37234
|
-
message: `${maxCount} console.log calls clustered in a ${
|
|
37300
|
+
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.`,
|
|
37235
37301
|
line: firstIdx >= 0 ? lines[firstIdx] : 1,
|
|
37236
37302
|
column: firstIdx >= 0 ? columns[firstIdx] : 1,
|
|
37237
37303
|
advice: "Replace debug logs with a proper debugger or logger.debug() \u2014 remove all console.log before shipping."
|
|
@@ -39805,6 +39871,216 @@ function isTautologicalAssertion(hit) {
|
|
|
39805
39871
|
return false;
|
|
39806
39872
|
}
|
|
39807
39873
|
|
|
39874
|
+
// src/rules/ts/enum-vs-as-const.ts
|
|
39875
|
+
var ENUM_DECL_REGEX = /^[ \t]*(?:export\s+)?(?:const\s+)?enum\s+[A-Z_][A-Za-z0-9_]*\s*\{/gm;
|
|
39876
|
+
var tsEnumVsAsConstRule = createRule({
|
|
39877
|
+
id: "ts/enum-vs-as-const",
|
|
39878
|
+
category: "typo",
|
|
39879
|
+
severity: "low",
|
|
39880
|
+
aiSpecific: true,
|
|
39881
|
+
description: "Uses `enum` \u2014 modern TS prefers `as const` objects",
|
|
39882
|
+
create(_context) {
|
|
39883
|
+
return {};
|
|
39884
|
+
},
|
|
39885
|
+
analyze(_context, facts) {
|
|
39886
|
+
const issues = [];
|
|
39887
|
+
const source = facts.v2?._source;
|
|
39888
|
+
if (!source) return issues;
|
|
39889
|
+
let match;
|
|
39890
|
+
ENUM_DECL_REGEX.lastIndex = 0;
|
|
39891
|
+
while ((match = ENUM_DECL_REGEX.exec(source)) !== null) {
|
|
39892
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
39893
|
+
issues.push({
|
|
39894
|
+
ruleId: "ts/enum-vs-as-const",
|
|
39895
|
+
category: "typo",
|
|
39896
|
+
severity: "low",
|
|
39897
|
+
aiSpecific: true,
|
|
39898
|
+
message: `'enum' is an AI / older-TS pattern \u2014 prefer 'as const' for a frozen object literal`,
|
|
39899
|
+
line,
|
|
39900
|
+
column: match[0].indexOf("enum") + 1,
|
|
39901
|
+
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.'
|
|
39902
|
+
});
|
|
39903
|
+
}
|
|
39904
|
+
return issues;
|
|
39905
|
+
}
|
|
39906
|
+
});
|
|
39907
|
+
|
|
39908
|
+
// src/rules/ts/excessive-type-assertion.ts
|
|
39909
|
+
var DEFAULT_MAX = 3;
|
|
39910
|
+
var 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;
|
|
39911
|
+
var AS_REGEX = /(?<![\w$])as\s+(?!const)([A-Z][\w$.,<>\[\]|&]*)/g;
|
|
39912
|
+
var tsExcessiveTypeAssertionRule = createRule({
|
|
39913
|
+
id: "ts/excessive-type-assertion",
|
|
39914
|
+
category: "typo",
|
|
39915
|
+
severity: "low",
|
|
39916
|
+
aiSpecific: true,
|
|
39917
|
+
description: "Function with >3 `as` type assertions \u2014 likely AI fighting the type system",
|
|
39918
|
+
create(_context) {
|
|
39919
|
+
return { maxAssertionsPerFunction: DEFAULT_MAX };
|
|
39920
|
+
},
|
|
39921
|
+
analyze(context, facts) {
|
|
39922
|
+
const issues = [];
|
|
39923
|
+
const source = facts.v2?._source;
|
|
39924
|
+
if (!source) return issues;
|
|
39925
|
+
let match;
|
|
39926
|
+
FN_DECL_REGEX.lastIndex = 0;
|
|
39927
|
+
while ((match = FN_DECL_REGEX.exec(source)) !== null) {
|
|
39928
|
+
const startIdx = match.index;
|
|
39929
|
+
const openBraceIdx = source.indexOf("{", startIdx);
|
|
39930
|
+
if (openBraceIdx < 0) continue;
|
|
39931
|
+
let depth = 1;
|
|
39932
|
+
let i = openBraceIdx + 1;
|
|
39933
|
+
while (i < source.length && depth > 0) {
|
|
39934
|
+
const ch = source[i];
|
|
39935
|
+
if (ch === "{") depth++;
|
|
39936
|
+
else if (ch === "}") depth--;
|
|
39937
|
+
i++;
|
|
39938
|
+
}
|
|
39939
|
+
const body = source.slice(openBraceIdx, i);
|
|
39940
|
+
const line = source.slice(0, startIdx).split("\n").length;
|
|
39941
|
+
let asCount = 0;
|
|
39942
|
+
const seen = /* @__PURE__ */ new Set();
|
|
39943
|
+
let asMatch;
|
|
39944
|
+
AS_REGEX.lastIndex = 0;
|
|
39945
|
+
while ((asMatch = AS_REGEX.exec(body)) !== null) {
|
|
39946
|
+
const captured = asMatch[1];
|
|
39947
|
+
if (seen.has(captured)) continue;
|
|
39948
|
+
seen.add(captured);
|
|
39949
|
+
asCount++;
|
|
39950
|
+
}
|
|
39951
|
+
if (asCount > context.maxAssertionsPerFunction) {
|
|
39952
|
+
issues.push({
|
|
39953
|
+
ruleId: "ts/excessive-type-assertion",
|
|
39954
|
+
category: "typo",
|
|
39955
|
+
severity: "low",
|
|
39956
|
+
aiSpecific: true,
|
|
39957
|
+
message: `Function has ${asCount} 'as' assertions (max ${context.maxAssertionsPerFunction}) \u2014 likely AI fighting the type system`,
|
|
39958
|
+
line,
|
|
39959
|
+
column: 1,
|
|
39960
|
+
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."
|
|
39961
|
+
});
|
|
39962
|
+
}
|
|
39963
|
+
}
|
|
39964
|
+
return issues;
|
|
39965
|
+
}
|
|
39966
|
+
});
|
|
39967
|
+
|
|
39968
|
+
// src/rules/ts/import-type-misuse.ts
|
|
39969
|
+
var INLINE_TYPE_IMPORT_REGEX = /^[ \t]*import\s*\{[^}]*\btype\s+[A-Za-z_]/gm;
|
|
39970
|
+
var tsImportTypeMisuseRule = createRule({
|
|
39971
|
+
id: "ts/import-type-misuse",
|
|
39972
|
+
category: "typo",
|
|
39973
|
+
severity: "low",
|
|
39974
|
+
aiSpecific: true,
|
|
39975
|
+
description: "Inline `import { type X }` \u2014 prefer `import type { X }` for clarity",
|
|
39976
|
+
create(_context) {
|
|
39977
|
+
return {};
|
|
39978
|
+
},
|
|
39979
|
+
analyze(_context, facts) {
|
|
39980
|
+
const issues = [];
|
|
39981
|
+
const source = facts.v2?._source;
|
|
39982
|
+
if (!source) return issues;
|
|
39983
|
+
let match;
|
|
39984
|
+
INLINE_TYPE_IMPORT_REGEX.lastIndex = 0;
|
|
39985
|
+
while ((match = INLINE_TYPE_IMPORT_REGEX.exec(source)) !== null) {
|
|
39986
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
39987
|
+
issues.push({
|
|
39988
|
+
ruleId: "ts/import-type-misuse",
|
|
39989
|
+
category: "typo",
|
|
39990
|
+
severity: "low",
|
|
39991
|
+
aiSpecific: true,
|
|
39992
|
+
message: "Inline `type` in a value import \u2014 split into a separate `import type` statement",
|
|
39993
|
+
line,
|
|
39994
|
+
column: match[0].indexOf("type") + 1,
|
|
39995
|
+
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.'
|
|
39996
|
+
});
|
|
39997
|
+
}
|
|
39998
|
+
return issues;
|
|
39999
|
+
}
|
|
40000
|
+
});
|
|
40001
|
+
|
|
40002
|
+
// src/rules/ts/never-vs-unknown.ts
|
|
40003
|
+
var NEVER_RETURN_REGEX = /^[ \t]*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)[^{]*:\s*(?:Promise<)?never\b[^{]*\{/gm;
|
|
40004
|
+
var THROW_OR_LOOP_REGEX = /\b(?:throw\b|while\s*\(|for\s*\(|process\.exit\b|System\.exit\b)/;
|
|
40005
|
+
var tsNeverVsUnknownRule = createRule({
|
|
40006
|
+
id: "ts/never-vs-unknown",
|
|
40007
|
+
category: "typo",
|
|
40008
|
+
severity: "low",
|
|
40009
|
+
aiSpecific: true,
|
|
40010
|
+
description: "Return type `: never` but body has no throw / loop / exit \u2014 likely AI misuse",
|
|
40011
|
+
create(_context) {
|
|
40012
|
+
return {};
|
|
40013
|
+
},
|
|
40014
|
+
analyze(_context, facts) {
|
|
40015
|
+
const issues = [];
|
|
40016
|
+
const source = facts.v2?._source;
|
|
40017
|
+
if (!source) return issues;
|
|
40018
|
+
let match;
|
|
40019
|
+
NEVER_RETURN_REGEX.lastIndex = 0;
|
|
40020
|
+
while ((match = NEVER_RETURN_REGEX.exec(source)) !== null) {
|
|
40021
|
+
const fnName = match[1];
|
|
40022
|
+
const startIdx = match.index;
|
|
40023
|
+
const line = source.slice(0, startIdx).split("\n").length;
|
|
40024
|
+
const openBraceIdx = source.indexOf("{", startIdx);
|
|
40025
|
+
if (openBraceIdx < 0) continue;
|
|
40026
|
+
let depth = 1;
|
|
40027
|
+
let i = openBraceIdx + 1;
|
|
40028
|
+
while (i < source.length && depth > 0) {
|
|
40029
|
+
const ch = source[i];
|
|
40030
|
+
if (ch === "{") depth++;
|
|
40031
|
+
else if (ch === "}") depth--;
|
|
40032
|
+
i++;
|
|
40033
|
+
}
|
|
40034
|
+
const body = source.slice(openBraceIdx, i);
|
|
40035
|
+
if (THROW_OR_LOOP_REGEX.test(body)) continue;
|
|
40036
|
+
issues.push({
|
|
40037
|
+
ruleId: "ts/never-vs-unknown",
|
|
40038
|
+
category: "typo",
|
|
40039
|
+
severity: "low",
|
|
40040
|
+
aiSpecific: true,
|
|
40041
|
+
message: `Function '${fnName}' returns 'never' but its body has no throw, loop, or exit \u2014 likely AI misuse`,
|
|
40042
|
+
line,
|
|
40043
|
+
column: match[0].indexOf("never") + 1,
|
|
40044
|
+
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.'
|
|
40045
|
+
});
|
|
40046
|
+
}
|
|
40047
|
+
return issues;
|
|
40048
|
+
}
|
|
40049
|
+
});
|
|
40050
|
+
|
|
40051
|
+
// src/rules/ts/optional-chain-overuse.ts
|
|
40052
|
+
var DEFAULT_MIN_CHAIN_LENGTH = 5;
|
|
40053
|
+
var tsOptionalChainOveruseRule = createRule({
|
|
40054
|
+
id: "ts/optional-chain-overuse",
|
|
40055
|
+
category: "logic",
|
|
40056
|
+
severity: "low",
|
|
40057
|
+
aiSpecific: true,
|
|
40058
|
+
description: "Optional chaining (?.) used 5+ times in a single chain \u2014 AI tends to chain rather than narrow",
|
|
40059
|
+
create(_context) {
|
|
40060
|
+
return { minChainLength: DEFAULT_MIN_CHAIN_LENGTH };
|
|
40061
|
+
},
|
|
40062
|
+
analyze(context, facts) {
|
|
40063
|
+
const issues = [];
|
|
40064
|
+
const expressions = facts.v2.logic?.logicalExpressions;
|
|
40065
|
+
if (!expressions) return issues;
|
|
40066
|
+
for (const expression of expressions) {
|
|
40067
|
+
if (expression.depth >= context.minChainLength && expression.isOptionalChainLike) {
|
|
40068
|
+
issues.push({
|
|
40069
|
+
ruleId: "ts/optional-chain-overuse",
|
|
40070
|
+
category: "logic",
|
|
40071
|
+
severity: "low",
|
|
40072
|
+
aiSpecific: true,
|
|
40073
|
+
message: `Optional chain depth ${expression.depth} \u2014 break with an intermediate variable or guard clause`,
|
|
40074
|
+
line: expression.line,
|
|
40075
|
+
column: expression.column,
|
|
40076
|
+
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."
|
|
40077
|
+
});
|
|
40078
|
+
}
|
|
40079
|
+
}
|
|
40080
|
+
return issues;
|
|
40081
|
+
}
|
|
40082
|
+
});
|
|
40083
|
+
|
|
39808
40084
|
// src/rules/typo/calc-fontsize.ts
|
|
39809
40085
|
var FONT_SIZE_RE = /\bfont-size\s*:\s*[^;]*\bcalc\s*\(/i;
|
|
39810
40086
|
var calcFontsizeRule = createRule({
|
|
@@ -41345,6 +41621,10 @@ var builtinRules = [
|
|
|
41345
41621
|
expiredCodeExampleRule,
|
|
41346
41622
|
staleFunctionReferenceRule,
|
|
41347
41623
|
stalePackageReferenceRule,
|
|
41624
|
+
dupIdenticalBlockRule,
|
|
41625
|
+
goErrorWrapWithoutContextRule,
|
|
41626
|
+
goNilSliceVsEmptyRule,
|
|
41627
|
+
goStructTagInconsistencyRule,
|
|
41348
41628
|
gapMonopolyRule,
|
|
41349
41629
|
mathElementUniformityRule,
|
|
41350
41630
|
mathGridUniformityRule,
|
|
@@ -41354,7 +41634,6 @@ var builtinRules = [
|
|
|
41354
41634
|
ghostDefensiveRule,
|
|
41355
41635
|
heapsDeviationRule,
|
|
41356
41636
|
keyPropMissingRule,
|
|
41357
|
-
ksDistributionShiftRule,
|
|
41358
41637
|
mathAnyDensityRule,
|
|
41359
41638
|
mathConsoleLogStormRule,
|
|
41360
41639
|
mathGiniClassUsageRule,
|
|
@@ -41388,6 +41667,11 @@ var builtinRules = [
|
|
|
41388
41667
|
fakePlaceholderRule,
|
|
41389
41668
|
missingEdgeCaseRule,
|
|
41390
41669
|
weakAssertionRule,
|
|
41670
|
+
tsEnumVsAsConstRule,
|
|
41671
|
+
tsExcessiveTypeAssertionRule,
|
|
41672
|
+
tsImportTypeMisuseRule,
|
|
41673
|
+
tsNeverVsUnknownRule,
|
|
41674
|
+
tsOptionalChainOveruseRule,
|
|
41391
41675
|
calcFontsizeRule,
|
|
41392
41676
|
calcRawPxRule,
|
|
41393
41677
|
clampOffscaleRule,
|
|
@@ -41558,7 +41842,7 @@ var signal_strength_default = {
|
|
|
41558
41842
|
precision: 0.9966,
|
|
41559
41843
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
41560
41844
|
verdict: "USEFUL",
|
|
41561
|
-
_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).",
|
|
41845
|
+
_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.",
|
|
41562
41846
|
aiSpecific: true,
|
|
41563
41847
|
_v7Verdict: "USEFUL",
|
|
41564
41848
|
_v7Lift: 182622.43,
|
|
@@ -41566,7 +41850,8 @@ var signal_strength_default = {
|
|
|
41566
41850
|
_v7FpRate: 0,
|
|
41567
41851
|
_v7Precision: 0.9957,
|
|
41568
41852
|
_v8Verdict: "USEFUL",
|
|
41569
|
-
_v8Lift: 99999
|
|
41853
|
+
_v8Lift: 99999,
|
|
41854
|
+
defaultOff: false
|
|
41570
41855
|
},
|
|
41571
41856
|
"ai/errors-near-eof": {
|
|
41572
41857
|
recall: 0.0948,
|
|
@@ -41815,7 +42100,7 @@ var signal_strength_default = {
|
|
|
41815
42100
|
precision: 0.9512,
|
|
41816
42101
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
41817
42102
|
verdict: "USEFUL",
|
|
41818
|
-
_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).",
|
|
42103
|
+
_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.",
|
|
41819
42104
|
aiSpecific: true,
|
|
41820
42105
|
_v7Verdict: "USEFUL",
|
|
41821
42106
|
_v7Lift: 7099.57,
|
|
@@ -41823,7 +42108,8 @@ var signal_strength_default = {
|
|
|
41823
42108
|
_v7FpRate: 1e-4,
|
|
41824
42109
|
_v7Precision: 0.929,
|
|
41825
42110
|
_v8Verdict: "USEFUL",
|
|
41826
|
-
_v8Lift: 99999
|
|
42111
|
+
_v8Lift: 99999,
|
|
42112
|
+
defaultOff: false
|
|
41827
42113
|
},
|
|
41828
42114
|
"context/import-path-mismatch": {
|
|
41829
42115
|
recall: 0.0681,
|
|
@@ -42007,7 +42293,7 @@ var signal_strength_default = {
|
|
|
42007
42293
|
precision: 0.8834,
|
|
42008
42294
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42009
42295
|
verdict: "USEFUL",
|
|
42010
|
-
_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.",
|
|
42296
|
+
_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.",
|
|
42011
42297
|
aiSpecific: true,
|
|
42012
42298
|
_v7Verdict: "DORMANT",
|
|
42013
42299
|
_v7Lift: 1,
|
|
@@ -42015,7 +42301,8 @@ var signal_strength_default = {
|
|
|
42015
42301
|
_v7FpRate: 0,
|
|
42016
42302
|
_v7Precision: 0,
|
|
42017
42303
|
_v8Verdict: "USEFUL",
|
|
42018
|
-
_v8Lift: 32.74
|
|
42304
|
+
_v8Lift: 32.74,
|
|
42305
|
+
defaultOff: false
|
|
42019
42306
|
},
|
|
42020
42307
|
"dead/unused-parameter": {
|
|
42021
42308
|
recall: 8e-4,
|
|
@@ -42103,6 +42390,78 @@ var signal_strength_default = {
|
|
|
42103
42390
|
_v8Verdict: "OK",
|
|
42104
42391
|
_v8Lift: 255.9
|
|
42105
42392
|
},
|
|
42393
|
+
"dup/identical-block": {
|
|
42394
|
+
recall: 0,
|
|
42395
|
+
fpRate: 0,
|
|
42396
|
+
ratio: 1,
|
|
42397
|
+
precision: 0,
|
|
42398
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42399
|
+
verdict: "DORMANT",
|
|
42400
|
+
_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.",
|
|
42401
|
+
aiSpecific: false,
|
|
42402
|
+
_v7Verdict: "DORMANT",
|
|
42403
|
+
_v7Lift: 1,
|
|
42404
|
+
_v7Recall: 0,
|
|
42405
|
+
_v7FpRate: 0,
|
|
42406
|
+
_v7Precision: 0,
|
|
42407
|
+
_v8Verdict: "DORMANT",
|
|
42408
|
+
_v8Lift: 1,
|
|
42409
|
+
defaultOff: true
|
|
42410
|
+
},
|
|
42411
|
+
"go/error-wrap-without-context": {
|
|
42412
|
+
recall: 0,
|
|
42413
|
+
fpRate: 0,
|
|
42414
|
+
ratio: 1,
|
|
42415
|
+
precision: 0,
|
|
42416
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42417
|
+
verdict: "DORMANT",
|
|
42418
|
+
_calibrationNote: "v0.19: new rule (fmt.Errorf wrap with generic message \u2014 needs operation context). Not yet calibrated. Scheduled for v9 calibration.",
|
|
42419
|
+
aiSpecific: true,
|
|
42420
|
+
_v7Verdict: "DORMANT",
|
|
42421
|
+
_v7Lift: 1,
|
|
42422
|
+
_v7Recall: 0,
|
|
42423
|
+
_v7FpRate: 0,
|
|
42424
|
+
_v7Precision: 0,
|
|
42425
|
+
_v8Verdict: "DORMANT",
|
|
42426
|
+
_v8Lift: 1,
|
|
42427
|
+
defaultOff: true
|
|
42428
|
+
},
|
|
42429
|
+
"go/nil-slice-vs-empty": {
|
|
42430
|
+
recall: 0,
|
|
42431
|
+
fpRate: 0,
|
|
42432
|
+
ratio: 1,
|
|
42433
|
+
precision: 0,
|
|
42434
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42435
|
+
verdict: "DORMANT",
|
|
42436
|
+
_calibrationNote: "v0.19: new rule (Variable declared nil slice but assigned empty slice). Not yet calibrated. Scheduled for v9 calibration.",
|
|
42437
|
+
aiSpecific: true,
|
|
42438
|
+
_v7Verdict: "DORMANT",
|
|
42439
|
+
_v7Lift: 1,
|
|
42440
|
+
_v7Recall: 0,
|
|
42441
|
+
_v7FpRate: 0,
|
|
42442
|
+
_v7Precision: 0,
|
|
42443
|
+
_v8Verdict: "DORMANT",
|
|
42444
|
+
_v8Lift: 1,
|
|
42445
|
+
defaultOff: true
|
|
42446
|
+
},
|
|
42447
|
+
"go/struct-tag-inconsistency": {
|
|
42448
|
+
recall: 0,
|
|
42449
|
+
fpRate: 0,
|
|
42450
|
+
ratio: 1,
|
|
42451
|
+
precision: 0,
|
|
42452
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42453
|
+
verdict: "DORMANT",
|
|
42454
|
+
_calibrationNote: "v0.19: new rule (Struct fields mix json tag styles). Not yet calibrated. Scheduled for v9 calibration.",
|
|
42455
|
+
aiSpecific: true,
|
|
42456
|
+
_v7Verdict: "DORMANT",
|
|
42457
|
+
_v7Lift: 1,
|
|
42458
|
+
_v7Recall: 0,
|
|
42459
|
+
_v7FpRate: 0,
|
|
42460
|
+
_v7Precision: 0,
|
|
42461
|
+
_v8Verdict: "DORMANT",
|
|
42462
|
+
_v8Lift: 1,
|
|
42463
|
+
defaultOff: true
|
|
42464
|
+
},
|
|
42106
42465
|
"layout/forced-layout": {
|
|
42107
42466
|
recall: 0,
|
|
42108
42467
|
fpRate: 0,
|
|
@@ -42231,7 +42590,7 @@ var signal_strength_default = {
|
|
|
42231
42590
|
precision: 0.8889,
|
|
42232
42591
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42233
42592
|
verdict: "USEFUL",
|
|
42234
|
-
_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).",
|
|
42593
|
+
_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.",
|
|
42235
42594
|
aiSpecific: true,
|
|
42236
42595
|
_v7Verdict: "USEFUL",
|
|
42237
42596
|
_v7Lift: 80917.5,
|
|
@@ -42239,7 +42598,8 @@ var signal_strength_default = {
|
|
|
42239
42598
|
_v7FpRate: 0,
|
|
42240
42599
|
_v7Precision: 0.8824,
|
|
42241
42600
|
_v8Verdict: "USEFUL",
|
|
42242
|
-
_v8Lift: 99999
|
|
42601
|
+
_v8Lift: 99999,
|
|
42602
|
+
defaultOff: false
|
|
42243
42603
|
},
|
|
42244
42604
|
"logic/heaps-deviation": {
|
|
42245
42605
|
recall: 0.0126,
|
|
@@ -42275,24 +42635,6 @@ var signal_strength_default = {
|
|
|
42275
42635
|
_v8Verdict: "USEFUL",
|
|
42276
42636
|
_v8Lift: 1760.69
|
|
42277
42637
|
},
|
|
42278
|
-
"logic/ks-distribution-shift": {
|
|
42279
|
-
recall: 0.6889,
|
|
42280
|
-
fpRate: 0.4411,
|
|
42281
|
-
ratio: 1.46,
|
|
42282
|
-
precision: 0.6457,
|
|
42283
|
-
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42284
|
-
verdict: "NOISY",
|
|
42285
|
-
_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).",
|
|
42286
|
-
aiSpecific: false,
|
|
42287
|
-
_v7Verdict: "USEFUL",
|
|
42288
|
-
_v7Lift: 2.09,
|
|
42289
|
-
_v7Recall: 0.6431,
|
|
42290
|
-
_v7FpRate: 0.34,
|
|
42291
|
-
_v7Precision: 0.7096,
|
|
42292
|
-
_v8Verdict: "INVERTED",
|
|
42293
|
-
_v8Lift: 0.71,
|
|
42294
|
-
defaultOff: true
|
|
42295
|
-
},
|
|
42296
42638
|
"logic/math-any-density": {
|
|
42297
42639
|
recall: 17e-4,
|
|
42298
42640
|
fpRate: 13e-4,
|
|
@@ -42658,7 +43000,7 @@ var signal_strength_default = {
|
|
|
42658
43000
|
precision: 1,
|
|
42659
43001
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42660
43002
|
verdict: "USEFUL",
|
|
42661
|
-
_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).",
|
|
43003
|
+
_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.",
|
|
42662
43004
|
aiSpecific: true,
|
|
42663
43005
|
_v7Verdict: "USEFUL",
|
|
42664
43006
|
_v7Lift: 99999,
|
|
@@ -42666,7 +43008,8 @@ var signal_strength_default = {
|
|
|
42666
43008
|
_v7FpRate: 0,
|
|
42667
43009
|
_v7Precision: 1,
|
|
42668
43010
|
_v8Verdict: "DORMANT",
|
|
42669
|
-
_v8Lift: 1
|
|
43011
|
+
_v8Lift: 1,
|
|
43012
|
+
defaultOff: false
|
|
42670
43013
|
},
|
|
42671
43014
|
"security/hardcoded-secret": {
|
|
42672
43015
|
recall: 14e-4,
|
|
@@ -42856,6 +43199,96 @@ var signal_strength_default = {
|
|
|
42856
43199
|
_v8Verdict: "USEFUL",
|
|
42857
43200
|
_v8Lift: 651.94
|
|
42858
43201
|
},
|
|
43202
|
+
"ts/enum-vs-as-const": {
|
|
43203
|
+
recall: 0,
|
|
43204
|
+
fpRate: 0,
|
|
43205
|
+
ratio: 1,
|
|
43206
|
+
precision: 0,
|
|
43207
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43208
|
+
verdict: "DORMANT",
|
|
43209
|
+
_calibrationNote: "v0.19: new rule (Uses `enum` \u2014 modern TS prefers `as const`). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43210
|
+
aiSpecific: true,
|
|
43211
|
+
_v7Verdict: "DORMANT",
|
|
43212
|
+
_v7Lift: 1,
|
|
43213
|
+
_v7Recall: 0,
|
|
43214
|
+
_v7FpRate: 0,
|
|
43215
|
+
_v7Precision: 0,
|
|
43216
|
+
_v8Verdict: "DORMANT",
|
|
43217
|
+
_v8Lift: 1,
|
|
43218
|
+
defaultOff: true
|
|
43219
|
+
},
|
|
43220
|
+
"ts/excessive-type-assertion": {
|
|
43221
|
+
recall: 0,
|
|
43222
|
+
fpRate: 0,
|
|
43223
|
+
ratio: 1,
|
|
43224
|
+
precision: 0,
|
|
43225
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43226
|
+
verdict: "DORMANT",
|
|
43227
|
+
_calibrationNote: "v0.19: new rule (Function with >3 `as` assertions \u2014 AI fighting the type system). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43228
|
+
aiSpecific: true,
|
|
43229
|
+
_v7Verdict: "DORMANT",
|
|
43230
|
+
_v7Lift: 1,
|
|
43231
|
+
_v7Recall: 0,
|
|
43232
|
+
_v7FpRate: 0,
|
|
43233
|
+
_v7Precision: 0,
|
|
43234
|
+
_v8Verdict: "DORMANT",
|
|
43235
|
+
_v8Lift: 1,
|
|
43236
|
+
defaultOff: true
|
|
43237
|
+
},
|
|
43238
|
+
"ts/import-type-misuse": {
|
|
43239
|
+
recall: 0,
|
|
43240
|
+
fpRate: 0,
|
|
43241
|
+
ratio: 1,
|
|
43242
|
+
precision: 0,
|
|
43243
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43244
|
+
verdict: "DORMANT",
|
|
43245
|
+
_calibrationNote: "v0.19: new rule (Inline `import { type X }` \u2014 prefer separate `import type`). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43246
|
+
aiSpecific: true,
|
|
43247
|
+
_v7Verdict: "DORMANT",
|
|
43248
|
+
_v7Lift: 1,
|
|
43249
|
+
_v7Recall: 0,
|
|
43250
|
+
_v7FpRate: 0,
|
|
43251
|
+
_v7Precision: 0,
|
|
43252
|
+
_v8Verdict: "DORMANT",
|
|
43253
|
+
_v8Lift: 1,
|
|
43254
|
+
defaultOff: true
|
|
43255
|
+
},
|
|
43256
|
+
"ts/never-vs-unknown": {
|
|
43257
|
+
recall: 0,
|
|
43258
|
+
fpRate: 0,
|
|
43259
|
+
ratio: 1,
|
|
43260
|
+
precision: 0,
|
|
43261
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43262
|
+
verdict: "DORMANT",
|
|
43263
|
+
_calibrationNote: "v0.19: new rule (Return type `never` but body has no throw/loop/exit). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43264
|
+
aiSpecific: true,
|
|
43265
|
+
_v7Verdict: "DORMANT",
|
|
43266
|
+
_v7Lift: 1,
|
|
43267
|
+
_v7Recall: 0,
|
|
43268
|
+
_v7FpRate: 0,
|
|
43269
|
+
_v7Precision: 0,
|
|
43270
|
+
_v8Verdict: "DORMANT",
|
|
43271
|
+
_v8Lift: 1,
|
|
43272
|
+
defaultOff: true
|
|
43273
|
+
},
|
|
43274
|
+
"ts/optional-chain-overuse": {
|
|
43275
|
+
recall: 0,
|
|
43276
|
+
fpRate: 0,
|
|
43277
|
+
ratio: 1,
|
|
43278
|
+
precision: 0,
|
|
43279
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43280
|
+
verdict: "DORMANT",
|
|
43281
|
+
_calibrationNote: "v0.19: new rule (Optional chain depth >= 5 \u2014 AI chains ?. rather than narrowing). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43282
|
+
aiSpecific: true,
|
|
43283
|
+
_v7Verdict: "DORMANT",
|
|
43284
|
+
_v7Lift: 1,
|
|
43285
|
+
_v7Recall: 0,
|
|
43286
|
+
_v7FpRate: 0,
|
|
43287
|
+
_v7Precision: 0,
|
|
43288
|
+
_v8Verdict: "DORMANT",
|
|
43289
|
+
_v8Lift: 1,
|
|
43290
|
+
defaultOff: true
|
|
43291
|
+
},
|
|
42859
43292
|
"typo/calc-fontsize": {
|
|
42860
43293
|
recall: 0,
|
|
42861
43294
|
fpRate: 0,
|
|
@@ -43158,7 +43591,7 @@ var signal_strength_default = {
|
|
|
43158
43591
|
precision: 0.9774,
|
|
43159
43592
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43160
43593
|
verdict: "USEFUL",
|
|
43161
|
-
_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).",
|
|
43594
|
+
_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.",
|
|
43162
43595
|
aiSpecific: false,
|
|
43163
43596
|
_v7Verdict: "USEFUL",
|
|
43164
43597
|
_v7Lift: 59285.01,
|
|
@@ -43166,7 +43599,8 @@ var signal_strength_default = {
|
|
|
43166
43599
|
_v7FpRate: 0,
|
|
43167
43600
|
_v7Precision: 0.9697,
|
|
43168
43601
|
_v8Verdict: "USEFUL",
|
|
43169
|
-
_v8Lift: 99999
|
|
43602
|
+
_v8Lift: 99999,
|
|
43603
|
+
defaultOff: false
|
|
43170
43604
|
},
|
|
43171
43605
|
"visual/spacing-scale-violation": {
|
|
43172
43606
|
recall: 86e-4,
|