slopbrick 0.18.9 → 0.20.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.
@@ -6,9 +6,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __esm = (fn, res) => function __init() {
10
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
- };
12
9
  var __export = (target, all) => {
13
10
  for (var name in all)
14
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -31,408 +28,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
28
  ));
32
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
30
 
34
- // src/engine/parser-rust.ts
35
- function getRustParser() {
36
- if (cachedParser) return { ok: true, parser: cachedParser };
37
- if (loadError) return { ok: false, error: loadError };
38
- try {
39
- rustLanguage = import_tree_sitter_rust.default;
40
- if (!rustLanguage || typeof rustLanguage !== "object") {
41
- throw new Error("tree-sitter-rust: language export is missing or malformed");
42
- }
43
- const parser = new import_tree_sitter.default();
44
- parser.setLanguage(rustLanguage);
45
- cachedParser = parser;
46
- return { ok: true, parser };
47
- } catch (err) {
48
- loadError = err instanceof Error ? err : new Error(String(err));
49
- return { ok: false, error: loadError };
50
- }
51
- }
52
- function effectiveParser() {
53
- if (forcedFailure) return { ok: false, error: forcedFailure };
54
- return getRustParser();
55
- }
56
- function parseRust(source) {
57
- const result = effectiveParser();
58
- if (!result.ok) return null;
59
- if (!source || source.trim() === "") return null;
60
- try {
61
- const rawTree = result.parser.parse(source);
62
- if (!rawTree) return null;
63
- const tree = rawTree;
64
- if (!tree || !tree.rootNode) return null;
65
- if (tree.rootNode.type === "ERROR" && tree.rootNode.childCount === 0) {
66
- return null;
67
- }
68
- return tree;
69
- } catch {
70
- return null;
71
- }
72
- }
73
- function isRustParserAvailable() {
74
- const result = getRustParser();
75
- return result.ok;
76
- }
77
- var import_tree_sitter, import_tree_sitter_rust, cachedParser, rustLanguage, loadError, forcedFailure;
78
- var init_parser_rust = __esm({
79
- "src/engine/parser-rust.ts"() {
80
- "use strict";
81
- import_tree_sitter = __toESM(require("tree-sitter"), 1);
82
- import_tree_sitter_rust = __toESM(require("tree-sitter-rust"), 1);
83
- cachedParser = null;
84
- rustLanguage = null;
85
- loadError = null;
86
- forcedFailure = null;
87
- }
88
- });
89
-
90
- // src/engine/visitors/rust.ts
91
- function parseRustFile(filePath, source, options = {}) {
92
- const empty = {
93
- imports: [],
94
- functions: [],
95
- structs: [],
96
- traits: [],
97
- impls: []
98
- };
99
- if (!isRustParserAvailable() || options.forceFallback) {
100
- return empty;
101
- }
102
- const tree = parseRust(source);
103
- if (!tree) return empty;
104
- return walkRustTree(tree);
105
- }
106
- function walkRustTree(tree) {
107
- const ctx = { inTestConfig: false };
108
- const out = {
109
- imports: [],
110
- functions: [],
111
- structs: [],
112
- traits: [],
113
- impls: []
114
- };
115
- walkChildren(tree.rootNode, ctx, out);
116
- return out;
117
- }
118
- function walkChildren(node, ctx, out) {
119
- for (let i = 0; i < node.namedChildCount; i++) {
120
- const child = node.namedChild(i);
121
- if (!child) continue;
122
- visitNode(child, ctx, out);
123
- }
124
- }
125
- function visitNode(node, ctx, out) {
126
- const innerCtx = { ...ctx };
127
- const attrState = readPrecedingAttributes(node);
128
- switch (node.type) {
129
- case "use_declaration": {
130
- out.imports.push(extractUse(node));
131
- return;
132
- }
133
- case "function_item": {
134
- const isMethod = isInImplBlock(node);
135
- out.functions.push(extractFunction(node, attrState, innerCtx.inTestConfig, isMethod));
136
- walkChildren(getField(node, "body") ?? node, innerCtx, out);
137
- return;
138
- }
139
- case "struct_item": {
140
- out.structs.push(extractStruct(node, attrState));
141
- const body = getField(node, "body");
142
- if (body) walkChildren(body, innerCtx, out);
143
- return;
144
- }
145
- case "trait_item": {
146
- out.traits.push(extractTrait(node, attrState));
147
- const body = getField(node, "body");
148
- if (body) walkChildren(body, innerCtx, out);
149
- return;
150
- }
151
- case "impl_item": {
152
- const implEntry = extractImpl(node, attrState, innerCtx);
153
- out.impls.push(implEntry.entry);
154
- const body = getField(node, "body");
155
- if (body) walkChildren(body, innerCtx, out);
156
- return;
157
- }
158
- case "mod_item": {
159
- const modIsTest = innerCtx.inTestConfig || attrState.isTestCfg;
160
- const modCtx = { ...innerCtx, inTestConfig: modIsTest };
161
- const body = getField(node, "body");
162
- if (body) walkChildren(body, modCtx, out);
163
- return;
164
- }
165
- default: {
166
- for (let i = 0; i < node.namedChildCount; i++) {
167
- const child = node.namedChild(i);
168
- if (child) visitNode(child, innerCtx, out);
169
- }
170
- }
171
- }
172
- }
173
- function readPrecedingAttributes(node) {
174
- const state = {
175
- isTestCfg: false,
176
- isTest: false,
177
- isPub: false,
178
- derives: []
179
- };
180
- const parent = node.parent;
181
- if (!parent) return state;
182
- for (let i = 0; i < parent.namedChildCount; i++) {
183
- const sibling = parent.namedChild(i);
184
- if (!sibling || sibling === node) continue;
185
- if (sibling.endIndex > node.startIndex) continue;
186
- if (sibling.type !== "attribute_item") continue;
187
- decodeAttribute(sibling, state);
188
- }
189
- return state;
190
- }
191
- function decodeAttribute(attr, state) {
192
- for (let i = 0; i < attr.namedChildCount; i++) {
193
- const child = attr.namedChild(i);
194
- if (!child || child.type !== "attribute") continue;
195
- const name = firstIdentifier(child);
196
- if (!name) continue;
197
- if (name === "cfg") {
198
- const cfgText = child.text;
199
- if (/\btest\b/.test(cfgText) && !/\bnot\(test\)/.test(cfgText)) {
200
- state.isTestCfg = true;
201
- }
202
- } else if (name === "test") {
203
- state.isTest = true;
204
- } else if (name === "derive") {
205
- for (let j = 0; j < child.namedChildCount; j++) {
206
- const inner = child.namedChild(j);
207
- if (inner) {
208
- const matches = inner.text.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g);
209
- for (const m of matches) state.derives.push(m[1]);
210
- }
211
- }
212
- }
213
- }
214
- }
215
- function firstIdentifier(node) {
216
- const text = node.text;
217
- const idMatch = text.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
218
- return idMatch ? idMatch[1] : null;
219
- }
220
- function extractUse(node) {
221
- const text = node.text;
222
- const argument = node.namedChild(0);
223
- const argField = node.childForFieldName("argument");
224
- const path = argField?.text ?? argument?.text ?? "";
225
- const names = [];
226
- let isGlob = false;
227
- const useListNode = findUseListNode(argument);
228
- if (useListNode) {
229
- for (let i = 0; i < useListNode.namedChildCount; i++) {
230
- const item = useListNode.namedChild(i);
231
- if (item.type === "use_wildcard") {
232
- isGlob = true;
233
- } else if (item.type === "identifier") {
234
- names.push({ name: item.text });
235
- } else if (item.type === "use_as_clause") {
236
- const alias = item.childForFieldName("alias");
237
- const binding = item.childForFieldName("path");
238
- if (binding) {
239
- names.push({ name: binding.text, alias: alias?.text });
240
- } else {
241
- names.push({ name: item.text });
242
- }
243
- } else if (item.type === "scoped_identifier") {
244
- const idents = collectIdentifiers(item);
245
- names.push({ name: idents[idents.length - 1] ?? item.text });
246
- } else if (item.text.includes(" as ")) {
247
- const [head, alias] = item.text.split(/\s+as\s+/);
248
- names.push({ name: (head ?? "").trim(), alias: alias?.trim() });
249
- } else {
250
- names.push({ name: item.text });
251
- }
252
- }
253
- } else if (argument?.type === "use_wildcard") {
254
- isGlob = true;
255
- } else if (argument) {
256
- const idents = collectIdentifiers(argument);
257
- const last = idents[idents.length - 1] ?? argument.text;
258
- names.push({ name: last });
259
- const asMatch = text.match(/\s+as\s+(\w+)\s*;?\s*$/);
260
- if (asMatch) names[0].alias = asMatch[1];
261
- }
262
- return {
263
- path: path.trim(),
264
- names,
265
- isGlob,
266
- line: node.startPosition.row + 1,
267
- column: node.startPosition.column
268
- };
269
- }
270
- function extractFunction(node, attrs, inheritedTestConfig, isMethod) {
271
- const nameNode = node.childForFieldName("name");
272
- const name = nameNode?.text ?? "<anon>";
273
- const params = node.childForFieldName("parameters");
274
- const body = node.childForFieldName("body");
275
- const visibilityMod = node.namedChild(0);
276
- const isPublic = visibilityMod?.type === "visibility_modifier";
277
- let receiver;
278
- if (isMethod && params) {
279
- const selfParam = findSelfParameter(params);
280
- if (selfParam) receiver = selfParam.text;
281
- }
282
- const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
283
- const inTestConfig = inheritedTestConfig || attrs.isTest || attrs.isTestCfg || isFunctionAttrTest(node);
284
- return {
285
- name,
286
- line: node.startPosition.row + 1,
287
- column: node.startPosition.column,
288
- isPublic,
289
- isMethod,
290
- receiver,
291
- bodyLines,
292
- inTestConfig
293
- };
294
- }
295
- function extractStruct(node, attrs) {
296
- const nameNode = node.childForFieldName("name");
297
- return {
298
- name: nameNode?.text ?? "<anon>",
299
- line: node.startPosition.row + 1,
300
- column: node.startPosition.column,
301
- isPublic: hasVisibility(node),
302
- isDerive: attrs.derives.length > 0,
303
- derives: [...attrs.derives]
304
- };
305
- }
306
- function extractTrait(node, _attrs) {
307
- const nameNode = node.childForFieldName("name");
308
- return {
309
- name: nameNode?.text ?? "<anon>",
310
- line: node.startPosition.row + 1,
311
- column: node.startPosition.column,
312
- isPublic: hasVisibility(node)
313
- };
314
- }
315
- function extractImpl(node, _attrs, _ctx) {
316
- const traitNode = node.childForFieldName("trait");
317
- const typeNode = node.childForFieldName("type");
318
- const typeText = typeNode?.text ?? "<anon>";
319
- const traitText = traitNode?.text;
320
- const body = node.childForFieldName("body");
321
- const methods = [];
322
- if (body) {
323
- for (let i = 0; i < body.namedChildCount; i++) {
324
- const child = body.namedChild(i);
325
- if (child?.type === "function_item") {
326
- const m = child.childForFieldName("name");
327
- if (m) methods.push(m.text);
328
- }
329
- }
330
- }
331
- return {
332
- entry: {
333
- type: typeText,
334
- trait: traitText ?? void 0,
335
- methods,
336
- line: node.startPosition.row + 1,
337
- column: node.startPosition.column
338
- }
339
- };
340
- }
341
- function findUseListNode(argument) {
342
- if (!argument) return null;
343
- if (argument.type === "use_list") return argument;
344
- if (argument.type === "scoped_use_list") {
345
- for (let i = 0; i < argument.namedChildCount; i++) {
346
- const c = argument.namedChild(i);
347
- if (c?.type === "use_list") return c;
348
- }
349
- }
350
- return null;
351
- }
352
- function getField(node, fieldName) {
353
- return node.childForFieldName(fieldName);
354
- }
355
- function collectIdentifiers(node) {
356
- const out = [];
357
- for (let i = 0; i < node.namedChildCount; i++) {
358
- const child = node.namedChild(i);
359
- if (!child) continue;
360
- if (child.type === "identifier" || child.type === "type_identifier") {
361
- out.push(child.text);
362
- } else if (child.type === "scoped_identifier" || child.type === "nested_identifier") {
363
- out.push(...collectIdentifiers(child));
364
- }
365
- }
366
- return out;
367
- }
368
- function hasVisibility(node) {
369
- return node.namedChild(0)?.type === "visibility_modifier";
370
- }
371
- function isInImplBlock(node) {
372
- for (let p = node.parent; p; p = p.parent) {
373
- if (p.type === "impl_item") return true;
374
- if (p.type === "function_item" || p.type === "source_file") return false;
375
- }
376
- return false;
377
- }
378
- function findSelfParameter(params) {
379
- for (let i = 0; i < params.namedChildCount; i++) {
380
- const child = params.namedChild(i);
381
- if (child?.type === "self_parameter") return child;
382
- }
383
- return null;
384
- }
385
- function isFunctionAttrTest(node) {
386
- void node;
387
- return false;
388
- }
389
- var SERVICE_SUFFIXES, SERVICE_SUFFIX_GROUP, RUST_SERVICE_STRUCT_RE, RUST_SERVICE_IMPL_RE;
390
- var init_rust = __esm({
391
- "src/engine/visitors/rust.ts"() {
392
- "use strict";
393
- init_parser_rust();
394
- SERVICE_SUFFIXES = [
395
- "Service",
396
- "Manager",
397
- "Handler",
398
- "Repository",
399
- "Controller",
400
- "Helper",
401
- "Factory",
402
- "Provider",
403
- "Store",
404
- "API",
405
- "Client",
406
- "Adapter",
407
- "Resolver",
408
- "Mapper",
409
- "Transformer",
410
- "Serializer",
411
- "Validator",
412
- "Strategy",
413
- "Facade",
414
- "Decorator",
415
- "Observer",
416
- "Builder",
417
- "Command",
418
- "Processor",
419
- "Worker",
420
- "Job",
421
- "Actor",
422
- "Executor"
423
- ];
424
- SERVICE_SUFFIX_GROUP = `(?:${SERVICE_SUFFIXES.join("|")})`;
425
- RUST_SERVICE_STRUCT_RE = new RegExp(
426
- `^(?:pub(?:\\(crate\\)|\\(super\\))?\\s+)?struct\\s+(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
427
- "gm"
428
- );
429
- RUST_SERVICE_IMPL_RE = new RegExp(
430
- `^impl(?:<[^>]+>)?\\s+(?:${SERVICE_SUFFIX_GROUP}\\s+for\\s+)?(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
431
- "gm"
432
- );
433
- }
434
- });
435
-
436
31
  // src/engine/worker.ts
437
32
  var worker_exports = {};
438
33
  __export(worker_exports, {
@@ -440,8 +35,8 @@ __export(worker_exports, {
440
35
  });
441
36
  module.exports = __toCommonJS(worker_exports);
442
37
  var import_node_worker_threads = require("worker_threads");
443
- var import_node_path11 = require("path");
444
- var import_node_path12 = require("path");
38
+ var import_node_path9 = require("path");
39
+ var import_node_path10 = require("path");
445
40
 
446
41
  // ../engine/dist/index.js
447
42
  var import_promises = require("fs/promises");
@@ -5463,76 +5058,6 @@ function tokenizeIdentifiers(source) {
5463
5058
  return tokens;
5464
5059
  }
5465
5060
  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
5061
 
5537
5062
  // src/engine/visitors/react.ts
5538
5063
  function isObject(node) {
@@ -7332,8 +6857,396 @@ function dispatchNode(node, parent, path, vctx) {
7332
6857
  return false;
7333
6858
  }
7334
6859
 
6860
+ // src/engine/parser-rust.ts
6861
+ var import_tree_sitter = __toESM(require("tree-sitter"), 1);
6862
+ var import_tree_sitter_rust = __toESM(require("tree-sitter-rust"), 1);
6863
+ var cachedParser = null;
6864
+ var rustLanguage = null;
6865
+ var loadError = null;
6866
+ function getRustParser() {
6867
+ if (cachedParser) return { ok: true, parser: cachedParser };
6868
+ if (loadError) return { ok: false, error: loadError };
6869
+ try {
6870
+ rustLanguage = import_tree_sitter_rust.default;
6871
+ if (!rustLanguage || typeof rustLanguage !== "object") {
6872
+ throw new Error("tree-sitter-rust: language export is missing or malformed");
6873
+ }
6874
+ const parser = new import_tree_sitter.default();
6875
+ parser.setLanguage(rustLanguage);
6876
+ cachedParser = parser;
6877
+ return { ok: true, parser };
6878
+ } catch (err) {
6879
+ loadError = err instanceof Error ? err : new Error(String(err));
6880
+ return { ok: false, error: loadError };
6881
+ }
6882
+ }
6883
+ var forcedFailure = null;
6884
+ function effectiveParser() {
6885
+ if (forcedFailure) return { ok: false, error: forcedFailure };
6886
+ return getRustParser();
6887
+ }
6888
+ function parseRust(source) {
6889
+ const result = effectiveParser();
6890
+ if (!result.ok) return null;
6891
+ if (!source || source.trim() === "") return null;
6892
+ try {
6893
+ const rawTree = result.parser.parse(source);
6894
+ if (!rawTree) return null;
6895
+ const tree = rawTree;
6896
+ if (!tree || !tree.rootNode) return null;
6897
+ if (tree.rootNode.type === "ERROR" && tree.rootNode.childCount === 0) {
6898
+ return null;
6899
+ }
6900
+ return tree;
6901
+ } catch {
6902
+ return null;
6903
+ }
6904
+ }
6905
+ function isRustParserAvailable() {
6906
+ const result = getRustParser();
6907
+ return result.ok;
6908
+ }
6909
+
6910
+ // src/engine/visitors/rust.ts
6911
+ var SERVICE_SUFFIXES = [
6912
+ "Service",
6913
+ "Manager",
6914
+ "Handler",
6915
+ "Repository",
6916
+ "Controller",
6917
+ "Helper",
6918
+ "Factory",
6919
+ "Provider",
6920
+ "Store",
6921
+ "API",
6922
+ "Client",
6923
+ "Adapter",
6924
+ "Resolver",
6925
+ "Mapper",
6926
+ "Transformer",
6927
+ "Serializer",
6928
+ "Validator",
6929
+ "Strategy",
6930
+ "Facade",
6931
+ "Decorator",
6932
+ "Observer",
6933
+ "Builder",
6934
+ "Command",
6935
+ "Processor",
6936
+ "Worker",
6937
+ "Job",
6938
+ "Actor",
6939
+ "Executor"
6940
+ ];
6941
+ var SERVICE_SUFFIX_GROUP = `(?:${SERVICE_SUFFIXES.join("|")})`;
6942
+ var RUST_SERVICE_STRUCT_RE = new RegExp(
6943
+ `^(?:pub(?:\\(crate\\)|\\(super\\))?\\s+)?struct\\s+(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
6944
+ "gm"
6945
+ );
6946
+ var RUST_SERVICE_IMPL_RE = new RegExp(
6947
+ `^impl(?:<[^>]+>)?\\s+(?:${SERVICE_SUFFIX_GROUP}\\s+for\\s+)?(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
6948
+ "gm"
6949
+ );
6950
+ function parseRustFile(filePath, source, options = {}) {
6951
+ const empty = {
6952
+ imports: [],
6953
+ functions: [],
6954
+ structs: [],
6955
+ traits: [],
6956
+ impls: []
6957
+ };
6958
+ if (!isRustParserAvailable() || options.forceFallback) {
6959
+ return empty;
6960
+ }
6961
+ const tree = parseRust(source);
6962
+ if (!tree) return empty;
6963
+ return walkRustTree(tree);
6964
+ }
6965
+ function walkRustTree(tree) {
6966
+ const ctx = { inTestConfig: false };
6967
+ const out = {
6968
+ imports: [],
6969
+ functions: [],
6970
+ structs: [],
6971
+ traits: [],
6972
+ impls: []
6973
+ };
6974
+ walkChildren(tree.rootNode, ctx, out);
6975
+ return out;
6976
+ }
6977
+ function walkChildren(node, ctx, out) {
6978
+ for (let i = 0; i < node.namedChildCount; i++) {
6979
+ const child = node.namedChild(i);
6980
+ if (!child) continue;
6981
+ visitNode(child, ctx, out);
6982
+ }
6983
+ }
6984
+ function visitNode(node, ctx, out) {
6985
+ const innerCtx = { ...ctx };
6986
+ const attrState = readPrecedingAttributes(node);
6987
+ switch (node.type) {
6988
+ case "use_declaration": {
6989
+ out.imports.push(extractUse(node));
6990
+ return;
6991
+ }
6992
+ case "function_item": {
6993
+ const isMethod = isInImplBlock(node);
6994
+ out.functions.push(extractFunction(node, attrState, innerCtx.inTestConfig, isMethod));
6995
+ walkChildren(getField(node, "body") ?? node, innerCtx, out);
6996
+ return;
6997
+ }
6998
+ case "struct_item": {
6999
+ out.structs.push(extractStruct(node, attrState));
7000
+ const body = getField(node, "body");
7001
+ if (body) walkChildren(body, innerCtx, out);
7002
+ return;
7003
+ }
7004
+ case "trait_item": {
7005
+ out.traits.push(extractTrait(node, attrState));
7006
+ const body = getField(node, "body");
7007
+ if (body) walkChildren(body, innerCtx, out);
7008
+ return;
7009
+ }
7010
+ case "impl_item": {
7011
+ const implEntry = extractImpl(node, attrState, innerCtx);
7012
+ out.impls.push(implEntry.entry);
7013
+ const body = getField(node, "body");
7014
+ if (body) walkChildren(body, innerCtx, out);
7015
+ return;
7016
+ }
7017
+ case "mod_item": {
7018
+ const modIsTest = innerCtx.inTestConfig || attrState.isTestCfg;
7019
+ const modCtx = { ...innerCtx, inTestConfig: modIsTest };
7020
+ const body = getField(node, "body");
7021
+ if (body) walkChildren(body, modCtx, out);
7022
+ return;
7023
+ }
7024
+ default: {
7025
+ for (let i = 0; i < node.namedChildCount; i++) {
7026
+ const child = node.namedChild(i);
7027
+ if (child) visitNode(child, innerCtx, out);
7028
+ }
7029
+ }
7030
+ }
7031
+ }
7032
+ function readPrecedingAttributes(node) {
7033
+ const state = {
7034
+ isTestCfg: false,
7035
+ isTest: false,
7036
+ isPub: false,
7037
+ derives: []
7038
+ };
7039
+ const parent = node.parent;
7040
+ if (!parent) return state;
7041
+ for (let i = 0; i < parent.namedChildCount; i++) {
7042
+ const sibling = parent.namedChild(i);
7043
+ if (!sibling || sibling === node) continue;
7044
+ if (sibling.endIndex > node.startIndex) continue;
7045
+ if (sibling.type !== "attribute_item") continue;
7046
+ decodeAttribute(sibling, state);
7047
+ }
7048
+ return state;
7049
+ }
7050
+ function decodeAttribute(attr, state) {
7051
+ for (let i = 0; i < attr.namedChildCount; i++) {
7052
+ const child = attr.namedChild(i);
7053
+ if (!child || child.type !== "attribute") continue;
7054
+ const name = firstIdentifier(child);
7055
+ if (!name) continue;
7056
+ if (name === "cfg") {
7057
+ const cfgText = child.text;
7058
+ if (/\btest\b/.test(cfgText) && !/\bnot\(test\)/.test(cfgText)) {
7059
+ state.isTestCfg = true;
7060
+ }
7061
+ } else if (name === "test") {
7062
+ state.isTest = true;
7063
+ } else if (name === "derive") {
7064
+ for (let j = 0; j < child.namedChildCount; j++) {
7065
+ const inner = child.namedChild(j);
7066
+ if (inner) {
7067
+ const matches = inner.text.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g);
7068
+ for (const m of matches) state.derives.push(m[1]);
7069
+ }
7070
+ }
7071
+ }
7072
+ }
7073
+ }
7074
+ function firstIdentifier(node) {
7075
+ const text = node.text;
7076
+ const idMatch = text.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
7077
+ return idMatch ? idMatch[1] : null;
7078
+ }
7079
+ function extractUse(node) {
7080
+ const text = node.text;
7081
+ const argument = node.namedChild(0);
7082
+ const argField = node.childForFieldName("argument");
7083
+ const path = argField?.text ?? argument?.text ?? "";
7084
+ const names = [];
7085
+ let isGlob = false;
7086
+ const useListNode = findUseListNode(argument);
7087
+ if (useListNode) {
7088
+ for (let i = 0; i < useListNode.namedChildCount; i++) {
7089
+ const item = useListNode.namedChild(i);
7090
+ if (item.type === "use_wildcard") {
7091
+ isGlob = true;
7092
+ } else if (item.type === "identifier") {
7093
+ names.push({ name: item.text });
7094
+ } else if (item.type === "use_as_clause") {
7095
+ const alias = item.childForFieldName("alias");
7096
+ const binding = item.childForFieldName("path");
7097
+ if (binding) {
7098
+ names.push({ name: binding.text, alias: alias?.text });
7099
+ } else {
7100
+ names.push({ name: item.text });
7101
+ }
7102
+ } else if (item.type === "scoped_identifier") {
7103
+ const idents = collectIdentifiers(item);
7104
+ names.push({ name: idents[idents.length - 1] ?? item.text });
7105
+ } else if (item.text.includes(" as ")) {
7106
+ const [head, alias] = item.text.split(/\s+as\s+/);
7107
+ names.push({ name: (head ?? "").trim(), alias: alias?.trim() });
7108
+ } else {
7109
+ names.push({ name: item.text });
7110
+ }
7111
+ }
7112
+ } else if (argument?.type === "use_wildcard") {
7113
+ isGlob = true;
7114
+ } else if (argument) {
7115
+ const idents = collectIdentifiers(argument);
7116
+ const last = idents[idents.length - 1] ?? argument.text;
7117
+ names.push({ name: last });
7118
+ const asMatch = text.match(/\s+as\s+(\w+)\s*;?\s*$/);
7119
+ if (asMatch) names[0].alias = asMatch[1];
7120
+ }
7121
+ return {
7122
+ path: path.trim(),
7123
+ names,
7124
+ isGlob,
7125
+ line: node.startPosition.row + 1,
7126
+ column: node.startPosition.column
7127
+ };
7128
+ }
7129
+ function extractFunction(node, attrs, inheritedTestConfig, isMethod) {
7130
+ const nameNode = node.childForFieldName("name");
7131
+ const name = nameNode?.text ?? "<anon>";
7132
+ const params = node.childForFieldName("parameters");
7133
+ const body = node.childForFieldName("body");
7134
+ const visibilityMod = node.namedChild(0);
7135
+ const isPublic = visibilityMod?.type === "visibility_modifier";
7136
+ let receiver;
7137
+ if (isMethod && params) {
7138
+ const selfParam = findSelfParameter(params);
7139
+ if (selfParam) receiver = selfParam.text;
7140
+ }
7141
+ const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
7142
+ const inTestConfig = inheritedTestConfig || attrs.isTest || attrs.isTestCfg || isFunctionAttrTest(node);
7143
+ return {
7144
+ name,
7145
+ line: node.startPosition.row + 1,
7146
+ column: node.startPosition.column,
7147
+ isPublic,
7148
+ isMethod,
7149
+ receiver,
7150
+ bodyLines,
7151
+ inTestConfig
7152
+ };
7153
+ }
7154
+ function extractStruct(node, attrs) {
7155
+ const nameNode = node.childForFieldName("name");
7156
+ return {
7157
+ name: nameNode?.text ?? "<anon>",
7158
+ line: node.startPosition.row + 1,
7159
+ column: node.startPosition.column,
7160
+ isPublic: hasVisibility(node),
7161
+ isDerive: attrs.derives.length > 0,
7162
+ derives: [...attrs.derives]
7163
+ };
7164
+ }
7165
+ function extractTrait(node, _attrs) {
7166
+ const nameNode = node.childForFieldName("name");
7167
+ return {
7168
+ name: nameNode?.text ?? "<anon>",
7169
+ line: node.startPosition.row + 1,
7170
+ column: node.startPosition.column,
7171
+ isPublic: hasVisibility(node)
7172
+ };
7173
+ }
7174
+ function extractImpl(node, _attrs, _ctx) {
7175
+ const traitNode = node.childForFieldName("trait");
7176
+ const typeNode = node.childForFieldName("type");
7177
+ const typeText = typeNode?.text ?? "<anon>";
7178
+ const traitText = traitNode?.text;
7179
+ const body = node.childForFieldName("body");
7180
+ const methods = [];
7181
+ if (body) {
7182
+ for (let i = 0; i < body.namedChildCount; i++) {
7183
+ const child = body.namedChild(i);
7184
+ if (child?.type === "function_item") {
7185
+ const m = child.childForFieldName("name");
7186
+ if (m) methods.push(m.text);
7187
+ }
7188
+ }
7189
+ }
7190
+ return {
7191
+ entry: {
7192
+ type: typeText,
7193
+ trait: traitText ?? void 0,
7194
+ methods,
7195
+ line: node.startPosition.row + 1,
7196
+ column: node.startPosition.column
7197
+ }
7198
+ };
7199
+ }
7200
+ function findUseListNode(argument) {
7201
+ if (!argument) return null;
7202
+ if (argument.type === "use_list") return argument;
7203
+ if (argument.type === "scoped_use_list") {
7204
+ for (let i = 0; i < argument.namedChildCount; i++) {
7205
+ const c = argument.namedChild(i);
7206
+ if (c?.type === "use_list") return c;
7207
+ }
7208
+ }
7209
+ return null;
7210
+ }
7211
+ function getField(node, fieldName) {
7212
+ return node.childForFieldName(fieldName);
7213
+ }
7214
+ function collectIdentifiers(node) {
7215
+ const out = [];
7216
+ for (let i = 0; i < node.namedChildCount; i++) {
7217
+ const child = node.namedChild(i);
7218
+ if (!child) continue;
7219
+ if (child.type === "identifier" || child.type === "type_identifier") {
7220
+ out.push(child.text);
7221
+ } else if (child.type === "scoped_identifier" || child.type === "nested_identifier") {
7222
+ out.push(...collectIdentifiers(child));
7223
+ }
7224
+ }
7225
+ return out;
7226
+ }
7227
+ function hasVisibility(node) {
7228
+ return node.namedChild(0)?.type === "visibility_modifier";
7229
+ }
7230
+ function isInImplBlock(node) {
7231
+ for (let p = node.parent; p; p = p.parent) {
7232
+ if (p.type === "impl_item") return true;
7233
+ if (p.type === "function_item" || p.type === "source_file") return false;
7234
+ }
7235
+ return false;
7236
+ }
7237
+ function findSelfParameter(params) {
7238
+ for (let i = 0; i < params.namedChildCount; i++) {
7239
+ const child = params.namedChild(i);
7240
+ if (child?.type === "self_parameter") return child;
7241
+ }
7242
+ return null;
7243
+ }
7244
+ function isFunctionAttrTest(node) {
7245
+ void node;
7246
+ return false;
7247
+ }
7248
+
7335
7249
  // src/engine/visitors/v2-build.ts
7336
- init_rust();
7337
7250
  var TAILWIND_COLOR_RE = /^(?:bg|text|border|ring|from|to|via|fill|stroke)-([a-z]+-\d+|white|black|transparent|current|\[.+?\])$/;
7338
7251
  var TAILWIND_SPACING_RE = /^(?:[pm][xytrbl]?|gap|space-[xy])-(\d+(?:\.\d+)?)$/;
7339
7252
  var TAILWIND_RADIUS_RE = /^(?:rounded(?:-[a-z]+)?)-(.+)$/;
@@ -35177,125 +35090,17 @@ var unusedParameterRule = createRule({
35177
35090
  });
35178
35091
 
35179
35092
  // src/rules/docs/broken-link.ts
35180
- var import_node_fs6 = require("fs");
35181
- var import_node_path6 = require("path");
35182
-
35183
- // src/engine/doc-freshness.ts
35184
- var import_node_fs5 = require("fs");
35185
- var import_node_path5 = require("path");
35186
- var import_globby2 = require("globby");
35187
-
35188
- // src/mcp/patterns.ts
35189
35093
  var import_node_fs3 = require("fs");
35190
35094
  var import_node_path3 = require("path");
35191
35095
 
35192
- // src/engine/discover.ts
35193
- var import_globby = require("globby");
35194
- var import_minimatch = require("minimatch");
35195
- var import_node_path = require("path");
35196
- var import_node_fs = require("fs");
35197
- var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte", ".astro", ".html"]);
35198
- var BACKEND_EXTENSIONS = /* @__PURE__ */ new Set([
35199
- ".py",
35200
- ".go",
35201
- // v0.14.0
35202
- ".swift",
35203
- ".kt",
35204
- ".kts",
35205
- ".dart",
35206
- ".rs",
35207
- ".cpp",
35208
- ".cc",
35209
- ".cxx",
35210
- ".c",
35211
- ".h",
35212
- ".hpp",
35213
- ".hxx",
35214
- ".java",
35215
- ".rb",
35216
- ".php"
35217
- ]);
35218
- var ALL_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
35219
- ...SOURCE_EXTENSIONS,
35220
- ...BACKEND_EXTENSIONS
35221
- ]);
35222
-
35223
- // src/config/conventions.ts
35096
+ // src/engine/doc-freshness.ts
35224
35097
  var import_node_fs2 = require("fs");
35225
35098
  var import_node_path2 = require("path");
35226
-
35227
- // src/mcp/patterns.ts
35228
- var ESM_IMPORT_RE = /(?:^|\n)\s*(?:import\s+(?:type\s+)?(?:[\w*${},\s]+\s+from\s+)?|import\s+|export\s+(?:type\s+)?[\w*${},\s]+\s+from\s+)(['"])([^'"]+)\1/g;
35229
- var DYNAMIC_IMPORT_RE = /import\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
35230
- var COMMONJS_REQUIRE_RE = /require\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
35231
- function extractImports(source) {
35232
- const seen = /* @__PURE__ */ new Set();
35233
- const out = [];
35234
- const push = (spec) => {
35235
- if (spec.startsWith(".") || spec.startsWith("/")) return;
35236
- if (seen.has(spec)) return;
35237
- seen.add(spec);
35238
- out.push(spec);
35239
- };
35240
- for (const re of [ESM_IMPORT_RE, DYNAMIC_IMPORT_RE, COMMONJS_REQUIRE_RE]) {
35241
- re.lastIndex = 0;
35242
- let m;
35243
- while ((m = re.exec(source)) !== null) {
35244
- push(m[2]);
35245
- }
35246
- }
35247
- return out;
35248
- }
35249
-
35250
- // src/rules/docs/expired-code-example.ts
35251
- var CODE_LANGS = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "javascript", "typescript"]);
35252
- function stripSubpath(spec) {
35253
- if (spec.startsWith("@")) return spec.split("/").slice(0, 2).join("/");
35254
- return spec.split("/")[0] ?? spec;
35255
- }
35256
- var expiredCodeExampleRule = createRule({
35257
- id: "docs/expired-code-example",
35258
- category: "docs",
35259
- severity: "medium",
35260
- aiSpecific: false,
35261
- description: "A fenced code example imports a package that is not declared in package.json.",
35262
- create(context) {
35263
- return { ...context, packages: declaredPackages(context.cwd) };
35264
- },
35265
- analyze(context, facts) {
35266
- const issues = [];
35267
- const source = facts.v2?._source;
35268
- if (!source) return issues;
35269
- const packages = declaredPackages(context.cwd);
35270
- const packageName = context.packageName;
35271
- if (packageName) packages.add(packageName);
35272
- const blocks = extractFencedCodeBlocks(source);
35273
- for (const block of blocks) {
35274
- if (!CODE_LANGS.has(block.lang)) continue;
35275
- if (block.body.split("\n").length < 2) continue;
35276
- const imports = extractImports(block.body);
35277
- for (const imp of imports) {
35278
- const pkgName = stripSubpath(imp);
35279
- if (packages.has(pkgName)) continue;
35280
- issues.push({
35281
- ruleId: "docs/expired-code-example",
35282
- category: "docs",
35283
- severity: "medium",
35284
- aiSpecific: false,
35285
- message: `Code example imports \`${imp}\` but \`${pkgName}\` is not in package.json.`,
35286
- line: block.line,
35287
- column: block.column,
35288
- advice: `Add \`${pkgName}\` to package.json or update the example.`
35289
- });
35290
- }
35291
- }
35292
- return issues;
35293
- }
35294
- });
35099
+ var import_globby = require("globby");
35295
35100
 
35296
35101
  // src/rules/docs/stale-function-reference.ts
35297
- var import_node_fs4 = require("fs");
35298
- var import_node_path4 = require("path");
35102
+ var import_node_fs = require("fs");
35103
+ var import_node_path = require("path");
35299
35104
  var RESERVED = /* @__PURE__ */ new Set([
35300
35105
  // JS reserved words
35301
35106
  "true",
@@ -35868,29 +35673,29 @@ var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs",
35868
35673
  var SOURCE_ROOTS = ["src", "lib", "app", "components"];
35869
35674
  var CAP = 200;
35870
35675
  function walk(dir, out, cap) {
35871
- if (!(0, import_node_fs4.existsSync)(dir) || out.length >= cap) return;
35676
+ if (!(0, import_node_fs.existsSync)(dir) || out.length >= cap) return;
35872
35677
  let entries;
35873
35678
  try {
35874
- entries = (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true });
35679
+ entries = (0, import_node_fs.readdirSync)(dir, { withFileTypes: true });
35875
35680
  } catch {
35876
35681
  return;
35877
35682
  }
35878
35683
  for (const entry of entries) {
35879
35684
  if (out.length >= cap) return;
35880
35685
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
35881
- const full = (0, import_node_path4.join)(dir, entry.name);
35686
+ const full = (0, import_node_path.join)(dir, entry.name);
35882
35687
  if (entry.isDirectory()) walk(full, out, cap);
35883
- else if (entry.isFile() && SOURCE_EXTS.has((0, import_node_path4.extname)(entry.name))) out.push(full);
35688
+ else if (entry.isFile() && SOURCE_EXTS.has((0, import_node_path.extname)(entry.name))) out.push(full);
35884
35689
  }
35885
35690
  }
35886
35691
  function collectExports(cwd) {
35887
35692
  const out = /* @__PURE__ */ new Set();
35888
35693
  const files = [];
35889
- for (const root of SOURCE_ROOTS) walk((0, import_node_path4.join)(cwd, root), files, CAP);
35694
+ for (const root of SOURCE_ROOTS) walk((0, import_node_path.join)(cwd, root), files, CAP);
35890
35695
  for (const file of files) {
35891
35696
  let source;
35892
35697
  try {
35893
- source = (0, import_node_fs4.readFileSync)(file, "utf-8");
35698
+ source = (0, import_node_fs.readFileSync)(file, "utf-8");
35894
35699
  } catch {
35895
35700
  continue;
35896
35701
  }
@@ -36153,38 +35958,6 @@ function extractInlineCodeSpans(source) {
36153
35958
  }
36154
35959
  return hits;
36155
35960
  }
36156
- function extractFencedCodeBlocks(source) {
36157
- const blocks = [];
36158
- const lines = source.split("\n");
36159
- let i = 0;
36160
- while (i < lines.length) {
36161
- const line = lines[i] ?? "";
36162
- const fenceMatch = /^```(\w*)\s*$/.exec(line);
36163
- if (!fenceMatch) {
36164
- i++;
36165
- continue;
36166
- }
36167
- const lang = fenceMatch[1] ?? "";
36168
- const startLine = i + 1;
36169
- const bodyLines = [];
36170
- i++;
36171
- while (i < lines.length) {
36172
- if (/^```\s*$/.test(lines[i] ?? "")) {
36173
- i++;
36174
- break;
36175
- }
36176
- bodyLines.push(lines[i] ?? "");
36177
- i++;
36178
- }
36179
- blocks.push({
36180
- lang,
36181
- body: bodyLines.join("\n"),
36182
- line: startLine,
36183
- column: 1
36184
- });
36185
- }
36186
- return blocks;
36187
- }
36188
35961
  function extractMarkdownLinks(source) {
36189
35962
  const hits = [];
36190
35963
  const re = /(?<!\!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
@@ -36201,10 +35974,10 @@ function extractMarkdownLinks(source) {
36201
35974
  }
36202
35975
  function declaredPackages(cwd) {
36203
35976
  const out = /* @__PURE__ */ new Set();
36204
- const pkgPath = (0, import_node_path5.join)(cwd, "package.json");
36205
- if (!(0, import_node_fs5.existsSync)(pkgPath)) return out;
35977
+ const pkgPath = (0, import_node_path2.join)(cwd, "package.json");
35978
+ if (!(0, import_node_fs2.existsSync)(pkgPath)) return out;
36206
35979
  try {
36207
- const raw = (0, import_node_fs5.readFileSync)(pkgPath, "utf-8");
35980
+ const raw = (0, import_node_fs2.readFileSync)(pkgPath, "utf-8");
36208
35981
  const pkg = JSON.parse(raw);
36209
35982
  for (const k of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
36210
35983
  const v = pkg[k];
@@ -36234,7 +36007,7 @@ var brokenLinkRule = createRule({
36234
36007
  const source = facts.v2?._source;
36235
36008
  if (!source) return issues;
36236
36009
  const links = extractMarkdownLinks(source);
36237
- const docDir = (0, import_node_path6.dirname)((0, import_node_path6.resolve)(context.cwd, context.filePath));
36010
+ const docDir = (0, import_node_path3.dirname)((0, import_node_path3.resolve)(context.cwd, context.filePath));
36238
36011
  for (const link of links) {
36239
36012
  const target = link.target;
36240
36013
  if (target.startsWith("http://") || target.startsWith("https://")) continue;
@@ -36244,8 +36017,8 @@ var brokenLinkRule = createRule({
36244
36017
  if (target.startsWith("/")) continue;
36245
36018
  const filePart = target.split("#")[0] ?? target;
36246
36019
  if (filePart === "") continue;
36247
- const resolved = (0, import_node_path6.join)(docDir, filePart);
36248
- if ((0, import_node_fs6.existsSync)(resolved)) continue;
36020
+ const resolved = (0, import_node_path3.join)(docDir, filePart);
36021
+ if ((0, import_node_fs3.existsSync)(resolved)) continue;
36249
36022
  issues.push({
36250
36023
  ruleId: "docs/broken-link",
36251
36024
  category: "docs",
@@ -36262,6 +36035,466 @@ var brokenLinkRule = createRule({
36262
36035
  }
36263
36036
  });
36264
36037
 
36038
+ // src/rules/dup/identical-block.ts
36039
+ var crypto = __toESM(require("crypto"), 1);
36040
+ var WINDOW_SIZE = 10;
36041
+ var MIN_NORMALIZED_LENGTH = 40;
36042
+ var HASH_PREFIX_LENGTH = 16;
36043
+ var DEDUP_CACHE = /* @__PURE__ */ new Map();
36044
+ function normalizeAndHash(lines) {
36045
+ const normalized = lines.map(
36046
+ (line) => line.replace(/\/\/.*$/, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").trim()
36047
+ ).filter((line) => line.length > 0).join("\n");
36048
+ if (normalized.length < MIN_NORMALIZED_LENGTH) return void 0;
36049
+ return crypto.createHash("sha1").update(normalized).digest("hex").slice(0, HASH_PREFIX_LENGTH);
36050
+ }
36051
+ var dupIdenticalBlockRule = createRule({
36052
+ id: "dup/identical-block",
36053
+ category: "logic",
36054
+ severity: "medium",
36055
+ aiSpecific: false,
36056
+ description: "Block of >=10 lines is identical across >=2 files (Type-1 clone detector)",
36057
+ create(_context) {
36058
+ return {};
36059
+ },
36060
+ analyze(_context, facts) {
36061
+ const issues = [];
36062
+ const source = facts.v2?._source;
36063
+ if (!source) return issues;
36064
+ const filePath = facts.filePath;
36065
+ const lines = source.split("\n");
36066
+ for (let i = 0; i <= lines.length - WINDOW_SIZE; i++) {
36067
+ const window = lines.slice(i, i + WINDOW_SIZE);
36068
+ const hash = normalizeAndHash(window);
36069
+ if (!hash) continue;
36070
+ const existing = DEDUP_CACHE.get(hash) ?? [];
36071
+ const matches = existing.filter((m) => m.file !== filePath);
36072
+ for (const match of matches) {
36073
+ issues.push({
36074
+ ruleId: "dup/identical-block",
36075
+ category: "logic",
36076
+ severity: "medium",
36077
+ aiSpecific: false,
36078
+ message: `Identical ${WINDOW_SIZE}-line block at line ${i + 1} also appears in ${match.file}:${match.line + 1}`,
36079
+ line: i + 1,
36080
+ column: 0,
36081
+ 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.",
36082
+ extras: {
36083
+ duplicateOf: {
36084
+ file: match.file,
36085
+ line: match.line + 1,
36086
+ hash
36087
+ }
36088
+ }
36089
+ });
36090
+ }
36091
+ existing.push({ file: filePath, line: i });
36092
+ DEDUP_CACHE.set(hash, existing);
36093
+ }
36094
+ return issues;
36095
+ }
36096
+ });
36097
+
36098
+ // src/rules/go/error-wrap-without-context.ts
36099
+ var ERR_WRAP_REGEX = /fmt\.Errorf\(\s*"((?:[^"\\]|\\.)*)"\s*,\s*([^)]+)\)/g;
36100
+ var GENERIC_PREFIX_REGEX = /^\s*(?:error|err|failed|wrap(?:ping)?|invalid)\s*(?::\s*)?%w\b/i;
36101
+ var goErrorWrapWithoutContextRule = createRule({
36102
+ id: "go/error-wrap-without-context",
36103
+ category: "typo",
36104
+ severity: "low",
36105
+ aiSpecific: true,
36106
+ description: 'fmt.Errorf wrap without operation context \u2014 AI defaults to generic "error: %w"',
36107
+ create(_context) {
36108
+ return {};
36109
+ },
36110
+ analyze(_context, facts) {
36111
+ const issues = [];
36112
+ const source = facts.v2?._source;
36113
+ if (!source) return issues;
36114
+ let match;
36115
+ ERR_WRAP_REGEX.lastIndex = 0;
36116
+ while ((match = ERR_WRAP_REGEX.exec(source)) !== null) {
36117
+ const formatString = match[1];
36118
+ if (!formatString.includes("%w")) continue;
36119
+ if (formatString.length >= 30) continue;
36120
+ if (!GENERIC_PREFIX_REGEX.test(formatString)) continue;
36121
+ const line = source.slice(0, match.index).split("\n").length;
36122
+ issues.push({
36123
+ ruleId: "go/error-wrap-without-context",
36124
+ category: "typo",
36125
+ severity: "low",
36126
+ aiSpecific: true,
36127
+ message: `fmt.Errorf wrap with generic message "${formatString}" \u2014 include the failing operation`,
36128
+ line,
36129
+ column: match[0].indexOf("fmt") + 1,
36130
+ 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'
36131
+ });
36132
+ }
36133
+ return issues;
36134
+ }
36135
+ });
36136
+
36137
+ // src/rules/go/nil-slice-vs-empty.ts
36138
+ var NIL_SLICE_DECL_REGEX = /^[\t ]*var\s+([A-Za-z_][A-Za-z0-9_]*)\s+\[\][\w.*]+\b/gm;
36139
+ var EMPTY_SLICE_ASSIGN_REGEX = /^[\t ]*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:\[\][\w.*]*\{\}|make\(\[\][\w.*]*)/gm;
36140
+ var goNilSliceVsEmptyRule = createRule({
36141
+ id: "go/nil-slice-vs-empty",
36142
+ category: "typo",
36143
+ severity: "low",
36144
+ aiSpecific: true,
36145
+ description: "Variable declared `var x []int` but later assigned `x = []int{}` or `make([]int, n)` \u2014 pick one form",
36146
+ create(_context) {
36147
+ return {};
36148
+ },
36149
+ analyze(_context, facts) {
36150
+ const issues = [];
36151
+ const source = facts.v2?._source;
36152
+ if (!source) return issues;
36153
+ const nilDecls = /* @__PURE__ */ new Map();
36154
+ let m;
36155
+ NIL_SLICE_DECL_REGEX.lastIndex = 0;
36156
+ while ((m = NIL_SLICE_DECL_REGEX.exec(source)) !== null) {
36157
+ nilDecls.set(m[1], source.slice(0, m.index).split("\n").length);
36158
+ }
36159
+ if (nilDecls.size === 0) return issues;
36160
+ EMPTY_SLICE_ASSIGN_REGEX.lastIndex = 0;
36161
+ while ((m = EMPTY_SLICE_ASSIGN_REGEX.exec(source)) !== null) {
36162
+ const name = m[1];
36163
+ if (!nilDecls.has(name)) continue;
36164
+ const declLine = nilDecls.get(name);
36165
+ const assignLine = source.slice(0, m.index).split("\n").length;
36166
+ if (assignLine <= declLine) continue;
36167
+ issues.push({
36168
+ ruleId: "go/nil-slice-vs-empty",
36169
+ category: "typo",
36170
+ severity: "low",
36171
+ aiSpecific: true,
36172
+ message: `Variable '${name}' declared as nil slice (line ${declLine}) but assigned an empty slice (line ${assignLine}) \u2014 pick one form`,
36173
+ line: declLine,
36174
+ column: 1,
36175
+ 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."
36176
+ });
36177
+ }
36178
+ return issues;
36179
+ }
36180
+ });
36181
+
36182
+ // src/rules/go/struct-tag-inconsistency.ts
36183
+ var JSON_TAG_REGEX = /`json:"([^",]+)(?:,([^"]+))?"`/g;
36184
+ var goStructTagInconsistencyRule = createRule({
36185
+ id: "go/struct-tag-inconsistency",
36186
+ category: "typo",
36187
+ severity: "low",
36188
+ aiSpecific: true,
36189
+ description: 'Struct fields mix json:"foo" and json:"foo,omitempty" \u2014 pick one convention per struct',
36190
+ create(_context) {
36191
+ return {};
36192
+ },
36193
+ analyze(_context, facts) {
36194
+ const issues = [];
36195
+ const source = facts.v2?._source;
36196
+ if (!source) return issues;
36197
+ const structRegex = /type\s+[A-Z][A-Za-z0-9_]*\s+struct\s*\{/g;
36198
+ let structMatch;
36199
+ while ((structMatch = structRegex.exec(source)) !== null) {
36200
+ const startIdx = structMatch.index;
36201
+ const openBrace = source.indexOf("{", startIdx);
36202
+ if (openBrace < 0) continue;
36203
+ let depth = 1;
36204
+ let i = openBrace + 1;
36205
+ while (i < source.length && depth > 0) {
36206
+ const ch = source[i];
36207
+ if (ch === "{") depth++;
36208
+ else if (ch === "}") depth--;
36209
+ i++;
36210
+ }
36211
+ const structBody = source.slice(openBrace, i);
36212
+ const structLine = source.slice(0, startIdx).split("\n").length;
36213
+ const styleCount = {};
36214
+ const tagMatches = [];
36215
+ let m;
36216
+ JSON_TAG_REGEX.lastIndex = 0;
36217
+ while ((m = JSON_TAG_REGEX.exec(structBody)) !== null) {
36218
+ const tag = m[1];
36219
+ const options = m[2] ?? "";
36220
+ const style = options ? "with-options" : "no-options";
36221
+ styleCount[style] = (styleCount[style] ?? 0) + 1;
36222
+ tagMatches.push({ tag, style, idx: openBrace + m.index });
36223
+ }
36224
+ const styles = Object.keys(styleCount);
36225
+ if (styles.length < 2 || tagMatches.length < 2) continue;
36226
+ const dominant = styles.reduce(
36227
+ (a, b) => (styleCount[a] ?? 0) >= (styleCount[b] ?? 0) ? a : b
36228
+ );
36229
+ const minority = tagMatches.filter((t) => t.style !== dominant);
36230
+ if (minority.length === 0) continue;
36231
+ for (const m2 of minority) {
36232
+ const line = source.slice(0, m2.idx).split("\n").length;
36233
+ issues.push({
36234
+ ruleId: "go/struct-tag-inconsistency",
36235
+ category: "typo",
36236
+ severity: "low",
36237
+ aiSpecific: true,
36238
+ 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"}`,
36239
+ line,
36240
+ column: 1,
36241
+ 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.'
36242
+ });
36243
+ }
36244
+ if (issues.length > 0) break;
36245
+ }
36246
+ return issues;
36247
+ }
36248
+ });
36249
+
36250
+ // src/rules/java/arraylist-vs-linkedlist.ts
36251
+ var NEW_LINKED_LIST_REGEX = /new\s+LinkedList\s*</g;
36252
+ var javaArraylistVsLinkedlistRule = createRule({
36253
+ id: "java/arraylist-vs-linkedlist",
36254
+ category: "typo",
36255
+ severity: "low",
36256
+ aiSpecific: true,
36257
+ description: "new LinkedList<>() \u2014 use ArrayList (Effective Java, Item 28)",
36258
+ create(_context) {
36259
+ return {};
36260
+ },
36261
+ analyze(_context, facts) {
36262
+ const issues = [];
36263
+ const source = facts.v2?._source;
36264
+ if (!source) return issues;
36265
+ let m;
36266
+ NEW_LINKED_LIST_REGEX.lastIndex = 0;
36267
+ while ((m = NEW_LINKED_LIST_REGEX.exec(source)) !== null) {
36268
+ const line = source.slice(0, m.index).split("\n").length;
36269
+ issues.push({
36270
+ ruleId: "java/arraylist-vs-linkedlist",
36271
+ category: "typo",
36272
+ severity: "low",
36273
+ aiSpecific: true,
36274
+ message: `new LinkedList at line ${line} \u2014 use ArrayList instead`,
36275
+ line,
36276
+ column: 1,
36277
+ advice: "Replace `new LinkedList<>()` with `new ArrayList<>()`. LinkedList is rarely the right choice (worse cache locality, 5x more memory per element, O(n) indexed access). Joshua Bloch (Effective Java, Item 28) recommends ArrayList unless you specifically need a Deque. AI agents default to LinkedList because of textbook examples. Reference: java/arraylist-vs-linkedlist v0.20."
36278
+ });
36279
+ }
36280
+ return issues;
36281
+ }
36282
+ });
36283
+
36284
+ // src/rules/java/empty-catch-block.ts
36285
+ var SINGLE_LINE_EMPTY_CATCH_REGEX = /catch\s*\([^)]*\)\s*\{\s*\}/g;
36286
+ var javaEmptyCatchBlockRule = createRule({
36287
+ id: "java/empty-catch-block",
36288
+ category: "logic",
36289
+ severity: "medium",
36290
+ aiSpecific: true,
36291
+ description: "Empty catch block \u2014 silently swallows exceptions",
36292
+ create(_context) {
36293
+ return {};
36294
+ },
36295
+ analyze(_context, facts) {
36296
+ const issues = [];
36297
+ const source = facts.v2?._source;
36298
+ if (!source) return issues;
36299
+ let m;
36300
+ SINGLE_LINE_EMPTY_CATCH_REGEX.lastIndex = 0;
36301
+ while ((m = SINGLE_LINE_EMPTY_CATCH_REGEX.exec(source)) !== null) {
36302
+ const line = source.slice(0, m.index).split("\n").length;
36303
+ issues.push({
36304
+ ruleId: "java/empty-catch-block",
36305
+ category: "logic",
36306
+ severity: "medium",
36307
+ aiSpecific: true,
36308
+ message: `Empty catch block at line ${line} \u2014 exception is silently swallowed`,
36309
+ line,
36310
+ column: 1,
36311
+ advice: 'Log the exception (`log.error("...", e)`), re-throw it, or both. Empty catch blocks hide bugs. The pattern is common in AI-generated code that wants to look defensive. Reference: java/empty-catch-block v0.20.'
36312
+ });
36313
+ }
36314
+ return issues;
36315
+ }
36316
+ });
36317
+
36318
+ // src/rules/java/legacy-date-api.ts
36319
+ var LEGACY_IMPORT_REGEX = /^import\s+(?:static\s+)?java\.(?:util|sql)\.(?:Date|Calendar|GregorianCalendar)\s*;/gm;
36320
+ var LEGACY_USAGE_REGEX = /\bnew\s+(?:Date|GregorianCalendar)\s*\(/g;
36321
+ var CALENDAR_GET_INSTANCE_REGEX = /Calendar\.getInstance\s*\(/g;
36322
+ var javaLegacyDateApiRule = createRule({
36323
+ id: "java/legacy-date-api",
36324
+ category: "typo",
36325
+ severity: "low",
36326
+ aiSpecific: true,
36327
+ description: "Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310) from Java 8+",
36328
+ create(_context) {
36329
+ return {};
36330
+ },
36331
+ analyze(_context, facts) {
36332
+ const issues = [];
36333
+ const source = facts.v2?._source;
36334
+ if (!source) return issues;
36335
+ const flagged = /* @__PURE__ */ new Set();
36336
+ let m;
36337
+ LEGACY_IMPORT_REGEX.lastIndex = 0;
36338
+ while ((m = LEGACY_IMPORT_REGEX.exec(source)) !== null) {
36339
+ const line = source.slice(0, m.index).split("\n").length;
36340
+ flagged.add(line);
36341
+ issues.push({
36342
+ ruleId: "java/legacy-date-api",
36343
+ category: "typo",
36344
+ severity: "low",
36345
+ aiSpecific: true,
36346
+ message: `Legacy date import at line ${line} \u2014 use java.time (JSR-310) from Java 8+`,
36347
+ line,
36348
+ column: 1,
36349
+ advice: "Replace `java.util.Date` / `java.util.Calendar` with `java.time` (`LocalDate`, `LocalDateTime`, `Instant`, `ZonedDateTime`). java.time is immutable, thread-safe, and has a much better API. AI agents default to the legacy API because their training data predates Java 8 (2014). Reference: java/legacy-date-api v0.20."
36350
+ });
36351
+ }
36352
+ LEGACY_USAGE_REGEX.lastIndex = 0;
36353
+ while ((m = LEGACY_USAGE_REGEX.exec(source)) !== null) {
36354
+ const line = source.slice(0, m.index).split("\n").length;
36355
+ if (flagged.has(line)) continue;
36356
+ issues.push({
36357
+ ruleId: "java/legacy-date-api",
36358
+ category: "typo",
36359
+ severity: "low",
36360
+ aiSpecific: true,
36361
+ message: `new Date() / GregorianCalendar at line ${line} \u2014 use java.time`,
36362
+ line,
36363
+ column: 1,
36364
+ advice: "Replace with `LocalDate.now()`, `Instant.now()`, or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
36365
+ });
36366
+ }
36367
+ CALENDAR_GET_INSTANCE_REGEX.lastIndex = 0;
36368
+ while ((m = CALENDAR_GET_INSTANCE_REGEX.exec(source)) !== null) {
36369
+ const line = source.slice(0, m.index).split("\n").length;
36370
+ if (flagged.has(line)) continue;
36371
+ issues.push({
36372
+ ruleId: "java/legacy-date-api",
36373
+ category: "typo",
36374
+ severity: "low",
36375
+ aiSpecific: true,
36376
+ message: `Calendar.getInstance() at line ${line} \u2014 use java.time`,
36377
+ line,
36378
+ column: 1,
36379
+ advice: "Replace with `LocalDate.now()` (date-only) or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
36380
+ });
36381
+ }
36382
+ return issues;
36383
+ }
36384
+ });
36385
+
36386
+ // src/rules/java/raw-type-overuse.ts
36387
+ var RAW_TYPE_REGEX = /\b(List|Map|Set|Collection|Iterable)\s+(?![<A-Z])(\w+)/g;
36388
+ var javaRawTypeOveruseRule = createRule({
36389
+ id: "java/raw-type-overuse",
36390
+ category: "typo",
36391
+ severity: "low",
36392
+ aiSpecific: true,
36393
+ description: "Raw type usage (List, Map, Set) \u2014 use generics (Effective Java, Item 23)",
36394
+ create(_context) {
36395
+ return {};
36396
+ },
36397
+ analyze(_context, facts) {
36398
+ const issues = [];
36399
+ const source = facts.v2?._source;
36400
+ if (!source) return issues;
36401
+ let m;
36402
+ RAW_TYPE_REGEX.lastIndex = 0;
36403
+ while ((m = RAW_TYPE_REGEX.exec(source)) !== null) {
36404
+ const typeName = m[1];
36405
+ const line = source.slice(0, m.index).split("\n").length;
36406
+ issues.push({
36407
+ ruleId: "java/raw-type-overuse",
36408
+ category: "typo",
36409
+ severity: "low",
36410
+ aiSpecific: true,
36411
+ message: `Raw type ${typeName} at line ${line} \u2014 add type parameters`,
36412
+ line,
36413
+ column: 1,
36414
+ advice: `Replace raw \`${typeName}\` with \`${typeName}<...>\`. Raw types disable generic type checking. Effective Java, Item 23: 'Don't use raw types in new code'. AI agents default to raw types when unsure of the correct generic parameters. Reference: java/raw-type-overuse v0.20.`
36415
+ });
36416
+ }
36417
+ return issues;
36418
+ }
36419
+ });
36420
+
36421
+ // src/rules/java/string-concat-loop.ts
36422
+ var STRING_CONCAT_REGEX = /(\b\w+)\s*=\s*\1\s*\+\s*[^;]+;|(\b\w+)\s*\+=\s*['"`]/g;
36423
+ var javaStringConcatLoopRule = createRule({
36424
+ id: "java/string-concat-loop",
36425
+ category: "perf",
36426
+ severity: "low",
36427
+ aiSpecific: true,
36428
+ description: "String concatenation in a loop \u2014 use StringBuilder",
36429
+ create(_context) {
36430
+ return {};
36431
+ },
36432
+ analyze(_context, facts) {
36433
+ const issues = [];
36434
+ const source = facts.v2?._source;
36435
+ if (!source) return issues;
36436
+ if (!/\b(for|while|do)\b/.test(source)) return issues;
36437
+ let m;
36438
+ STRING_CONCAT_REGEX.lastIndex = 0;
36439
+ while ((m = STRING_CONCAT_REGEX.exec(source)) !== null) {
36440
+ const line = source.slice(0, m.index).split("\n").length;
36441
+ issues.push({
36442
+ ruleId: "java/string-concat-loop",
36443
+ category: "perf",
36444
+ severity: "low",
36445
+ aiSpecific: true,
36446
+ message: `String concatenation in a loop at line ${line} \u2014 use StringBuilder`,
36447
+ line,
36448
+ column: 1,
36449
+ advice: "Declare a `StringBuilder` outside the loop: `StringBuilder sb = new StringBuilder(); sb.append(...);` then `return sb.toString();` after the loop. String concatenation in a loop is O(n\xB2) \u2014 each iteration copies the prior string. AI agents concatenate strings in loops because of training-data examples. Reference: java/string-concat-loop v0.20."
36450
+ });
36451
+ }
36452
+ return issues;
36453
+ }
36454
+ });
36455
+
36456
+ // src/rules/java/system-out-println.ts
36457
+ var PRINTLN_REGEX = /System\.out\.println\s*\(/g;
36458
+ var DEFAULT_THRESHOLD = 1;
36459
+ var javaSystemOutPrintlnRule = createRule({
36460
+ id: "java/system-out-println",
36461
+ category: "typo",
36462
+ severity: "low",
36463
+ aiSpecific: true,
36464
+ description: "System.out.println in production code \u2014 use a logger (SLF4J, Log4j, etc.)",
36465
+ create(_context) {
36466
+ return { threshold: DEFAULT_THRESHOLD };
36467
+ },
36468
+ analyze(context, facts) {
36469
+ const issues = [];
36470
+ const source = facts.v2?._source;
36471
+ if (!source) return issues;
36472
+ const matches = [];
36473
+ let m;
36474
+ PRINTLN_REGEX.lastIndex = 0;
36475
+ while ((m = PRINTLN_REGEX.exec(source)) !== null) {
36476
+ matches.push(m.index);
36477
+ }
36478
+ if (matches.length <= context.threshold) return issues;
36479
+ const cap = Math.min(matches.length, 10);
36480
+ for (let i = 0; i < cap; i++) {
36481
+ const idx = matches[i];
36482
+ const line = source.slice(0, idx).split("\n").length;
36483
+ issues.push({
36484
+ ruleId: "java/system-out-println",
36485
+ category: "typo",
36486
+ severity: "low",
36487
+ aiSpecific: true,
36488
+ message: `System.out.println at line ${line} \u2014 use a logger for production output`,
36489
+ line,
36490
+ column: 1,
36491
+ advice: "Replace with `private static final Logger log = LoggerFactory.getLogger(...);` then `log.info(...)`. AI agents default to println because their training data has countless textbook examples. Real Java code uses a logger. Reference: java/system-out-println v0.20."
36492
+ });
36493
+ }
36494
+ return issues;
36495
+ }
36496
+ });
36497
+
36265
36498
  // src/rules/layout/gap-monopoly.ts
36266
36499
  var GAP_RE = /\bgap(?:-x|-y)?-(\d+)\b/g;
36267
36500
  var gapMonopolyRule = createRule({
@@ -36614,16 +36847,16 @@ var DEFAULT_CONFIG = {
36614
36847
  };
36615
36848
 
36616
36849
  // src/config/detect/monorepo.ts
36617
- var import_node_fs7 = require("fs");
36618
- var import_node_path7 = require("path");
36850
+ var import_node_fs4 = require("fs");
36851
+ var import_node_path4 = require("path");
36619
36852
 
36620
36853
  // src/config/detect/styling.ts
36621
- var import_node_fs8 = require("fs");
36622
- var import_node_path8 = require("path");
36854
+ var import_node_fs5 = require("fs");
36855
+ var import_node_path5 = require("path");
36623
36856
 
36624
36857
  // src/config/detect/stack.ts
36625
- var import_node_fs9 = require("fs");
36626
- var import_node_path9 = require("path");
36858
+ var import_node_fs6 = require("fs");
36859
+ var import_node_path6 = require("path");
36627
36860
 
36628
36861
  // src/config/presets.ts
36629
36862
  var REACT_ONLY_RULES = {
@@ -36672,8 +36905,8 @@ var FRAMEWORK_PRESETS = {
36672
36905
  };
36673
36906
 
36674
36907
  // src/config/load.ts
36675
- var import_node_fs10 = require("fs");
36676
- var import_node_path10 = require("path");
36908
+ var import_node_fs8 = require("fs");
36909
+ var import_node_path8 = require("path");
36677
36910
  var import_node_module = require("module");
36678
36911
 
36679
36912
  // src/engine/logger.ts
@@ -36695,6 +36928,10 @@ function setLoggerQuiet(quiet) {
36695
36928
  logger = createLogger(quiet);
36696
36929
  }
36697
36930
 
36931
+ // src/config/conventions.ts
36932
+ var import_node_fs7 = require("fs");
36933
+ var import_node_path7 = require("path");
36934
+
36698
36935
  // src/config/init.ts
36699
36936
  var STRICTNESS_PRESETS = {
36700
36937
  strict: {
@@ -37049,82 +37286,6 @@ var keyPropMissingRule = createRule({
37049
37286
  }
37050
37287
  });
37051
37288
 
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
37289
  // src/rules/logic/math-any-density.ts
37129
37290
  var ANY_PER_100_LINES = 5;
37130
37291
  var MIN_ABSOLUTE = 6;
@@ -37186,7 +37347,7 @@ var mathAnyDensityRule = createRule({
37186
37347
  });
37187
37348
 
37188
37349
  // src/rules/logic/math-console-log-storm.ts
37189
- var WINDOW_SIZE = 30;
37350
+ var WINDOW_SIZE2 = 30;
37190
37351
  var STORM_THRESHOLD = 5;
37191
37352
  var CONSOLE_LOG_RE = /\bconsole\.log\s*\(/g;
37192
37353
  var mathConsoleLogStormRule = createRule({
@@ -37217,7 +37378,7 @@ var mathConsoleLogStormRule = createRule({
37217
37378
  let maxEndLine = 0;
37218
37379
  let i = 0;
37219
37380
  for (let j = 0; j < lines.length; j++) {
37220
- while (lines[j] - lines[i] > WINDOW_SIZE) i++;
37381
+ while (lines[j] - lines[i] > WINDOW_SIZE2) i++;
37221
37382
  const count = j - i + 1;
37222
37383
  if (count > maxCount) {
37223
37384
  maxCount = count;
@@ -37231,7 +37392,7 @@ var mathConsoleLogStormRule = createRule({
37231
37392
  category: "logic",
37232
37393
  severity: "high",
37233
37394
  aiSpecific: true,
37234
- message: `${maxCount} console.log calls clustered in a ${WINDOW_SIZE}-line window ending at line ${maxEndLine}. AI debug-sprays logs in a single function; humans use one strategic log.`,
37395
+ 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
37396
  line: firstIdx >= 0 ? lines[firstIdx] : 1,
37236
37397
  column: firstIdx >= 0 ? columns[firstIdx] : 1,
37237
37398
  advice: "Replace debug logs with a proper debugger or logger.debug() \u2014 remove all console.log before shipping."
@@ -38136,7 +38297,6 @@ function scanForStringlyParams(paramText) {
38136
38297
  }
38137
38298
 
38138
38299
  // src/rules/rust/todo-macro.ts
38139
- init_parser_rust();
38140
38300
  var TODO_MACROS = /* @__PURE__ */ new Set(["todo", "unimplemented", "todo_unimplemented"]);
38141
38301
  var rustTodoMacroRule = createRule({
38142
38302
  id: "rust/todo-macro",
@@ -38272,7 +38432,6 @@ function collectReferencedNames(source) {
38272
38432
  }
38273
38433
 
38274
38434
  // src/rules/rust/unwrap-in-production.ts
38275
- init_parser_rust();
38276
38435
  var UNWRAP_METHODS = /* @__PURE__ */ new Set(["unwrap", "expect", "unwrap_or_else"]);
38277
38436
  var rustUnwrapInProductionRule = createRule({
38278
38437
  id: "rust/unwrap-in-production",
@@ -39409,7 +39568,7 @@ function looksRealistic(value) {
39409
39568
 
39410
39569
  // src/rules/test/missing-edge-case.ts
39411
39570
  var import_core4 = require("@swc/core");
39412
- var import_node_fs11 = require("fs");
39571
+ var import_node_fs9 = require("fs");
39413
39572
  var MAX_PER_FILE = 20;
39414
39573
  var missingEdgeCaseRule = createRule({
39415
39574
  id: "test/missing-edge-case",
@@ -39431,7 +39590,7 @@ var missingEdgeCaseRule = createRule({
39431
39590
  const testFileSources = /* @__PURE__ */ new Map();
39432
39591
  for (const testFile of discoverTestFiles(cwd)) {
39433
39592
  try {
39434
- testFileSources.set(testFile, (0, import_node_fs11.readFileSync)(testFile, "utf-8"));
39593
+ testFileSources.set(testFile, (0, import_node_fs9.readFileSync)(testFile, "utf-8"));
39435
39594
  } catch {
39436
39595
  }
39437
39596
  }
@@ -39685,8 +39844,8 @@ function discoverTestFiles(cwd) {
39685
39844
  const found = [];
39686
39845
  for (const root of roots) {
39687
39846
  const abs = `${cwd}/${root}`;
39688
- if (!(0, import_node_fs11.existsSync)(abs)) continue;
39689
- walk2(abs, found, import_node_fs11.readdirSync, import_node_fs11.statSync);
39847
+ if (!(0, import_node_fs9.existsSync)(abs)) continue;
39848
+ walk2(abs, found, import_node_fs9.readdirSync, import_node_fs9.statSync);
39690
39849
  if (found.length > 200) break;
39691
39850
  }
39692
39851
  return found;
@@ -39805,6 +39964,216 @@ function isTautologicalAssertion(hit) {
39805
39964
  return false;
39806
39965
  }
39807
39966
 
39967
+ // src/rules/ts/enum-vs-as-const.ts
39968
+ var ENUM_DECL_REGEX = /^[ \t]*(?:export\s+)?(?:const\s+)?enum\s+[A-Z_][A-Za-z0-9_]*\s*\{/gm;
39969
+ var tsEnumVsAsConstRule = createRule({
39970
+ id: "ts/enum-vs-as-const",
39971
+ category: "typo",
39972
+ severity: "low",
39973
+ aiSpecific: true,
39974
+ description: "Uses `enum` \u2014 modern TS prefers `as const` objects",
39975
+ create(_context) {
39976
+ return {};
39977
+ },
39978
+ analyze(_context, facts) {
39979
+ const issues = [];
39980
+ const source = facts.v2?._source;
39981
+ if (!source) return issues;
39982
+ let match;
39983
+ ENUM_DECL_REGEX.lastIndex = 0;
39984
+ while ((match = ENUM_DECL_REGEX.exec(source)) !== null) {
39985
+ const line = source.slice(0, match.index).split("\n").length;
39986
+ issues.push({
39987
+ ruleId: "ts/enum-vs-as-const",
39988
+ category: "typo",
39989
+ severity: "low",
39990
+ aiSpecific: true,
39991
+ message: `'enum' is an AI / older-TS pattern \u2014 prefer 'as const' for a frozen object literal`,
39992
+ line,
39993
+ column: match[0].indexOf("enum") + 1,
39994
+ 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.'
39995
+ });
39996
+ }
39997
+ return issues;
39998
+ }
39999
+ });
40000
+
40001
+ // src/rules/ts/excessive-type-assertion.ts
40002
+ var DEFAULT_MAX = 3;
40003
+ 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;
40004
+ var AS_REGEX = /(?<![\w$])as\s+(?!const)([A-Z][\w$.,<>\[\]|&]*)/g;
40005
+ var tsExcessiveTypeAssertionRule = createRule({
40006
+ id: "ts/excessive-type-assertion",
40007
+ category: "typo",
40008
+ severity: "low",
40009
+ aiSpecific: true,
40010
+ description: "Function with >3 `as` type assertions \u2014 likely AI fighting the type system",
40011
+ create(_context) {
40012
+ return { maxAssertionsPerFunction: DEFAULT_MAX };
40013
+ },
40014
+ analyze(context, facts) {
40015
+ const issues = [];
40016
+ const source = facts.v2?._source;
40017
+ if (!source) return issues;
40018
+ let match;
40019
+ FN_DECL_REGEX.lastIndex = 0;
40020
+ while ((match = FN_DECL_REGEX.exec(source)) !== null) {
40021
+ const startIdx = match.index;
40022
+ const openBraceIdx = source.indexOf("{", startIdx);
40023
+ if (openBraceIdx < 0) continue;
40024
+ let depth = 1;
40025
+ let i = openBraceIdx + 1;
40026
+ while (i < source.length && depth > 0) {
40027
+ const ch = source[i];
40028
+ if (ch === "{") depth++;
40029
+ else if (ch === "}") depth--;
40030
+ i++;
40031
+ }
40032
+ const body = source.slice(openBraceIdx, i);
40033
+ const line = source.slice(0, startIdx).split("\n").length;
40034
+ let asCount = 0;
40035
+ const seen = /* @__PURE__ */ new Set();
40036
+ let asMatch;
40037
+ AS_REGEX.lastIndex = 0;
40038
+ while ((asMatch = AS_REGEX.exec(body)) !== null) {
40039
+ const captured = asMatch[1];
40040
+ if (seen.has(captured)) continue;
40041
+ seen.add(captured);
40042
+ asCount++;
40043
+ }
40044
+ if (asCount > context.maxAssertionsPerFunction) {
40045
+ issues.push({
40046
+ ruleId: "ts/excessive-type-assertion",
40047
+ category: "typo",
40048
+ severity: "low",
40049
+ aiSpecific: true,
40050
+ message: `Function has ${asCount} 'as' assertions (max ${context.maxAssertionsPerFunction}) \u2014 likely AI fighting the type system`,
40051
+ line,
40052
+ column: 1,
40053
+ 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."
40054
+ });
40055
+ }
40056
+ }
40057
+ return issues;
40058
+ }
40059
+ });
40060
+
40061
+ // src/rules/ts/import-type-misuse.ts
40062
+ var INLINE_TYPE_IMPORT_REGEX = /^[ \t]*import\s*\{[^}]*\btype\s+[A-Za-z_]/gm;
40063
+ var tsImportTypeMisuseRule = createRule({
40064
+ id: "ts/import-type-misuse",
40065
+ category: "typo",
40066
+ severity: "low",
40067
+ aiSpecific: true,
40068
+ description: "Inline `import { type X }` \u2014 prefer `import type { X }` for clarity",
40069
+ create(_context) {
40070
+ return {};
40071
+ },
40072
+ analyze(_context, facts) {
40073
+ const issues = [];
40074
+ const source = facts.v2?._source;
40075
+ if (!source) return issues;
40076
+ let match;
40077
+ INLINE_TYPE_IMPORT_REGEX.lastIndex = 0;
40078
+ while ((match = INLINE_TYPE_IMPORT_REGEX.exec(source)) !== null) {
40079
+ const line = source.slice(0, match.index).split("\n").length;
40080
+ issues.push({
40081
+ ruleId: "ts/import-type-misuse",
40082
+ category: "typo",
40083
+ severity: "low",
40084
+ aiSpecific: true,
40085
+ message: "Inline `type` in a value import \u2014 split into a separate `import type` statement",
40086
+ line,
40087
+ column: match[0].indexOf("type") + 1,
40088
+ 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.'
40089
+ });
40090
+ }
40091
+ return issues;
40092
+ }
40093
+ });
40094
+
40095
+ // src/rules/ts/never-vs-unknown.ts
40096
+ var NEVER_RETURN_REGEX = /^[ \t]*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)[^{]*:\s*(?:Promise<)?never\b[^{]*\{/gm;
40097
+ var THROW_OR_LOOP_REGEX = /\b(?:throw\b|while\s*\(|for\s*\(|process\.exit\b|System\.exit\b)/;
40098
+ var tsNeverVsUnknownRule = createRule({
40099
+ id: "ts/never-vs-unknown",
40100
+ category: "typo",
40101
+ severity: "low",
40102
+ aiSpecific: true,
40103
+ description: "Return type `: never` but body has no throw / loop / exit \u2014 likely AI misuse",
40104
+ create(_context) {
40105
+ return {};
40106
+ },
40107
+ analyze(_context, facts) {
40108
+ const issues = [];
40109
+ const source = facts.v2?._source;
40110
+ if (!source) return issues;
40111
+ let match;
40112
+ NEVER_RETURN_REGEX.lastIndex = 0;
40113
+ while ((match = NEVER_RETURN_REGEX.exec(source)) !== null) {
40114
+ const fnName = match[1];
40115
+ const startIdx = match.index;
40116
+ const line = source.slice(0, startIdx).split("\n").length;
40117
+ const openBraceIdx = source.indexOf("{", startIdx);
40118
+ if (openBraceIdx < 0) continue;
40119
+ let depth = 1;
40120
+ let i = openBraceIdx + 1;
40121
+ while (i < source.length && depth > 0) {
40122
+ const ch = source[i];
40123
+ if (ch === "{") depth++;
40124
+ else if (ch === "}") depth--;
40125
+ i++;
40126
+ }
40127
+ const body = source.slice(openBraceIdx, i);
40128
+ if (THROW_OR_LOOP_REGEX.test(body)) continue;
40129
+ issues.push({
40130
+ ruleId: "ts/never-vs-unknown",
40131
+ category: "typo",
40132
+ severity: "low",
40133
+ aiSpecific: true,
40134
+ message: `Function '${fnName}' returns 'never' but its body has no throw, loop, or exit \u2014 likely AI misuse`,
40135
+ line,
40136
+ column: match[0].indexOf("never") + 1,
40137
+ 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.'
40138
+ });
40139
+ }
40140
+ return issues;
40141
+ }
40142
+ });
40143
+
40144
+ // src/rules/ts/optional-chain-overuse.ts
40145
+ var DEFAULT_MIN_CHAIN_LENGTH = 5;
40146
+ var tsOptionalChainOveruseRule = createRule({
40147
+ id: "ts/optional-chain-overuse",
40148
+ category: "logic",
40149
+ severity: "low",
40150
+ aiSpecific: true,
40151
+ description: "Optional chaining (?.) used 5+ times in a single chain \u2014 AI tends to chain rather than narrow",
40152
+ create(_context) {
40153
+ return { minChainLength: DEFAULT_MIN_CHAIN_LENGTH };
40154
+ },
40155
+ analyze(context, facts) {
40156
+ const issues = [];
40157
+ const expressions = facts.v2.logic?.logicalExpressions;
40158
+ if (!expressions) return issues;
40159
+ for (const expression of expressions) {
40160
+ if (expression.depth >= context.minChainLength && expression.isOptionalChainLike) {
40161
+ issues.push({
40162
+ ruleId: "ts/optional-chain-overuse",
40163
+ category: "logic",
40164
+ severity: "low",
40165
+ aiSpecific: true,
40166
+ message: `Optional chain depth ${expression.depth} \u2014 break with an intermediate variable or guard clause`,
40167
+ line: expression.line,
40168
+ column: expression.column,
40169
+ 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."
40170
+ });
40171
+ }
40172
+ }
40173
+ return issues;
40174
+ }
40175
+ });
40176
+
39808
40177
  // src/rules/typo/calc-fontsize.ts
39809
40178
  var FONT_SIZE_RE = /\bfont-size\s*:\s*[^;]*\bcalc\s*\(/i;
39810
40179
  var calcFontsizeRule = createRule({
@@ -41342,9 +41711,18 @@ var builtinRules = [
41342
41711
  unusedLocalRule,
41343
41712
  unusedParameterRule,
41344
41713
  brokenLinkRule,
41345
- expiredCodeExampleRule,
41346
41714
  staleFunctionReferenceRule,
41347
41715
  stalePackageReferenceRule,
41716
+ dupIdenticalBlockRule,
41717
+ goErrorWrapWithoutContextRule,
41718
+ goNilSliceVsEmptyRule,
41719
+ goStructTagInconsistencyRule,
41720
+ javaArraylistVsLinkedlistRule,
41721
+ javaEmptyCatchBlockRule,
41722
+ javaLegacyDateApiRule,
41723
+ javaRawTypeOveruseRule,
41724
+ javaStringConcatLoopRule,
41725
+ javaSystemOutPrintlnRule,
41348
41726
  gapMonopolyRule,
41349
41727
  mathElementUniformityRule,
41350
41728
  mathGridUniformityRule,
@@ -41354,7 +41732,6 @@ var builtinRules = [
41354
41732
  ghostDefensiveRule,
41355
41733
  heapsDeviationRule,
41356
41734
  keyPropMissingRule,
41357
- ksDistributionShiftRule,
41358
41735
  mathAnyDensityRule,
41359
41736
  mathConsoleLogStormRule,
41360
41737
  mathGiniClassUsageRule,
@@ -41388,6 +41765,11 @@ var builtinRules = [
41388
41765
  fakePlaceholderRule,
41389
41766
  missingEdgeCaseRule,
41390
41767
  weakAssertionRule,
41768
+ tsEnumVsAsConstRule,
41769
+ tsExcessiveTypeAssertionRule,
41770
+ tsImportTypeMisuseRule,
41771
+ tsNeverVsUnknownRule,
41772
+ tsOptionalChainOveruseRule,
41391
41773
  calcFontsizeRule,
41392
41774
  calcRawPxRule,
41393
41775
  clampOffscaleRule,
@@ -41558,7 +41940,7 @@ var signal_strength_default = {
41558
41940
  precision: 0.9966,
41559
41941
  lastCalibratedAt: "2026-07-01T00:00:00Z",
41560
41942
  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).",
41943
+ _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
41944
  aiSpecific: true,
41563
41945
  _v7Verdict: "USEFUL",
41564
41946
  _v7Lift: 182622.43,
@@ -41566,7 +41948,8 @@ var signal_strength_default = {
41566
41948
  _v7FpRate: 0,
41567
41949
  _v7Precision: 0.9957,
41568
41950
  _v8Verdict: "USEFUL",
41569
- _v8Lift: 99999
41951
+ _v8Lift: 99999,
41952
+ defaultOff: false
41570
41953
  },
41571
41954
  "ai/errors-near-eof": {
41572
41955
  recall: 0.0948,
@@ -41815,7 +42198,7 @@ var signal_strength_default = {
41815
42198
  precision: 0.9512,
41816
42199
  lastCalibratedAt: "2026-07-01T00:00:00Z",
41817
42200
  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).",
42201
+ _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
42202
  aiSpecific: true,
41820
42203
  _v7Verdict: "USEFUL",
41821
42204
  _v7Lift: 7099.57,
@@ -41823,7 +42206,8 @@ var signal_strength_default = {
41823
42206
  _v7FpRate: 1e-4,
41824
42207
  _v7Precision: 0.929,
41825
42208
  _v8Verdict: "USEFUL",
41826
- _v8Lift: 99999
42209
+ _v8Lift: 99999,
42210
+ defaultOff: false
41827
42211
  },
41828
42212
  "context/import-path-mismatch": {
41829
42213
  recall: 0.0681,
@@ -42007,7 +42391,7 @@ var signal_strength_default = {
42007
42391
  precision: 0.8834,
42008
42392
  lastCalibratedAt: "2026-07-01T00:00:00Z",
42009
42393
  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.",
42394
+ _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
42395
  aiSpecific: true,
42012
42396
  _v7Verdict: "DORMANT",
42013
42397
  _v7Lift: 1,
@@ -42015,7 +42399,8 @@ var signal_strength_default = {
42015
42399
  _v7FpRate: 0,
42016
42400
  _v7Precision: 0,
42017
42401
  _v8Verdict: "USEFUL",
42018
- _v8Lift: 32.74
42402
+ _v8Lift: 32.74,
42403
+ defaultOff: false
42019
42404
  },
42020
42405
  "dead/unused-parameter": {
42021
42406
  recall: 8e-4,
@@ -42051,24 +42436,6 @@ var signal_strength_default = {
42051
42436
  _v8Verdict: "OK",
42052
42437
  _v8Lift: 48.52
42053
42438
  },
42054
- "docs/expired-code-example": {
42055
- recall: 0,
42056
- fpRate: 0,
42057
- ratio: 0,
42058
- precision: 0,
42059
- lastCalibratedAt: "2026-07-01T00:00:00Z",
42060
- verdict: "INVERTED",
42061
- _calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=0, FP=3, P=0.0%, FPR=0.00%, lift=0.00. v7 was DORMANT (TP=0, FP=0, lift=1.00). v8 was INVERTED (TP=0, FP=3).",
42062
- aiSpecific: false,
42063
- _v7Verdict: "DORMANT",
42064
- _v7Lift: 1,
42065
- _v7Recall: 0,
42066
- _v7FpRate: 0,
42067
- _v7Precision: 0,
42068
- _v8Verdict: "INVERTED",
42069
- _v8Lift: 0,
42070
- defaultOff: true
42071
- },
42072
42439
  "docs/stale-function-reference": {
42073
42440
  recall: 26e-4,
42074
42441
  fpRate: 5e-4,
@@ -42103,6 +42470,186 @@ var signal_strength_default = {
42103
42470
  _v8Verdict: "OK",
42104
42471
  _v8Lift: 255.9
42105
42472
  },
42473
+ "dup/identical-block": {
42474
+ recall: 0,
42475
+ fpRate: 0,
42476
+ ratio: 1,
42477
+ precision: 0,
42478
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42479
+ verdict: "DORMANT",
42480
+ _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.",
42481
+ aiSpecific: false,
42482
+ _v7Verdict: "DORMANT",
42483
+ _v7Lift: 1,
42484
+ _v7Recall: 0,
42485
+ _v7FpRate: 0,
42486
+ _v7Precision: 0,
42487
+ _v8Verdict: "DORMANT",
42488
+ _v8Lift: 1,
42489
+ defaultOff: true
42490
+ },
42491
+ "go/error-wrap-without-context": {
42492
+ recall: 0,
42493
+ fpRate: 0,
42494
+ ratio: 1,
42495
+ precision: 0,
42496
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42497
+ verdict: "DORMANT",
42498
+ _calibrationNote: "v0.19: new rule (fmt.Errorf wrap with generic message \u2014 needs operation context). Not yet calibrated. Scheduled for v9 calibration.",
42499
+ aiSpecific: true,
42500
+ _v7Verdict: "DORMANT",
42501
+ _v7Lift: 1,
42502
+ _v7Recall: 0,
42503
+ _v7FpRate: 0,
42504
+ _v7Precision: 0,
42505
+ _v8Verdict: "DORMANT",
42506
+ _v8Lift: 1,
42507
+ defaultOff: true
42508
+ },
42509
+ "go/nil-slice-vs-empty": {
42510
+ recall: 0,
42511
+ fpRate: 0,
42512
+ ratio: 1,
42513
+ precision: 0,
42514
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42515
+ verdict: "DORMANT",
42516
+ _calibrationNote: "v0.19: new rule (Variable declared nil slice but assigned empty slice). Not yet calibrated. Scheduled for v9 calibration.",
42517
+ aiSpecific: true,
42518
+ _v7Verdict: "DORMANT",
42519
+ _v7Lift: 1,
42520
+ _v7Recall: 0,
42521
+ _v7FpRate: 0,
42522
+ _v7Precision: 0,
42523
+ _v8Verdict: "DORMANT",
42524
+ _v8Lift: 1,
42525
+ defaultOff: true
42526
+ },
42527
+ "go/struct-tag-inconsistency": {
42528
+ recall: 0,
42529
+ fpRate: 0,
42530
+ ratio: 1,
42531
+ precision: 0,
42532
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42533
+ verdict: "DORMANT",
42534
+ _calibrationNote: "v0.19: new rule (Struct fields mix json tag styles). Not yet calibrated. Scheduled for v9 calibration.",
42535
+ aiSpecific: true,
42536
+ _v7Verdict: "DORMANT",
42537
+ _v7Lift: 1,
42538
+ _v7Recall: 0,
42539
+ _v7FpRate: 0,
42540
+ _v7Precision: 0,
42541
+ _v8Verdict: "DORMANT",
42542
+ _v8Lift: 1,
42543
+ defaultOff: true
42544
+ },
42545
+ "java/arraylist-vs-linkedlist": {
42546
+ recall: 0,
42547
+ fpRate: 0,
42548
+ ratio: 1,
42549
+ precision: 0,
42550
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42551
+ verdict: "DORMANT",
42552
+ _calibrationNote: "v0.20: new rule (new LinkedList<>() \u2014 use ArrayList (Effective Java Item 28)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
42553
+ aiSpecific: true,
42554
+ _v7Verdict: "DORMANT",
42555
+ _v7Lift: 1,
42556
+ _v7Recall: 0,
42557
+ _v7FpRate: 0,
42558
+ _v7Precision: 0,
42559
+ _v8Verdict: "DORMANT",
42560
+ _v8Lift: 1,
42561
+ defaultOff: true
42562
+ },
42563
+ "java/empty-catch-block": {
42564
+ recall: 0,
42565
+ fpRate: 0,
42566
+ ratio: 1,
42567
+ precision: 0,
42568
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42569
+ verdict: "DORMANT",
42570
+ _calibrationNote: "v0.20: new rule (Empty catch block \u2014 silently swallows exceptions). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
42571
+ aiSpecific: true,
42572
+ _v7Verdict: "DORMANT",
42573
+ _v7Lift: 1,
42574
+ _v7Recall: 0,
42575
+ _v7FpRate: 0,
42576
+ _v7Precision: 0,
42577
+ _v8Verdict: "DORMANT",
42578
+ _v8Lift: 1,
42579
+ defaultOff: true
42580
+ },
42581
+ "java/legacy-date-api": {
42582
+ recall: 0,
42583
+ fpRate: 0,
42584
+ ratio: 1,
42585
+ precision: 0,
42586
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42587
+ verdict: "DORMANT",
42588
+ _calibrationNote: "v0.20: new rule (Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
42589
+ aiSpecific: true,
42590
+ _v7Verdict: "DORMANT",
42591
+ _v7Lift: 1,
42592
+ _v7Recall: 0,
42593
+ _v7FpRate: 0,
42594
+ _v7Precision: 0,
42595
+ _v8Verdict: "DORMANT",
42596
+ _v8Lift: 1,
42597
+ defaultOff: true
42598
+ },
42599
+ "java/raw-type-overuse": {
42600
+ recall: 0,
42601
+ fpRate: 0,
42602
+ ratio: 1,
42603
+ precision: 0,
42604
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42605
+ verdict: "DORMANT",
42606
+ _calibrationNote: "v0.20: new rule (Raw type usage \u2014 use generics). DORMANT until v9 Java corpus calibration.",
42607
+ aiSpecific: true,
42608
+ _v7Verdict: "DORMANT",
42609
+ _v7Lift: 1,
42610
+ _v7Recall: 0,
42611
+ _v7FpRate: 0,
42612
+ _v7Precision: 0,
42613
+ _v8Verdict: "DORMANT",
42614
+ _v8Lift: 1,
42615
+ defaultOff: true
42616
+ },
42617
+ "java/string-concat-loop": {
42618
+ recall: 0,
42619
+ fpRate: 0,
42620
+ ratio: 1,
42621
+ precision: 0,
42622
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42623
+ verdict: "DORMANT",
42624
+ _calibrationNote: "v0.20: new rule (String concat in loop \u2014 use StringBuilder). DORMANT until v9 Java corpus calibration.",
42625
+ aiSpecific: true,
42626
+ _v7Verdict: "DORMANT",
42627
+ _v7Lift: 1,
42628
+ _v7Recall: 0,
42629
+ _v7FpRate: 0,
42630
+ _v7Precision: 0,
42631
+ _v8Verdict: "DORMANT",
42632
+ _v8Lift: 1,
42633
+ defaultOff: true
42634
+ },
42635
+ "java/system-out-println": {
42636
+ recall: 0,
42637
+ fpRate: 0,
42638
+ ratio: 1,
42639
+ precision: 0,
42640
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42641
+ verdict: "DORMANT",
42642
+ _calibrationNote: "v0.20: new rule (System.out.println in production code \u2014 use a logger). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
42643
+ aiSpecific: true,
42644
+ _v7Verdict: "DORMANT",
42645
+ _v7Lift: 1,
42646
+ _v7Recall: 0,
42647
+ _v7FpRate: 0,
42648
+ _v7Precision: 0,
42649
+ _v8Verdict: "DORMANT",
42650
+ _v8Lift: 1,
42651
+ defaultOff: true
42652
+ },
42106
42653
  "layout/forced-layout": {
42107
42654
  recall: 0,
42108
42655
  fpRate: 0,
@@ -42231,7 +42778,7 @@ var signal_strength_default = {
42231
42778
  precision: 0.8889,
42232
42779
  lastCalibratedAt: "2026-07-01T00:00:00Z",
42233
42780
  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).",
42781
+ _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
42782
  aiSpecific: true,
42236
42783
  _v7Verdict: "USEFUL",
42237
42784
  _v7Lift: 80917.5,
@@ -42239,7 +42786,8 @@ var signal_strength_default = {
42239
42786
  _v7FpRate: 0,
42240
42787
  _v7Precision: 0.8824,
42241
42788
  _v8Verdict: "USEFUL",
42242
- _v8Lift: 99999
42789
+ _v8Lift: 99999,
42790
+ defaultOff: false
42243
42791
  },
42244
42792
  "logic/heaps-deviation": {
42245
42793
  recall: 0.0126,
@@ -42275,24 +42823,6 @@ var signal_strength_default = {
42275
42823
  _v8Verdict: "USEFUL",
42276
42824
  _v8Lift: 1760.69
42277
42825
  },
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
42826
  "logic/math-any-density": {
42297
42827
  recall: 17e-4,
42298
42828
  fpRate: 13e-4,
@@ -42658,7 +43188,7 @@ var signal_strength_default = {
42658
43188
  precision: 1,
42659
43189
  lastCalibratedAt: "2026-07-01T00:00:00Z",
42660
43190
  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).",
43191
+ _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
43192
  aiSpecific: true,
42663
43193
  _v7Verdict: "USEFUL",
42664
43194
  _v7Lift: 99999,
@@ -42666,7 +43196,8 @@ var signal_strength_default = {
42666
43196
  _v7FpRate: 0,
42667
43197
  _v7Precision: 1,
42668
43198
  _v8Verdict: "DORMANT",
42669
- _v8Lift: 1
43199
+ _v8Lift: 1,
43200
+ defaultOff: false
42670
43201
  },
42671
43202
  "security/hardcoded-secret": {
42672
43203
  recall: 14e-4,
@@ -42856,6 +43387,96 @@ var signal_strength_default = {
42856
43387
  _v8Verdict: "USEFUL",
42857
43388
  _v8Lift: 651.94
42858
43389
  },
43390
+ "ts/enum-vs-as-const": {
43391
+ recall: 0,
43392
+ fpRate: 0,
43393
+ ratio: 1,
43394
+ precision: 0,
43395
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
43396
+ verdict: "DORMANT",
43397
+ _calibrationNote: "v0.19: new rule (Uses `enum` \u2014 modern TS prefers `as const`). Not yet calibrated. Scheduled for v9 calibration.",
43398
+ aiSpecific: true,
43399
+ _v7Verdict: "DORMANT",
43400
+ _v7Lift: 1,
43401
+ _v7Recall: 0,
43402
+ _v7FpRate: 0,
43403
+ _v7Precision: 0,
43404
+ _v8Verdict: "DORMANT",
43405
+ _v8Lift: 1,
43406
+ defaultOff: true
43407
+ },
43408
+ "ts/excessive-type-assertion": {
43409
+ recall: 0,
43410
+ fpRate: 0,
43411
+ ratio: 1,
43412
+ precision: 0,
43413
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
43414
+ verdict: "DORMANT",
43415
+ _calibrationNote: "v0.19: new rule (Function with >3 `as` assertions \u2014 AI fighting the type system). Not yet calibrated. Scheduled for v9 calibration.",
43416
+ aiSpecific: true,
43417
+ _v7Verdict: "DORMANT",
43418
+ _v7Lift: 1,
43419
+ _v7Recall: 0,
43420
+ _v7FpRate: 0,
43421
+ _v7Precision: 0,
43422
+ _v8Verdict: "DORMANT",
43423
+ _v8Lift: 1,
43424
+ defaultOff: true
43425
+ },
43426
+ "ts/import-type-misuse": {
43427
+ recall: 0,
43428
+ fpRate: 0,
43429
+ ratio: 1,
43430
+ precision: 0,
43431
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
43432
+ verdict: "DORMANT",
43433
+ _calibrationNote: "v0.19: new rule (Inline `import { type X }` \u2014 prefer separate `import type`). Not yet calibrated. Scheduled for v9 calibration.",
43434
+ aiSpecific: true,
43435
+ _v7Verdict: "DORMANT",
43436
+ _v7Lift: 1,
43437
+ _v7Recall: 0,
43438
+ _v7FpRate: 0,
43439
+ _v7Precision: 0,
43440
+ _v8Verdict: "DORMANT",
43441
+ _v8Lift: 1,
43442
+ defaultOff: true
43443
+ },
43444
+ "ts/never-vs-unknown": {
43445
+ recall: 0,
43446
+ fpRate: 0,
43447
+ ratio: 1,
43448
+ precision: 0,
43449
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
43450
+ verdict: "DORMANT",
43451
+ _calibrationNote: "v0.19: new rule (Return type `never` but body has no throw/loop/exit). Not yet calibrated. Scheduled for v9 calibration.",
43452
+ aiSpecific: true,
43453
+ _v7Verdict: "DORMANT",
43454
+ _v7Lift: 1,
43455
+ _v7Recall: 0,
43456
+ _v7FpRate: 0,
43457
+ _v7Precision: 0,
43458
+ _v8Verdict: "DORMANT",
43459
+ _v8Lift: 1,
43460
+ defaultOff: true
43461
+ },
43462
+ "ts/optional-chain-overuse": {
43463
+ recall: 0,
43464
+ fpRate: 0,
43465
+ ratio: 1,
43466
+ precision: 0,
43467
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
43468
+ verdict: "DORMANT",
43469
+ _calibrationNote: "v0.19: new rule (Optional chain depth >= 5 \u2014 AI chains ?. rather than narrowing). Not yet calibrated. Scheduled for v9 calibration.",
43470
+ aiSpecific: true,
43471
+ _v7Verdict: "DORMANT",
43472
+ _v7Lift: 1,
43473
+ _v7Recall: 0,
43474
+ _v7FpRate: 0,
43475
+ _v7Precision: 0,
43476
+ _v8Verdict: "DORMANT",
43477
+ _v8Lift: 1,
43478
+ defaultOff: true
43479
+ },
42859
43480
  "typo/calc-fontsize": {
42860
43481
  recall: 0,
42861
43482
  fpRate: 0,
@@ -43158,7 +43779,7 @@ var signal_strength_default = {
43158
43779
  precision: 0.9774,
43159
43780
  lastCalibratedAt: "2026-07-01T00:00:00Z",
43160
43781
  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).",
43782
+ _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
43783
  aiSpecific: false,
43163
43784
  _v7Verdict: "USEFUL",
43164
43785
  _v7Lift: 59285.01,
@@ -43166,7 +43787,8 @@ var signal_strength_default = {
43166
43787
  _v7FpRate: 0,
43167
43788
  _v7Precision: 0.9697,
43168
43789
  _v8Verdict: "USEFUL",
43169
- _v8Lift: 99999
43790
+ _v8Lift: 99999,
43791
+ defaultOff: false
43170
43792
  },
43171
43793
  "visual/spacing-scale-violation": {
43172
43794
  recall: 86e-4,
@@ -43284,7 +43906,7 @@ function loadSignalStrength() {
43284
43906
  function buildParserCacheConfig(cwd) {
43285
43907
  const envVal = process.env.SLOP_AUDIT_CACHE;
43286
43908
  const enabled = envVal === "1" || envVal === "true";
43287
- const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? (0, import_node_path12.join)(cwd, ".slopbrick", "cache", "ast");
43909
+ const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? (0, import_node_path10.join)(cwd, ".slopbrick", "cache", "ast");
43288
43910
  return { enabled, root };
43289
43911
  }
43290
43912
  function applyRuleOverrides(issues, rules) {
@@ -43302,7 +43924,7 @@ function applyRuleOverrides(issues, rules) {
43302
43924
  }
43303
43925
  async function scanFile(filePath, config, registry, cwd = process.cwd()) {
43304
43926
  const cache = buildParserCacheConfig(cwd);
43305
- const ext = (0, import_node_path11.extname)(filePath).toLowerCase();
43927
+ const ext = (0, import_node_path9.extname)(filePath).toLowerCase();
43306
43928
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
43307
43929
  ".swift",
43308
43930
  ".kt",