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.
@@ -1,419 +1,13 @@
1
1
  var __defProp = Object.defineProperty;
2
- var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __esm = (fn, res) => function __init() {
4
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
- };
6
2
  var __export = (target, all) => {
7
3
  for (var name in all)
8
4
  __defProp(target, name, { get: all[name], enumerable: true });
9
5
  };
10
6
 
11
- // src/engine/parser-rust.ts
12
- import Parser from "tree-sitter";
13
- import Rust from "tree-sitter-rust";
14
- function getRustParser() {
15
- if (cachedParser) return { ok: true, parser: cachedParser };
16
- if (loadError) return { ok: false, error: loadError };
17
- try {
18
- rustLanguage = Rust;
19
- if (!rustLanguage || typeof rustLanguage !== "object") {
20
- throw new Error("tree-sitter-rust: language export is missing or malformed");
21
- }
22
- const parser = new Parser();
23
- parser.setLanguage(rustLanguage);
24
- cachedParser = parser;
25
- return { ok: true, parser };
26
- } catch (err) {
27
- loadError = err instanceof Error ? err : new Error(String(err));
28
- return { ok: false, error: loadError };
29
- }
30
- }
31
- function effectiveParser() {
32
- if (forcedFailure) return { ok: false, error: forcedFailure };
33
- return getRustParser();
34
- }
35
- function parseRust(source) {
36
- const result = effectiveParser();
37
- if (!result.ok) return null;
38
- if (!source || source.trim() === "") return null;
39
- try {
40
- const rawTree = result.parser.parse(source);
41
- if (!rawTree) return null;
42
- const tree = rawTree;
43
- if (!tree || !tree.rootNode) return null;
44
- if (tree.rootNode.type === "ERROR" && tree.rootNode.childCount === 0) {
45
- return null;
46
- }
47
- return tree;
48
- } catch {
49
- return null;
50
- }
51
- }
52
- function isRustParserAvailable() {
53
- const result = getRustParser();
54
- return result.ok;
55
- }
56
- var cachedParser, rustLanguage, loadError, forcedFailure;
57
- var init_parser_rust = __esm({
58
- "src/engine/parser-rust.ts"() {
59
- "use strict";
60
- cachedParser = null;
61
- rustLanguage = null;
62
- loadError = null;
63
- forcedFailure = null;
64
- }
65
- });
66
-
67
- // src/engine/visitors/rust.ts
68
- function parseRustFile(filePath, source, options = {}) {
69
- const empty = {
70
- imports: [],
71
- functions: [],
72
- structs: [],
73
- traits: [],
74
- impls: []
75
- };
76
- if (!isRustParserAvailable() || options.forceFallback) {
77
- return empty;
78
- }
79
- const tree = parseRust(source);
80
- if (!tree) return empty;
81
- return walkRustTree(tree);
82
- }
83
- function walkRustTree(tree) {
84
- const ctx = { inTestConfig: false };
85
- const out = {
86
- imports: [],
87
- functions: [],
88
- structs: [],
89
- traits: [],
90
- impls: []
91
- };
92
- walkChildren(tree.rootNode, ctx, out);
93
- return out;
94
- }
95
- function walkChildren(node, ctx, out) {
96
- for (let i = 0; i < node.namedChildCount; i++) {
97
- const child = node.namedChild(i);
98
- if (!child) continue;
99
- visitNode(child, ctx, out);
100
- }
101
- }
102
- function visitNode(node, ctx, out) {
103
- const innerCtx = { ...ctx };
104
- const attrState = readPrecedingAttributes(node);
105
- switch (node.type) {
106
- case "use_declaration": {
107
- out.imports.push(extractUse(node));
108
- return;
109
- }
110
- case "function_item": {
111
- const isMethod = isInImplBlock(node);
112
- out.functions.push(extractFunction(node, attrState, innerCtx.inTestConfig, isMethod));
113
- walkChildren(getField(node, "body") ?? node, innerCtx, out);
114
- return;
115
- }
116
- case "struct_item": {
117
- out.structs.push(extractStruct(node, attrState));
118
- const body = getField(node, "body");
119
- if (body) walkChildren(body, innerCtx, out);
120
- return;
121
- }
122
- case "trait_item": {
123
- out.traits.push(extractTrait(node, attrState));
124
- const body = getField(node, "body");
125
- if (body) walkChildren(body, innerCtx, out);
126
- return;
127
- }
128
- case "impl_item": {
129
- const implEntry = extractImpl(node, attrState, innerCtx);
130
- out.impls.push(implEntry.entry);
131
- const body = getField(node, "body");
132
- if (body) walkChildren(body, innerCtx, out);
133
- return;
134
- }
135
- case "mod_item": {
136
- const modIsTest = innerCtx.inTestConfig || attrState.isTestCfg;
137
- const modCtx = { ...innerCtx, inTestConfig: modIsTest };
138
- const body = getField(node, "body");
139
- if (body) walkChildren(body, modCtx, out);
140
- return;
141
- }
142
- default: {
143
- for (let i = 0; i < node.namedChildCount; i++) {
144
- const child = node.namedChild(i);
145
- if (child) visitNode(child, innerCtx, out);
146
- }
147
- }
148
- }
149
- }
150
- function readPrecedingAttributes(node) {
151
- const state = {
152
- isTestCfg: false,
153
- isTest: false,
154
- isPub: false,
155
- derives: []
156
- };
157
- const parent = node.parent;
158
- if (!parent) return state;
159
- for (let i = 0; i < parent.namedChildCount; i++) {
160
- const sibling = parent.namedChild(i);
161
- if (!sibling || sibling === node) continue;
162
- if (sibling.endIndex > node.startIndex) continue;
163
- if (sibling.type !== "attribute_item") continue;
164
- decodeAttribute(sibling, state);
165
- }
166
- return state;
167
- }
168
- function decodeAttribute(attr, state) {
169
- for (let i = 0; i < attr.namedChildCount; i++) {
170
- const child = attr.namedChild(i);
171
- if (!child || child.type !== "attribute") continue;
172
- const name = firstIdentifier(child);
173
- if (!name) continue;
174
- if (name === "cfg") {
175
- const cfgText = child.text;
176
- if (/\btest\b/.test(cfgText) && !/\bnot\(test\)/.test(cfgText)) {
177
- state.isTestCfg = true;
178
- }
179
- } else if (name === "test") {
180
- state.isTest = true;
181
- } else if (name === "derive") {
182
- for (let j = 0; j < child.namedChildCount; j++) {
183
- const inner = child.namedChild(j);
184
- if (inner) {
185
- const matches = inner.text.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g);
186
- for (const m of matches) state.derives.push(m[1]);
187
- }
188
- }
189
- }
190
- }
191
- }
192
- function firstIdentifier(node) {
193
- const text = node.text;
194
- const idMatch = text.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
195
- return idMatch ? idMatch[1] : null;
196
- }
197
- function extractUse(node) {
198
- const text = node.text;
199
- const argument = node.namedChild(0);
200
- const argField = node.childForFieldName("argument");
201
- const path = argField?.text ?? argument?.text ?? "";
202
- const names = [];
203
- let isGlob = false;
204
- const useListNode = findUseListNode(argument);
205
- if (useListNode) {
206
- for (let i = 0; i < useListNode.namedChildCount; i++) {
207
- const item = useListNode.namedChild(i);
208
- if (item.type === "use_wildcard") {
209
- isGlob = true;
210
- } else if (item.type === "identifier") {
211
- names.push({ name: item.text });
212
- } else if (item.type === "use_as_clause") {
213
- const alias = item.childForFieldName("alias");
214
- const binding = item.childForFieldName("path");
215
- if (binding) {
216
- names.push({ name: binding.text, alias: alias?.text });
217
- } else {
218
- names.push({ name: item.text });
219
- }
220
- } else if (item.type === "scoped_identifier") {
221
- const idents = collectIdentifiers(item);
222
- names.push({ name: idents[idents.length - 1] ?? item.text });
223
- } else if (item.text.includes(" as ")) {
224
- const [head, alias] = item.text.split(/\s+as\s+/);
225
- names.push({ name: (head ?? "").trim(), alias: alias?.trim() });
226
- } else {
227
- names.push({ name: item.text });
228
- }
229
- }
230
- } else if (argument?.type === "use_wildcard") {
231
- isGlob = true;
232
- } else if (argument) {
233
- const idents = collectIdentifiers(argument);
234
- const last = idents[idents.length - 1] ?? argument.text;
235
- names.push({ name: last });
236
- const asMatch = text.match(/\s+as\s+(\w+)\s*;?\s*$/);
237
- if (asMatch) names[0].alias = asMatch[1];
238
- }
239
- return {
240
- path: path.trim(),
241
- names,
242
- isGlob,
243
- line: node.startPosition.row + 1,
244
- column: node.startPosition.column
245
- };
246
- }
247
- function extractFunction(node, attrs, inheritedTestConfig, isMethod) {
248
- const nameNode = node.childForFieldName("name");
249
- const name = nameNode?.text ?? "<anon>";
250
- const params = node.childForFieldName("parameters");
251
- const body = node.childForFieldName("body");
252
- const visibilityMod = node.namedChild(0);
253
- const isPublic = visibilityMod?.type === "visibility_modifier";
254
- let receiver;
255
- if (isMethod && params) {
256
- const selfParam = findSelfParameter(params);
257
- if (selfParam) receiver = selfParam.text;
258
- }
259
- const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
260
- const inTestConfig = inheritedTestConfig || attrs.isTest || attrs.isTestCfg || isFunctionAttrTest(node);
261
- return {
262
- name,
263
- line: node.startPosition.row + 1,
264
- column: node.startPosition.column,
265
- isPublic,
266
- isMethod,
267
- receiver,
268
- bodyLines,
269
- inTestConfig
270
- };
271
- }
272
- function extractStruct(node, attrs) {
273
- const nameNode = node.childForFieldName("name");
274
- return {
275
- name: nameNode?.text ?? "<anon>",
276
- line: node.startPosition.row + 1,
277
- column: node.startPosition.column,
278
- isPublic: hasVisibility(node),
279
- isDerive: attrs.derives.length > 0,
280
- derives: [...attrs.derives]
281
- };
282
- }
283
- function extractTrait(node, _attrs) {
284
- const nameNode = node.childForFieldName("name");
285
- return {
286
- name: nameNode?.text ?? "<anon>",
287
- line: node.startPosition.row + 1,
288
- column: node.startPosition.column,
289
- isPublic: hasVisibility(node)
290
- };
291
- }
292
- function extractImpl(node, _attrs, _ctx) {
293
- const traitNode = node.childForFieldName("trait");
294
- const typeNode = node.childForFieldName("type");
295
- const typeText = typeNode?.text ?? "<anon>";
296
- const traitText = traitNode?.text;
297
- const body = node.childForFieldName("body");
298
- const methods = [];
299
- if (body) {
300
- for (let i = 0; i < body.namedChildCount; i++) {
301
- const child = body.namedChild(i);
302
- if (child?.type === "function_item") {
303
- const m = child.childForFieldName("name");
304
- if (m) methods.push(m.text);
305
- }
306
- }
307
- }
308
- return {
309
- entry: {
310
- type: typeText,
311
- trait: traitText ?? void 0,
312
- methods,
313
- line: node.startPosition.row + 1,
314
- column: node.startPosition.column
315
- }
316
- };
317
- }
318
- function findUseListNode(argument) {
319
- if (!argument) return null;
320
- if (argument.type === "use_list") return argument;
321
- if (argument.type === "scoped_use_list") {
322
- for (let i = 0; i < argument.namedChildCount; i++) {
323
- const c = argument.namedChild(i);
324
- if (c?.type === "use_list") return c;
325
- }
326
- }
327
- return null;
328
- }
329
- function getField(node, fieldName) {
330
- return node.childForFieldName(fieldName);
331
- }
332
- function collectIdentifiers(node) {
333
- const out = [];
334
- for (let i = 0; i < node.namedChildCount; i++) {
335
- const child = node.namedChild(i);
336
- if (!child) continue;
337
- if (child.type === "identifier" || child.type === "type_identifier") {
338
- out.push(child.text);
339
- } else if (child.type === "scoped_identifier" || child.type === "nested_identifier") {
340
- out.push(...collectIdentifiers(child));
341
- }
342
- }
343
- return out;
344
- }
345
- function hasVisibility(node) {
346
- return node.namedChild(0)?.type === "visibility_modifier";
347
- }
348
- function isInImplBlock(node) {
349
- for (let p = node.parent; p; p = p.parent) {
350
- if (p.type === "impl_item") return true;
351
- if (p.type === "function_item" || p.type === "source_file") return false;
352
- }
353
- return false;
354
- }
355
- function findSelfParameter(params) {
356
- for (let i = 0; i < params.namedChildCount; i++) {
357
- const child = params.namedChild(i);
358
- if (child?.type === "self_parameter") return child;
359
- }
360
- return null;
361
- }
362
- function isFunctionAttrTest(node) {
363
- void node;
364
- return false;
365
- }
366
- var SERVICE_SUFFIXES, SERVICE_SUFFIX_GROUP, RUST_SERVICE_STRUCT_RE, RUST_SERVICE_IMPL_RE;
367
- var init_rust = __esm({
368
- "src/engine/visitors/rust.ts"() {
369
- "use strict";
370
- init_parser_rust();
371
- SERVICE_SUFFIXES = [
372
- "Service",
373
- "Manager",
374
- "Handler",
375
- "Repository",
376
- "Controller",
377
- "Helper",
378
- "Factory",
379
- "Provider",
380
- "Store",
381
- "API",
382
- "Client",
383
- "Adapter",
384
- "Resolver",
385
- "Mapper",
386
- "Transformer",
387
- "Serializer",
388
- "Validator",
389
- "Strategy",
390
- "Facade",
391
- "Decorator",
392
- "Observer",
393
- "Builder",
394
- "Command",
395
- "Processor",
396
- "Worker",
397
- "Job",
398
- "Actor",
399
- "Executor"
400
- ];
401
- SERVICE_SUFFIX_GROUP = `(?:${SERVICE_SUFFIXES.join("|")})`;
402
- RUST_SERVICE_STRUCT_RE = new RegExp(
403
- `^(?:pub(?:\\(crate\\)|\\(super\\))?\\s+)?struct\\s+(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
404
- "gm"
405
- );
406
- RUST_SERVICE_IMPL_RE = new RegExp(
407
- `^impl(?:<[^>]+>)?\\s+(?:${SERVICE_SUFFIX_GROUP}\\s+for\\s+)?(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
408
- "gm"
409
- );
410
- }
411
- });
412
-
413
7
  // src/engine/worker.ts
414
8
  import { isMainThread, parentPort, workerData } from "worker_threads";
415
- import { extname as extname6 } from "path";
416
- import { join as join12 } from "path";
9
+ import { extname as extname4 } from "path";
10
+ import { join as join11 } from "path";
417
11
 
418
12
  // ../engine/dist/index.js
419
13
  import { readFile, writeFile, mkdir, access } from "fs/promises";
@@ -5435,76 +5029,6 @@ function tokenizeIdentifiers(source) {
5435
5029
  return tokens;
5436
5030
  }
5437
5031
  var SQRT_2_TIMES_LN_2 = Math.sqrt(2 * Math.LN2);
5438
- function ecdfAt(sortedSamples, x) {
5439
- let lo = 0;
5440
- let hi = sortedSamples.length;
5441
- while (lo < hi) {
5442
- const mid = lo + hi >>> 1;
5443
- if (sortedSamples[mid] <= x) lo = mid + 1;
5444
- else hi = mid;
5445
- }
5446
- return lo / sortedSamples.length;
5447
- }
5448
- function ksStatistic(sampleA, sampleB) {
5449
- if (sampleA.length === 0 || sampleB.length === 0) return 1;
5450
- const sortedA = [...sampleA].sort((a, b) => a - b);
5451
- const sortedB = [...sampleB].sort((a, b) => a - b);
5452
- const allPoints = [...sortedA, ...sortedB].sort((a, b) => a - b);
5453
- let maxDiff = 0;
5454
- for (const x of allPoints) {
5455
- const fa = ecdfAt(sortedA, x);
5456
- const fb = ecdfAt(sortedB, x);
5457
- const diff = Math.abs(fa - fb);
5458
- if (diff > maxDiff) maxDiff = diff;
5459
- }
5460
- return maxDiff;
5461
- }
5462
- function ksPValue(statistic, n, m) {
5463
- if (n === 0 || m === 0) return 1;
5464
- if (statistic < 0) return 1;
5465
- if (statistic > 1) return 0;
5466
- const lambda = Math.sqrt(n * m / (n + m)) * statistic;
5467
- if (lambda > 3.6) return 0;
5468
- let p = 0;
5469
- for (let j = 1; j < 1e3; j++) {
5470
- const term = 2 * Math.pow(-1, j - 1) * Math.exp(-2 * j * j * lambda * lambda);
5471
- p += term;
5472
- if (Math.abs(term) < 1e-15) break;
5473
- }
5474
- return Math.max(0, Math.min(1, p));
5475
- }
5476
- function ksTest(sampleA, sampleB, alpha = 0.05) {
5477
- const statistic = ksStatistic(sampleA, sampleB);
5478
- const pValue = ksPValue(statistic, sampleA.length, sampleB.length);
5479
- return {
5480
- statistic,
5481
- pValue,
5482
- significant: pValue < alpha,
5483
- n: sampleA.length,
5484
- m: sampleB.length
5485
- };
5486
- }
5487
- function multiFeatureKsTest(features, baselines, alpha = 0.05) {
5488
- const featureNames = [...features.keys()];
5489
- const k = featureNames.length;
5490
- const bonferroniAlpha = k > 0 ? alpha / k : alpha;
5491
- const perFeature = /* @__PURE__ */ new Map();
5492
- const significantFeatures = [];
5493
- for (const name of featureNames) {
5494
- const sample = features.get(name);
5495
- const baseline = baselines.get(name);
5496
- if (!sample || !baseline) continue;
5497
- const result = ksTest(sample, baseline, bonferroniAlpha);
5498
- perFeature.set(name, result);
5499
- if (result.significant) significantFeatures.push(name);
5500
- }
5501
- return {
5502
- perFeature,
5503
- bonferroniAlpha,
5504
- anySignificant: significantFeatures.length > 0,
5505
- significantFeatures
5506
- };
5507
- }
5508
5032
 
5509
5033
  // src/engine/visitors/react.ts
5510
5034
  function isObject(node) {
@@ -7304,8 +6828,396 @@ function dispatchNode(node, parent, path, vctx) {
7304
6828
  return false;
7305
6829
  }
7306
6830
 
6831
+ // src/engine/parser-rust.ts
6832
+ import Parser from "tree-sitter";
6833
+ import Rust from "tree-sitter-rust";
6834
+ var cachedParser = null;
6835
+ var rustLanguage = null;
6836
+ var loadError = null;
6837
+ function getRustParser() {
6838
+ if (cachedParser) return { ok: true, parser: cachedParser };
6839
+ if (loadError) return { ok: false, error: loadError };
6840
+ try {
6841
+ rustLanguage = Rust;
6842
+ if (!rustLanguage || typeof rustLanguage !== "object") {
6843
+ throw new Error("tree-sitter-rust: language export is missing or malformed");
6844
+ }
6845
+ const parser = new Parser();
6846
+ parser.setLanguage(rustLanguage);
6847
+ cachedParser = parser;
6848
+ return { ok: true, parser };
6849
+ } catch (err) {
6850
+ loadError = err instanceof Error ? err : new Error(String(err));
6851
+ return { ok: false, error: loadError };
6852
+ }
6853
+ }
6854
+ var forcedFailure = null;
6855
+ function effectiveParser() {
6856
+ if (forcedFailure) return { ok: false, error: forcedFailure };
6857
+ return getRustParser();
6858
+ }
6859
+ function parseRust(source) {
6860
+ const result = effectiveParser();
6861
+ if (!result.ok) return null;
6862
+ if (!source || source.trim() === "") return null;
6863
+ try {
6864
+ const rawTree = result.parser.parse(source);
6865
+ if (!rawTree) return null;
6866
+ const tree = rawTree;
6867
+ if (!tree || !tree.rootNode) return null;
6868
+ if (tree.rootNode.type === "ERROR" && tree.rootNode.childCount === 0) {
6869
+ return null;
6870
+ }
6871
+ return tree;
6872
+ } catch {
6873
+ return null;
6874
+ }
6875
+ }
6876
+ function isRustParserAvailable() {
6877
+ const result = getRustParser();
6878
+ return result.ok;
6879
+ }
6880
+
6881
+ // src/engine/visitors/rust.ts
6882
+ var SERVICE_SUFFIXES = [
6883
+ "Service",
6884
+ "Manager",
6885
+ "Handler",
6886
+ "Repository",
6887
+ "Controller",
6888
+ "Helper",
6889
+ "Factory",
6890
+ "Provider",
6891
+ "Store",
6892
+ "API",
6893
+ "Client",
6894
+ "Adapter",
6895
+ "Resolver",
6896
+ "Mapper",
6897
+ "Transformer",
6898
+ "Serializer",
6899
+ "Validator",
6900
+ "Strategy",
6901
+ "Facade",
6902
+ "Decorator",
6903
+ "Observer",
6904
+ "Builder",
6905
+ "Command",
6906
+ "Processor",
6907
+ "Worker",
6908
+ "Job",
6909
+ "Actor",
6910
+ "Executor"
6911
+ ];
6912
+ var SERVICE_SUFFIX_GROUP = `(?:${SERVICE_SUFFIXES.join("|")})`;
6913
+ var RUST_SERVICE_STRUCT_RE = new RegExp(
6914
+ `^(?:pub(?:\\(crate\\)|\\(super\\))?\\s+)?struct\\s+(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
6915
+ "gm"
6916
+ );
6917
+ var RUST_SERVICE_IMPL_RE = new RegExp(
6918
+ `^impl(?:<[^>]+>)?\\s+(?:${SERVICE_SUFFIX_GROUP}\\s+for\\s+)?(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
6919
+ "gm"
6920
+ );
6921
+ function parseRustFile(filePath, source, options = {}) {
6922
+ const empty = {
6923
+ imports: [],
6924
+ functions: [],
6925
+ structs: [],
6926
+ traits: [],
6927
+ impls: []
6928
+ };
6929
+ if (!isRustParserAvailable() || options.forceFallback) {
6930
+ return empty;
6931
+ }
6932
+ const tree = parseRust(source);
6933
+ if (!tree) return empty;
6934
+ return walkRustTree(tree);
6935
+ }
6936
+ function walkRustTree(tree) {
6937
+ const ctx = { inTestConfig: false };
6938
+ const out = {
6939
+ imports: [],
6940
+ functions: [],
6941
+ structs: [],
6942
+ traits: [],
6943
+ impls: []
6944
+ };
6945
+ walkChildren(tree.rootNode, ctx, out);
6946
+ return out;
6947
+ }
6948
+ function walkChildren(node, ctx, out) {
6949
+ for (let i = 0; i < node.namedChildCount; i++) {
6950
+ const child = node.namedChild(i);
6951
+ if (!child) continue;
6952
+ visitNode(child, ctx, out);
6953
+ }
6954
+ }
6955
+ function visitNode(node, ctx, out) {
6956
+ const innerCtx = { ...ctx };
6957
+ const attrState = readPrecedingAttributes(node);
6958
+ switch (node.type) {
6959
+ case "use_declaration": {
6960
+ out.imports.push(extractUse(node));
6961
+ return;
6962
+ }
6963
+ case "function_item": {
6964
+ const isMethod = isInImplBlock(node);
6965
+ out.functions.push(extractFunction(node, attrState, innerCtx.inTestConfig, isMethod));
6966
+ walkChildren(getField(node, "body") ?? node, innerCtx, out);
6967
+ return;
6968
+ }
6969
+ case "struct_item": {
6970
+ out.structs.push(extractStruct(node, attrState));
6971
+ const body = getField(node, "body");
6972
+ if (body) walkChildren(body, innerCtx, out);
6973
+ return;
6974
+ }
6975
+ case "trait_item": {
6976
+ out.traits.push(extractTrait(node, attrState));
6977
+ const body = getField(node, "body");
6978
+ if (body) walkChildren(body, innerCtx, out);
6979
+ return;
6980
+ }
6981
+ case "impl_item": {
6982
+ const implEntry = extractImpl(node, attrState, innerCtx);
6983
+ out.impls.push(implEntry.entry);
6984
+ const body = getField(node, "body");
6985
+ if (body) walkChildren(body, innerCtx, out);
6986
+ return;
6987
+ }
6988
+ case "mod_item": {
6989
+ const modIsTest = innerCtx.inTestConfig || attrState.isTestCfg;
6990
+ const modCtx = { ...innerCtx, inTestConfig: modIsTest };
6991
+ const body = getField(node, "body");
6992
+ if (body) walkChildren(body, modCtx, out);
6993
+ return;
6994
+ }
6995
+ default: {
6996
+ for (let i = 0; i < node.namedChildCount; i++) {
6997
+ const child = node.namedChild(i);
6998
+ if (child) visitNode(child, innerCtx, out);
6999
+ }
7000
+ }
7001
+ }
7002
+ }
7003
+ function readPrecedingAttributes(node) {
7004
+ const state = {
7005
+ isTestCfg: false,
7006
+ isTest: false,
7007
+ isPub: false,
7008
+ derives: []
7009
+ };
7010
+ const parent = node.parent;
7011
+ if (!parent) return state;
7012
+ for (let i = 0; i < parent.namedChildCount; i++) {
7013
+ const sibling = parent.namedChild(i);
7014
+ if (!sibling || sibling === node) continue;
7015
+ if (sibling.endIndex > node.startIndex) continue;
7016
+ if (sibling.type !== "attribute_item") continue;
7017
+ decodeAttribute(sibling, state);
7018
+ }
7019
+ return state;
7020
+ }
7021
+ function decodeAttribute(attr, state) {
7022
+ for (let i = 0; i < attr.namedChildCount; i++) {
7023
+ const child = attr.namedChild(i);
7024
+ if (!child || child.type !== "attribute") continue;
7025
+ const name = firstIdentifier(child);
7026
+ if (!name) continue;
7027
+ if (name === "cfg") {
7028
+ const cfgText = child.text;
7029
+ if (/\btest\b/.test(cfgText) && !/\bnot\(test\)/.test(cfgText)) {
7030
+ state.isTestCfg = true;
7031
+ }
7032
+ } else if (name === "test") {
7033
+ state.isTest = true;
7034
+ } else if (name === "derive") {
7035
+ for (let j = 0; j < child.namedChildCount; j++) {
7036
+ const inner = child.namedChild(j);
7037
+ if (inner) {
7038
+ const matches = inner.text.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g);
7039
+ for (const m of matches) state.derives.push(m[1]);
7040
+ }
7041
+ }
7042
+ }
7043
+ }
7044
+ }
7045
+ function firstIdentifier(node) {
7046
+ const text = node.text;
7047
+ const idMatch = text.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
7048
+ return idMatch ? idMatch[1] : null;
7049
+ }
7050
+ function extractUse(node) {
7051
+ const text = node.text;
7052
+ const argument = node.namedChild(0);
7053
+ const argField = node.childForFieldName("argument");
7054
+ const path = argField?.text ?? argument?.text ?? "";
7055
+ const names = [];
7056
+ let isGlob = false;
7057
+ const useListNode = findUseListNode(argument);
7058
+ if (useListNode) {
7059
+ for (let i = 0; i < useListNode.namedChildCount; i++) {
7060
+ const item = useListNode.namedChild(i);
7061
+ if (item.type === "use_wildcard") {
7062
+ isGlob = true;
7063
+ } else if (item.type === "identifier") {
7064
+ names.push({ name: item.text });
7065
+ } else if (item.type === "use_as_clause") {
7066
+ const alias = item.childForFieldName("alias");
7067
+ const binding = item.childForFieldName("path");
7068
+ if (binding) {
7069
+ names.push({ name: binding.text, alias: alias?.text });
7070
+ } else {
7071
+ names.push({ name: item.text });
7072
+ }
7073
+ } else if (item.type === "scoped_identifier") {
7074
+ const idents = collectIdentifiers(item);
7075
+ names.push({ name: idents[idents.length - 1] ?? item.text });
7076
+ } else if (item.text.includes(" as ")) {
7077
+ const [head, alias] = item.text.split(/\s+as\s+/);
7078
+ names.push({ name: (head ?? "").trim(), alias: alias?.trim() });
7079
+ } else {
7080
+ names.push({ name: item.text });
7081
+ }
7082
+ }
7083
+ } else if (argument?.type === "use_wildcard") {
7084
+ isGlob = true;
7085
+ } else if (argument) {
7086
+ const idents = collectIdentifiers(argument);
7087
+ const last = idents[idents.length - 1] ?? argument.text;
7088
+ names.push({ name: last });
7089
+ const asMatch = text.match(/\s+as\s+(\w+)\s*;?\s*$/);
7090
+ if (asMatch) names[0].alias = asMatch[1];
7091
+ }
7092
+ return {
7093
+ path: path.trim(),
7094
+ names,
7095
+ isGlob,
7096
+ line: node.startPosition.row + 1,
7097
+ column: node.startPosition.column
7098
+ };
7099
+ }
7100
+ function extractFunction(node, attrs, inheritedTestConfig, isMethod) {
7101
+ const nameNode = node.childForFieldName("name");
7102
+ const name = nameNode?.text ?? "<anon>";
7103
+ const params = node.childForFieldName("parameters");
7104
+ const body = node.childForFieldName("body");
7105
+ const visibilityMod = node.namedChild(0);
7106
+ const isPublic = visibilityMod?.type === "visibility_modifier";
7107
+ let receiver;
7108
+ if (isMethod && params) {
7109
+ const selfParam = findSelfParameter(params);
7110
+ if (selfParam) receiver = selfParam.text;
7111
+ }
7112
+ const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
7113
+ const inTestConfig = inheritedTestConfig || attrs.isTest || attrs.isTestCfg || isFunctionAttrTest(node);
7114
+ return {
7115
+ name,
7116
+ line: node.startPosition.row + 1,
7117
+ column: node.startPosition.column,
7118
+ isPublic,
7119
+ isMethod,
7120
+ receiver,
7121
+ bodyLines,
7122
+ inTestConfig
7123
+ };
7124
+ }
7125
+ function extractStruct(node, attrs) {
7126
+ const nameNode = node.childForFieldName("name");
7127
+ return {
7128
+ name: nameNode?.text ?? "<anon>",
7129
+ line: node.startPosition.row + 1,
7130
+ column: node.startPosition.column,
7131
+ isPublic: hasVisibility(node),
7132
+ isDerive: attrs.derives.length > 0,
7133
+ derives: [...attrs.derives]
7134
+ };
7135
+ }
7136
+ function extractTrait(node, _attrs) {
7137
+ const nameNode = node.childForFieldName("name");
7138
+ return {
7139
+ name: nameNode?.text ?? "<anon>",
7140
+ line: node.startPosition.row + 1,
7141
+ column: node.startPosition.column,
7142
+ isPublic: hasVisibility(node)
7143
+ };
7144
+ }
7145
+ function extractImpl(node, _attrs, _ctx) {
7146
+ const traitNode = node.childForFieldName("trait");
7147
+ const typeNode = node.childForFieldName("type");
7148
+ const typeText = typeNode?.text ?? "<anon>";
7149
+ const traitText = traitNode?.text;
7150
+ const body = node.childForFieldName("body");
7151
+ const methods = [];
7152
+ if (body) {
7153
+ for (let i = 0; i < body.namedChildCount; i++) {
7154
+ const child = body.namedChild(i);
7155
+ if (child?.type === "function_item") {
7156
+ const m = child.childForFieldName("name");
7157
+ if (m) methods.push(m.text);
7158
+ }
7159
+ }
7160
+ }
7161
+ return {
7162
+ entry: {
7163
+ type: typeText,
7164
+ trait: traitText ?? void 0,
7165
+ methods,
7166
+ line: node.startPosition.row + 1,
7167
+ column: node.startPosition.column
7168
+ }
7169
+ };
7170
+ }
7171
+ function findUseListNode(argument) {
7172
+ if (!argument) return null;
7173
+ if (argument.type === "use_list") return argument;
7174
+ if (argument.type === "scoped_use_list") {
7175
+ for (let i = 0; i < argument.namedChildCount; i++) {
7176
+ const c = argument.namedChild(i);
7177
+ if (c?.type === "use_list") return c;
7178
+ }
7179
+ }
7180
+ return null;
7181
+ }
7182
+ function getField(node, fieldName) {
7183
+ return node.childForFieldName(fieldName);
7184
+ }
7185
+ function collectIdentifiers(node) {
7186
+ const out = [];
7187
+ for (let i = 0; i < node.namedChildCount; i++) {
7188
+ const child = node.namedChild(i);
7189
+ if (!child) continue;
7190
+ if (child.type === "identifier" || child.type === "type_identifier") {
7191
+ out.push(child.text);
7192
+ } else if (child.type === "scoped_identifier" || child.type === "nested_identifier") {
7193
+ out.push(...collectIdentifiers(child));
7194
+ }
7195
+ }
7196
+ return out;
7197
+ }
7198
+ function hasVisibility(node) {
7199
+ return node.namedChild(0)?.type === "visibility_modifier";
7200
+ }
7201
+ function isInImplBlock(node) {
7202
+ for (let p = node.parent; p; p = p.parent) {
7203
+ if (p.type === "impl_item") return true;
7204
+ if (p.type === "function_item" || p.type === "source_file") return false;
7205
+ }
7206
+ return false;
7207
+ }
7208
+ function findSelfParameter(params) {
7209
+ for (let i = 0; i < params.namedChildCount; i++) {
7210
+ const child = params.namedChild(i);
7211
+ if (child?.type === "self_parameter") return child;
7212
+ }
7213
+ return null;
7214
+ }
7215
+ function isFunctionAttrTest(node) {
7216
+ void node;
7217
+ return false;
7218
+ }
7219
+
7307
7220
  // src/engine/visitors/v2-build.ts
7308
- init_rust();
7309
7221
  var TAILWIND_COLOR_RE = /^(?:bg|text|border|ring|from|to|via|fill|stroke)-([a-z]+-\d+|white|black|transparent|current|\[.+?\])$/;
7310
7222
  var TAILWIND_SPACING_RE = /^(?:[pm][xytrbl]?|gap|space-[xy])-(\d+(?:\.\d+)?)$/;
7311
7223
  var TAILWIND_RADIUS_RE = /^(?:rounded(?:-[a-z]+)?)-(.+)$/;
@@ -35149,125 +35061,17 @@ var unusedParameterRule = createRule({
35149
35061
  });
35150
35062
 
35151
35063
  // src/rules/docs/broken-link.ts
35152
- import { existsSync as existsSync4 } from "fs";
35153
- import { dirname as dirname4, join as join7, resolve as resolve2 } from "path";
35064
+ import { existsSync as existsSync3 } from "fs";
35065
+ import { dirname as dirname3, join as join5, resolve } from "path";
35154
35066
 
35155
35067
  // src/engine/doc-freshness.ts
35156
- import { readFileSync as readFileSync5, existsSync as existsSync3 } from "fs";
35157
- import { join as join6, dirname as dirname3, relative as relative2 } from "path";
35158
- import { globby as globby2 } from "globby";
35159
-
35160
- // src/mcp/patterns.ts
35161
- import { readFileSync as readFileSync3 } from "fs";
35162
- import { basename as basename2, extname as extname2 } from "path";
35163
-
35164
- // src/engine/discover.ts
35068
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
35069
+ import { join as join4, dirname as dirname2, relative } from "path";
35165
35070
  import { globby } from "globby";
35166
- import { minimatch } from "minimatch";
35167
- import { resolve, extname, relative, sep, basename, dirname as dirname2, join as join3 } from "path";
35168
- import { readFileSync } from "fs";
35169
- var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte", ".astro", ".html"]);
35170
- var BACKEND_EXTENSIONS = /* @__PURE__ */ new Set([
35171
- ".py",
35172
- ".go",
35173
- // v0.14.0
35174
- ".swift",
35175
- ".kt",
35176
- ".kts",
35177
- ".dart",
35178
- ".rs",
35179
- ".cpp",
35180
- ".cc",
35181
- ".cxx",
35182
- ".c",
35183
- ".h",
35184
- ".hpp",
35185
- ".hxx",
35186
- ".java",
35187
- ".rb",
35188
- ".php"
35189
- ]);
35190
- var ALL_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
35191
- ...SOURCE_EXTENSIONS,
35192
- ...BACKEND_EXTENSIONS
35193
- ]);
35194
-
35195
- // src/config/conventions.ts
35196
- import { existsSync, readFileSync as readFileSync2 } from "fs";
35197
- import { join as join4 } from "path";
35198
-
35199
- // src/mcp/patterns.ts
35200
- 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;
35201
- var DYNAMIC_IMPORT_RE = /import\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
35202
- var COMMONJS_REQUIRE_RE = /require\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
35203
- function extractImports(source) {
35204
- const seen = /* @__PURE__ */ new Set();
35205
- const out = [];
35206
- const push = (spec) => {
35207
- if (spec.startsWith(".") || spec.startsWith("/")) return;
35208
- if (seen.has(spec)) return;
35209
- seen.add(spec);
35210
- out.push(spec);
35211
- };
35212
- for (const re of [ESM_IMPORT_RE, DYNAMIC_IMPORT_RE, COMMONJS_REQUIRE_RE]) {
35213
- re.lastIndex = 0;
35214
- let m;
35215
- while ((m = re.exec(source)) !== null) {
35216
- push(m[2]);
35217
- }
35218
- }
35219
- return out;
35220
- }
35221
-
35222
- // src/rules/docs/expired-code-example.ts
35223
- var CODE_LANGS = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "javascript", "typescript"]);
35224
- function stripSubpath(spec) {
35225
- if (spec.startsWith("@")) return spec.split("/").slice(0, 2).join("/");
35226
- return spec.split("/")[0] ?? spec;
35227
- }
35228
- var expiredCodeExampleRule = createRule({
35229
- id: "docs/expired-code-example",
35230
- category: "docs",
35231
- severity: "medium",
35232
- aiSpecific: false,
35233
- description: "A fenced code example imports a package that is not declared in package.json.",
35234
- create(context) {
35235
- return { ...context, packages: declaredPackages(context.cwd) };
35236
- },
35237
- analyze(context, facts) {
35238
- const issues = [];
35239
- const source = facts.v2?._source;
35240
- if (!source) return issues;
35241
- const packages = declaredPackages(context.cwd);
35242
- const packageName = context.packageName;
35243
- if (packageName) packages.add(packageName);
35244
- const blocks = extractFencedCodeBlocks(source);
35245
- for (const block of blocks) {
35246
- if (!CODE_LANGS.has(block.lang)) continue;
35247
- if (block.body.split("\n").length < 2) continue;
35248
- const imports = extractImports(block.body);
35249
- for (const imp of imports) {
35250
- const pkgName = stripSubpath(imp);
35251
- if (packages.has(pkgName)) continue;
35252
- issues.push({
35253
- ruleId: "docs/expired-code-example",
35254
- category: "docs",
35255
- severity: "medium",
35256
- aiSpecific: false,
35257
- message: `Code example imports \`${imp}\` but \`${pkgName}\` is not in package.json.`,
35258
- line: block.line,
35259
- column: block.column,
35260
- advice: `Add \`${pkgName}\` to package.json or update the example.`
35261
- });
35262
- }
35263
- }
35264
- return issues;
35265
- }
35266
- });
35267
35071
 
35268
35072
  // src/rules/docs/stale-function-reference.ts
35269
- import { readFileSync as readFileSync4, readdirSync, existsSync as existsSync2 } from "fs";
35270
- import { join as join5, extname as extname3 } from "path";
35073
+ import { readFileSync, readdirSync, existsSync } from "fs";
35074
+ import { join as join3, extname } from "path";
35271
35075
  var RESERVED = /* @__PURE__ */ new Set([
35272
35076
  // JS reserved words
35273
35077
  "true",
@@ -35840,7 +35644,7 @@ var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs",
35840
35644
  var SOURCE_ROOTS = ["src", "lib", "app", "components"];
35841
35645
  var CAP = 200;
35842
35646
  function walk(dir, out, cap) {
35843
- if (!existsSync2(dir) || out.length >= cap) return;
35647
+ if (!existsSync(dir) || out.length >= cap) return;
35844
35648
  let entries;
35845
35649
  try {
35846
35650
  entries = readdirSync(dir, { withFileTypes: true });
@@ -35850,19 +35654,19 @@ function walk(dir, out, cap) {
35850
35654
  for (const entry of entries) {
35851
35655
  if (out.length >= cap) return;
35852
35656
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
35853
- const full = join5(dir, entry.name);
35657
+ const full = join3(dir, entry.name);
35854
35658
  if (entry.isDirectory()) walk(full, out, cap);
35855
- else if (entry.isFile() && SOURCE_EXTS.has(extname3(entry.name))) out.push(full);
35659
+ else if (entry.isFile() && SOURCE_EXTS.has(extname(entry.name))) out.push(full);
35856
35660
  }
35857
35661
  }
35858
35662
  function collectExports(cwd) {
35859
35663
  const out = /* @__PURE__ */ new Set();
35860
35664
  const files = [];
35861
- for (const root of SOURCE_ROOTS) walk(join5(cwd, root), files, CAP);
35665
+ for (const root of SOURCE_ROOTS) walk(join3(cwd, root), files, CAP);
35862
35666
  for (const file of files) {
35863
35667
  let source;
35864
35668
  try {
35865
- source = readFileSync4(file, "utf-8");
35669
+ source = readFileSync(file, "utf-8");
35866
35670
  } catch {
35867
35671
  continue;
35868
35672
  }
@@ -36125,38 +35929,6 @@ function extractInlineCodeSpans(source) {
36125
35929
  }
36126
35930
  return hits;
36127
35931
  }
36128
- function extractFencedCodeBlocks(source) {
36129
- const blocks = [];
36130
- const lines = source.split("\n");
36131
- let i = 0;
36132
- while (i < lines.length) {
36133
- const line = lines[i] ?? "";
36134
- const fenceMatch = /^```(\w*)\s*$/.exec(line);
36135
- if (!fenceMatch) {
36136
- i++;
36137
- continue;
36138
- }
36139
- const lang = fenceMatch[1] ?? "";
36140
- const startLine = i + 1;
36141
- const bodyLines = [];
36142
- i++;
36143
- while (i < lines.length) {
36144
- if (/^```\s*$/.test(lines[i] ?? "")) {
36145
- i++;
36146
- break;
36147
- }
36148
- bodyLines.push(lines[i] ?? "");
36149
- i++;
36150
- }
36151
- blocks.push({
36152
- lang,
36153
- body: bodyLines.join("\n"),
36154
- line: startLine,
36155
- column: 1
36156
- });
36157
- }
36158
- return blocks;
36159
- }
36160
35932
  function extractMarkdownLinks(source) {
36161
35933
  const hits = [];
36162
35934
  const re = /(?<!\!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
@@ -36173,10 +35945,10 @@ function extractMarkdownLinks(source) {
36173
35945
  }
36174
35946
  function declaredPackages(cwd) {
36175
35947
  const out = /* @__PURE__ */ new Set();
36176
- const pkgPath = join6(cwd, "package.json");
36177
- if (!existsSync3(pkgPath)) return out;
35948
+ const pkgPath = join4(cwd, "package.json");
35949
+ if (!existsSync2(pkgPath)) return out;
36178
35950
  try {
36179
- const raw = readFileSync5(pkgPath, "utf-8");
35951
+ const raw = readFileSync2(pkgPath, "utf-8");
36180
35952
  const pkg = JSON.parse(raw);
36181
35953
  for (const k of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
36182
35954
  const v = pkg[k];
@@ -36206,7 +35978,7 @@ var brokenLinkRule = createRule({
36206
35978
  const source = facts.v2?._source;
36207
35979
  if (!source) return issues;
36208
35980
  const links = extractMarkdownLinks(source);
36209
- const docDir = dirname4(resolve2(context.cwd, context.filePath));
35981
+ const docDir = dirname3(resolve(context.cwd, context.filePath));
36210
35982
  for (const link of links) {
36211
35983
  const target = link.target;
36212
35984
  if (target.startsWith("http://") || target.startsWith("https://")) continue;
@@ -36216,8 +35988,8 @@ var brokenLinkRule = createRule({
36216
35988
  if (target.startsWith("/")) continue;
36217
35989
  const filePart = target.split("#")[0] ?? target;
36218
35990
  if (filePart === "") continue;
36219
- const resolved = join7(docDir, filePart);
36220
- if (existsSync4(resolved)) continue;
35991
+ const resolved = join5(docDir, filePart);
35992
+ if (existsSync3(resolved)) continue;
36221
35993
  issues.push({
36222
35994
  ruleId: "docs/broken-link",
36223
35995
  category: "docs",
@@ -36234,6 +36006,466 @@ var brokenLinkRule = createRule({
36234
36006
  }
36235
36007
  });
36236
36008
 
36009
+ // src/rules/dup/identical-block.ts
36010
+ import * as crypto from "crypto";
36011
+ var WINDOW_SIZE = 10;
36012
+ var MIN_NORMALIZED_LENGTH = 40;
36013
+ var HASH_PREFIX_LENGTH = 16;
36014
+ var DEDUP_CACHE = /* @__PURE__ */ new Map();
36015
+ function normalizeAndHash(lines) {
36016
+ const normalized = lines.map(
36017
+ (line) => line.replace(/\/\/.*$/, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s+/g, " ").trim()
36018
+ ).filter((line) => line.length > 0).join("\n");
36019
+ if (normalized.length < MIN_NORMALIZED_LENGTH) return void 0;
36020
+ return crypto.createHash("sha1").update(normalized).digest("hex").slice(0, HASH_PREFIX_LENGTH);
36021
+ }
36022
+ var dupIdenticalBlockRule = createRule({
36023
+ id: "dup/identical-block",
36024
+ category: "logic",
36025
+ severity: "medium",
36026
+ aiSpecific: false,
36027
+ description: "Block of >=10 lines is identical across >=2 files (Type-1 clone detector)",
36028
+ create(_context) {
36029
+ return {};
36030
+ },
36031
+ analyze(_context, facts) {
36032
+ const issues = [];
36033
+ const source = facts.v2?._source;
36034
+ if (!source) return issues;
36035
+ const filePath = facts.filePath;
36036
+ const lines = source.split("\n");
36037
+ for (let i = 0; i <= lines.length - WINDOW_SIZE; i++) {
36038
+ const window = lines.slice(i, i + WINDOW_SIZE);
36039
+ const hash = normalizeAndHash(window);
36040
+ if (!hash) continue;
36041
+ const existing = DEDUP_CACHE.get(hash) ?? [];
36042
+ const matches = existing.filter((m) => m.file !== filePath);
36043
+ for (const match of matches) {
36044
+ issues.push({
36045
+ ruleId: "dup/identical-block",
36046
+ category: "logic",
36047
+ severity: "medium",
36048
+ aiSpecific: false,
36049
+ message: `Identical ${WINDOW_SIZE}-line block at line ${i + 1} also appears in ${match.file}:${match.line + 1}`,
36050
+ line: i + 1,
36051
+ column: 0,
36052
+ 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.",
36053
+ extras: {
36054
+ duplicateOf: {
36055
+ file: match.file,
36056
+ line: match.line + 1,
36057
+ hash
36058
+ }
36059
+ }
36060
+ });
36061
+ }
36062
+ existing.push({ file: filePath, line: i });
36063
+ DEDUP_CACHE.set(hash, existing);
36064
+ }
36065
+ return issues;
36066
+ }
36067
+ });
36068
+
36069
+ // src/rules/go/error-wrap-without-context.ts
36070
+ var ERR_WRAP_REGEX = /fmt\.Errorf\(\s*"((?:[^"\\]|\\.)*)"\s*,\s*([^)]+)\)/g;
36071
+ var GENERIC_PREFIX_REGEX = /^\s*(?:error|err|failed|wrap(?:ping)?|invalid)\s*(?::\s*)?%w\b/i;
36072
+ var goErrorWrapWithoutContextRule = createRule({
36073
+ id: "go/error-wrap-without-context",
36074
+ category: "typo",
36075
+ severity: "low",
36076
+ aiSpecific: true,
36077
+ description: 'fmt.Errorf wrap without operation context \u2014 AI defaults to generic "error: %w"',
36078
+ create(_context) {
36079
+ return {};
36080
+ },
36081
+ analyze(_context, facts) {
36082
+ const issues = [];
36083
+ const source = facts.v2?._source;
36084
+ if (!source) return issues;
36085
+ let match;
36086
+ ERR_WRAP_REGEX.lastIndex = 0;
36087
+ while ((match = ERR_WRAP_REGEX.exec(source)) !== null) {
36088
+ const formatString = match[1];
36089
+ if (!formatString.includes("%w")) continue;
36090
+ if (formatString.length >= 30) continue;
36091
+ if (!GENERIC_PREFIX_REGEX.test(formatString)) continue;
36092
+ const line = source.slice(0, match.index).split("\n").length;
36093
+ issues.push({
36094
+ ruleId: "go/error-wrap-without-context",
36095
+ category: "typo",
36096
+ severity: "low",
36097
+ aiSpecific: true,
36098
+ message: `fmt.Errorf wrap with generic message "${formatString}" \u2014 include the failing operation`,
36099
+ line,
36100
+ column: match[0].indexOf("fmt") + 1,
36101
+ 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'
36102
+ });
36103
+ }
36104
+ return issues;
36105
+ }
36106
+ });
36107
+
36108
+ // src/rules/go/nil-slice-vs-empty.ts
36109
+ var NIL_SLICE_DECL_REGEX = /^[\t ]*var\s+([A-Za-z_][A-Za-z0-9_]*)\s+\[\][\w.*]+\b/gm;
36110
+ var EMPTY_SLICE_ASSIGN_REGEX = /^[\t ]*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:\[\][\w.*]*\{\}|make\(\[\][\w.*]*)/gm;
36111
+ var goNilSliceVsEmptyRule = createRule({
36112
+ id: "go/nil-slice-vs-empty",
36113
+ category: "typo",
36114
+ severity: "low",
36115
+ aiSpecific: true,
36116
+ description: "Variable declared `var x []int` but later assigned `x = []int{}` or `make([]int, n)` \u2014 pick one form",
36117
+ create(_context) {
36118
+ return {};
36119
+ },
36120
+ analyze(_context, facts) {
36121
+ const issues = [];
36122
+ const source = facts.v2?._source;
36123
+ if (!source) return issues;
36124
+ const nilDecls = /* @__PURE__ */ new Map();
36125
+ let m;
36126
+ NIL_SLICE_DECL_REGEX.lastIndex = 0;
36127
+ while ((m = NIL_SLICE_DECL_REGEX.exec(source)) !== null) {
36128
+ nilDecls.set(m[1], source.slice(0, m.index).split("\n").length);
36129
+ }
36130
+ if (nilDecls.size === 0) return issues;
36131
+ EMPTY_SLICE_ASSIGN_REGEX.lastIndex = 0;
36132
+ while ((m = EMPTY_SLICE_ASSIGN_REGEX.exec(source)) !== null) {
36133
+ const name = m[1];
36134
+ if (!nilDecls.has(name)) continue;
36135
+ const declLine = nilDecls.get(name);
36136
+ const assignLine = source.slice(0, m.index).split("\n").length;
36137
+ if (assignLine <= declLine) continue;
36138
+ issues.push({
36139
+ ruleId: "go/nil-slice-vs-empty",
36140
+ category: "typo",
36141
+ severity: "low",
36142
+ aiSpecific: true,
36143
+ message: `Variable '${name}' declared as nil slice (line ${declLine}) but assigned an empty slice (line ${assignLine}) \u2014 pick one form`,
36144
+ line: declLine,
36145
+ column: 1,
36146
+ 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."
36147
+ });
36148
+ }
36149
+ return issues;
36150
+ }
36151
+ });
36152
+
36153
+ // src/rules/go/struct-tag-inconsistency.ts
36154
+ var JSON_TAG_REGEX = /`json:"([^",]+)(?:,([^"]+))?"`/g;
36155
+ var goStructTagInconsistencyRule = createRule({
36156
+ id: "go/struct-tag-inconsistency",
36157
+ category: "typo",
36158
+ severity: "low",
36159
+ aiSpecific: true,
36160
+ description: 'Struct fields mix json:"foo" and json:"foo,omitempty" \u2014 pick one convention per struct',
36161
+ create(_context) {
36162
+ return {};
36163
+ },
36164
+ analyze(_context, facts) {
36165
+ const issues = [];
36166
+ const source = facts.v2?._source;
36167
+ if (!source) return issues;
36168
+ const structRegex = /type\s+[A-Z][A-Za-z0-9_]*\s+struct\s*\{/g;
36169
+ let structMatch;
36170
+ while ((structMatch = structRegex.exec(source)) !== null) {
36171
+ const startIdx = structMatch.index;
36172
+ const openBrace = source.indexOf("{", startIdx);
36173
+ if (openBrace < 0) continue;
36174
+ let depth = 1;
36175
+ let i = openBrace + 1;
36176
+ while (i < source.length && depth > 0) {
36177
+ const ch = source[i];
36178
+ if (ch === "{") depth++;
36179
+ else if (ch === "}") depth--;
36180
+ i++;
36181
+ }
36182
+ const structBody = source.slice(openBrace, i);
36183
+ const structLine = source.slice(0, startIdx).split("\n").length;
36184
+ const styleCount = {};
36185
+ const tagMatches = [];
36186
+ let m;
36187
+ JSON_TAG_REGEX.lastIndex = 0;
36188
+ while ((m = JSON_TAG_REGEX.exec(structBody)) !== null) {
36189
+ const tag = m[1];
36190
+ const options = m[2] ?? "";
36191
+ const style = options ? "with-options" : "no-options";
36192
+ styleCount[style] = (styleCount[style] ?? 0) + 1;
36193
+ tagMatches.push({ tag, style, idx: openBrace + m.index });
36194
+ }
36195
+ const styles = Object.keys(styleCount);
36196
+ if (styles.length < 2 || tagMatches.length < 2) continue;
36197
+ const dominant = styles.reduce(
36198
+ (a, b) => (styleCount[a] ?? 0) >= (styleCount[b] ?? 0) ? a : b
36199
+ );
36200
+ const minority = tagMatches.filter((t) => t.style !== dominant);
36201
+ if (minority.length === 0) continue;
36202
+ for (const m2 of minority) {
36203
+ const line = source.slice(0, m2.idx).split("\n").length;
36204
+ issues.push({
36205
+ ruleId: "go/struct-tag-inconsistency",
36206
+ category: "typo",
36207
+ severity: "low",
36208
+ aiSpecific: true,
36209
+ 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"}`,
36210
+ line,
36211
+ column: 1,
36212
+ 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.'
36213
+ });
36214
+ }
36215
+ if (issues.length > 0) break;
36216
+ }
36217
+ return issues;
36218
+ }
36219
+ });
36220
+
36221
+ // src/rules/java/arraylist-vs-linkedlist.ts
36222
+ var NEW_LINKED_LIST_REGEX = /new\s+LinkedList\s*</g;
36223
+ var javaArraylistVsLinkedlistRule = createRule({
36224
+ id: "java/arraylist-vs-linkedlist",
36225
+ category: "typo",
36226
+ severity: "low",
36227
+ aiSpecific: true,
36228
+ description: "new LinkedList<>() \u2014 use ArrayList (Effective Java, Item 28)",
36229
+ create(_context) {
36230
+ return {};
36231
+ },
36232
+ analyze(_context, facts) {
36233
+ const issues = [];
36234
+ const source = facts.v2?._source;
36235
+ if (!source) return issues;
36236
+ let m;
36237
+ NEW_LINKED_LIST_REGEX.lastIndex = 0;
36238
+ while ((m = NEW_LINKED_LIST_REGEX.exec(source)) !== null) {
36239
+ const line = source.slice(0, m.index).split("\n").length;
36240
+ issues.push({
36241
+ ruleId: "java/arraylist-vs-linkedlist",
36242
+ category: "typo",
36243
+ severity: "low",
36244
+ aiSpecific: true,
36245
+ message: `new LinkedList at line ${line} \u2014 use ArrayList instead`,
36246
+ line,
36247
+ column: 1,
36248
+ 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."
36249
+ });
36250
+ }
36251
+ return issues;
36252
+ }
36253
+ });
36254
+
36255
+ // src/rules/java/empty-catch-block.ts
36256
+ var SINGLE_LINE_EMPTY_CATCH_REGEX = /catch\s*\([^)]*\)\s*\{\s*\}/g;
36257
+ var javaEmptyCatchBlockRule = createRule({
36258
+ id: "java/empty-catch-block",
36259
+ category: "logic",
36260
+ severity: "medium",
36261
+ aiSpecific: true,
36262
+ description: "Empty catch block \u2014 silently swallows exceptions",
36263
+ create(_context) {
36264
+ return {};
36265
+ },
36266
+ analyze(_context, facts) {
36267
+ const issues = [];
36268
+ const source = facts.v2?._source;
36269
+ if (!source) return issues;
36270
+ let m;
36271
+ SINGLE_LINE_EMPTY_CATCH_REGEX.lastIndex = 0;
36272
+ while ((m = SINGLE_LINE_EMPTY_CATCH_REGEX.exec(source)) !== null) {
36273
+ const line = source.slice(0, m.index).split("\n").length;
36274
+ issues.push({
36275
+ ruleId: "java/empty-catch-block",
36276
+ category: "logic",
36277
+ severity: "medium",
36278
+ aiSpecific: true,
36279
+ message: `Empty catch block at line ${line} \u2014 exception is silently swallowed`,
36280
+ line,
36281
+ column: 1,
36282
+ 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.'
36283
+ });
36284
+ }
36285
+ return issues;
36286
+ }
36287
+ });
36288
+
36289
+ // src/rules/java/legacy-date-api.ts
36290
+ var LEGACY_IMPORT_REGEX = /^import\s+(?:static\s+)?java\.(?:util|sql)\.(?:Date|Calendar|GregorianCalendar)\s*;/gm;
36291
+ var LEGACY_USAGE_REGEX = /\bnew\s+(?:Date|GregorianCalendar)\s*\(/g;
36292
+ var CALENDAR_GET_INSTANCE_REGEX = /Calendar\.getInstance\s*\(/g;
36293
+ var javaLegacyDateApiRule = createRule({
36294
+ id: "java/legacy-date-api",
36295
+ category: "typo",
36296
+ severity: "low",
36297
+ aiSpecific: true,
36298
+ description: "Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310) from Java 8+",
36299
+ create(_context) {
36300
+ return {};
36301
+ },
36302
+ analyze(_context, facts) {
36303
+ const issues = [];
36304
+ const source = facts.v2?._source;
36305
+ if (!source) return issues;
36306
+ const flagged = /* @__PURE__ */ new Set();
36307
+ let m;
36308
+ LEGACY_IMPORT_REGEX.lastIndex = 0;
36309
+ while ((m = LEGACY_IMPORT_REGEX.exec(source)) !== null) {
36310
+ const line = source.slice(0, m.index).split("\n").length;
36311
+ flagged.add(line);
36312
+ issues.push({
36313
+ ruleId: "java/legacy-date-api",
36314
+ category: "typo",
36315
+ severity: "low",
36316
+ aiSpecific: true,
36317
+ message: `Legacy date import at line ${line} \u2014 use java.time (JSR-310) from Java 8+`,
36318
+ line,
36319
+ column: 1,
36320
+ 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."
36321
+ });
36322
+ }
36323
+ LEGACY_USAGE_REGEX.lastIndex = 0;
36324
+ while ((m = LEGACY_USAGE_REGEX.exec(source)) !== null) {
36325
+ const line = source.slice(0, m.index).split("\n").length;
36326
+ if (flagged.has(line)) continue;
36327
+ issues.push({
36328
+ ruleId: "java/legacy-date-api",
36329
+ category: "typo",
36330
+ severity: "low",
36331
+ aiSpecific: true,
36332
+ message: `new Date() / GregorianCalendar at line ${line} \u2014 use java.time`,
36333
+ line,
36334
+ column: 1,
36335
+ advice: "Replace with `LocalDate.now()`, `Instant.now()`, or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
36336
+ });
36337
+ }
36338
+ CALENDAR_GET_INSTANCE_REGEX.lastIndex = 0;
36339
+ while ((m = CALENDAR_GET_INSTANCE_REGEX.exec(source)) !== null) {
36340
+ const line = source.slice(0, m.index).split("\n").length;
36341
+ if (flagged.has(line)) continue;
36342
+ issues.push({
36343
+ ruleId: "java/legacy-date-api",
36344
+ category: "typo",
36345
+ severity: "low",
36346
+ aiSpecific: true,
36347
+ message: `Calendar.getInstance() at line ${line} \u2014 use java.time`,
36348
+ line,
36349
+ column: 1,
36350
+ advice: "Replace with `LocalDate.now()` (date-only) or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
36351
+ });
36352
+ }
36353
+ return issues;
36354
+ }
36355
+ });
36356
+
36357
+ // src/rules/java/raw-type-overuse.ts
36358
+ var RAW_TYPE_REGEX = /\b(List|Map|Set|Collection|Iterable)\s+(?![<A-Z])(\w+)/g;
36359
+ var javaRawTypeOveruseRule = createRule({
36360
+ id: "java/raw-type-overuse",
36361
+ category: "typo",
36362
+ severity: "low",
36363
+ aiSpecific: true,
36364
+ description: "Raw type usage (List, Map, Set) \u2014 use generics (Effective Java, Item 23)",
36365
+ create(_context) {
36366
+ return {};
36367
+ },
36368
+ analyze(_context, facts) {
36369
+ const issues = [];
36370
+ const source = facts.v2?._source;
36371
+ if (!source) return issues;
36372
+ let m;
36373
+ RAW_TYPE_REGEX.lastIndex = 0;
36374
+ while ((m = RAW_TYPE_REGEX.exec(source)) !== null) {
36375
+ const typeName = m[1];
36376
+ const line = source.slice(0, m.index).split("\n").length;
36377
+ issues.push({
36378
+ ruleId: "java/raw-type-overuse",
36379
+ category: "typo",
36380
+ severity: "low",
36381
+ aiSpecific: true,
36382
+ message: `Raw type ${typeName} at line ${line} \u2014 add type parameters`,
36383
+ line,
36384
+ column: 1,
36385
+ 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.`
36386
+ });
36387
+ }
36388
+ return issues;
36389
+ }
36390
+ });
36391
+
36392
+ // src/rules/java/string-concat-loop.ts
36393
+ var STRING_CONCAT_REGEX = /(\b\w+)\s*=\s*\1\s*\+\s*[^;]+;|(\b\w+)\s*\+=\s*['"`]/g;
36394
+ var javaStringConcatLoopRule = createRule({
36395
+ id: "java/string-concat-loop",
36396
+ category: "perf",
36397
+ severity: "low",
36398
+ aiSpecific: true,
36399
+ description: "String concatenation in a loop \u2014 use StringBuilder",
36400
+ create(_context) {
36401
+ return {};
36402
+ },
36403
+ analyze(_context, facts) {
36404
+ const issues = [];
36405
+ const source = facts.v2?._source;
36406
+ if (!source) return issues;
36407
+ if (!/\b(for|while|do)\b/.test(source)) return issues;
36408
+ let m;
36409
+ STRING_CONCAT_REGEX.lastIndex = 0;
36410
+ while ((m = STRING_CONCAT_REGEX.exec(source)) !== null) {
36411
+ const line = source.slice(0, m.index).split("\n").length;
36412
+ issues.push({
36413
+ ruleId: "java/string-concat-loop",
36414
+ category: "perf",
36415
+ severity: "low",
36416
+ aiSpecific: true,
36417
+ message: `String concatenation in a loop at line ${line} \u2014 use StringBuilder`,
36418
+ line,
36419
+ column: 1,
36420
+ 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."
36421
+ });
36422
+ }
36423
+ return issues;
36424
+ }
36425
+ });
36426
+
36427
+ // src/rules/java/system-out-println.ts
36428
+ var PRINTLN_REGEX = /System\.out\.println\s*\(/g;
36429
+ var DEFAULT_THRESHOLD = 1;
36430
+ var javaSystemOutPrintlnRule = createRule({
36431
+ id: "java/system-out-println",
36432
+ category: "typo",
36433
+ severity: "low",
36434
+ aiSpecific: true,
36435
+ description: "System.out.println in production code \u2014 use a logger (SLF4J, Log4j, etc.)",
36436
+ create(_context) {
36437
+ return { threshold: DEFAULT_THRESHOLD };
36438
+ },
36439
+ analyze(context, facts) {
36440
+ const issues = [];
36441
+ const source = facts.v2?._source;
36442
+ if (!source) return issues;
36443
+ const matches = [];
36444
+ let m;
36445
+ PRINTLN_REGEX.lastIndex = 0;
36446
+ while ((m = PRINTLN_REGEX.exec(source)) !== null) {
36447
+ matches.push(m.index);
36448
+ }
36449
+ if (matches.length <= context.threshold) return issues;
36450
+ const cap = Math.min(matches.length, 10);
36451
+ for (let i = 0; i < cap; i++) {
36452
+ const idx = matches[i];
36453
+ const line = source.slice(0, idx).split("\n").length;
36454
+ issues.push({
36455
+ ruleId: "java/system-out-println",
36456
+ category: "typo",
36457
+ severity: "low",
36458
+ aiSpecific: true,
36459
+ message: `System.out.println at line ${line} \u2014 use a logger for production output`,
36460
+ line,
36461
+ column: 1,
36462
+ 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."
36463
+ });
36464
+ }
36465
+ return issues;
36466
+ }
36467
+ });
36468
+
36237
36469
  // src/rules/layout/gap-monopoly.ts
36238
36470
  var GAP_RE = /\bgap(?:-x|-y)?-(\d+)\b/g;
36239
36471
  var gapMonopolyRule = createRule({
@@ -36586,16 +36818,16 @@ var DEFAULT_CONFIG = {
36586
36818
  };
36587
36819
 
36588
36820
  // src/config/detect/monorepo.ts
36589
- import { existsSync as existsSync5, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync } from "fs";
36590
- import { dirname as dirname5, join as join8, resolve as resolve3 } from "path";
36821
+ import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
36822
+ import { dirname as dirname4, join as join6, resolve as resolve2 } from "path";
36591
36823
 
36592
36824
  // src/config/detect/styling.ts
36593
- import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
36594
- import { join as join9, resolve as resolve4 } from "path";
36825
+ import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
36826
+ import { join as join7, resolve as resolve3 } from "path";
36595
36827
 
36596
36828
  // src/config/detect/stack.ts
36597
- import { existsSync as existsSync7, readFileSync as readFileSync8, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
36598
- import { extname as extname4, join as join10, resolve as resolve5 } from "path";
36829
+ import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
36830
+ import { extname as extname2, join as join8, resolve as resolve4 } from "path";
36599
36831
 
36600
36832
  // src/config/presets.ts
36601
36833
  var REACT_ONLY_RULES = {
@@ -36644,8 +36876,8 @@ var FRAMEWORK_PRESETS = {
36644
36876
  };
36645
36877
 
36646
36878
  // src/config/load.ts
36647
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
36648
- import { dirname as dirname6, extname as extname5, join as join11, resolve as resolve6 } from "path";
36879
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
36880
+ import { dirname as dirname5, extname as extname3, join as join10, resolve as resolve5 } from "path";
36649
36881
  import { createRequire } from "module";
36650
36882
 
36651
36883
  // src/engine/logger.ts
@@ -36667,6 +36899,10 @@ function setLoggerQuiet(quiet) {
36667
36899
  logger = createLogger(quiet);
36668
36900
  }
36669
36901
 
36902
+ // src/config/conventions.ts
36903
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
36904
+ import { join as join9 } from "path";
36905
+
36670
36906
  // src/config/init.ts
36671
36907
  var STRICTNESS_PRESETS = {
36672
36908
  strict: {
@@ -37021,82 +37257,6 @@ var keyPropMissingRule = createRule({
37021
37257
  }
37022
37258
  });
37023
37259
 
37024
- // src/rules/logic/ks-distribution-shift.ts
37025
- var MIN_SAMPLES_PER_FEATURE = 20;
37026
- function extractFileFeatures(source) {
37027
- const lines = source.split("\n");
37028
- const lineLengths = lines.map((l) => l.length);
37029
- const identifierLengths = [];
37030
- const idRe = /[A-Za-z_$][A-Za-z0-9_$]*/g;
37031
- let m;
37032
- while ((m = idRe.exec(source)) !== null) {
37033
- identifierLengths.push(m[0].length);
37034
- }
37035
- const commentDensity = lines.map((l) => {
37036
- const trimmed = l.trim();
37037
- if (trimmed.length === 0) return 0;
37038
- const commentChars = (trimmed.match(/^\/\/.*$/)?.[0]?.length ?? 0) + (trimmed.match(/^\s*\/\*.*?\*\/\s*$/)?.at(0)?.length ?? 0);
37039
- return commentChars / trimmed.length;
37040
- });
37041
- return { lineLengths, identifierLengths, commentDensity };
37042
- }
37043
- var ksDistributionShiftRule = createRule({
37044
- id: "logic/ks-distribution-shift",
37045
- category: "logic",
37046
- severity: "medium",
37047
- aiSpecific: false,
37048
- description: "Multi-feature Kolmogorov\u2013Smirnov distribution-shift vs corpus baseline (Bonferroni-corrected). Peer-reviewed ML distribution-shift detector (arXiv:2510.15996, Oct 2025).",
37049
- create(context) {
37050
- return context;
37051
- },
37052
- analyze(_context, facts) {
37053
- const issues = [];
37054
- if (!facts.v2) return issues;
37055
- const source = facts.v2._source ?? "";
37056
- if (source.length < 200) return issues;
37057
- const features = extractFileFeatures(source);
37058
- const samples = /* @__PURE__ */ new Map([
37059
- ["lineLengths", features.lineLengths],
37060
- ["identifierLengths", features.identifierLengths],
37061
- ["commentDensity", features.commentDensity]
37062
- ]);
37063
- const baselines = getCorpusBaselines();
37064
- const baselinesMap = /* @__PURE__ */ new Map();
37065
- if (baselines) {
37066
- baselinesMap.set("lineLengths", baselines.features.lineLengths.sample);
37067
- baselinesMap.set("identifierLengths", baselines.features.identifierLengths.sample);
37068
- baselinesMap.set("commentDensity", baselines.features.commentDensity.sample);
37069
- } else {
37070
- baselinesMap.set("lineLengths", [20, 25, 30, 32, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100]);
37071
- baselinesMap.set("identifierLengths", [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 24, 28]);
37072
- baselinesMap.set("commentDensity", [0, 0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5]);
37073
- }
37074
- for (const [name, vals] of samples) {
37075
- if (vals.length < MIN_SAMPLES_PER_FEATURE) samples.delete(name);
37076
- }
37077
- if (samples.size === 0) return issues;
37078
- const result = multiFeatureKsTest(samples, baselinesMap, 0.05);
37079
- if (!result.anySignificant) return issues;
37080
- const shifted = result.significantFeatures.join(", ");
37081
- const details = result.significantFeatures.map((name) => {
37082
- const r = result.perFeature.get(name);
37083
- if (!r) return name;
37084
- return `${name} (D=${r.statistic.toFixed(3)}, p=${r.pValue.toExponential(2)})`;
37085
- }).join("; ");
37086
- issues.push({
37087
- ruleId: "logic/ks-distribution-shift",
37088
- category: "logic",
37089
- severity: "medium",
37090
- aiSpecific: true,
37091
- message: `Distribution shift detected on ${result.significantFeatures.length} of ${result.perFeature.size} features (Bonferroni \u03B1=${result.bonferroniAlpha.toExponential(2)}). Features: ${shifted}. Detail: ${details}.`,
37092
- line: 1,
37093
- column: 1,
37094
- advice: "Inspect the shifted features. KS detects both AI anomalies and production-rot anomalies (it is symmetric); combine with Heaps/Zipf for AI-specific signal."
37095
- });
37096
- return issues;
37097
- }
37098
- });
37099
-
37100
37260
  // src/rules/logic/math-any-density.ts
37101
37261
  var ANY_PER_100_LINES = 5;
37102
37262
  var MIN_ABSOLUTE = 6;
@@ -37158,7 +37318,7 @@ var mathAnyDensityRule = createRule({
37158
37318
  });
37159
37319
 
37160
37320
  // src/rules/logic/math-console-log-storm.ts
37161
- var WINDOW_SIZE = 30;
37321
+ var WINDOW_SIZE2 = 30;
37162
37322
  var STORM_THRESHOLD = 5;
37163
37323
  var CONSOLE_LOG_RE = /\bconsole\.log\s*\(/g;
37164
37324
  var mathConsoleLogStormRule = createRule({
@@ -37189,7 +37349,7 @@ var mathConsoleLogStormRule = createRule({
37189
37349
  let maxEndLine = 0;
37190
37350
  let i = 0;
37191
37351
  for (let j = 0; j < lines.length; j++) {
37192
- while (lines[j] - lines[i] > WINDOW_SIZE) i++;
37352
+ while (lines[j] - lines[i] > WINDOW_SIZE2) i++;
37193
37353
  const count = j - i + 1;
37194
37354
  if (count > maxCount) {
37195
37355
  maxCount = count;
@@ -37203,7 +37363,7 @@ var mathConsoleLogStormRule = createRule({
37203
37363
  category: "logic",
37204
37364
  severity: "high",
37205
37365
  aiSpecific: true,
37206
- 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.`,
37366
+ message: `${maxCount} console.log calls clustered in a ${WINDOW_SIZE2}-line window ending at line ${maxEndLine}. AI debug-sprays logs in a single function; humans use one strategic log.`,
37207
37367
  line: firstIdx >= 0 ? lines[firstIdx] : 1,
37208
37368
  column: firstIdx >= 0 ? columns[firstIdx] : 1,
37209
37369
  advice: "Replace debug logs with a proper debugger or logger.debug() \u2014 remove all console.log before shipping."
@@ -38108,7 +38268,6 @@ function scanForStringlyParams(paramText) {
38108
38268
  }
38109
38269
 
38110
38270
  // src/rules/rust/todo-macro.ts
38111
- init_parser_rust();
38112
38271
  var TODO_MACROS = /* @__PURE__ */ new Set(["todo", "unimplemented", "todo_unimplemented"]);
38113
38272
  var rustTodoMacroRule = createRule({
38114
38273
  id: "rust/todo-macro",
@@ -38244,7 +38403,6 @@ function collectReferencedNames(source) {
38244
38403
  }
38245
38404
 
38246
38405
  // src/rules/rust/unwrap-in-production.ts
38247
- init_parser_rust();
38248
38406
  var UNWRAP_METHODS = /* @__PURE__ */ new Set(["unwrap", "expect", "unwrap_or_else"]);
38249
38407
  var rustUnwrapInProductionRule = createRule({
38250
38408
  id: "rust/unwrap-in-production",
@@ -39007,7 +39165,7 @@ var unsafeHtmlRenderRule = createRule({
39007
39165
  });
39008
39166
 
39009
39167
  // src/rules/test/utils.ts
39010
- import { createHash as createHash2 } from "crypto";
39168
+ import { createHash as createHash3 } from "crypto";
39011
39169
  function isTestFile(filePath) {
39012
39170
  const normalized = filePath.replace(/\\/g, "/");
39013
39171
  if (/(^|\/)__tests__\//.test(normalized)) return true;
@@ -39167,7 +39325,7 @@ function extractSetupBlocks(source, minLines = 3) {
39167
39325
  }).trim();
39168
39326
  const lineCount = rawBody.split("\n").length;
39169
39327
  if (lineCount < minLines) continue;
39170
- const bodyHash = createHash2("sha1").update(normalizedBody).digest("hex");
39328
+ const bodyHash = createHash3("sha1").update(normalizedBody).digest("hex");
39171
39329
  const { line, column } = lineColumnOf(source, m.index);
39172
39330
  hits.push({
39173
39331
  kind,
@@ -39381,7 +39539,7 @@ function looksRealistic(value) {
39381
39539
 
39382
39540
  // src/rules/test/missing-edge-case.ts
39383
39541
  import { parseSync as parseSync2 } from "@swc/core";
39384
- import { existsSync as existsSync9, readFileSync as readFileSync10, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
39542
+ import { existsSync as existsSync9, readFileSync as readFileSync8, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
39385
39543
  var MAX_PER_FILE = 20;
39386
39544
  var missingEdgeCaseRule = createRule({
39387
39545
  id: "test/missing-edge-case",
@@ -39403,7 +39561,7 @@ var missingEdgeCaseRule = createRule({
39403
39561
  const testFileSources = /* @__PURE__ */ new Map();
39404
39562
  for (const testFile of discoverTestFiles(cwd)) {
39405
39563
  try {
39406
- testFileSources.set(testFile, readFileSync10(testFile, "utf-8"));
39564
+ testFileSources.set(testFile, readFileSync8(testFile, "utf-8"));
39407
39565
  } catch {
39408
39566
  }
39409
39567
  }
@@ -39777,6 +39935,216 @@ function isTautologicalAssertion(hit) {
39777
39935
  return false;
39778
39936
  }
39779
39937
 
39938
+ // src/rules/ts/enum-vs-as-const.ts
39939
+ var ENUM_DECL_REGEX = /^[ \t]*(?:export\s+)?(?:const\s+)?enum\s+[A-Z_][A-Za-z0-9_]*\s*\{/gm;
39940
+ var tsEnumVsAsConstRule = createRule({
39941
+ id: "ts/enum-vs-as-const",
39942
+ category: "typo",
39943
+ severity: "low",
39944
+ aiSpecific: true,
39945
+ description: "Uses `enum` \u2014 modern TS prefers `as const` objects",
39946
+ create(_context) {
39947
+ return {};
39948
+ },
39949
+ analyze(_context, facts) {
39950
+ const issues = [];
39951
+ const source = facts.v2?._source;
39952
+ if (!source) return issues;
39953
+ let match;
39954
+ ENUM_DECL_REGEX.lastIndex = 0;
39955
+ while ((match = ENUM_DECL_REGEX.exec(source)) !== null) {
39956
+ const line = source.slice(0, match.index).split("\n").length;
39957
+ issues.push({
39958
+ ruleId: "ts/enum-vs-as-const",
39959
+ category: "typo",
39960
+ severity: "low",
39961
+ aiSpecific: true,
39962
+ message: `'enum' is an AI / older-TS pattern \u2014 prefer 'as const' for a frozen object literal`,
39963
+ line,
39964
+ column: match[0].indexOf("enum") + 1,
39965
+ 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.'
39966
+ });
39967
+ }
39968
+ return issues;
39969
+ }
39970
+ });
39971
+
39972
+ // src/rules/ts/excessive-type-assertion.ts
39973
+ var DEFAULT_MAX = 3;
39974
+ 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;
39975
+ var AS_REGEX = /(?<![\w$])as\s+(?!const)([A-Z][\w$.,<>\[\]|&]*)/g;
39976
+ var tsExcessiveTypeAssertionRule = createRule({
39977
+ id: "ts/excessive-type-assertion",
39978
+ category: "typo",
39979
+ severity: "low",
39980
+ aiSpecific: true,
39981
+ description: "Function with >3 `as` type assertions \u2014 likely AI fighting the type system",
39982
+ create(_context) {
39983
+ return { maxAssertionsPerFunction: DEFAULT_MAX };
39984
+ },
39985
+ analyze(context, facts) {
39986
+ const issues = [];
39987
+ const source = facts.v2?._source;
39988
+ if (!source) return issues;
39989
+ let match;
39990
+ FN_DECL_REGEX.lastIndex = 0;
39991
+ while ((match = FN_DECL_REGEX.exec(source)) !== null) {
39992
+ const startIdx = match.index;
39993
+ const openBraceIdx = source.indexOf("{", startIdx);
39994
+ if (openBraceIdx < 0) continue;
39995
+ let depth = 1;
39996
+ let i = openBraceIdx + 1;
39997
+ while (i < source.length && depth > 0) {
39998
+ const ch = source[i];
39999
+ if (ch === "{") depth++;
40000
+ else if (ch === "}") depth--;
40001
+ i++;
40002
+ }
40003
+ const body = source.slice(openBraceIdx, i);
40004
+ const line = source.slice(0, startIdx).split("\n").length;
40005
+ let asCount = 0;
40006
+ const seen = /* @__PURE__ */ new Set();
40007
+ let asMatch;
40008
+ AS_REGEX.lastIndex = 0;
40009
+ while ((asMatch = AS_REGEX.exec(body)) !== null) {
40010
+ const captured = asMatch[1];
40011
+ if (seen.has(captured)) continue;
40012
+ seen.add(captured);
40013
+ asCount++;
40014
+ }
40015
+ if (asCount > context.maxAssertionsPerFunction) {
40016
+ issues.push({
40017
+ ruleId: "ts/excessive-type-assertion",
40018
+ category: "typo",
40019
+ severity: "low",
40020
+ aiSpecific: true,
40021
+ message: `Function has ${asCount} 'as' assertions (max ${context.maxAssertionsPerFunction}) \u2014 likely AI fighting the type system`,
40022
+ line,
40023
+ column: 1,
40024
+ 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."
40025
+ });
40026
+ }
40027
+ }
40028
+ return issues;
40029
+ }
40030
+ });
40031
+
40032
+ // src/rules/ts/import-type-misuse.ts
40033
+ var INLINE_TYPE_IMPORT_REGEX = /^[ \t]*import\s*\{[^}]*\btype\s+[A-Za-z_]/gm;
40034
+ var tsImportTypeMisuseRule = createRule({
40035
+ id: "ts/import-type-misuse",
40036
+ category: "typo",
40037
+ severity: "low",
40038
+ aiSpecific: true,
40039
+ description: "Inline `import { type X }` \u2014 prefer `import type { X }` for clarity",
40040
+ create(_context) {
40041
+ return {};
40042
+ },
40043
+ analyze(_context, facts) {
40044
+ const issues = [];
40045
+ const source = facts.v2?._source;
40046
+ if (!source) return issues;
40047
+ let match;
40048
+ INLINE_TYPE_IMPORT_REGEX.lastIndex = 0;
40049
+ while ((match = INLINE_TYPE_IMPORT_REGEX.exec(source)) !== null) {
40050
+ const line = source.slice(0, match.index).split("\n").length;
40051
+ issues.push({
40052
+ ruleId: "ts/import-type-misuse",
40053
+ category: "typo",
40054
+ severity: "low",
40055
+ aiSpecific: true,
40056
+ message: "Inline `type` in a value import \u2014 split into a separate `import type` statement",
40057
+ line,
40058
+ column: match[0].indexOf("type") + 1,
40059
+ 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.'
40060
+ });
40061
+ }
40062
+ return issues;
40063
+ }
40064
+ });
40065
+
40066
+ // src/rules/ts/never-vs-unknown.ts
40067
+ var NEVER_RETURN_REGEX = /^[ \t]*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)[^{]*:\s*(?:Promise<)?never\b[^{]*\{/gm;
40068
+ var THROW_OR_LOOP_REGEX = /\b(?:throw\b|while\s*\(|for\s*\(|process\.exit\b|System\.exit\b)/;
40069
+ var tsNeverVsUnknownRule = createRule({
40070
+ id: "ts/never-vs-unknown",
40071
+ category: "typo",
40072
+ severity: "low",
40073
+ aiSpecific: true,
40074
+ description: "Return type `: never` but body has no throw / loop / exit \u2014 likely AI misuse",
40075
+ create(_context) {
40076
+ return {};
40077
+ },
40078
+ analyze(_context, facts) {
40079
+ const issues = [];
40080
+ const source = facts.v2?._source;
40081
+ if (!source) return issues;
40082
+ let match;
40083
+ NEVER_RETURN_REGEX.lastIndex = 0;
40084
+ while ((match = NEVER_RETURN_REGEX.exec(source)) !== null) {
40085
+ const fnName = match[1];
40086
+ const startIdx = match.index;
40087
+ const line = source.slice(0, startIdx).split("\n").length;
40088
+ const openBraceIdx = source.indexOf("{", startIdx);
40089
+ if (openBraceIdx < 0) continue;
40090
+ let depth = 1;
40091
+ let i = openBraceIdx + 1;
40092
+ while (i < source.length && depth > 0) {
40093
+ const ch = source[i];
40094
+ if (ch === "{") depth++;
40095
+ else if (ch === "}") depth--;
40096
+ i++;
40097
+ }
40098
+ const body = source.slice(openBraceIdx, i);
40099
+ if (THROW_OR_LOOP_REGEX.test(body)) continue;
40100
+ issues.push({
40101
+ ruleId: "ts/never-vs-unknown",
40102
+ category: "typo",
40103
+ severity: "low",
40104
+ aiSpecific: true,
40105
+ message: `Function '${fnName}' returns 'never' but its body has no throw, loop, or exit \u2014 likely AI misuse`,
40106
+ line,
40107
+ column: match[0].indexOf("never") + 1,
40108
+ 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.'
40109
+ });
40110
+ }
40111
+ return issues;
40112
+ }
40113
+ });
40114
+
40115
+ // src/rules/ts/optional-chain-overuse.ts
40116
+ var DEFAULT_MIN_CHAIN_LENGTH = 5;
40117
+ var tsOptionalChainOveruseRule = createRule({
40118
+ id: "ts/optional-chain-overuse",
40119
+ category: "logic",
40120
+ severity: "low",
40121
+ aiSpecific: true,
40122
+ description: "Optional chaining (?.) used 5+ times in a single chain \u2014 AI tends to chain rather than narrow",
40123
+ create(_context) {
40124
+ return { minChainLength: DEFAULT_MIN_CHAIN_LENGTH };
40125
+ },
40126
+ analyze(context, facts) {
40127
+ const issues = [];
40128
+ const expressions = facts.v2.logic?.logicalExpressions;
40129
+ if (!expressions) return issues;
40130
+ for (const expression of expressions) {
40131
+ if (expression.depth >= context.minChainLength && expression.isOptionalChainLike) {
40132
+ issues.push({
40133
+ ruleId: "ts/optional-chain-overuse",
40134
+ category: "logic",
40135
+ severity: "low",
40136
+ aiSpecific: true,
40137
+ message: `Optional chain depth ${expression.depth} \u2014 break with an intermediate variable or guard clause`,
40138
+ line: expression.line,
40139
+ column: expression.column,
40140
+ 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."
40141
+ });
40142
+ }
40143
+ }
40144
+ return issues;
40145
+ }
40146
+ });
40147
+
39780
40148
  // src/rules/typo/calc-fontsize.ts
39781
40149
  var FONT_SIZE_RE = /\bfont-size\s*:\s*[^;]*\bcalc\s*\(/i;
39782
40150
  var calcFontsizeRule = createRule({
@@ -41314,9 +41682,18 @@ var builtinRules = [
41314
41682
  unusedLocalRule,
41315
41683
  unusedParameterRule,
41316
41684
  brokenLinkRule,
41317
- expiredCodeExampleRule,
41318
41685
  staleFunctionReferenceRule,
41319
41686
  stalePackageReferenceRule,
41687
+ dupIdenticalBlockRule,
41688
+ goErrorWrapWithoutContextRule,
41689
+ goNilSliceVsEmptyRule,
41690
+ goStructTagInconsistencyRule,
41691
+ javaArraylistVsLinkedlistRule,
41692
+ javaEmptyCatchBlockRule,
41693
+ javaLegacyDateApiRule,
41694
+ javaRawTypeOveruseRule,
41695
+ javaStringConcatLoopRule,
41696
+ javaSystemOutPrintlnRule,
41320
41697
  gapMonopolyRule,
41321
41698
  mathElementUniformityRule,
41322
41699
  mathGridUniformityRule,
@@ -41326,7 +41703,6 @@ var builtinRules = [
41326
41703
  ghostDefensiveRule,
41327
41704
  heapsDeviationRule,
41328
41705
  keyPropMissingRule,
41329
- ksDistributionShiftRule,
41330
41706
  mathAnyDensityRule,
41331
41707
  mathConsoleLogStormRule,
41332
41708
  mathGiniClassUsageRule,
@@ -41360,6 +41736,11 @@ var builtinRules = [
41360
41736
  fakePlaceholderRule,
41361
41737
  missingEdgeCaseRule,
41362
41738
  weakAssertionRule,
41739
+ tsEnumVsAsConstRule,
41740
+ tsExcessiveTypeAssertionRule,
41741
+ tsImportTypeMisuseRule,
41742
+ tsNeverVsUnknownRule,
41743
+ tsOptionalChainOveruseRule,
41363
41744
  calcFontsizeRule,
41364
41745
  calcRawPxRule,
41365
41746
  clampOffscaleRule,
@@ -41530,7 +41911,7 @@ var signal_strength_default = {
41530
41911
  precision: 0.9966,
41531
41912
  lastCalibratedAt: "2026-07-01T00:00:00Z",
41532
41913
  verdict: "USEFUL",
41533
- _calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=294, FP=1, P=99.7%, FPR=0.00%, lift=251225.49. v7 was USEFUL (TP=231, FP=1, lift=182622.43). v8 was USEFUL (TP=63, FP=0).",
41914
+ _calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=294, FP=1, P=99.7%, FPR=0.00%, lift=251225.49. v7 was USEFUL (TP=231, FP=1, lift=182622.43). v8 was USEFUL (TP=63, FP=0). v0.19 default-on (explicit defaultOff: false): P 99.7% / 251k lift \u2014 core AI fingerprint.",
41534
41915
  aiSpecific: true,
41535
41916
  _v7Verdict: "USEFUL",
41536
41917
  _v7Lift: 182622.43,
@@ -41538,7 +41919,8 @@ var signal_strength_default = {
41538
41919
  _v7FpRate: 0,
41539
41920
  _v7Precision: 0.9957,
41540
41921
  _v8Verdict: "USEFUL",
41541
- _v8Lift: 99999
41922
+ _v8Lift: 99999,
41923
+ defaultOff: false
41542
41924
  },
41543
41925
  "ai/errors-near-eof": {
41544
41926
  recall: 0.0948,
@@ -41787,7 +42169,7 @@ var signal_strength_default = {
41787
42169
  precision: 0.9512,
41788
42170
  lastCalibratedAt: "2026-07-01T00:00:00Z",
41789
42171
  verdict: "USEFUL",
41790
- _calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=468, FP=24, P=95.1%, FPR=0.01%, lift=9990.98. v7 was USEFUL (TP=314, FP=24, lift=7099.57). v8 was USEFUL (TP=154, FP=0).",
42172
+ _calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=468, FP=24, P=95.1%, FPR=0.01%, lift=9990.98. v7 was USEFUL (TP=314, FP=24, lift=7099.57). v8 was USEFUL (TP=154, FP=0). v0.19 default-on (explicit defaultOff: false): P 95.1% / 9990x lift \u2014 UI bug.",
41791
42173
  aiSpecific: true,
41792
42174
  _v7Verdict: "USEFUL",
41793
42175
  _v7Lift: 7099.57,
@@ -41795,7 +42177,8 @@ var signal_strength_default = {
41795
42177
  _v7FpRate: 1e-4,
41796
42178
  _v7Precision: 0.929,
41797
42179
  _v8Verdict: "USEFUL",
41798
- _v8Lift: 99999
42180
+ _v8Lift: 99999,
42181
+ defaultOff: false
41799
42182
  },
41800
42183
  "context/import-path-mismatch": {
41801
42184
  recall: 0.0681,
@@ -41979,7 +42362,7 @@ var signal_strength_default = {
41979
42362
  precision: 0.8834,
41980
42363
  lastCalibratedAt: "2026-07-01T00:00:00Z",
41981
42364
  verdict: "USEFUL",
41982
- _calibrationNote: "v8.5 (v0.18.9): v85 verdict=USEFUL, v7 was DORMANT, v8 was USEFUL. v0.18.8 v8a first measurement (1000 files): see docs/research/v0.18.8-dead-rules-measurement.md.",
42365
+ _calibrationNote: "v8.5 (v0.18.9): v85 verdict=USEFUL, v7 was DORMANT, v8 was USEFUL. v0.18.8 v8a first measurement (1000 files): see docs/research/v0.18.8-dead-rules-measurement.md. v0.19 default-on (explicit defaultOff: false): P 88.3% / FPR 0.74% / 120x lift \u2014 code hygiene.",
41983
42366
  aiSpecific: true,
41984
42367
  _v7Verdict: "DORMANT",
41985
42368
  _v7Lift: 1,
@@ -41987,7 +42370,8 @@ var signal_strength_default = {
41987
42370
  _v7FpRate: 0,
41988
42371
  _v7Precision: 0,
41989
42372
  _v8Verdict: "USEFUL",
41990
- _v8Lift: 32.74
42373
+ _v8Lift: 32.74,
42374
+ defaultOff: false
41991
42375
  },
41992
42376
  "dead/unused-parameter": {
41993
42377
  recall: 8e-4,
@@ -42023,24 +42407,6 @@ var signal_strength_default = {
42023
42407
  _v8Verdict: "OK",
42024
42408
  _v8Lift: 48.52
42025
42409
  },
42026
- "docs/expired-code-example": {
42027
- recall: 0,
42028
- fpRate: 0,
42029
- ratio: 0,
42030
- precision: 0,
42031
- lastCalibratedAt: "2026-07-01T00:00:00Z",
42032
- verdict: "INVERTED",
42033
- _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).",
42034
- aiSpecific: false,
42035
- _v7Verdict: "DORMANT",
42036
- _v7Lift: 1,
42037
- _v7Recall: 0,
42038
- _v7FpRate: 0,
42039
- _v7Precision: 0,
42040
- _v8Verdict: "INVERTED",
42041
- _v8Lift: 0,
42042
- defaultOff: true
42043
- },
42044
42410
  "docs/stale-function-reference": {
42045
42411
  recall: 26e-4,
42046
42412
  fpRate: 5e-4,
@@ -42075,6 +42441,186 @@ var signal_strength_default = {
42075
42441
  _v8Verdict: "OK",
42076
42442
  _v8Lift: 255.9
42077
42443
  },
42444
+ "dup/identical-block": {
42445
+ recall: 0,
42446
+ fpRate: 0,
42447
+ ratio: 1,
42448
+ precision: 0,
42449
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42450
+ verdict: "DORMANT",
42451
+ _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.",
42452
+ aiSpecific: false,
42453
+ _v7Verdict: "DORMANT",
42454
+ _v7Lift: 1,
42455
+ _v7Recall: 0,
42456
+ _v7FpRate: 0,
42457
+ _v7Precision: 0,
42458
+ _v8Verdict: "DORMANT",
42459
+ _v8Lift: 1,
42460
+ defaultOff: true
42461
+ },
42462
+ "go/error-wrap-without-context": {
42463
+ recall: 0,
42464
+ fpRate: 0,
42465
+ ratio: 1,
42466
+ precision: 0,
42467
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42468
+ verdict: "DORMANT",
42469
+ _calibrationNote: "v0.19: new rule (fmt.Errorf wrap with generic message \u2014 needs operation context). Not yet calibrated. Scheduled for v9 calibration.",
42470
+ aiSpecific: true,
42471
+ _v7Verdict: "DORMANT",
42472
+ _v7Lift: 1,
42473
+ _v7Recall: 0,
42474
+ _v7FpRate: 0,
42475
+ _v7Precision: 0,
42476
+ _v8Verdict: "DORMANT",
42477
+ _v8Lift: 1,
42478
+ defaultOff: true
42479
+ },
42480
+ "go/nil-slice-vs-empty": {
42481
+ recall: 0,
42482
+ fpRate: 0,
42483
+ ratio: 1,
42484
+ precision: 0,
42485
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42486
+ verdict: "DORMANT",
42487
+ _calibrationNote: "v0.19: new rule (Variable declared nil slice but assigned empty slice). Not yet calibrated. Scheduled for v9 calibration.",
42488
+ aiSpecific: true,
42489
+ _v7Verdict: "DORMANT",
42490
+ _v7Lift: 1,
42491
+ _v7Recall: 0,
42492
+ _v7FpRate: 0,
42493
+ _v7Precision: 0,
42494
+ _v8Verdict: "DORMANT",
42495
+ _v8Lift: 1,
42496
+ defaultOff: true
42497
+ },
42498
+ "go/struct-tag-inconsistency": {
42499
+ recall: 0,
42500
+ fpRate: 0,
42501
+ ratio: 1,
42502
+ precision: 0,
42503
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42504
+ verdict: "DORMANT",
42505
+ _calibrationNote: "v0.19: new rule (Struct fields mix json tag styles). Not yet calibrated. Scheduled for v9 calibration.",
42506
+ aiSpecific: true,
42507
+ _v7Verdict: "DORMANT",
42508
+ _v7Lift: 1,
42509
+ _v7Recall: 0,
42510
+ _v7FpRate: 0,
42511
+ _v7Precision: 0,
42512
+ _v8Verdict: "DORMANT",
42513
+ _v8Lift: 1,
42514
+ defaultOff: true
42515
+ },
42516
+ "java/arraylist-vs-linkedlist": {
42517
+ recall: 0,
42518
+ fpRate: 0,
42519
+ ratio: 1,
42520
+ precision: 0,
42521
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42522
+ verdict: "DORMANT",
42523
+ _calibrationNote: "v0.20: new rule (new LinkedList<>() \u2014 use ArrayList (Effective Java Item 28)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
42524
+ aiSpecific: true,
42525
+ _v7Verdict: "DORMANT",
42526
+ _v7Lift: 1,
42527
+ _v7Recall: 0,
42528
+ _v7FpRate: 0,
42529
+ _v7Precision: 0,
42530
+ _v8Verdict: "DORMANT",
42531
+ _v8Lift: 1,
42532
+ defaultOff: true
42533
+ },
42534
+ "java/empty-catch-block": {
42535
+ recall: 0,
42536
+ fpRate: 0,
42537
+ ratio: 1,
42538
+ precision: 0,
42539
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42540
+ verdict: "DORMANT",
42541
+ _calibrationNote: "v0.20: new rule (Empty catch block \u2014 silently swallows exceptions). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
42542
+ aiSpecific: true,
42543
+ _v7Verdict: "DORMANT",
42544
+ _v7Lift: 1,
42545
+ _v7Recall: 0,
42546
+ _v7FpRate: 0,
42547
+ _v7Precision: 0,
42548
+ _v8Verdict: "DORMANT",
42549
+ _v8Lift: 1,
42550
+ defaultOff: true
42551
+ },
42552
+ "java/legacy-date-api": {
42553
+ recall: 0,
42554
+ fpRate: 0,
42555
+ ratio: 1,
42556
+ precision: 0,
42557
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42558
+ verdict: "DORMANT",
42559
+ _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.",
42560
+ aiSpecific: true,
42561
+ _v7Verdict: "DORMANT",
42562
+ _v7Lift: 1,
42563
+ _v7Recall: 0,
42564
+ _v7FpRate: 0,
42565
+ _v7Precision: 0,
42566
+ _v8Verdict: "DORMANT",
42567
+ _v8Lift: 1,
42568
+ defaultOff: true
42569
+ },
42570
+ "java/raw-type-overuse": {
42571
+ recall: 0,
42572
+ fpRate: 0,
42573
+ ratio: 1,
42574
+ precision: 0,
42575
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42576
+ verdict: "DORMANT",
42577
+ _calibrationNote: "v0.20: new rule (Raw type usage \u2014 use generics). DORMANT until v9 Java corpus calibration.",
42578
+ aiSpecific: true,
42579
+ _v7Verdict: "DORMANT",
42580
+ _v7Lift: 1,
42581
+ _v7Recall: 0,
42582
+ _v7FpRate: 0,
42583
+ _v7Precision: 0,
42584
+ _v8Verdict: "DORMANT",
42585
+ _v8Lift: 1,
42586
+ defaultOff: true
42587
+ },
42588
+ "java/string-concat-loop": {
42589
+ recall: 0,
42590
+ fpRate: 0,
42591
+ ratio: 1,
42592
+ precision: 0,
42593
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42594
+ verdict: "DORMANT",
42595
+ _calibrationNote: "v0.20: new rule (String concat in loop \u2014 use StringBuilder). DORMANT until v9 Java corpus calibration.",
42596
+ aiSpecific: true,
42597
+ _v7Verdict: "DORMANT",
42598
+ _v7Lift: 1,
42599
+ _v7Recall: 0,
42600
+ _v7FpRate: 0,
42601
+ _v7Precision: 0,
42602
+ _v8Verdict: "DORMANT",
42603
+ _v8Lift: 1,
42604
+ defaultOff: true
42605
+ },
42606
+ "java/system-out-println": {
42607
+ recall: 0,
42608
+ fpRate: 0,
42609
+ ratio: 1,
42610
+ precision: 0,
42611
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42612
+ verdict: "DORMANT",
42613
+ _calibrationNote: "v0.20: new rule (System.out.println in production code \u2014 use a logger). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
42614
+ aiSpecific: true,
42615
+ _v7Verdict: "DORMANT",
42616
+ _v7Lift: 1,
42617
+ _v7Recall: 0,
42618
+ _v7FpRate: 0,
42619
+ _v7Precision: 0,
42620
+ _v8Verdict: "DORMANT",
42621
+ _v8Lift: 1,
42622
+ defaultOff: true
42623
+ },
42078
42624
  "layout/forced-layout": {
42079
42625
  recall: 0,
42080
42626
  fpRate: 0,
@@ -42203,7 +42749,7 @@ var signal_strength_default = {
42203
42749
  precision: 0.8889,
42204
42750
  lastCalibratedAt: "2026-07-01T00:00:00Z",
42205
42751
  verdict: "USEFUL",
42206
- _calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=16, FP=2, P=88.9%, FPR=0.00%, lift=112035.56. v7 was USEFUL (TP=15, FP=2, lift=80917.50). v8 was USEFUL (TP=1, FP=0).",
42752
+ _calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=16, FP=2, P=88.9%, FPR=0.00%, lift=112035.56. v7 was USEFUL (TP=15, FP=2, lift=80917.50). v8 was USEFUL (TP=1, FP=0). v0.19 default-on (explicit defaultOff: false): P 88.9% / 112k lift \u2014 code smell.",
42207
42753
  aiSpecific: true,
42208
42754
  _v7Verdict: "USEFUL",
42209
42755
  _v7Lift: 80917.5,
@@ -42211,7 +42757,8 @@ var signal_strength_default = {
42211
42757
  _v7FpRate: 0,
42212
42758
  _v7Precision: 0.8824,
42213
42759
  _v8Verdict: "USEFUL",
42214
- _v8Lift: 99999
42760
+ _v8Lift: 99999,
42761
+ defaultOff: false
42215
42762
  },
42216
42763
  "logic/heaps-deviation": {
42217
42764
  recall: 0.0126,
@@ -42247,24 +42794,6 @@ var signal_strength_default = {
42247
42794
  _v8Verdict: "USEFUL",
42248
42795
  _v8Lift: 1760.69
42249
42796
  },
42250
- "logic/ks-distribution-shift": {
42251
- recall: 0.6889,
42252
- fpRate: 0.4411,
42253
- ratio: 1.46,
42254
- precision: 0.6457,
42255
- lastCalibratedAt: "2026-07-01T00:00:00Z",
42256
- verdict: "NOISY",
42257
- _calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=202658, FP=111193, P=64.6%, FPR=44.11%, lift=1.46. v7 was USEFUL (TP=152391, FP=62358, lift=2.09). v8 was INVERTED (TP=50267, FP=48835).",
42258
- aiSpecific: false,
42259
- _v7Verdict: "USEFUL",
42260
- _v7Lift: 2.09,
42261
- _v7Recall: 0.6431,
42262
- _v7FpRate: 0.34,
42263
- _v7Precision: 0.7096,
42264
- _v8Verdict: "INVERTED",
42265
- _v8Lift: 0.71,
42266
- defaultOff: true
42267
- },
42268
42797
  "logic/math-any-density": {
42269
42798
  recall: 17e-4,
42270
42799
  fpRate: 13e-4,
@@ -42630,7 +43159,7 @@ var signal_strength_default = {
42630
43159
  precision: 1,
42631
43160
  lastCalibratedAt: "2026-07-01T00:00:00Z",
42632
43161
  verdict: "USEFUL",
42633
- _calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=1, FP=0, P=100.0%, FPR=0.00%, lift=inf. v7 was USEFUL (TP=1, FP=0, lift=inf). v8 was DORMANT (TP=0, FP=0).",
43162
+ _calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=1, FP=0, P=100.0%, FPR=0.00%, lift=inf. v7 was USEFUL (TP=1, FP=0, lift=inf). v8 was DORMANT (TP=0, FP=0). v0.19 default-on (explicit defaultOff: false): P 100% / inf lift \u2014 security must always be on.",
42634
43163
  aiSpecific: true,
42635
43164
  _v7Verdict: "USEFUL",
42636
43165
  _v7Lift: 99999,
@@ -42638,7 +43167,8 @@ var signal_strength_default = {
42638
43167
  _v7FpRate: 0,
42639
43168
  _v7Precision: 1,
42640
43169
  _v8Verdict: "DORMANT",
42641
- _v8Lift: 1
43170
+ _v8Lift: 1,
43171
+ defaultOff: false
42642
43172
  },
42643
43173
  "security/hardcoded-secret": {
42644
43174
  recall: 14e-4,
@@ -42828,6 +43358,96 @@ var signal_strength_default = {
42828
43358
  _v8Verdict: "USEFUL",
42829
43359
  _v8Lift: 651.94
42830
43360
  },
43361
+ "ts/enum-vs-as-const": {
43362
+ recall: 0,
43363
+ fpRate: 0,
43364
+ ratio: 1,
43365
+ precision: 0,
43366
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
43367
+ verdict: "DORMANT",
43368
+ _calibrationNote: "v0.19: new rule (Uses `enum` \u2014 modern TS prefers `as const`). Not yet calibrated. Scheduled for v9 calibration.",
43369
+ aiSpecific: true,
43370
+ _v7Verdict: "DORMANT",
43371
+ _v7Lift: 1,
43372
+ _v7Recall: 0,
43373
+ _v7FpRate: 0,
43374
+ _v7Precision: 0,
43375
+ _v8Verdict: "DORMANT",
43376
+ _v8Lift: 1,
43377
+ defaultOff: true
43378
+ },
43379
+ "ts/excessive-type-assertion": {
43380
+ recall: 0,
43381
+ fpRate: 0,
43382
+ ratio: 1,
43383
+ precision: 0,
43384
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
43385
+ verdict: "DORMANT",
43386
+ _calibrationNote: "v0.19: new rule (Function with >3 `as` assertions \u2014 AI fighting the type system). Not yet calibrated. Scheduled for v9 calibration.",
43387
+ aiSpecific: true,
43388
+ _v7Verdict: "DORMANT",
43389
+ _v7Lift: 1,
43390
+ _v7Recall: 0,
43391
+ _v7FpRate: 0,
43392
+ _v7Precision: 0,
43393
+ _v8Verdict: "DORMANT",
43394
+ _v8Lift: 1,
43395
+ defaultOff: true
43396
+ },
43397
+ "ts/import-type-misuse": {
43398
+ recall: 0,
43399
+ fpRate: 0,
43400
+ ratio: 1,
43401
+ precision: 0,
43402
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
43403
+ verdict: "DORMANT",
43404
+ _calibrationNote: "v0.19: new rule (Inline `import { type X }` \u2014 prefer separate `import type`). Not yet calibrated. Scheduled for v9 calibration.",
43405
+ aiSpecific: true,
43406
+ _v7Verdict: "DORMANT",
43407
+ _v7Lift: 1,
43408
+ _v7Recall: 0,
43409
+ _v7FpRate: 0,
43410
+ _v7Precision: 0,
43411
+ _v8Verdict: "DORMANT",
43412
+ _v8Lift: 1,
43413
+ defaultOff: true
43414
+ },
43415
+ "ts/never-vs-unknown": {
43416
+ recall: 0,
43417
+ fpRate: 0,
43418
+ ratio: 1,
43419
+ precision: 0,
43420
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
43421
+ verdict: "DORMANT",
43422
+ _calibrationNote: "v0.19: new rule (Return type `never` but body has no throw/loop/exit). Not yet calibrated. Scheduled for v9 calibration.",
43423
+ aiSpecific: true,
43424
+ _v7Verdict: "DORMANT",
43425
+ _v7Lift: 1,
43426
+ _v7Recall: 0,
43427
+ _v7FpRate: 0,
43428
+ _v7Precision: 0,
43429
+ _v8Verdict: "DORMANT",
43430
+ _v8Lift: 1,
43431
+ defaultOff: true
43432
+ },
43433
+ "ts/optional-chain-overuse": {
43434
+ recall: 0,
43435
+ fpRate: 0,
43436
+ ratio: 1,
43437
+ precision: 0,
43438
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
43439
+ verdict: "DORMANT",
43440
+ _calibrationNote: "v0.19: new rule (Optional chain depth >= 5 \u2014 AI chains ?. rather than narrowing). Not yet calibrated. Scheduled for v9 calibration.",
43441
+ aiSpecific: true,
43442
+ _v7Verdict: "DORMANT",
43443
+ _v7Lift: 1,
43444
+ _v7Recall: 0,
43445
+ _v7FpRate: 0,
43446
+ _v7Precision: 0,
43447
+ _v8Verdict: "DORMANT",
43448
+ _v8Lift: 1,
43449
+ defaultOff: true
43450
+ },
42831
43451
  "typo/calc-fontsize": {
42832
43452
  recall: 0,
42833
43453
  fpRate: 0,
@@ -43130,7 +43750,7 @@ var signal_strength_default = {
43130
43750
  precision: 0.9774,
43131
43751
  lastCalibratedAt: "2026-07-01T00:00:00Z",
43132
43752
  verdict: "USEFUL",
43133
- _calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=130, FP=3, P=97.7%, FPR=0.00%, lift=82131.33. v7 was USEFUL (TP=96, FP=3, lift=59285.01). v8 was USEFUL (TP=34, FP=0).",
43753
+ _calibrationNote: "v8.5 calibration (v0.18.9, 2026-07-01): v7+v8 combined corpus (252080 neg + 294178 pos). v8.5 TP=130, FP=3, P=97.7%, FPR=0.00%, lift=82131.33. v7 was USEFUL (TP=96, FP=3, lift=59285.01). v8 was USEFUL (TP=34, FP=0). v0.19 default-on (explicit defaultOff: false): P 97.7% / 82k lift \u2014 UI consistency.",
43134
43754
  aiSpecific: false,
43135
43755
  _v7Verdict: "USEFUL",
43136
43756
  _v7Lift: 59285.01,
@@ -43138,7 +43758,8 @@ var signal_strength_default = {
43138
43758
  _v7FpRate: 0,
43139
43759
  _v7Precision: 0.9697,
43140
43760
  _v8Verdict: "USEFUL",
43141
- _v8Lift: 99999
43761
+ _v8Lift: 99999,
43762
+ defaultOff: false
43142
43763
  },
43143
43764
  "visual/spacing-scale-violation": {
43144
43765
  recall: 86e-4,
@@ -43256,7 +43877,7 @@ function loadSignalStrength() {
43256
43877
  function buildParserCacheConfig(cwd) {
43257
43878
  const envVal = process.env.SLOP_AUDIT_CACHE;
43258
43879
  const enabled = envVal === "1" || envVal === "true";
43259
- const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? join12(cwd, ".slopbrick", "cache", "ast");
43880
+ const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? join11(cwd, ".slopbrick", "cache", "ast");
43260
43881
  return { enabled, root };
43261
43882
  }
43262
43883
  function applyRuleOverrides(issues, rules) {
@@ -43274,7 +43895,7 @@ function applyRuleOverrides(issues, rules) {
43274
43895
  }
43275
43896
  async function scanFile(filePath, config, registry, cwd = process.cwd()) {
43276
43897
  const cache = buildParserCacheConfig(cwd);
43277
- const ext = extname6(filePath).toLowerCase();
43898
+ const ext = extname4(filePath).toLowerCase();
43278
43899
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
43279
43900
  ".swift",
43280
43901
  ".kt",