slopbrick 0.19.0 → 0.21.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";
@@ -7234,8 +6828,396 @@ function dispatchNode(node, parent, path, vctx) {
7234
6828
  return false;
7235
6829
  }
7236
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
+
7237
7220
  // src/engine/visitors/v2-build.ts
7238
- init_rust();
7239
7221
  var TAILWIND_COLOR_RE = /^(?:bg|text|border|ring|from|to|via|fill|stroke)-([a-z]+-\d+|white|black|transparent|current|\[.+?\])$/;
7240
7222
  var TAILWIND_SPACING_RE = /^(?:[pm][xytrbl]?|gap|space-[xy])-(\d+(?:\.\d+)?)$/;
7241
7223
  var TAILWIND_RADIUS_RE = /^(?:rounded(?:-[a-z]+)?)-(.+)$/;
@@ -32871,8 +32853,8 @@ function getCorpusBaselines() {
32871
32853
 
32872
32854
  // src/rules/ai/comment-ratio.ts
32873
32855
  var MIN_FILE_LINES = 20;
32874
- var FALLBACK_LOW = 0.05;
32875
- var FALLBACK_HIGH = 0.45;
32856
+ var FALLBACK_LOW = 0.02;
32857
+ var FALLBACK_HIGH = 0.55;
32876
32858
  var LINE_COMMENT_RE = /^\s*(?:\/\/|#|--|;|%)/;
32877
32859
  var BLOCK_COMMENT_OPEN_RE = /\/\*/;
32878
32860
  var aiCommentRatioRule = createRule({
@@ -33001,9 +32983,9 @@ function std(xs) {
33001
32983
 
33002
32984
  // src/rules/ai/compression-profile.ts
33003
32985
  var MIN_BYTES = 2e3;
33004
- var GZIP_RATIO_AI_THRESHOLD = 0.5;
33005
- var MEAN_LINE_NCD_HUMAN_MAX = 0.3;
33006
- var NCD_CV_AI_MAX = 0.5;
32986
+ var GZIP_RATIO_AI_THRESHOLD = 0.6;
32987
+ var MEAN_LINE_NCD_HUMAN_MAX = 0.25;
32988
+ var NCD_CV_AI_MAX = 0.4;
33007
32989
  var aiCompressionProfileRule = createRule({
33008
32990
  id: "ai/compression-profile",
33009
32991
  category: "ai",
@@ -33158,6 +33140,13 @@ var aiDefaultReactStackRule = createRule({
33158
33140
  category: "ai",
33159
33141
  severity: "low",
33160
33142
  aiSpecific: true,
33143
+ // v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.001
33144
+ // (fires 1 time per 1,000 files). The "≥3 of default stack"
33145
+ // threshold catches almost nothing on real codebases — the
33146
+ // default stack is now ubiquitous, so the signal is near-zero.
33147
+ // Disable until the threshold is lowered or the rule is split
33148
+ // into per-library detectors.
33149
+ defaultOff: true,
33161
33150
  description: "File uses \u22653 of the default React stack (Next.js + TanStack Query + Zustand + shadcn/ui) \u2014 Sascha 2025 + Nam et al. MSR 2026",
33162
33151
  create(context) {
33163
33152
  return context;
@@ -33410,6 +33399,10 @@ var aiLibraryReinventionRule = createRule({
33410
33399
  category: "ai",
33411
33400
  severity: "medium",
33412
33401
  aiSpecific: true,
33402
+ // v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.000
33403
+ // (fires never on the corpus). The "≥2 reinvented patterns"
33404
+ // threshold is too strict. Disable until rewritten.
33405
+ defaultOff: true,
33413
33406
  description: "File reinvents \u22652 common patterns (date picker, form validation, chart, modal, etc.) without importing the canonical library \u2014 GitClear 2025 + Cui et al. 2025",
33414
33407
  create(context) {
33415
33408
  return context;
@@ -33596,6 +33589,12 @@ var aiMarkdownLeakageRule = createRule({
33596
33589
  category: "ai",
33597
33590
  severity: "high",
33598
33591
  aiSpecific: true,
33592
+ // v0.20.0 calibration: v8.5 verdict = INVERTED (lift 65,504× but
33593
+ // recall 0.000 — fires never on the corpus, and when it does fire
33594
+ // it's wrong). The rule was on by default, contributing false
33595
+ // positives to the self-scan. Disable until the rule is rewritten
33596
+ // or the v9 corpus shows it works.
33597
+ defaultOff: true,
33599
33598
  description: "Stray Markdown fence or bare language name at top of file \u2014 AI tools leak the fenced-block format into code",
33600
33599
  create(context) {
33601
33600
  return context;
@@ -34021,6 +34020,11 @@ var aiTextLikeRatioRule = createRule({
34021
34020
  category: "ai",
34022
34021
  severity: "low",
34023
34022
  aiSpecific: true,
34023
+ // v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.000
34024
+ // (fires never on the corpus). The rule is too specific to ever
34025
+ // fire on real code — the prose-detection heuristic is too
34026
+ // conservative. Disable until rewritten.
34027
+ defaultOff: true,
34024
34028
  description: "High ratio of prose-like lines embedded in code \u2014 AI tools often leave natural-language explanations in source files (Yotkova 2026)",
34025
34029
  create(context) {
34026
34030
  return context;
@@ -35079,125 +35083,17 @@ var unusedParameterRule = createRule({
35079
35083
  });
35080
35084
 
35081
35085
  // src/rules/docs/broken-link.ts
35082
- import { existsSync as existsSync4 } from "fs";
35083
- import { dirname as dirname4, join as join7, resolve as resolve2 } from "path";
35086
+ import { existsSync as existsSync3 } from "fs";
35087
+ import { dirname as dirname3, join as join5, resolve } from "path";
35084
35088
 
35085
35089
  // src/engine/doc-freshness.ts
35086
- import { readFileSync as readFileSync5, existsSync as existsSync3 } from "fs";
35087
- import { join as join6, dirname as dirname3, relative as relative2 } from "path";
35088
- import { globby as globby2 } from "globby";
35089
-
35090
- // src/mcp/patterns.ts
35091
- import { readFileSync as readFileSync3 } from "fs";
35092
- import { basename as basename2, extname as extname2 } from "path";
35093
-
35094
- // src/engine/discover.ts
35090
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
35091
+ import { join as join4, dirname as dirname2, relative } from "path";
35095
35092
  import { globby } from "globby";
35096
- import { minimatch } from "minimatch";
35097
- import { resolve, extname, relative, sep, basename, dirname as dirname2, join as join3 } from "path";
35098
- import { readFileSync } from "fs";
35099
- var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte", ".astro", ".html"]);
35100
- var BACKEND_EXTENSIONS = /* @__PURE__ */ new Set([
35101
- ".py",
35102
- ".go",
35103
- // v0.14.0
35104
- ".swift",
35105
- ".kt",
35106
- ".kts",
35107
- ".dart",
35108
- ".rs",
35109
- ".cpp",
35110
- ".cc",
35111
- ".cxx",
35112
- ".c",
35113
- ".h",
35114
- ".hpp",
35115
- ".hxx",
35116
- ".java",
35117
- ".rb",
35118
- ".php"
35119
- ]);
35120
- var ALL_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
35121
- ...SOURCE_EXTENSIONS,
35122
- ...BACKEND_EXTENSIONS
35123
- ]);
35124
-
35125
- // src/config/conventions.ts
35126
- import { existsSync, readFileSync as readFileSync2 } from "fs";
35127
- import { join as join4 } from "path";
35128
-
35129
- // src/mcp/patterns.ts
35130
- 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;
35131
- var DYNAMIC_IMPORT_RE = /import\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
35132
- var COMMONJS_REQUIRE_RE = /require\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
35133
- function extractImports(source) {
35134
- const seen = /* @__PURE__ */ new Set();
35135
- const out = [];
35136
- const push = (spec) => {
35137
- if (spec.startsWith(".") || spec.startsWith("/")) return;
35138
- if (seen.has(spec)) return;
35139
- seen.add(spec);
35140
- out.push(spec);
35141
- };
35142
- for (const re of [ESM_IMPORT_RE, DYNAMIC_IMPORT_RE, COMMONJS_REQUIRE_RE]) {
35143
- re.lastIndex = 0;
35144
- let m;
35145
- while ((m = re.exec(source)) !== null) {
35146
- push(m[2]);
35147
- }
35148
- }
35149
- return out;
35150
- }
35151
-
35152
- // src/rules/docs/expired-code-example.ts
35153
- var CODE_LANGS = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "javascript", "typescript"]);
35154
- function stripSubpath(spec) {
35155
- if (spec.startsWith("@")) return spec.split("/").slice(0, 2).join("/");
35156
- return spec.split("/")[0] ?? spec;
35157
- }
35158
- var expiredCodeExampleRule = createRule({
35159
- id: "docs/expired-code-example",
35160
- category: "docs",
35161
- severity: "medium",
35162
- aiSpecific: false,
35163
- description: "A fenced code example imports a package that is not declared in package.json.",
35164
- create(context) {
35165
- return { ...context, packages: declaredPackages(context.cwd) };
35166
- },
35167
- analyze(context, facts) {
35168
- const issues = [];
35169
- const source = facts.v2?._source;
35170
- if (!source) return issues;
35171
- const packages = declaredPackages(context.cwd);
35172
- const packageName = context.packageName;
35173
- if (packageName) packages.add(packageName);
35174
- const blocks = extractFencedCodeBlocks(source);
35175
- for (const block of blocks) {
35176
- if (!CODE_LANGS.has(block.lang)) continue;
35177
- if (block.body.split("\n").length < 2) continue;
35178
- const imports = extractImports(block.body);
35179
- for (const imp of imports) {
35180
- const pkgName = stripSubpath(imp);
35181
- if (packages.has(pkgName)) continue;
35182
- issues.push({
35183
- ruleId: "docs/expired-code-example",
35184
- category: "docs",
35185
- severity: "medium",
35186
- aiSpecific: false,
35187
- message: `Code example imports \`${imp}\` but \`${pkgName}\` is not in package.json.`,
35188
- line: block.line,
35189
- column: block.column,
35190
- advice: `Add \`${pkgName}\` to package.json or update the example.`
35191
- });
35192
- }
35193
- }
35194
- return issues;
35195
- }
35196
- });
35197
35093
 
35198
35094
  // src/rules/docs/stale-function-reference.ts
35199
- import { readFileSync as readFileSync4, readdirSync, existsSync as existsSync2 } from "fs";
35200
- import { join as join5, extname as extname3 } from "path";
35095
+ import { readFileSync, readdirSync, existsSync } from "fs";
35096
+ import { join as join3, extname } from "path";
35201
35097
  var RESERVED = /* @__PURE__ */ new Set([
35202
35098
  // JS reserved words
35203
35099
  "true",
@@ -35770,7 +35666,7 @@ var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs",
35770
35666
  var SOURCE_ROOTS = ["src", "lib", "app", "components"];
35771
35667
  var CAP = 200;
35772
35668
  function walk(dir, out, cap) {
35773
- if (!existsSync2(dir) || out.length >= cap) return;
35669
+ if (!existsSync(dir) || out.length >= cap) return;
35774
35670
  let entries;
35775
35671
  try {
35776
35672
  entries = readdirSync(dir, { withFileTypes: true });
@@ -35780,19 +35676,19 @@ function walk(dir, out, cap) {
35780
35676
  for (const entry of entries) {
35781
35677
  if (out.length >= cap) return;
35782
35678
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
35783
- const full = join5(dir, entry.name);
35679
+ const full = join3(dir, entry.name);
35784
35680
  if (entry.isDirectory()) walk(full, out, cap);
35785
- else if (entry.isFile() && SOURCE_EXTS.has(extname3(entry.name))) out.push(full);
35681
+ else if (entry.isFile() && SOURCE_EXTS.has(extname(entry.name))) out.push(full);
35786
35682
  }
35787
35683
  }
35788
35684
  function collectExports(cwd) {
35789
35685
  const out = /* @__PURE__ */ new Set();
35790
35686
  const files = [];
35791
- for (const root of SOURCE_ROOTS) walk(join5(cwd, root), files, CAP);
35687
+ for (const root of SOURCE_ROOTS) walk(join3(cwd, root), files, CAP);
35792
35688
  for (const file of files) {
35793
35689
  let source;
35794
35690
  try {
35795
- source = readFileSync4(file, "utf-8");
35691
+ source = readFileSync(file, "utf-8");
35796
35692
  } catch {
35797
35693
  continue;
35798
35694
  }
@@ -35805,7 +35701,7 @@ function collectExports(cwd) {
35805
35701
  /\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g,
35806
35702
  // v0.18.6: also collect field names from `export interface` and
35807
35703
  // `export type` declarations. Without this, fields like
35808
- // `crossFileDrift`, `aiQuality`, `engineeringHygiene` are
35704
+ // `crossFileDrift`, `aiSlopScore`, `engineeringHygiene` are
35809
35705
  // flagged as stale even though they're valid type fields.
35810
35706
  /^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\s*[?:]/gm
35811
35707
  ]) {
@@ -36055,38 +35951,6 @@ function extractInlineCodeSpans(source) {
36055
35951
  }
36056
35952
  return hits;
36057
35953
  }
36058
- function extractFencedCodeBlocks(source) {
36059
- const blocks = [];
36060
- const lines = source.split("\n");
36061
- let i = 0;
36062
- while (i < lines.length) {
36063
- const line = lines[i] ?? "";
36064
- const fenceMatch = /^```(\w*)\s*$/.exec(line);
36065
- if (!fenceMatch) {
36066
- i++;
36067
- continue;
36068
- }
36069
- const lang = fenceMatch[1] ?? "";
36070
- const startLine = i + 1;
36071
- const bodyLines = [];
36072
- i++;
36073
- while (i < lines.length) {
36074
- if (/^```\s*$/.test(lines[i] ?? "")) {
36075
- i++;
36076
- break;
36077
- }
36078
- bodyLines.push(lines[i] ?? "");
36079
- i++;
36080
- }
36081
- blocks.push({
36082
- lang,
36083
- body: bodyLines.join("\n"),
36084
- line: startLine,
36085
- column: 1
36086
- });
36087
- }
36088
- return blocks;
36089
- }
36090
35954
  function extractMarkdownLinks(source) {
36091
35955
  const hits = [];
36092
35956
  const re = /(?<!\!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
@@ -36103,10 +35967,10 @@ function extractMarkdownLinks(source) {
36103
35967
  }
36104
35968
  function declaredPackages(cwd) {
36105
35969
  const out = /* @__PURE__ */ new Set();
36106
- const pkgPath = join6(cwd, "package.json");
36107
- if (!existsSync3(pkgPath)) return out;
35970
+ const pkgPath = join4(cwd, "package.json");
35971
+ if (!existsSync2(pkgPath)) return out;
36108
35972
  try {
36109
- const raw = readFileSync5(pkgPath, "utf-8");
35973
+ const raw = readFileSync2(pkgPath, "utf-8");
36110
35974
  const pkg = JSON.parse(raw);
36111
35975
  for (const k of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
36112
35976
  const v = pkg[k];
@@ -36136,7 +36000,7 @@ var brokenLinkRule = createRule({
36136
36000
  const source = facts.v2?._source;
36137
36001
  if (!source) return issues;
36138
36002
  const links = extractMarkdownLinks(source);
36139
- const docDir = dirname4(resolve2(context.cwd, context.filePath));
36003
+ const docDir = dirname3(resolve(context.cwd, context.filePath));
36140
36004
  for (const link of links) {
36141
36005
  const target = link.target;
36142
36006
  if (target.startsWith("http://") || target.startsWith("https://")) continue;
@@ -36146,8 +36010,8 @@ var brokenLinkRule = createRule({
36146
36010
  if (target.startsWith("/")) continue;
36147
36011
  const filePart = target.split("#")[0] ?? target;
36148
36012
  if (filePart === "") continue;
36149
- const resolved = join7(docDir, filePart);
36150
- if (existsSync4(resolved)) continue;
36013
+ const resolved = join5(docDir, filePart);
36014
+ if (existsSync3(resolved)) continue;
36151
36015
  issues.push({
36152
36016
  ruleId: "docs/broken-link",
36153
36017
  category: "docs",
@@ -36376,10 +36240,261 @@ var goStructTagInconsistencyRule = createRule({
36376
36240
  }
36377
36241
  });
36378
36242
 
36243
+ // src/rules/java/arraylist-vs-linkedlist.ts
36244
+ var NEW_LINKED_LIST_REGEX = /new\s+LinkedList\s*</g;
36245
+ var javaArraylistVsLinkedlistRule = createRule({
36246
+ id: "java/arraylist-vs-linkedlist",
36247
+ category: "typo",
36248
+ severity: "low",
36249
+ aiSpecific: true,
36250
+ description: "new LinkedList<>() \u2014 use ArrayList (Effective Java, Item 28)",
36251
+ create(_context) {
36252
+ return {};
36253
+ },
36254
+ analyze(_context, facts) {
36255
+ const issues = [];
36256
+ const source = facts.v2?._source;
36257
+ if (!source) return issues;
36258
+ let m;
36259
+ NEW_LINKED_LIST_REGEX.lastIndex = 0;
36260
+ while ((m = NEW_LINKED_LIST_REGEX.exec(source)) !== null) {
36261
+ const line = source.slice(0, m.index).split("\n").length;
36262
+ issues.push({
36263
+ ruleId: "java/arraylist-vs-linkedlist",
36264
+ category: "typo",
36265
+ severity: "low",
36266
+ aiSpecific: true,
36267
+ message: `new LinkedList at line ${line} \u2014 use ArrayList instead`,
36268
+ line,
36269
+ column: 1,
36270
+ 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."
36271
+ });
36272
+ }
36273
+ return issues;
36274
+ }
36275
+ });
36276
+
36277
+ // src/rules/java/empty-catch-block.ts
36278
+ var SINGLE_LINE_EMPTY_CATCH_REGEX = /catch\s*\([^)]*\)\s*\{\s*\}/g;
36279
+ var javaEmptyCatchBlockRule = createRule({
36280
+ id: "java/empty-catch-block",
36281
+ category: "logic",
36282
+ severity: "medium",
36283
+ aiSpecific: true,
36284
+ description: "Empty catch block \u2014 silently swallows exceptions",
36285
+ create(_context) {
36286
+ return {};
36287
+ },
36288
+ analyze(_context, facts) {
36289
+ const issues = [];
36290
+ const source = facts.v2?._source;
36291
+ if (!source) return issues;
36292
+ let m;
36293
+ SINGLE_LINE_EMPTY_CATCH_REGEX.lastIndex = 0;
36294
+ while ((m = SINGLE_LINE_EMPTY_CATCH_REGEX.exec(source)) !== null) {
36295
+ const line = source.slice(0, m.index).split("\n").length;
36296
+ issues.push({
36297
+ ruleId: "java/empty-catch-block",
36298
+ category: "logic",
36299
+ severity: "medium",
36300
+ aiSpecific: true,
36301
+ message: `Empty catch block at line ${line} \u2014 exception is silently swallowed`,
36302
+ line,
36303
+ column: 1,
36304
+ 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.'
36305
+ });
36306
+ }
36307
+ return issues;
36308
+ }
36309
+ });
36310
+
36311
+ // src/rules/java/legacy-date-api.ts
36312
+ var LEGACY_IMPORT_REGEX = /^import\s+(?:static\s+)?java\.(?:util|sql)\.(?:Date|Calendar|GregorianCalendar)\s*;/gm;
36313
+ var LEGACY_USAGE_REGEX = /\bnew\s+(?:Date|GregorianCalendar)\s*\(/g;
36314
+ var CALENDAR_GET_INSTANCE_REGEX = /Calendar\.getInstance\s*\(/g;
36315
+ var javaLegacyDateApiRule = createRule({
36316
+ id: "java/legacy-date-api",
36317
+ category: "typo",
36318
+ severity: "low",
36319
+ aiSpecific: true,
36320
+ description: "Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310) from Java 8+",
36321
+ create(_context) {
36322
+ return {};
36323
+ },
36324
+ analyze(_context, facts) {
36325
+ const issues = [];
36326
+ const source = facts.v2?._source;
36327
+ if (!source) return issues;
36328
+ const flagged = /* @__PURE__ */ new Set();
36329
+ let m;
36330
+ LEGACY_IMPORT_REGEX.lastIndex = 0;
36331
+ while ((m = LEGACY_IMPORT_REGEX.exec(source)) !== null) {
36332
+ const line = source.slice(0, m.index).split("\n").length;
36333
+ flagged.add(line);
36334
+ issues.push({
36335
+ ruleId: "java/legacy-date-api",
36336
+ category: "typo",
36337
+ severity: "low",
36338
+ aiSpecific: true,
36339
+ message: `Legacy date import at line ${line} \u2014 use java.time (JSR-310) from Java 8+`,
36340
+ line,
36341
+ column: 1,
36342
+ 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."
36343
+ });
36344
+ }
36345
+ LEGACY_USAGE_REGEX.lastIndex = 0;
36346
+ while ((m = LEGACY_USAGE_REGEX.exec(source)) !== null) {
36347
+ const line = source.slice(0, m.index).split("\n").length;
36348
+ if (flagged.has(line)) continue;
36349
+ issues.push({
36350
+ ruleId: "java/legacy-date-api",
36351
+ category: "typo",
36352
+ severity: "low",
36353
+ aiSpecific: true,
36354
+ message: `new Date() / GregorianCalendar at line ${line} \u2014 use java.time`,
36355
+ line,
36356
+ column: 1,
36357
+ advice: "Replace with `LocalDate.now()`, `Instant.now()`, or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
36358
+ });
36359
+ }
36360
+ CALENDAR_GET_INSTANCE_REGEX.lastIndex = 0;
36361
+ while ((m = CALENDAR_GET_INSTANCE_REGEX.exec(source)) !== null) {
36362
+ const line = source.slice(0, m.index).split("\n").length;
36363
+ if (flagged.has(line)) continue;
36364
+ issues.push({
36365
+ ruleId: "java/legacy-date-api",
36366
+ category: "typo",
36367
+ severity: "low",
36368
+ aiSpecific: true,
36369
+ message: `Calendar.getInstance() at line ${line} \u2014 use java.time`,
36370
+ line,
36371
+ column: 1,
36372
+ advice: "Replace with `LocalDate.now()` (date-only) or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
36373
+ });
36374
+ }
36375
+ return issues;
36376
+ }
36377
+ });
36378
+
36379
+ // src/rules/java/raw-type-overuse.ts
36380
+ var RAW_TYPE_REGEX = /\b(List|Map|Set|Collection|Iterable)\s+(?![<A-Z])(\w+)/g;
36381
+ var javaRawTypeOveruseRule = createRule({
36382
+ id: "java/raw-type-overuse",
36383
+ category: "typo",
36384
+ severity: "low",
36385
+ aiSpecific: true,
36386
+ description: "Raw type usage (List, Map, Set) \u2014 use generics (Effective Java, Item 23)",
36387
+ create(_context) {
36388
+ return {};
36389
+ },
36390
+ analyze(_context, facts) {
36391
+ const issues = [];
36392
+ const source = facts.v2?._source;
36393
+ if (!source) return issues;
36394
+ let m;
36395
+ RAW_TYPE_REGEX.lastIndex = 0;
36396
+ while ((m = RAW_TYPE_REGEX.exec(source)) !== null) {
36397
+ const typeName = m[1];
36398
+ const line = source.slice(0, m.index).split("\n").length;
36399
+ issues.push({
36400
+ ruleId: "java/raw-type-overuse",
36401
+ category: "typo",
36402
+ severity: "low",
36403
+ aiSpecific: true,
36404
+ message: `Raw type ${typeName} at line ${line} \u2014 add type parameters`,
36405
+ line,
36406
+ column: 1,
36407
+ 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.`
36408
+ });
36409
+ }
36410
+ return issues;
36411
+ }
36412
+ });
36413
+
36414
+ // src/rules/java/string-concat-loop.ts
36415
+ var STRING_CONCAT_REGEX = /(\b\w+)\s*=\s*\1\s*\+\s*[^;]+;|(\b\w+)\s*\+=\s*['"`]/g;
36416
+ var javaStringConcatLoopRule = createRule({
36417
+ id: "java/string-concat-loop",
36418
+ category: "perf",
36419
+ severity: "low",
36420
+ aiSpecific: true,
36421
+ description: "String concatenation in a loop \u2014 use StringBuilder",
36422
+ create(_context) {
36423
+ return {};
36424
+ },
36425
+ analyze(_context, facts) {
36426
+ const issues = [];
36427
+ const source = facts.v2?._source;
36428
+ if (!source) return issues;
36429
+ if (!/\b(for|while|do)\b/.test(source)) return issues;
36430
+ let m;
36431
+ STRING_CONCAT_REGEX.lastIndex = 0;
36432
+ while ((m = STRING_CONCAT_REGEX.exec(source)) !== null) {
36433
+ const line = source.slice(0, m.index).split("\n").length;
36434
+ issues.push({
36435
+ ruleId: "java/string-concat-loop",
36436
+ category: "perf",
36437
+ severity: "low",
36438
+ aiSpecific: true,
36439
+ message: `String concatenation in a loop at line ${line} \u2014 use StringBuilder`,
36440
+ line,
36441
+ column: 1,
36442
+ 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."
36443
+ });
36444
+ }
36445
+ return issues;
36446
+ }
36447
+ });
36448
+
36449
+ // src/rules/java/system-out-println.ts
36450
+ var PRINTLN_REGEX = /System\.out\.println\s*\(/g;
36451
+ var DEFAULT_THRESHOLD = 1;
36452
+ var javaSystemOutPrintlnRule = createRule({
36453
+ id: "java/system-out-println",
36454
+ category: "typo",
36455
+ severity: "low",
36456
+ aiSpecific: true,
36457
+ description: "System.out.println in production code \u2014 use a logger (SLF4J, Log4j, etc.)",
36458
+ create(_context) {
36459
+ return { threshold: DEFAULT_THRESHOLD };
36460
+ },
36461
+ analyze(context, facts) {
36462
+ const issues = [];
36463
+ const source = facts.v2?._source;
36464
+ if (!source) return issues;
36465
+ const matches = [];
36466
+ let m;
36467
+ PRINTLN_REGEX.lastIndex = 0;
36468
+ while ((m = PRINTLN_REGEX.exec(source)) !== null) {
36469
+ matches.push(m.index);
36470
+ }
36471
+ if (matches.length <= context.threshold) return issues;
36472
+ const cap = Math.min(matches.length, 10);
36473
+ for (let i = 0; i < cap; i++) {
36474
+ const idx = matches[i];
36475
+ const line = source.slice(0, idx).split("\n").length;
36476
+ issues.push({
36477
+ ruleId: "java/system-out-println",
36478
+ category: "typo",
36479
+ severity: "low",
36480
+ aiSpecific: true,
36481
+ message: `System.out.println at line ${line} \u2014 use a logger for production output`,
36482
+ line,
36483
+ column: 1,
36484
+ 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."
36485
+ });
36486
+ }
36487
+ return issues;
36488
+ }
36489
+ });
36490
+
36379
36491
  // src/rules/layout/gap-monopoly.ts
36380
36492
  var GAP_RE = /\bgap(?:-x|-y)?-(\d+)\b/g;
36381
36493
  var gapMonopolyRule = createRule({
36382
36494
  id: "layout/gap-monopoly",
36495
+ // v0.20.0 calibration: recall 0.000, fires never. Disable
36496
+ // until rewritten.
36497
+ defaultOff: true,
36383
36498
  category: "layout",
36384
36499
  severity: "medium",
36385
36500
  aiSpecific: false,
@@ -36476,6 +36591,9 @@ var mathElementUniformityRule = createRule({
36476
36591
  var GRID_COLS_RE = /\bgrid-cols-(\d+)\b/g;
36477
36592
  var mathGridUniformityRule = createRule({
36478
36593
  id: "layout/math-grid-uniformity",
36594
+ // v0.20.0 calibration: recall 0.000, fires never. Disable
36595
+ // until rewritten.
36596
+ defaultOff: true,
36479
36597
  category: "layout",
36480
36598
  severity: "high",
36481
36599
  aiSpecific: true,
@@ -36728,16 +36846,16 @@ var DEFAULT_CONFIG = {
36728
36846
  };
36729
36847
 
36730
36848
  // src/config/detect/monorepo.ts
36731
- import { existsSync as existsSync5, readFileSync as readFileSync6, readdirSync as readdirSync2, statSync } from "fs";
36732
- import { dirname as dirname5, join as join8, resolve as resolve3 } from "path";
36849
+ import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
36850
+ import { dirname as dirname4, join as join6, resolve as resolve2 } from "path";
36733
36851
 
36734
36852
  // src/config/detect/styling.ts
36735
- import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
36736
- import { join as join9, resolve as resolve4 } from "path";
36853
+ import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
36854
+ import { join as join7, resolve as resolve3 } from "path";
36737
36855
 
36738
36856
  // src/config/detect/stack.ts
36739
- import { existsSync as existsSync7, readFileSync as readFileSync8, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
36740
- import { extname as extname4, join as join10, resolve as resolve5 } from "path";
36857
+ import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
36858
+ import { extname as extname2, join as join8, resolve as resolve4 } from "path";
36741
36859
 
36742
36860
  // src/config/presets.ts
36743
36861
  var REACT_ONLY_RULES = {
@@ -36786,8 +36904,8 @@ var FRAMEWORK_PRESETS = {
36786
36904
  };
36787
36905
 
36788
36906
  // src/config/load.ts
36789
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
36790
- import { dirname as dirname6, extname as extname5, join as join11, resolve as resolve6 } from "path";
36907
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
36908
+ import { dirname as dirname5, extname as extname3, join as join10, resolve as resolve5 } from "path";
36791
36909
  import { createRequire } from "module";
36792
36910
 
36793
36911
  // src/engine/logger.ts
@@ -36809,6 +36927,10 @@ function setLoggerQuiet(quiet) {
36809
36927
  logger = createLogger(quiet);
36810
36928
  }
36811
36929
 
36930
+ // src/config/conventions.ts
36931
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
36932
+ import { join as join9 } from "path";
36933
+
36812
36934
  // src/config/init.ts
36813
36935
  var STRICTNESS_PRESETS = {
36814
36936
  strict: {
@@ -36923,6 +37045,9 @@ function checkInlineValue(value, scale) {
36923
37045
  }
36924
37046
  var spacingGridRule = createRule({
36925
37047
  id: "layout/spacing-grid",
37048
+ // v0.20.0 calibration: recall 0.000, fires never. Disable
37049
+ // until rewritten.
37050
+ defaultOff: true,
36926
37051
  category: "layout",
36927
37052
  severity: "medium",
36928
37053
  aiSpecific: false,
@@ -37032,15 +37157,9 @@ var boundaryViolationRule = createRule({
37032
37157
  if (context.supportsRsc === false) return [];
37033
37158
  if (!facts.v2) return [];
37034
37159
  const issues = [];
37035
- const hookLines = /* @__PURE__ */ new Map();
37036
- for (const h of facts.v2.logic.hooks) {
37037
- const arr = hookLines.get(h.name) ?? [];
37038
- arr.push({ line: h.line, column: h.column });
37039
- hookLines.set(h.name, arr);
37040
- }
37041
37160
  for (const component of facts.v2.components) {
37042
37161
  if (!component.isServerComponent) continue;
37043
- for (const hook of facts.v2.logic.hooks) {
37162
+ for (const hook of component.hookCalls) {
37044
37163
  if (!context.clientHooks.has(hook.name)) continue;
37045
37164
  issues.push({
37046
37165
  ruleId: "logic/boundary-violation",
@@ -37048,8 +37167,8 @@ var boundaryViolationRule = createRule({
37048
37167
  severity: "high",
37049
37168
  aiSpecific: true,
37050
37169
  message: `'${hook.name}' only works in interactive client components. This component runs on the server.`,
37051
- line: component.line,
37052
- column: component.column,
37170
+ line: hook.line,
37171
+ column: hook.column,
37053
37172
  advice: "Add the 'use client' directive at the top of this file, or move the stateful part to a separate client component.",
37054
37173
  fix: {
37055
37174
  kind: "insert",
@@ -38174,7 +38293,6 @@ function scanForStringlyParams(paramText) {
38174
38293
  }
38175
38294
 
38176
38295
  // src/rules/rust/todo-macro.ts
38177
- init_parser_rust();
38178
38296
  var TODO_MACROS = /* @__PURE__ */ new Set(["todo", "unimplemented", "todo_unimplemented"]);
38179
38297
  var rustTodoMacroRule = createRule({
38180
38298
  id: "rust/todo-macro",
@@ -38310,7 +38428,6 @@ function collectReferencedNames(source) {
38310
38428
  }
38311
38429
 
38312
38430
  // src/rules/rust/unwrap-in-production.ts
38313
- init_parser_rust();
38314
38431
  var UNWRAP_METHODS = /* @__PURE__ */ new Set(["unwrap", "expect", "unwrap_or_else"]);
38315
38432
  var rustUnwrapInProductionRule = createRule({
38316
38433
  id: "rust/unwrap-in-production",
@@ -39447,7 +39564,7 @@ function looksRealistic(value) {
39447
39564
 
39448
39565
  // src/rules/test/missing-edge-case.ts
39449
39566
  import { parseSync as parseSync2 } from "@swc/core";
39450
- import { existsSync as existsSync9, readFileSync as readFileSync10, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
39567
+ import { existsSync as existsSync9, readFileSync as readFileSync8, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
39451
39568
  var MAX_PER_FILE = 20;
39452
39569
  var missingEdgeCaseRule = createRule({
39453
39570
  id: "test/missing-edge-case",
@@ -39469,7 +39586,7 @@ var missingEdgeCaseRule = createRule({
39469
39586
  const testFileSources = /* @__PURE__ */ new Map();
39470
39587
  for (const testFile of discoverTestFiles(cwd)) {
39471
39588
  try {
39472
- testFileSources.set(testFile, readFileSync10(testFile, "utf-8"));
39589
+ testFileSources.set(testFile, readFileSync8(testFile, "utf-8"));
39473
39590
  } catch {
39474
39591
  }
39475
39592
  }
@@ -39835,7 +39952,12 @@ var WEAK_MATCHERS = /* @__PURE__ */ new Set([
39835
39952
  "toBeUndefined",
39836
39953
  "toBeTruthy",
39837
39954
  "toBeFalsy",
39838
- "toBeNull"
39955
+ "toBeNull",
39956
+ "toBeGreaterThan",
39957
+ "toBeLessThan",
39958
+ "toContain",
39959
+ "toHaveLength",
39960
+ "toBeInstanceOf"
39839
39961
  ]);
39840
39962
  function isTautologicalAssertion(hit) {
39841
39963
  if (hit.matcherArg && hit.expectArg && hit.expectArg === hit.matcherArg) return true;
@@ -40676,6 +40798,10 @@ function rgbToHue([r, g, b]) {
40676
40798
  }
40677
40799
  var mathColorClusterRule = createRule({
40678
40800
  id: "visual/math-color-cluster",
40801
+ // v0.20.0 calibration: recall 0.000, fires never. The
40802
+ // "math-color cluster" heuristic is too specific to ever fire
40803
+ // on real code. Disable until rewritten.
40804
+ defaultOff: true,
40679
40805
  category: "visual",
40680
40806
  severity: "high",
40681
40807
  aiSpecific: true,
@@ -40730,6 +40856,9 @@ var FONT_IMPORT_HINT_RE = /next\/font|@import\s+url|fonts\.googleapis|fonts\.gst
40730
40856
  var NEXT_FONT_IMPORT_RE = /import\s+.*from\s+['"]next\/font\/(?:google|local)['"]/;
40731
40857
  var mathDefaultFontRule = createRule({
40732
40858
  id: "visual/math-default-font",
40859
+ // v0.20.0 calibration: recall 0.001, fires 1 per 1,000 files.
40860
+ // Too specific to be useful. Disable until rewritten.
40861
+ defaultOff: true,
40733
40862
  category: "visual",
40734
40863
  severity: "high",
40735
40864
  aiSpecific: true,
@@ -40796,6 +40925,9 @@ var TEXT_SIZE_MAP = {
40796
40925
  var TEXT_SIZE_RE = /\btext-(?:xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl)\b/g;
40797
40926
  var mathFontEntropyRule = createRule({
40798
40927
  id: "visual/math-font-entropy",
40928
+ // v0.20.0 calibration: recall 0.004, fires 0 times on self-scan
40929
+ // (verified). Disable until rewritten.
40930
+ defaultOff: true,
40799
40931
  category: "visual",
40800
40932
  severity: "high",
40801
40933
  aiSpecific: true,
@@ -40918,6 +41050,10 @@ var ROUNDED_MAP = {
40918
41050
  var ROUNDED_RE = /\brounded(?:-none|-sm|-md|-lg|-xl|-2xl|-3xl|-full)?\b/g;
40919
41051
  var mathRoundedEntropyRule = createRule({
40920
41052
  id: "visual/math-rounded-entropy",
41053
+ // v0.20.0 calibration: recall 0.004, fires 0 times on self-scan
41054
+ // (verified). The entropy heuristic is too specific to ever fire
41055
+ // on real code. Disable until rewritten.
41056
+ defaultOff: true,
40921
41057
  category: "visual",
40922
41058
  severity: "high",
40923
41059
  aiSpecific: true,
@@ -40958,6 +41094,9 @@ var mathRoundedEntropyRule = createRule({
40958
41094
  var SPACING_PREFIX_RE2 = /\b(p|px|py|pt|pb|pl|pr|m|mx|my|mt|mb|ml|mr|gap|gap-x|gap-y)-(\d+)\b/g;
40959
41095
  var mathSpacingEntropyRule = createRule({
40960
41096
  id: "visual/math-spacing-entropy",
41097
+ // v0.20.0 calibration: recall 0.002, fires 0 times on self-scan
41098
+ // (verified). Disable until rewritten.
41099
+ defaultOff: true,
40961
41100
  category: "visual",
40962
41101
  severity: "medium",
40963
41102
  aiSpecific: true,
@@ -40995,7 +41134,7 @@ var mathSpacingEntropyRule = createRule({
40995
41134
 
40996
41135
  // src/rules/visual/naturalness-anomaly.ts
40997
41136
  var MIN_LENGTH = 50;
40998
- var DISTINCT_RATIO_FLOOR = 0.3;
41137
+ var DISTINCT_RATIO_FLOOR = 0.2;
40999
41138
  var naturalnessAnomalyRule = createRule({
41000
41139
  id: "visual/naturalness-anomaly",
41001
41140
  category: "visual",
@@ -41040,6 +41179,9 @@ var naturalnessAnomalyRule = createRule({
41040
41179
  var SCALE_TOLERANCE = 1e-3;
41041
41180
  var radiusScaleViolationRule = createRule({
41042
41181
  id: "visual/radius-scale-violation",
41182
+ // v0.20.0 calibration: recall 0.000, fires never. Disable
41183
+ // until rewritten.
41184
+ defaultOff: true,
41043
41185
  category: "visual",
41044
41186
  severity: "medium",
41045
41187
  aiSpecific: false,
@@ -41224,6 +41366,10 @@ var ALT_HANDLER_RE = /^(?:onClick|onPointerDown|onKey(?:Down|Up|Press))$/;
41224
41366
  var ALT_ROLE_RE = /^(?:button|application|tab|menuitem)$/;
41225
41367
  var draggingMovementsRule = createRule({
41226
41368
  id: "wcag/dragging-movements",
41369
+ // v0.20.0 calibration: v8.5 = DORMANT but defaultOff: false
41370
+ // was a bug — DORMANT rules should be off by default. The
41371
+ // rule was contributing 0 useful fires. Disable.
41372
+ defaultOff: true,
41227
41373
  category: "wcag",
41228
41374
  severity: "medium",
41229
41375
  aiSpecific: false,
@@ -41370,6 +41516,11 @@ function scanForMissingAlt(source) {
41370
41516
  }
41371
41517
  var missingAltRule = createRule({
41372
41518
  id: "wcag/missing-alt",
41519
+ // v0.20.0 calibration: lift 1.0, recall 0.000. Lift of 1.0
41520
+ // means the rule has zero discriminative power — it fires
41521
+ // equally on positive and negative examples. Dead rule.
41522
+ // Disable until rewritten.
41523
+ defaultOff: true,
41373
41524
  category: "wcag",
41374
41525
  severity: "medium",
41375
41526
  aiSpecific: false,
@@ -41590,13 +41741,18 @@ var builtinRules = [
41590
41741
  unusedLocalRule,
41591
41742
  unusedParameterRule,
41592
41743
  brokenLinkRule,
41593
- expiredCodeExampleRule,
41594
41744
  staleFunctionReferenceRule,
41595
41745
  stalePackageReferenceRule,
41596
41746
  dupIdenticalBlockRule,
41597
41747
  goErrorWrapWithoutContextRule,
41598
41748
  goNilSliceVsEmptyRule,
41599
41749
  goStructTagInconsistencyRule,
41750
+ javaArraylistVsLinkedlistRule,
41751
+ javaEmptyCatchBlockRule,
41752
+ javaLegacyDateApiRule,
41753
+ javaRawTypeOveruseRule,
41754
+ javaStringConcatLoopRule,
41755
+ javaSystemOutPrintlnRule,
41600
41756
  gapMonopolyRule,
41601
41757
  mathElementUniformityRule,
41602
41758
  mathGridUniformityRule,
@@ -42310,24 +42466,6 @@ var signal_strength_default = {
42310
42466
  _v8Verdict: "OK",
42311
42467
  _v8Lift: 48.52
42312
42468
  },
42313
- "docs/expired-code-example": {
42314
- recall: 0,
42315
- fpRate: 0,
42316
- ratio: 0,
42317
- precision: 0,
42318
- lastCalibratedAt: "2026-07-01T00:00:00Z",
42319
- verdict: "INVERTED",
42320
- _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).",
42321
- aiSpecific: false,
42322
- _v7Verdict: "DORMANT",
42323
- _v7Lift: 1,
42324
- _v7Recall: 0,
42325
- _v7FpRate: 0,
42326
- _v7Precision: 0,
42327
- _v8Verdict: "INVERTED",
42328
- _v8Lift: 0,
42329
- defaultOff: true
42330
- },
42331
42469
  "docs/stale-function-reference": {
42332
42470
  recall: 26e-4,
42333
42471
  fpRate: 5e-4,
@@ -42434,6 +42572,114 @@ var signal_strength_default = {
42434
42572
  _v8Lift: 1,
42435
42573
  defaultOff: true
42436
42574
  },
42575
+ "java/arraylist-vs-linkedlist": {
42576
+ recall: 0,
42577
+ fpRate: 0,
42578
+ ratio: 1,
42579
+ precision: 0,
42580
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42581
+ verdict: "DORMANT",
42582
+ _calibrationNote: "v0.20: new rule (new LinkedList<>() \u2014 use ArrayList (Effective Java Item 28)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
42583
+ aiSpecific: true,
42584
+ _v7Verdict: "DORMANT",
42585
+ _v7Lift: 1,
42586
+ _v7Recall: 0,
42587
+ _v7FpRate: 0,
42588
+ _v7Precision: 0,
42589
+ _v8Verdict: "DORMANT",
42590
+ _v8Lift: 1,
42591
+ defaultOff: true
42592
+ },
42593
+ "java/empty-catch-block": {
42594
+ recall: 0,
42595
+ fpRate: 0,
42596
+ ratio: 1,
42597
+ precision: 0,
42598
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42599
+ verdict: "DORMANT",
42600
+ _calibrationNote: "v0.20: new rule (Empty catch block \u2014 silently swallows exceptions). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
42601
+ aiSpecific: true,
42602
+ _v7Verdict: "DORMANT",
42603
+ _v7Lift: 1,
42604
+ _v7Recall: 0,
42605
+ _v7FpRate: 0,
42606
+ _v7Precision: 0,
42607
+ _v8Verdict: "DORMANT",
42608
+ _v8Lift: 1,
42609
+ defaultOff: true
42610
+ },
42611
+ "java/legacy-date-api": {
42612
+ recall: 0,
42613
+ fpRate: 0,
42614
+ ratio: 1,
42615
+ precision: 0,
42616
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42617
+ verdict: "DORMANT",
42618
+ _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.",
42619
+ aiSpecific: true,
42620
+ _v7Verdict: "DORMANT",
42621
+ _v7Lift: 1,
42622
+ _v7Recall: 0,
42623
+ _v7FpRate: 0,
42624
+ _v7Precision: 0,
42625
+ _v8Verdict: "DORMANT",
42626
+ _v8Lift: 1,
42627
+ defaultOff: true
42628
+ },
42629
+ "java/raw-type-overuse": {
42630
+ recall: 0,
42631
+ fpRate: 0,
42632
+ ratio: 1,
42633
+ precision: 0,
42634
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42635
+ verdict: "DORMANT",
42636
+ _calibrationNote: "v0.20: new rule (Raw type usage \u2014 use generics). DORMANT until v9 Java corpus calibration.",
42637
+ aiSpecific: true,
42638
+ _v7Verdict: "DORMANT",
42639
+ _v7Lift: 1,
42640
+ _v7Recall: 0,
42641
+ _v7FpRate: 0,
42642
+ _v7Precision: 0,
42643
+ _v8Verdict: "DORMANT",
42644
+ _v8Lift: 1,
42645
+ defaultOff: true
42646
+ },
42647
+ "java/string-concat-loop": {
42648
+ recall: 0,
42649
+ fpRate: 0,
42650
+ ratio: 1,
42651
+ precision: 0,
42652
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42653
+ verdict: "DORMANT",
42654
+ _calibrationNote: "v0.20: new rule (String concat in loop \u2014 use StringBuilder). DORMANT until v9 Java corpus calibration.",
42655
+ aiSpecific: true,
42656
+ _v7Verdict: "DORMANT",
42657
+ _v7Lift: 1,
42658
+ _v7Recall: 0,
42659
+ _v7FpRate: 0,
42660
+ _v7Precision: 0,
42661
+ _v8Verdict: "DORMANT",
42662
+ _v8Lift: 1,
42663
+ defaultOff: true
42664
+ },
42665
+ "java/system-out-println": {
42666
+ recall: 0,
42667
+ fpRate: 0,
42668
+ ratio: 1,
42669
+ precision: 0,
42670
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42671
+ verdict: "DORMANT",
42672
+ _calibrationNote: "v0.20: new rule (System.out.println in production code \u2014 use a logger). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
42673
+ aiSpecific: true,
42674
+ _v7Verdict: "DORMANT",
42675
+ _v7Lift: 1,
42676
+ _v7Recall: 0,
42677
+ _v7FpRate: 0,
42678
+ _v7Precision: 0,
42679
+ _v8Verdict: "DORMANT",
42680
+ _v8Lift: 1,
42681
+ defaultOff: true
42682
+ },
42437
42683
  "layout/forced-layout": {
42438
42684
  recall: 0,
42439
42685
  fpRate: 0,
@@ -43690,7 +43936,7 @@ function loadSignalStrength() {
43690
43936
  function buildParserCacheConfig(cwd) {
43691
43937
  const envVal = process.env.SLOP_AUDIT_CACHE;
43692
43938
  const enabled = envVal === "1" || envVal === "true";
43693
- const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? join12(cwd, ".slopbrick", "cache", "ast");
43939
+ const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? join11(cwd, ".slopbrick", "cache", "ast");
43694
43940
  return { enabled, root };
43695
43941
  }
43696
43942
  function applyRuleOverrides(issues, rules) {
@@ -43708,7 +43954,7 @@ function applyRuleOverrides(issues, rules) {
43708
43954
  }
43709
43955
  async function scanFile(filePath, config, registry, cwd = process.cwd()) {
43710
43956
  const cache = buildParserCacheConfig(cwd);
43711
- const ext = extname6(filePath).toLowerCase();
43957
+ const ext = extname4(filePath).toLowerCase();
43712
43958
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
43713
43959
  ".swift",
43714
43960
  ".kt",