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.js
CHANGED
|
@@ -5435,76 +5435,6 @@ function tokenizeIdentifiers(source) {
|
|
|
5435
5435
|
return tokens;
|
|
5436
5436
|
}
|
|
5437
5437
|
var SQRT_2_TIMES_LN_2 = Math.sqrt(2 * Math.LN2);
|
|
5438
|
-
function ecdfAt(sortedSamples, x) {
|
|
5439
|
-
let lo = 0;
|
|
5440
|
-
let hi = sortedSamples.length;
|
|
5441
|
-
while (lo < hi) {
|
|
5442
|
-
const mid = lo + hi >>> 1;
|
|
5443
|
-
if (sortedSamples[mid] <= x) lo = mid + 1;
|
|
5444
|
-
else hi = mid;
|
|
5445
|
-
}
|
|
5446
|
-
return lo / sortedSamples.length;
|
|
5447
|
-
}
|
|
5448
|
-
function ksStatistic(sampleA, sampleB) {
|
|
5449
|
-
if (sampleA.length === 0 || sampleB.length === 0) return 1;
|
|
5450
|
-
const sortedA = [...sampleA].sort((a, b) => a - b);
|
|
5451
|
-
const sortedB = [...sampleB].sort((a, b) => a - b);
|
|
5452
|
-
const allPoints = [...sortedA, ...sortedB].sort((a, b) => a - b);
|
|
5453
|
-
let maxDiff = 0;
|
|
5454
|
-
for (const x of allPoints) {
|
|
5455
|
-
const fa = ecdfAt(sortedA, x);
|
|
5456
|
-
const fb = ecdfAt(sortedB, x);
|
|
5457
|
-
const diff = Math.abs(fa - fb);
|
|
5458
|
-
if (diff > maxDiff) maxDiff = diff;
|
|
5459
|
-
}
|
|
5460
|
-
return maxDiff;
|
|
5461
|
-
}
|
|
5462
|
-
function ksPValue(statistic, n, m) {
|
|
5463
|
-
if (n === 0 || m === 0) return 1;
|
|
5464
|
-
if (statistic < 0) return 1;
|
|
5465
|
-
if (statistic > 1) return 0;
|
|
5466
|
-
const lambda = Math.sqrt(n * m / (n + m)) * statistic;
|
|
5467
|
-
if (lambda > 3.6) return 0;
|
|
5468
|
-
let p = 0;
|
|
5469
|
-
for (let j = 1; j < 1e3; j++) {
|
|
5470
|
-
const term = 2 * Math.pow(-1, j - 1) * Math.exp(-2 * j * j * lambda * lambda);
|
|
5471
|
-
p += term;
|
|
5472
|
-
if (Math.abs(term) < 1e-15) break;
|
|
5473
|
-
}
|
|
5474
|
-
return Math.max(0, Math.min(1, p));
|
|
5475
|
-
}
|
|
5476
|
-
function ksTest(sampleA, sampleB, alpha = 0.05) {
|
|
5477
|
-
const statistic = ksStatistic(sampleA, sampleB);
|
|
5478
|
-
const pValue = ksPValue(statistic, sampleA.length, sampleB.length);
|
|
5479
|
-
return {
|
|
5480
|
-
statistic,
|
|
5481
|
-
pValue,
|
|
5482
|
-
significant: pValue < alpha,
|
|
5483
|
-
n: sampleA.length,
|
|
5484
|
-
m: sampleB.length
|
|
5485
|
-
};
|
|
5486
|
-
}
|
|
5487
|
-
function multiFeatureKsTest(features, baselines, alpha = 0.05) {
|
|
5488
|
-
const featureNames = [...features.keys()];
|
|
5489
|
-
const k = featureNames.length;
|
|
5490
|
-
const bonferroniAlpha = k > 0 ? alpha / k : alpha;
|
|
5491
|
-
const perFeature = /* @__PURE__ */ new Map();
|
|
5492
|
-
const significantFeatures = [];
|
|
5493
|
-
for (const name of featureNames) {
|
|
5494
|
-
const sample = features.get(name);
|
|
5495
|
-
const baseline = baselines.get(name);
|
|
5496
|
-
if (!sample || !baseline) continue;
|
|
5497
|
-
const result = ksTest(sample, baseline, bonferroniAlpha);
|
|
5498
|
-
perFeature.set(name, result);
|
|
5499
|
-
if (result.significant) significantFeatures.push(name);
|
|
5500
|
-
}
|
|
5501
|
-
return {
|
|
5502
|
-
perFeature,
|
|
5503
|
-
bonferroniAlpha,
|
|
5504
|
-
anySignificant: significantFeatures.length > 0,
|
|
5505
|
-
significantFeatures
|
|
5506
|
-
};
|
|
5507
|
-
}
|
|
5508
5438
|
|
|
5509
5439
|
// src/engine/visitors/react.ts
|
|
5510
5440
|
function isObject(node) {
|
|
@@ -36234,6 +36164,218 @@ var brokenLinkRule = createRule({
|
|
|
36234
36164
|
}
|
|
36235
36165
|
});
|
|
36236
36166
|
|
|
36167
|
+
// src/rules/dup/identical-block.ts
|
|
36168
|
+
import * as crypto from "crypto";
|
|
36169
|
+
var WINDOW_SIZE = 10;
|
|
36170
|
+
var MIN_NORMALIZED_LENGTH = 40;
|
|
36171
|
+
var HASH_PREFIX_LENGTH = 16;
|
|
36172
|
+
var DEDUP_CACHE = /* @__PURE__ */ new Map();
|
|
36173
|
+
function normalizeAndHash(lines) {
|
|
36174
|
+
const normalized = lines.map(
|
|
36175
|
+
(line) => line.replace(/\/\/.*$/, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").trim()
|
|
36176
|
+
).filter((line) => line.length > 0).join("\n");
|
|
36177
|
+
if (normalized.length < MIN_NORMALIZED_LENGTH) return void 0;
|
|
36178
|
+
return crypto.createHash("sha1").update(normalized).digest("hex").slice(0, HASH_PREFIX_LENGTH);
|
|
36179
|
+
}
|
|
36180
|
+
var dupIdenticalBlockRule = createRule({
|
|
36181
|
+
id: "dup/identical-block",
|
|
36182
|
+
category: "logic",
|
|
36183
|
+
severity: "medium",
|
|
36184
|
+
aiSpecific: false,
|
|
36185
|
+
description: "Block of >=10 lines is identical across >=2 files (Type-1 clone detector)",
|
|
36186
|
+
create(_context) {
|
|
36187
|
+
return {};
|
|
36188
|
+
},
|
|
36189
|
+
analyze(_context, facts) {
|
|
36190
|
+
const issues = [];
|
|
36191
|
+
const source = facts.v2?._source;
|
|
36192
|
+
if (!source) return issues;
|
|
36193
|
+
const filePath = facts.filePath;
|
|
36194
|
+
const lines = source.split("\n");
|
|
36195
|
+
for (let i = 0; i <= lines.length - WINDOW_SIZE; i++) {
|
|
36196
|
+
const window = lines.slice(i, i + WINDOW_SIZE);
|
|
36197
|
+
const hash = normalizeAndHash(window);
|
|
36198
|
+
if (!hash) continue;
|
|
36199
|
+
const existing = DEDUP_CACHE.get(hash) ?? [];
|
|
36200
|
+
const matches = existing.filter((m) => m.file !== filePath);
|
|
36201
|
+
for (const match of matches) {
|
|
36202
|
+
issues.push({
|
|
36203
|
+
ruleId: "dup/identical-block",
|
|
36204
|
+
category: "logic",
|
|
36205
|
+
severity: "medium",
|
|
36206
|
+
aiSpecific: false,
|
|
36207
|
+
message: `Identical ${WINDOW_SIZE}-line block at line ${i + 1} also appears in ${match.file}:${match.line + 1}`,
|
|
36208
|
+
line: i + 1,
|
|
36209
|
+
column: 0,
|
|
36210
|
+
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.",
|
|
36211
|
+
extras: {
|
|
36212
|
+
duplicateOf: {
|
|
36213
|
+
file: match.file,
|
|
36214
|
+
line: match.line + 1,
|
|
36215
|
+
hash
|
|
36216
|
+
}
|
|
36217
|
+
}
|
|
36218
|
+
});
|
|
36219
|
+
}
|
|
36220
|
+
existing.push({ file: filePath, line: i });
|
|
36221
|
+
DEDUP_CACHE.set(hash, existing);
|
|
36222
|
+
}
|
|
36223
|
+
return issues;
|
|
36224
|
+
}
|
|
36225
|
+
});
|
|
36226
|
+
|
|
36227
|
+
// src/rules/go/error-wrap-without-context.ts
|
|
36228
|
+
var ERR_WRAP_REGEX = /fmt\.Errorf\(\s*"((?:[^"\\]|\\.)*)"\s*,\s*([^)]+)\)/g;
|
|
36229
|
+
var GENERIC_PREFIX_REGEX = /^\s*(?:error|err|failed|wrap(?:ping)?|invalid)\s*(?::\s*)?%w\b/i;
|
|
36230
|
+
var goErrorWrapWithoutContextRule = createRule({
|
|
36231
|
+
id: "go/error-wrap-without-context",
|
|
36232
|
+
category: "typo",
|
|
36233
|
+
severity: "low",
|
|
36234
|
+
aiSpecific: true,
|
|
36235
|
+
description: 'fmt.Errorf wrap without operation context \u2014 AI defaults to generic "error: %w"',
|
|
36236
|
+
create(_context) {
|
|
36237
|
+
return {};
|
|
36238
|
+
},
|
|
36239
|
+
analyze(_context, facts) {
|
|
36240
|
+
const issues = [];
|
|
36241
|
+
const source = facts.v2?._source;
|
|
36242
|
+
if (!source) return issues;
|
|
36243
|
+
let match;
|
|
36244
|
+
ERR_WRAP_REGEX.lastIndex = 0;
|
|
36245
|
+
while ((match = ERR_WRAP_REGEX.exec(source)) !== null) {
|
|
36246
|
+
const formatString = match[1];
|
|
36247
|
+
if (!formatString.includes("%w")) continue;
|
|
36248
|
+
if (formatString.length >= 30) continue;
|
|
36249
|
+
if (!GENERIC_PREFIX_REGEX.test(formatString)) continue;
|
|
36250
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
36251
|
+
issues.push({
|
|
36252
|
+
ruleId: "go/error-wrap-without-context",
|
|
36253
|
+
category: "typo",
|
|
36254
|
+
severity: "low",
|
|
36255
|
+
aiSpecific: true,
|
|
36256
|
+
message: `fmt.Errorf wrap with generic message "${formatString}" \u2014 include the failing operation`,
|
|
36257
|
+
line,
|
|
36258
|
+
column: match[0].indexOf("fmt") + 1,
|
|
36259
|
+
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'
|
|
36260
|
+
});
|
|
36261
|
+
}
|
|
36262
|
+
return issues;
|
|
36263
|
+
}
|
|
36264
|
+
});
|
|
36265
|
+
|
|
36266
|
+
// src/rules/go/nil-slice-vs-empty.ts
|
|
36267
|
+
var NIL_SLICE_DECL_REGEX = /^[\t ]*var\s+([A-Za-z_][A-Za-z0-9_]*)\s+\[\][\w.*]+\b/gm;
|
|
36268
|
+
var EMPTY_SLICE_ASSIGN_REGEX = /^[\t ]*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:\[\][\w.*]*\{\}|make\(\[\][\w.*]*)/gm;
|
|
36269
|
+
var goNilSliceVsEmptyRule = createRule({
|
|
36270
|
+
id: "go/nil-slice-vs-empty",
|
|
36271
|
+
category: "typo",
|
|
36272
|
+
severity: "low",
|
|
36273
|
+
aiSpecific: true,
|
|
36274
|
+
description: "Variable declared `var x []int` but later assigned `x = []int{}` or `make([]int, n)` \u2014 pick one form",
|
|
36275
|
+
create(_context) {
|
|
36276
|
+
return {};
|
|
36277
|
+
},
|
|
36278
|
+
analyze(_context, facts) {
|
|
36279
|
+
const issues = [];
|
|
36280
|
+
const source = facts.v2?._source;
|
|
36281
|
+
if (!source) return issues;
|
|
36282
|
+
const nilDecls = /* @__PURE__ */ new Map();
|
|
36283
|
+
let m;
|
|
36284
|
+
NIL_SLICE_DECL_REGEX.lastIndex = 0;
|
|
36285
|
+
while ((m = NIL_SLICE_DECL_REGEX.exec(source)) !== null) {
|
|
36286
|
+
nilDecls.set(m[1], source.slice(0, m.index).split("\n").length);
|
|
36287
|
+
}
|
|
36288
|
+
if (nilDecls.size === 0) return issues;
|
|
36289
|
+
EMPTY_SLICE_ASSIGN_REGEX.lastIndex = 0;
|
|
36290
|
+
while ((m = EMPTY_SLICE_ASSIGN_REGEX.exec(source)) !== null) {
|
|
36291
|
+
const name = m[1];
|
|
36292
|
+
if (!nilDecls.has(name)) continue;
|
|
36293
|
+
const declLine = nilDecls.get(name);
|
|
36294
|
+
const assignLine = source.slice(0, m.index).split("\n").length;
|
|
36295
|
+
if (assignLine <= declLine) continue;
|
|
36296
|
+
issues.push({
|
|
36297
|
+
ruleId: "go/nil-slice-vs-empty",
|
|
36298
|
+
category: "typo",
|
|
36299
|
+
severity: "low",
|
|
36300
|
+
aiSpecific: true,
|
|
36301
|
+
message: `Variable '${name}' declared as nil slice (line ${declLine}) but assigned an empty slice (line ${assignLine}) \u2014 pick one form`,
|
|
36302
|
+
line: declLine,
|
|
36303
|
+
column: 1,
|
|
36304
|
+
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."
|
|
36305
|
+
});
|
|
36306
|
+
}
|
|
36307
|
+
return issues;
|
|
36308
|
+
}
|
|
36309
|
+
});
|
|
36310
|
+
|
|
36311
|
+
// src/rules/go/struct-tag-inconsistency.ts
|
|
36312
|
+
var JSON_TAG_REGEX = /`json:"([^",]+)(?:,([^"]+))?"`/g;
|
|
36313
|
+
var goStructTagInconsistencyRule = createRule({
|
|
36314
|
+
id: "go/struct-tag-inconsistency",
|
|
36315
|
+
category: "typo",
|
|
36316
|
+
severity: "low",
|
|
36317
|
+
aiSpecific: true,
|
|
36318
|
+
description: 'Struct fields mix json:"foo" and json:"foo,omitempty" \u2014 pick one convention per struct',
|
|
36319
|
+
create(_context) {
|
|
36320
|
+
return {};
|
|
36321
|
+
},
|
|
36322
|
+
analyze(_context, facts) {
|
|
36323
|
+
const issues = [];
|
|
36324
|
+
const source = facts.v2?._source;
|
|
36325
|
+
if (!source) return issues;
|
|
36326
|
+
const structRegex = /type\s+[A-Z][A-Za-z0-9_]*\s+struct\s*\{/g;
|
|
36327
|
+
let structMatch;
|
|
36328
|
+
while ((structMatch = structRegex.exec(source)) !== null) {
|
|
36329
|
+
const startIdx = structMatch.index;
|
|
36330
|
+
const openBrace = source.indexOf("{", startIdx);
|
|
36331
|
+
if (openBrace < 0) continue;
|
|
36332
|
+
let depth = 1;
|
|
36333
|
+
let i = openBrace + 1;
|
|
36334
|
+
while (i < source.length && depth > 0) {
|
|
36335
|
+
const ch = source[i];
|
|
36336
|
+
if (ch === "{") depth++;
|
|
36337
|
+
else if (ch === "}") depth--;
|
|
36338
|
+
i++;
|
|
36339
|
+
}
|
|
36340
|
+
const structBody = source.slice(openBrace, i);
|
|
36341
|
+
const structLine = source.slice(0, startIdx).split("\n").length;
|
|
36342
|
+
const styleCount = {};
|
|
36343
|
+
const tagMatches = [];
|
|
36344
|
+
let m;
|
|
36345
|
+
JSON_TAG_REGEX.lastIndex = 0;
|
|
36346
|
+
while ((m = JSON_TAG_REGEX.exec(structBody)) !== null) {
|
|
36347
|
+
const tag = m[1];
|
|
36348
|
+
const options = m[2] ?? "";
|
|
36349
|
+
const style = options ? "with-options" : "no-options";
|
|
36350
|
+
styleCount[style] = (styleCount[style] ?? 0) + 1;
|
|
36351
|
+
tagMatches.push({ tag, style, idx: openBrace + m.index });
|
|
36352
|
+
}
|
|
36353
|
+
const styles = Object.keys(styleCount);
|
|
36354
|
+
if (styles.length < 2 || tagMatches.length < 2) continue;
|
|
36355
|
+
const dominant = styles.reduce(
|
|
36356
|
+
(a, b) => (styleCount[a] ?? 0) >= (styleCount[b] ?? 0) ? a : b
|
|
36357
|
+
);
|
|
36358
|
+
const minority = tagMatches.filter((t) => t.style !== dominant);
|
|
36359
|
+
if (minority.length === 0) continue;
|
|
36360
|
+
for (const m2 of minority) {
|
|
36361
|
+
const line = source.slice(0, m2.idx).split("\n").length;
|
|
36362
|
+
issues.push({
|
|
36363
|
+
ruleId: "go/struct-tag-inconsistency",
|
|
36364
|
+
category: "typo",
|
|
36365
|
+
severity: "low",
|
|
36366
|
+
aiSpecific: true,
|
|
36367
|
+
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"}`,
|
|
36368
|
+
line,
|
|
36369
|
+
column: 1,
|
|
36370
|
+
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.'
|
|
36371
|
+
});
|
|
36372
|
+
}
|
|
36373
|
+
if (issues.length > 0) break;
|
|
36374
|
+
}
|
|
36375
|
+
return issues;
|
|
36376
|
+
}
|
|
36377
|
+
});
|
|
36378
|
+
|
|
36237
36379
|
// src/rules/layout/gap-monopoly.ts
|
|
36238
36380
|
var GAP_RE = /\bgap(?:-x|-y)?-(\d+)\b/g;
|
|
36239
36381
|
var gapMonopolyRule = createRule({
|
|
@@ -37021,82 +37163,6 @@ var keyPropMissingRule = createRule({
|
|
|
37021
37163
|
}
|
|
37022
37164
|
});
|
|
37023
37165
|
|
|
37024
|
-
// src/rules/logic/ks-distribution-shift.ts
|
|
37025
|
-
var MIN_SAMPLES_PER_FEATURE = 20;
|
|
37026
|
-
function extractFileFeatures(source) {
|
|
37027
|
-
const lines = source.split("\n");
|
|
37028
|
-
const lineLengths = lines.map((l) => l.length);
|
|
37029
|
-
const identifierLengths = [];
|
|
37030
|
-
const idRe = /[A-Za-z_$][A-Za-z0-9_$]*/g;
|
|
37031
|
-
let m;
|
|
37032
|
-
while ((m = idRe.exec(source)) !== null) {
|
|
37033
|
-
identifierLengths.push(m[0].length);
|
|
37034
|
-
}
|
|
37035
|
-
const commentDensity = lines.map((l) => {
|
|
37036
|
-
const trimmed = l.trim();
|
|
37037
|
-
if (trimmed.length === 0) return 0;
|
|
37038
|
-
const commentChars = (trimmed.match(/^\/\/.*$/)?.[0]?.length ?? 0) + (trimmed.match(/^\s*\/\*.*?\*\/\s*$/)?.at(0)?.length ?? 0);
|
|
37039
|
-
return commentChars / trimmed.length;
|
|
37040
|
-
});
|
|
37041
|
-
return { lineLengths, identifierLengths, commentDensity };
|
|
37042
|
-
}
|
|
37043
|
-
var ksDistributionShiftRule = createRule({
|
|
37044
|
-
id: "logic/ks-distribution-shift",
|
|
37045
|
-
category: "logic",
|
|
37046
|
-
severity: "medium",
|
|
37047
|
-
aiSpecific: false,
|
|
37048
|
-
description: "Multi-feature Kolmogorov\u2013Smirnov distribution-shift vs corpus baseline (Bonferroni-corrected). Peer-reviewed ML distribution-shift detector (arXiv:2510.15996, Oct 2025).",
|
|
37049
|
-
create(context) {
|
|
37050
|
-
return context;
|
|
37051
|
-
},
|
|
37052
|
-
analyze(_context, facts) {
|
|
37053
|
-
const issues = [];
|
|
37054
|
-
if (!facts.v2) return issues;
|
|
37055
|
-
const source = facts.v2._source ?? "";
|
|
37056
|
-
if (source.length < 200) return issues;
|
|
37057
|
-
const features = extractFileFeatures(source);
|
|
37058
|
-
const samples = /* @__PURE__ */ new Map([
|
|
37059
|
-
["lineLengths", features.lineLengths],
|
|
37060
|
-
["identifierLengths", features.identifierLengths],
|
|
37061
|
-
["commentDensity", features.commentDensity]
|
|
37062
|
-
]);
|
|
37063
|
-
const baselines = getCorpusBaselines();
|
|
37064
|
-
const baselinesMap = /* @__PURE__ */ new Map();
|
|
37065
|
-
if (baselines) {
|
|
37066
|
-
baselinesMap.set("lineLengths", baselines.features.lineLengths.sample);
|
|
37067
|
-
baselinesMap.set("identifierLengths", baselines.features.identifierLengths.sample);
|
|
37068
|
-
baselinesMap.set("commentDensity", baselines.features.commentDensity.sample);
|
|
37069
|
-
} else {
|
|
37070
|
-
baselinesMap.set("lineLengths", [20, 25, 30, 32, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100]);
|
|
37071
|
-
baselinesMap.set("identifierLengths", [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 24, 28]);
|
|
37072
|
-
baselinesMap.set("commentDensity", [0, 0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5]);
|
|
37073
|
-
}
|
|
37074
|
-
for (const [name, vals] of samples) {
|
|
37075
|
-
if (vals.length < MIN_SAMPLES_PER_FEATURE) samples.delete(name);
|
|
37076
|
-
}
|
|
37077
|
-
if (samples.size === 0) return issues;
|
|
37078
|
-
const result = multiFeatureKsTest(samples, baselinesMap, 0.05);
|
|
37079
|
-
if (!result.anySignificant) return issues;
|
|
37080
|
-
const shifted = result.significantFeatures.join(", ");
|
|
37081
|
-
const details = result.significantFeatures.map((name) => {
|
|
37082
|
-
const r = result.perFeature.get(name);
|
|
37083
|
-
if (!r) return name;
|
|
37084
|
-
return `${name} (D=${r.statistic.toFixed(3)}, p=${r.pValue.toExponential(2)})`;
|
|
37085
|
-
}).join("; ");
|
|
37086
|
-
issues.push({
|
|
37087
|
-
ruleId: "logic/ks-distribution-shift",
|
|
37088
|
-
category: "logic",
|
|
37089
|
-
severity: "medium",
|
|
37090
|
-
aiSpecific: true,
|
|
37091
|
-
message: `Distribution shift detected on ${result.significantFeatures.length} of ${result.perFeature.size} features (Bonferroni \u03B1=${result.bonferroniAlpha.toExponential(2)}). Features: ${shifted}. Detail: ${details}.`,
|
|
37092
|
-
line: 1,
|
|
37093
|
-
column: 1,
|
|
37094
|
-
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."
|
|
37095
|
-
});
|
|
37096
|
-
return issues;
|
|
37097
|
-
}
|
|
37098
|
-
});
|
|
37099
|
-
|
|
37100
37166
|
// src/rules/logic/math-any-density.ts
|
|
37101
37167
|
var ANY_PER_100_LINES = 5;
|
|
37102
37168
|
var MIN_ABSOLUTE = 6;
|
|
@@ -37158,7 +37224,7 @@ var mathAnyDensityRule = createRule({
|
|
|
37158
37224
|
});
|
|
37159
37225
|
|
|
37160
37226
|
// src/rules/logic/math-console-log-storm.ts
|
|
37161
|
-
var
|
|
37227
|
+
var WINDOW_SIZE2 = 30;
|
|
37162
37228
|
var STORM_THRESHOLD = 5;
|
|
37163
37229
|
var CONSOLE_LOG_RE = /\bconsole\.log\s*\(/g;
|
|
37164
37230
|
var mathConsoleLogStormRule = createRule({
|
|
@@ -37189,7 +37255,7 @@ var mathConsoleLogStormRule = createRule({
|
|
|
37189
37255
|
let maxEndLine = 0;
|
|
37190
37256
|
let i = 0;
|
|
37191
37257
|
for (let j = 0; j < lines.length; j++) {
|
|
37192
|
-
while (lines[j] - lines[i] >
|
|
37258
|
+
while (lines[j] - lines[i] > WINDOW_SIZE2) i++;
|
|
37193
37259
|
const count = j - i + 1;
|
|
37194
37260
|
if (count > maxCount) {
|
|
37195
37261
|
maxCount = count;
|
|
@@ -37203,7 +37269,7 @@ var mathConsoleLogStormRule = createRule({
|
|
|
37203
37269
|
category: "logic",
|
|
37204
37270
|
severity: "high",
|
|
37205
37271
|
aiSpecific: true,
|
|
37206
|
-
message: `${maxCount} console.log calls clustered in a ${
|
|
37272
|
+
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.`,
|
|
37207
37273
|
line: firstIdx >= 0 ? lines[firstIdx] : 1,
|
|
37208
37274
|
column: firstIdx >= 0 ? columns[firstIdx] : 1,
|
|
37209
37275
|
advice: "Replace debug logs with a proper debugger or logger.debug() \u2014 remove all console.log before shipping."
|
|
@@ -39007,7 +39073,7 @@ var unsafeHtmlRenderRule = createRule({
|
|
|
39007
39073
|
});
|
|
39008
39074
|
|
|
39009
39075
|
// src/rules/test/utils.ts
|
|
39010
|
-
import { createHash as
|
|
39076
|
+
import { createHash as createHash3 } from "crypto";
|
|
39011
39077
|
function isTestFile(filePath) {
|
|
39012
39078
|
const normalized = filePath.replace(/\\/g, "/");
|
|
39013
39079
|
if (/(^|\/)__tests__\//.test(normalized)) return true;
|
|
@@ -39167,7 +39233,7 @@ function extractSetupBlocks(source, minLines = 3) {
|
|
|
39167
39233
|
}).trim();
|
|
39168
39234
|
const lineCount = rawBody.split("\n").length;
|
|
39169
39235
|
if (lineCount < minLines) continue;
|
|
39170
|
-
const bodyHash =
|
|
39236
|
+
const bodyHash = createHash3("sha1").update(normalizedBody).digest("hex");
|
|
39171
39237
|
const { line, column } = lineColumnOf(source, m.index);
|
|
39172
39238
|
hits.push({
|
|
39173
39239
|
kind,
|
|
@@ -39777,6 +39843,216 @@ function isTautologicalAssertion(hit) {
|
|
|
39777
39843
|
return false;
|
|
39778
39844
|
}
|
|
39779
39845
|
|
|
39846
|
+
// src/rules/ts/enum-vs-as-const.ts
|
|
39847
|
+
var ENUM_DECL_REGEX = /^[ \t]*(?:export\s+)?(?:const\s+)?enum\s+[A-Z_][A-Za-z0-9_]*\s*\{/gm;
|
|
39848
|
+
var tsEnumVsAsConstRule = createRule({
|
|
39849
|
+
id: "ts/enum-vs-as-const",
|
|
39850
|
+
category: "typo",
|
|
39851
|
+
severity: "low",
|
|
39852
|
+
aiSpecific: true,
|
|
39853
|
+
description: "Uses `enum` \u2014 modern TS prefers `as const` objects",
|
|
39854
|
+
create(_context) {
|
|
39855
|
+
return {};
|
|
39856
|
+
},
|
|
39857
|
+
analyze(_context, facts) {
|
|
39858
|
+
const issues = [];
|
|
39859
|
+
const source = facts.v2?._source;
|
|
39860
|
+
if (!source) return issues;
|
|
39861
|
+
let match;
|
|
39862
|
+
ENUM_DECL_REGEX.lastIndex = 0;
|
|
39863
|
+
while ((match = ENUM_DECL_REGEX.exec(source)) !== null) {
|
|
39864
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
39865
|
+
issues.push({
|
|
39866
|
+
ruleId: "ts/enum-vs-as-const",
|
|
39867
|
+
category: "typo",
|
|
39868
|
+
severity: "low",
|
|
39869
|
+
aiSpecific: true,
|
|
39870
|
+
message: `'enum' is an AI / older-TS pattern \u2014 prefer 'as const' for a frozen object literal`,
|
|
39871
|
+
line,
|
|
39872
|
+
column: match[0].indexOf("enum") + 1,
|
|
39873
|
+
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.'
|
|
39874
|
+
});
|
|
39875
|
+
}
|
|
39876
|
+
return issues;
|
|
39877
|
+
}
|
|
39878
|
+
});
|
|
39879
|
+
|
|
39880
|
+
// src/rules/ts/excessive-type-assertion.ts
|
|
39881
|
+
var DEFAULT_MAX = 3;
|
|
39882
|
+
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;
|
|
39883
|
+
var AS_REGEX = /(?<![\w$])as\s+(?!const)([A-Z][\w$.,<>\[\]|&]*)/g;
|
|
39884
|
+
var tsExcessiveTypeAssertionRule = createRule({
|
|
39885
|
+
id: "ts/excessive-type-assertion",
|
|
39886
|
+
category: "typo",
|
|
39887
|
+
severity: "low",
|
|
39888
|
+
aiSpecific: true,
|
|
39889
|
+
description: "Function with >3 `as` type assertions \u2014 likely AI fighting the type system",
|
|
39890
|
+
create(_context) {
|
|
39891
|
+
return { maxAssertionsPerFunction: DEFAULT_MAX };
|
|
39892
|
+
},
|
|
39893
|
+
analyze(context, facts) {
|
|
39894
|
+
const issues = [];
|
|
39895
|
+
const source = facts.v2?._source;
|
|
39896
|
+
if (!source) return issues;
|
|
39897
|
+
let match;
|
|
39898
|
+
FN_DECL_REGEX.lastIndex = 0;
|
|
39899
|
+
while ((match = FN_DECL_REGEX.exec(source)) !== null) {
|
|
39900
|
+
const startIdx = match.index;
|
|
39901
|
+
const openBraceIdx = source.indexOf("{", startIdx);
|
|
39902
|
+
if (openBraceIdx < 0) continue;
|
|
39903
|
+
let depth = 1;
|
|
39904
|
+
let i = openBraceIdx + 1;
|
|
39905
|
+
while (i < source.length && depth > 0) {
|
|
39906
|
+
const ch = source[i];
|
|
39907
|
+
if (ch === "{") depth++;
|
|
39908
|
+
else if (ch === "}") depth--;
|
|
39909
|
+
i++;
|
|
39910
|
+
}
|
|
39911
|
+
const body = source.slice(openBraceIdx, i);
|
|
39912
|
+
const line = source.slice(0, startIdx).split("\n").length;
|
|
39913
|
+
let asCount = 0;
|
|
39914
|
+
const seen = /* @__PURE__ */ new Set();
|
|
39915
|
+
let asMatch;
|
|
39916
|
+
AS_REGEX.lastIndex = 0;
|
|
39917
|
+
while ((asMatch = AS_REGEX.exec(body)) !== null) {
|
|
39918
|
+
const captured = asMatch[1];
|
|
39919
|
+
if (seen.has(captured)) continue;
|
|
39920
|
+
seen.add(captured);
|
|
39921
|
+
asCount++;
|
|
39922
|
+
}
|
|
39923
|
+
if (asCount > context.maxAssertionsPerFunction) {
|
|
39924
|
+
issues.push({
|
|
39925
|
+
ruleId: "ts/excessive-type-assertion",
|
|
39926
|
+
category: "typo",
|
|
39927
|
+
severity: "low",
|
|
39928
|
+
aiSpecific: true,
|
|
39929
|
+
message: `Function has ${asCount} 'as' assertions (max ${context.maxAssertionsPerFunction}) \u2014 likely AI fighting the type system`,
|
|
39930
|
+
line,
|
|
39931
|
+
column: 1,
|
|
39932
|
+
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."
|
|
39933
|
+
});
|
|
39934
|
+
}
|
|
39935
|
+
}
|
|
39936
|
+
return issues;
|
|
39937
|
+
}
|
|
39938
|
+
});
|
|
39939
|
+
|
|
39940
|
+
// src/rules/ts/import-type-misuse.ts
|
|
39941
|
+
var INLINE_TYPE_IMPORT_REGEX = /^[ \t]*import\s*\{[^}]*\btype\s+[A-Za-z_]/gm;
|
|
39942
|
+
var tsImportTypeMisuseRule = createRule({
|
|
39943
|
+
id: "ts/import-type-misuse",
|
|
39944
|
+
category: "typo",
|
|
39945
|
+
severity: "low",
|
|
39946
|
+
aiSpecific: true,
|
|
39947
|
+
description: "Inline `import { type X }` \u2014 prefer `import type { X }` for clarity",
|
|
39948
|
+
create(_context) {
|
|
39949
|
+
return {};
|
|
39950
|
+
},
|
|
39951
|
+
analyze(_context, facts) {
|
|
39952
|
+
const issues = [];
|
|
39953
|
+
const source = facts.v2?._source;
|
|
39954
|
+
if (!source) return issues;
|
|
39955
|
+
let match;
|
|
39956
|
+
INLINE_TYPE_IMPORT_REGEX.lastIndex = 0;
|
|
39957
|
+
while ((match = INLINE_TYPE_IMPORT_REGEX.exec(source)) !== null) {
|
|
39958
|
+
const line = source.slice(0, match.index).split("\n").length;
|
|
39959
|
+
issues.push({
|
|
39960
|
+
ruleId: "ts/import-type-misuse",
|
|
39961
|
+
category: "typo",
|
|
39962
|
+
severity: "low",
|
|
39963
|
+
aiSpecific: true,
|
|
39964
|
+
message: "Inline `type` in a value import \u2014 split into a separate `import type` statement",
|
|
39965
|
+
line,
|
|
39966
|
+
column: match[0].indexOf("type") + 1,
|
|
39967
|
+
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.'
|
|
39968
|
+
});
|
|
39969
|
+
}
|
|
39970
|
+
return issues;
|
|
39971
|
+
}
|
|
39972
|
+
});
|
|
39973
|
+
|
|
39974
|
+
// src/rules/ts/never-vs-unknown.ts
|
|
39975
|
+
var NEVER_RETURN_REGEX = /^[ \t]*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)[^{]*:\s*(?:Promise<)?never\b[^{]*\{/gm;
|
|
39976
|
+
var THROW_OR_LOOP_REGEX = /\b(?:throw\b|while\s*\(|for\s*\(|process\.exit\b|System\.exit\b)/;
|
|
39977
|
+
var tsNeverVsUnknownRule = createRule({
|
|
39978
|
+
id: "ts/never-vs-unknown",
|
|
39979
|
+
category: "typo",
|
|
39980
|
+
severity: "low",
|
|
39981
|
+
aiSpecific: true,
|
|
39982
|
+
description: "Return type `: never` but body has no throw / loop / exit \u2014 likely AI misuse",
|
|
39983
|
+
create(_context) {
|
|
39984
|
+
return {};
|
|
39985
|
+
},
|
|
39986
|
+
analyze(_context, facts) {
|
|
39987
|
+
const issues = [];
|
|
39988
|
+
const source = facts.v2?._source;
|
|
39989
|
+
if (!source) return issues;
|
|
39990
|
+
let match;
|
|
39991
|
+
NEVER_RETURN_REGEX.lastIndex = 0;
|
|
39992
|
+
while ((match = NEVER_RETURN_REGEX.exec(source)) !== null) {
|
|
39993
|
+
const fnName = match[1];
|
|
39994
|
+
const startIdx = match.index;
|
|
39995
|
+
const line = source.slice(0, startIdx).split("\n").length;
|
|
39996
|
+
const openBraceIdx = source.indexOf("{", startIdx);
|
|
39997
|
+
if (openBraceIdx < 0) continue;
|
|
39998
|
+
let depth = 1;
|
|
39999
|
+
let i = openBraceIdx + 1;
|
|
40000
|
+
while (i < source.length && depth > 0) {
|
|
40001
|
+
const ch = source[i];
|
|
40002
|
+
if (ch === "{") depth++;
|
|
40003
|
+
else if (ch === "}") depth--;
|
|
40004
|
+
i++;
|
|
40005
|
+
}
|
|
40006
|
+
const body = source.slice(openBraceIdx, i);
|
|
40007
|
+
if (THROW_OR_LOOP_REGEX.test(body)) continue;
|
|
40008
|
+
issues.push({
|
|
40009
|
+
ruleId: "ts/never-vs-unknown",
|
|
40010
|
+
category: "typo",
|
|
40011
|
+
severity: "low",
|
|
40012
|
+
aiSpecific: true,
|
|
40013
|
+
message: `Function '${fnName}' returns 'never' but its body has no throw, loop, or exit \u2014 likely AI misuse`,
|
|
40014
|
+
line,
|
|
40015
|
+
column: match[0].indexOf("never") + 1,
|
|
40016
|
+
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.'
|
|
40017
|
+
});
|
|
40018
|
+
}
|
|
40019
|
+
return issues;
|
|
40020
|
+
}
|
|
40021
|
+
});
|
|
40022
|
+
|
|
40023
|
+
// src/rules/ts/optional-chain-overuse.ts
|
|
40024
|
+
var DEFAULT_MIN_CHAIN_LENGTH = 5;
|
|
40025
|
+
var tsOptionalChainOveruseRule = createRule({
|
|
40026
|
+
id: "ts/optional-chain-overuse",
|
|
40027
|
+
category: "logic",
|
|
40028
|
+
severity: "low",
|
|
40029
|
+
aiSpecific: true,
|
|
40030
|
+
description: "Optional chaining (?.) used 5+ times in a single chain \u2014 AI tends to chain rather than narrow",
|
|
40031
|
+
create(_context) {
|
|
40032
|
+
return { minChainLength: DEFAULT_MIN_CHAIN_LENGTH };
|
|
40033
|
+
},
|
|
40034
|
+
analyze(context, facts) {
|
|
40035
|
+
const issues = [];
|
|
40036
|
+
const expressions = facts.v2.logic?.logicalExpressions;
|
|
40037
|
+
if (!expressions) return issues;
|
|
40038
|
+
for (const expression of expressions) {
|
|
40039
|
+
if (expression.depth >= context.minChainLength && expression.isOptionalChainLike) {
|
|
40040
|
+
issues.push({
|
|
40041
|
+
ruleId: "ts/optional-chain-overuse",
|
|
40042
|
+
category: "logic",
|
|
40043
|
+
severity: "low",
|
|
40044
|
+
aiSpecific: true,
|
|
40045
|
+
message: `Optional chain depth ${expression.depth} \u2014 break with an intermediate variable or guard clause`,
|
|
40046
|
+
line: expression.line,
|
|
40047
|
+
column: expression.column,
|
|
40048
|
+
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."
|
|
40049
|
+
});
|
|
40050
|
+
}
|
|
40051
|
+
}
|
|
40052
|
+
return issues;
|
|
40053
|
+
}
|
|
40054
|
+
});
|
|
40055
|
+
|
|
39780
40056
|
// src/rules/typo/calc-fontsize.ts
|
|
39781
40057
|
var FONT_SIZE_RE = /\bfont-size\s*:\s*[^;]*\bcalc\s*\(/i;
|
|
39782
40058
|
var calcFontsizeRule = createRule({
|
|
@@ -41317,6 +41593,10 @@ var builtinRules = [
|
|
|
41317
41593
|
expiredCodeExampleRule,
|
|
41318
41594
|
staleFunctionReferenceRule,
|
|
41319
41595
|
stalePackageReferenceRule,
|
|
41596
|
+
dupIdenticalBlockRule,
|
|
41597
|
+
goErrorWrapWithoutContextRule,
|
|
41598
|
+
goNilSliceVsEmptyRule,
|
|
41599
|
+
goStructTagInconsistencyRule,
|
|
41320
41600
|
gapMonopolyRule,
|
|
41321
41601
|
mathElementUniformityRule,
|
|
41322
41602
|
mathGridUniformityRule,
|
|
@@ -41326,7 +41606,6 @@ var builtinRules = [
|
|
|
41326
41606
|
ghostDefensiveRule,
|
|
41327
41607
|
heapsDeviationRule,
|
|
41328
41608
|
keyPropMissingRule,
|
|
41329
|
-
ksDistributionShiftRule,
|
|
41330
41609
|
mathAnyDensityRule,
|
|
41331
41610
|
mathConsoleLogStormRule,
|
|
41332
41611
|
mathGiniClassUsageRule,
|
|
@@ -41360,6 +41639,11 @@ var builtinRules = [
|
|
|
41360
41639
|
fakePlaceholderRule,
|
|
41361
41640
|
missingEdgeCaseRule,
|
|
41362
41641
|
weakAssertionRule,
|
|
41642
|
+
tsEnumVsAsConstRule,
|
|
41643
|
+
tsExcessiveTypeAssertionRule,
|
|
41644
|
+
tsImportTypeMisuseRule,
|
|
41645
|
+
tsNeverVsUnknownRule,
|
|
41646
|
+
tsOptionalChainOveruseRule,
|
|
41363
41647
|
calcFontsizeRule,
|
|
41364
41648
|
calcRawPxRule,
|
|
41365
41649
|
clampOffscaleRule,
|
|
@@ -41530,7 +41814,7 @@ var signal_strength_default = {
|
|
|
41530
41814
|
precision: 0.9966,
|
|
41531
41815
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
41532
41816
|
verdict: "USEFUL",
|
|
41533
|
-
_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).",
|
|
41817
|
+
_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.",
|
|
41534
41818
|
aiSpecific: true,
|
|
41535
41819
|
_v7Verdict: "USEFUL",
|
|
41536
41820
|
_v7Lift: 182622.43,
|
|
@@ -41538,7 +41822,8 @@ var signal_strength_default = {
|
|
|
41538
41822
|
_v7FpRate: 0,
|
|
41539
41823
|
_v7Precision: 0.9957,
|
|
41540
41824
|
_v8Verdict: "USEFUL",
|
|
41541
|
-
_v8Lift: 99999
|
|
41825
|
+
_v8Lift: 99999,
|
|
41826
|
+
defaultOff: false
|
|
41542
41827
|
},
|
|
41543
41828
|
"ai/errors-near-eof": {
|
|
41544
41829
|
recall: 0.0948,
|
|
@@ -41787,7 +42072,7 @@ var signal_strength_default = {
|
|
|
41787
42072
|
precision: 0.9512,
|
|
41788
42073
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
41789
42074
|
verdict: "USEFUL",
|
|
41790
|
-
_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).",
|
|
42075
|
+
_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.",
|
|
41791
42076
|
aiSpecific: true,
|
|
41792
42077
|
_v7Verdict: "USEFUL",
|
|
41793
42078
|
_v7Lift: 7099.57,
|
|
@@ -41795,7 +42080,8 @@ var signal_strength_default = {
|
|
|
41795
42080
|
_v7FpRate: 1e-4,
|
|
41796
42081
|
_v7Precision: 0.929,
|
|
41797
42082
|
_v8Verdict: "USEFUL",
|
|
41798
|
-
_v8Lift: 99999
|
|
42083
|
+
_v8Lift: 99999,
|
|
42084
|
+
defaultOff: false
|
|
41799
42085
|
},
|
|
41800
42086
|
"context/import-path-mismatch": {
|
|
41801
42087
|
recall: 0.0681,
|
|
@@ -41979,7 +42265,7 @@ var signal_strength_default = {
|
|
|
41979
42265
|
precision: 0.8834,
|
|
41980
42266
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
41981
42267
|
verdict: "USEFUL",
|
|
41982
|
-
_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.",
|
|
42268
|
+
_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.",
|
|
41983
42269
|
aiSpecific: true,
|
|
41984
42270
|
_v7Verdict: "DORMANT",
|
|
41985
42271
|
_v7Lift: 1,
|
|
@@ -41987,7 +42273,8 @@ var signal_strength_default = {
|
|
|
41987
42273
|
_v7FpRate: 0,
|
|
41988
42274
|
_v7Precision: 0,
|
|
41989
42275
|
_v8Verdict: "USEFUL",
|
|
41990
|
-
_v8Lift: 32.74
|
|
42276
|
+
_v8Lift: 32.74,
|
|
42277
|
+
defaultOff: false
|
|
41991
42278
|
},
|
|
41992
42279
|
"dead/unused-parameter": {
|
|
41993
42280
|
recall: 8e-4,
|
|
@@ -42075,6 +42362,78 @@ var signal_strength_default = {
|
|
|
42075
42362
|
_v8Verdict: "OK",
|
|
42076
42363
|
_v8Lift: 255.9
|
|
42077
42364
|
},
|
|
42365
|
+
"dup/identical-block": {
|
|
42366
|
+
recall: 0,
|
|
42367
|
+
fpRate: 0,
|
|
42368
|
+
ratio: 1,
|
|
42369
|
+
precision: 0,
|
|
42370
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42371
|
+
verdict: "DORMANT",
|
|
42372
|
+
_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.",
|
|
42373
|
+
aiSpecific: false,
|
|
42374
|
+
_v7Verdict: "DORMANT",
|
|
42375
|
+
_v7Lift: 1,
|
|
42376
|
+
_v7Recall: 0,
|
|
42377
|
+
_v7FpRate: 0,
|
|
42378
|
+
_v7Precision: 0,
|
|
42379
|
+
_v8Verdict: "DORMANT",
|
|
42380
|
+
_v8Lift: 1,
|
|
42381
|
+
defaultOff: true
|
|
42382
|
+
},
|
|
42383
|
+
"go/error-wrap-without-context": {
|
|
42384
|
+
recall: 0,
|
|
42385
|
+
fpRate: 0,
|
|
42386
|
+
ratio: 1,
|
|
42387
|
+
precision: 0,
|
|
42388
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42389
|
+
verdict: "DORMANT",
|
|
42390
|
+
_calibrationNote: "v0.19: new rule (fmt.Errorf wrap with generic message \u2014 needs operation context). Not yet calibrated. Scheduled for v9 calibration.",
|
|
42391
|
+
aiSpecific: true,
|
|
42392
|
+
_v7Verdict: "DORMANT",
|
|
42393
|
+
_v7Lift: 1,
|
|
42394
|
+
_v7Recall: 0,
|
|
42395
|
+
_v7FpRate: 0,
|
|
42396
|
+
_v7Precision: 0,
|
|
42397
|
+
_v8Verdict: "DORMANT",
|
|
42398
|
+
_v8Lift: 1,
|
|
42399
|
+
defaultOff: true
|
|
42400
|
+
},
|
|
42401
|
+
"go/nil-slice-vs-empty": {
|
|
42402
|
+
recall: 0,
|
|
42403
|
+
fpRate: 0,
|
|
42404
|
+
ratio: 1,
|
|
42405
|
+
precision: 0,
|
|
42406
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42407
|
+
verdict: "DORMANT",
|
|
42408
|
+
_calibrationNote: "v0.19: new rule (Variable declared nil slice but assigned empty slice). Not yet calibrated. Scheduled for v9 calibration.",
|
|
42409
|
+
aiSpecific: true,
|
|
42410
|
+
_v7Verdict: "DORMANT",
|
|
42411
|
+
_v7Lift: 1,
|
|
42412
|
+
_v7Recall: 0,
|
|
42413
|
+
_v7FpRate: 0,
|
|
42414
|
+
_v7Precision: 0,
|
|
42415
|
+
_v8Verdict: "DORMANT",
|
|
42416
|
+
_v8Lift: 1,
|
|
42417
|
+
defaultOff: true
|
|
42418
|
+
},
|
|
42419
|
+
"go/struct-tag-inconsistency": {
|
|
42420
|
+
recall: 0,
|
|
42421
|
+
fpRate: 0,
|
|
42422
|
+
ratio: 1,
|
|
42423
|
+
precision: 0,
|
|
42424
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42425
|
+
verdict: "DORMANT",
|
|
42426
|
+
_calibrationNote: "v0.19: new rule (Struct fields mix json tag styles). Not yet calibrated. Scheduled for v9 calibration.",
|
|
42427
|
+
aiSpecific: true,
|
|
42428
|
+
_v7Verdict: "DORMANT",
|
|
42429
|
+
_v7Lift: 1,
|
|
42430
|
+
_v7Recall: 0,
|
|
42431
|
+
_v7FpRate: 0,
|
|
42432
|
+
_v7Precision: 0,
|
|
42433
|
+
_v8Verdict: "DORMANT",
|
|
42434
|
+
_v8Lift: 1,
|
|
42435
|
+
defaultOff: true
|
|
42436
|
+
},
|
|
42078
42437
|
"layout/forced-layout": {
|
|
42079
42438
|
recall: 0,
|
|
42080
42439
|
fpRate: 0,
|
|
@@ -42203,7 +42562,7 @@ var signal_strength_default = {
|
|
|
42203
42562
|
precision: 0.8889,
|
|
42204
42563
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42205
42564
|
verdict: "USEFUL",
|
|
42206
|
-
_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).",
|
|
42565
|
+
_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.",
|
|
42207
42566
|
aiSpecific: true,
|
|
42208
42567
|
_v7Verdict: "USEFUL",
|
|
42209
42568
|
_v7Lift: 80917.5,
|
|
@@ -42211,7 +42570,8 @@ var signal_strength_default = {
|
|
|
42211
42570
|
_v7FpRate: 0,
|
|
42212
42571
|
_v7Precision: 0.8824,
|
|
42213
42572
|
_v8Verdict: "USEFUL",
|
|
42214
|
-
_v8Lift: 99999
|
|
42573
|
+
_v8Lift: 99999,
|
|
42574
|
+
defaultOff: false
|
|
42215
42575
|
},
|
|
42216
42576
|
"logic/heaps-deviation": {
|
|
42217
42577
|
recall: 0.0126,
|
|
@@ -42247,24 +42607,6 @@ var signal_strength_default = {
|
|
|
42247
42607
|
_v8Verdict: "USEFUL",
|
|
42248
42608
|
_v8Lift: 1760.69
|
|
42249
42609
|
},
|
|
42250
|
-
"logic/ks-distribution-shift": {
|
|
42251
|
-
recall: 0.6889,
|
|
42252
|
-
fpRate: 0.4411,
|
|
42253
|
-
ratio: 1.46,
|
|
42254
|
-
precision: 0.6457,
|
|
42255
|
-
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42256
|
-
verdict: "NOISY",
|
|
42257
|
-
_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).",
|
|
42258
|
-
aiSpecific: false,
|
|
42259
|
-
_v7Verdict: "USEFUL",
|
|
42260
|
-
_v7Lift: 2.09,
|
|
42261
|
-
_v7Recall: 0.6431,
|
|
42262
|
-
_v7FpRate: 0.34,
|
|
42263
|
-
_v7Precision: 0.7096,
|
|
42264
|
-
_v8Verdict: "INVERTED",
|
|
42265
|
-
_v8Lift: 0.71,
|
|
42266
|
-
defaultOff: true
|
|
42267
|
-
},
|
|
42268
42610
|
"logic/math-any-density": {
|
|
42269
42611
|
recall: 17e-4,
|
|
42270
42612
|
fpRate: 13e-4,
|
|
@@ -42630,7 +42972,7 @@ var signal_strength_default = {
|
|
|
42630
42972
|
precision: 1,
|
|
42631
42973
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
42632
42974
|
verdict: "USEFUL",
|
|
42633
|
-
_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).",
|
|
42975
|
+
_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.",
|
|
42634
42976
|
aiSpecific: true,
|
|
42635
42977
|
_v7Verdict: "USEFUL",
|
|
42636
42978
|
_v7Lift: 99999,
|
|
@@ -42638,7 +42980,8 @@ var signal_strength_default = {
|
|
|
42638
42980
|
_v7FpRate: 0,
|
|
42639
42981
|
_v7Precision: 1,
|
|
42640
42982
|
_v8Verdict: "DORMANT",
|
|
42641
|
-
_v8Lift: 1
|
|
42983
|
+
_v8Lift: 1,
|
|
42984
|
+
defaultOff: false
|
|
42642
42985
|
},
|
|
42643
42986
|
"security/hardcoded-secret": {
|
|
42644
42987
|
recall: 14e-4,
|
|
@@ -42828,6 +43171,96 @@ var signal_strength_default = {
|
|
|
42828
43171
|
_v8Verdict: "USEFUL",
|
|
42829
43172
|
_v8Lift: 651.94
|
|
42830
43173
|
},
|
|
43174
|
+
"ts/enum-vs-as-const": {
|
|
43175
|
+
recall: 0,
|
|
43176
|
+
fpRate: 0,
|
|
43177
|
+
ratio: 1,
|
|
43178
|
+
precision: 0,
|
|
43179
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43180
|
+
verdict: "DORMANT",
|
|
43181
|
+
_calibrationNote: "v0.19: new rule (Uses `enum` \u2014 modern TS prefers `as const`). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43182
|
+
aiSpecific: true,
|
|
43183
|
+
_v7Verdict: "DORMANT",
|
|
43184
|
+
_v7Lift: 1,
|
|
43185
|
+
_v7Recall: 0,
|
|
43186
|
+
_v7FpRate: 0,
|
|
43187
|
+
_v7Precision: 0,
|
|
43188
|
+
_v8Verdict: "DORMANT",
|
|
43189
|
+
_v8Lift: 1,
|
|
43190
|
+
defaultOff: true
|
|
43191
|
+
},
|
|
43192
|
+
"ts/excessive-type-assertion": {
|
|
43193
|
+
recall: 0,
|
|
43194
|
+
fpRate: 0,
|
|
43195
|
+
ratio: 1,
|
|
43196
|
+
precision: 0,
|
|
43197
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43198
|
+
verdict: "DORMANT",
|
|
43199
|
+
_calibrationNote: "v0.19: new rule (Function with >3 `as` assertions \u2014 AI fighting the type system). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43200
|
+
aiSpecific: true,
|
|
43201
|
+
_v7Verdict: "DORMANT",
|
|
43202
|
+
_v7Lift: 1,
|
|
43203
|
+
_v7Recall: 0,
|
|
43204
|
+
_v7FpRate: 0,
|
|
43205
|
+
_v7Precision: 0,
|
|
43206
|
+
_v8Verdict: "DORMANT",
|
|
43207
|
+
_v8Lift: 1,
|
|
43208
|
+
defaultOff: true
|
|
43209
|
+
},
|
|
43210
|
+
"ts/import-type-misuse": {
|
|
43211
|
+
recall: 0,
|
|
43212
|
+
fpRate: 0,
|
|
43213
|
+
ratio: 1,
|
|
43214
|
+
precision: 0,
|
|
43215
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43216
|
+
verdict: "DORMANT",
|
|
43217
|
+
_calibrationNote: "v0.19: new rule (Inline `import { type X }` \u2014 prefer separate `import type`). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43218
|
+
aiSpecific: true,
|
|
43219
|
+
_v7Verdict: "DORMANT",
|
|
43220
|
+
_v7Lift: 1,
|
|
43221
|
+
_v7Recall: 0,
|
|
43222
|
+
_v7FpRate: 0,
|
|
43223
|
+
_v7Precision: 0,
|
|
43224
|
+
_v8Verdict: "DORMANT",
|
|
43225
|
+
_v8Lift: 1,
|
|
43226
|
+
defaultOff: true
|
|
43227
|
+
},
|
|
43228
|
+
"ts/never-vs-unknown": {
|
|
43229
|
+
recall: 0,
|
|
43230
|
+
fpRate: 0,
|
|
43231
|
+
ratio: 1,
|
|
43232
|
+
precision: 0,
|
|
43233
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43234
|
+
verdict: "DORMANT",
|
|
43235
|
+
_calibrationNote: "v0.19: new rule (Return type `never` but body has no throw/loop/exit). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43236
|
+
aiSpecific: true,
|
|
43237
|
+
_v7Verdict: "DORMANT",
|
|
43238
|
+
_v7Lift: 1,
|
|
43239
|
+
_v7Recall: 0,
|
|
43240
|
+
_v7FpRate: 0,
|
|
43241
|
+
_v7Precision: 0,
|
|
43242
|
+
_v8Verdict: "DORMANT",
|
|
43243
|
+
_v8Lift: 1,
|
|
43244
|
+
defaultOff: true
|
|
43245
|
+
},
|
|
43246
|
+
"ts/optional-chain-overuse": {
|
|
43247
|
+
recall: 0,
|
|
43248
|
+
fpRate: 0,
|
|
43249
|
+
ratio: 1,
|
|
43250
|
+
precision: 0,
|
|
43251
|
+
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43252
|
+
verdict: "DORMANT",
|
|
43253
|
+
_calibrationNote: "v0.19: new rule (Optional chain depth >= 5 \u2014 AI chains ?. rather than narrowing). Not yet calibrated. Scheduled for v9 calibration.",
|
|
43254
|
+
aiSpecific: true,
|
|
43255
|
+
_v7Verdict: "DORMANT",
|
|
43256
|
+
_v7Lift: 1,
|
|
43257
|
+
_v7Recall: 0,
|
|
43258
|
+
_v7FpRate: 0,
|
|
43259
|
+
_v7Precision: 0,
|
|
43260
|
+
_v8Verdict: "DORMANT",
|
|
43261
|
+
_v8Lift: 1,
|
|
43262
|
+
defaultOff: true
|
|
43263
|
+
},
|
|
42831
43264
|
"typo/calc-fontsize": {
|
|
42832
43265
|
recall: 0,
|
|
42833
43266
|
fpRate: 0,
|
|
@@ -43130,7 +43563,7 @@ var signal_strength_default = {
|
|
|
43130
43563
|
precision: 0.9774,
|
|
43131
43564
|
lastCalibratedAt: "2026-07-01T00:00:00Z",
|
|
43132
43565
|
verdict: "USEFUL",
|
|
43133
|
-
_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).",
|
|
43566
|
+
_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.",
|
|
43134
43567
|
aiSpecific: false,
|
|
43135
43568
|
_v7Verdict: "USEFUL",
|
|
43136
43569
|
_v7Lift: 59285.01,
|
|
@@ -43138,7 +43571,8 @@ var signal_strength_default = {
|
|
|
43138
43571
|
_v7FpRate: 0,
|
|
43139
43572
|
_v7Precision: 0.9697,
|
|
43140
43573
|
_v8Verdict: "USEFUL",
|
|
43141
|
-
_v8Lift: 99999
|
|
43574
|
+
_v8Lift: 99999,
|
|
43575
|
+
defaultOff: false
|
|
43142
43576
|
},
|
|
43143
43577
|
"visual/spacing-scale-violation": {
|
|
43144
43578
|
recall: 86e-4,
|