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.
@@ -6,9 +6,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __esm = (fn, res) => function __init() {
10
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
- };
12
9
  var __export = (target, all) => {
13
10
  for (var name in all)
14
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -31,408 +28,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
28
  ));
32
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
30
 
34
- // src/engine/parser-rust.ts
35
- function getRustParser() {
36
- if (cachedParser) return { ok: true, parser: cachedParser };
37
- if (loadError) return { ok: false, error: loadError };
38
- try {
39
- rustLanguage = import_tree_sitter_rust.default;
40
- if (!rustLanguage || typeof rustLanguage !== "object") {
41
- throw new Error("tree-sitter-rust: language export is missing or malformed");
42
- }
43
- const parser = new import_tree_sitter.default();
44
- parser.setLanguage(rustLanguage);
45
- cachedParser = parser;
46
- return { ok: true, parser };
47
- } catch (err) {
48
- loadError = err instanceof Error ? err : new Error(String(err));
49
- return { ok: false, error: loadError };
50
- }
51
- }
52
- function effectiveParser() {
53
- if (forcedFailure) return { ok: false, error: forcedFailure };
54
- return getRustParser();
55
- }
56
- function parseRust(source) {
57
- const result = effectiveParser();
58
- if (!result.ok) return null;
59
- if (!source || source.trim() === "") return null;
60
- try {
61
- const rawTree = result.parser.parse(source);
62
- if (!rawTree) return null;
63
- const tree = rawTree;
64
- if (!tree || !tree.rootNode) return null;
65
- if (tree.rootNode.type === "ERROR" && tree.rootNode.childCount === 0) {
66
- return null;
67
- }
68
- return tree;
69
- } catch {
70
- return null;
71
- }
72
- }
73
- function isRustParserAvailable() {
74
- const result = getRustParser();
75
- return result.ok;
76
- }
77
- var import_tree_sitter, import_tree_sitter_rust, cachedParser, rustLanguage, loadError, forcedFailure;
78
- var init_parser_rust = __esm({
79
- "src/engine/parser-rust.ts"() {
80
- "use strict";
81
- import_tree_sitter = __toESM(require("tree-sitter"), 1);
82
- import_tree_sitter_rust = __toESM(require("tree-sitter-rust"), 1);
83
- cachedParser = null;
84
- rustLanguage = null;
85
- loadError = null;
86
- forcedFailure = null;
87
- }
88
- });
89
-
90
- // src/engine/visitors/rust.ts
91
- function parseRustFile(filePath, source, options = {}) {
92
- const empty = {
93
- imports: [],
94
- functions: [],
95
- structs: [],
96
- traits: [],
97
- impls: []
98
- };
99
- if (!isRustParserAvailable() || options.forceFallback) {
100
- return empty;
101
- }
102
- const tree = parseRust(source);
103
- if (!tree) return empty;
104
- return walkRustTree(tree);
105
- }
106
- function walkRustTree(tree) {
107
- const ctx = { inTestConfig: false };
108
- const out = {
109
- imports: [],
110
- functions: [],
111
- structs: [],
112
- traits: [],
113
- impls: []
114
- };
115
- walkChildren(tree.rootNode, ctx, out);
116
- return out;
117
- }
118
- function walkChildren(node, ctx, out) {
119
- for (let i = 0; i < node.namedChildCount; i++) {
120
- const child = node.namedChild(i);
121
- if (!child) continue;
122
- visitNode(child, ctx, out);
123
- }
124
- }
125
- function visitNode(node, ctx, out) {
126
- const innerCtx = { ...ctx };
127
- const attrState = readPrecedingAttributes(node);
128
- switch (node.type) {
129
- case "use_declaration": {
130
- out.imports.push(extractUse(node));
131
- return;
132
- }
133
- case "function_item": {
134
- const isMethod = isInImplBlock(node);
135
- out.functions.push(extractFunction(node, attrState, innerCtx.inTestConfig, isMethod));
136
- walkChildren(getField(node, "body") ?? node, innerCtx, out);
137
- return;
138
- }
139
- case "struct_item": {
140
- out.structs.push(extractStruct(node, attrState));
141
- const body = getField(node, "body");
142
- if (body) walkChildren(body, innerCtx, out);
143
- return;
144
- }
145
- case "trait_item": {
146
- out.traits.push(extractTrait(node, attrState));
147
- const body = getField(node, "body");
148
- if (body) walkChildren(body, innerCtx, out);
149
- return;
150
- }
151
- case "impl_item": {
152
- const implEntry = extractImpl(node, attrState, innerCtx);
153
- out.impls.push(implEntry.entry);
154
- const body = getField(node, "body");
155
- if (body) walkChildren(body, innerCtx, out);
156
- return;
157
- }
158
- case "mod_item": {
159
- const modIsTest = innerCtx.inTestConfig || attrState.isTestCfg;
160
- const modCtx = { ...innerCtx, inTestConfig: modIsTest };
161
- const body = getField(node, "body");
162
- if (body) walkChildren(body, modCtx, out);
163
- return;
164
- }
165
- default: {
166
- for (let i = 0; i < node.namedChildCount; i++) {
167
- const child = node.namedChild(i);
168
- if (child) visitNode(child, innerCtx, out);
169
- }
170
- }
171
- }
172
- }
173
- function readPrecedingAttributes(node) {
174
- const state = {
175
- isTestCfg: false,
176
- isTest: false,
177
- isPub: false,
178
- derives: []
179
- };
180
- const parent = node.parent;
181
- if (!parent) return state;
182
- for (let i = 0; i < parent.namedChildCount; i++) {
183
- const sibling = parent.namedChild(i);
184
- if (!sibling || sibling === node) continue;
185
- if (sibling.endIndex > node.startIndex) continue;
186
- if (sibling.type !== "attribute_item") continue;
187
- decodeAttribute(sibling, state);
188
- }
189
- return state;
190
- }
191
- function decodeAttribute(attr, state) {
192
- for (let i = 0; i < attr.namedChildCount; i++) {
193
- const child = attr.namedChild(i);
194
- if (!child || child.type !== "attribute") continue;
195
- const name = firstIdentifier(child);
196
- if (!name) continue;
197
- if (name === "cfg") {
198
- const cfgText = child.text;
199
- if (/\btest\b/.test(cfgText) && !/\bnot\(test\)/.test(cfgText)) {
200
- state.isTestCfg = true;
201
- }
202
- } else if (name === "test") {
203
- state.isTest = true;
204
- } else if (name === "derive") {
205
- for (let j = 0; j < child.namedChildCount; j++) {
206
- const inner = child.namedChild(j);
207
- if (inner) {
208
- const matches = inner.text.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g);
209
- for (const m of matches) state.derives.push(m[1]);
210
- }
211
- }
212
- }
213
- }
214
- }
215
- function firstIdentifier(node) {
216
- const text = node.text;
217
- const idMatch = text.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
218
- return idMatch ? idMatch[1] : null;
219
- }
220
- function extractUse(node) {
221
- const text = node.text;
222
- const argument = node.namedChild(0);
223
- const argField = node.childForFieldName("argument");
224
- const path = argField?.text ?? argument?.text ?? "";
225
- const names = [];
226
- let isGlob = false;
227
- const useListNode = findUseListNode(argument);
228
- if (useListNode) {
229
- for (let i = 0; i < useListNode.namedChildCount; i++) {
230
- const item = useListNode.namedChild(i);
231
- if (item.type === "use_wildcard") {
232
- isGlob = true;
233
- } else if (item.type === "identifier") {
234
- names.push({ name: item.text });
235
- } else if (item.type === "use_as_clause") {
236
- const alias = item.childForFieldName("alias");
237
- const binding = item.childForFieldName("path");
238
- if (binding) {
239
- names.push({ name: binding.text, alias: alias?.text });
240
- } else {
241
- names.push({ name: item.text });
242
- }
243
- } else if (item.type === "scoped_identifier") {
244
- const idents = collectIdentifiers(item);
245
- names.push({ name: idents[idents.length - 1] ?? item.text });
246
- } else if (item.text.includes(" as ")) {
247
- const [head, alias] = item.text.split(/\s+as\s+/);
248
- names.push({ name: (head ?? "").trim(), alias: alias?.trim() });
249
- } else {
250
- names.push({ name: item.text });
251
- }
252
- }
253
- } else if (argument?.type === "use_wildcard") {
254
- isGlob = true;
255
- } else if (argument) {
256
- const idents = collectIdentifiers(argument);
257
- const last = idents[idents.length - 1] ?? argument.text;
258
- names.push({ name: last });
259
- const asMatch = text.match(/\s+as\s+(\w+)\s*;?\s*$/);
260
- if (asMatch) names[0].alias = asMatch[1];
261
- }
262
- return {
263
- path: path.trim(),
264
- names,
265
- isGlob,
266
- line: node.startPosition.row + 1,
267
- column: node.startPosition.column
268
- };
269
- }
270
- function extractFunction(node, attrs, inheritedTestConfig, isMethod) {
271
- const nameNode = node.childForFieldName("name");
272
- const name = nameNode?.text ?? "<anon>";
273
- const params = node.childForFieldName("parameters");
274
- const body = node.childForFieldName("body");
275
- const visibilityMod = node.namedChild(0);
276
- const isPublic = visibilityMod?.type === "visibility_modifier";
277
- let receiver;
278
- if (isMethod && params) {
279
- const selfParam = findSelfParameter(params);
280
- if (selfParam) receiver = selfParam.text;
281
- }
282
- const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
283
- const inTestConfig = inheritedTestConfig || attrs.isTest || attrs.isTestCfg || isFunctionAttrTest(node);
284
- return {
285
- name,
286
- line: node.startPosition.row + 1,
287
- column: node.startPosition.column,
288
- isPublic,
289
- isMethod,
290
- receiver,
291
- bodyLines,
292
- inTestConfig
293
- };
294
- }
295
- function extractStruct(node, attrs) {
296
- const nameNode = node.childForFieldName("name");
297
- return {
298
- name: nameNode?.text ?? "<anon>",
299
- line: node.startPosition.row + 1,
300
- column: node.startPosition.column,
301
- isPublic: hasVisibility(node),
302
- isDerive: attrs.derives.length > 0,
303
- derives: [...attrs.derives]
304
- };
305
- }
306
- function extractTrait(node, _attrs) {
307
- const nameNode = node.childForFieldName("name");
308
- return {
309
- name: nameNode?.text ?? "<anon>",
310
- line: node.startPosition.row + 1,
311
- column: node.startPosition.column,
312
- isPublic: hasVisibility(node)
313
- };
314
- }
315
- function extractImpl(node, _attrs, _ctx) {
316
- const traitNode = node.childForFieldName("trait");
317
- const typeNode = node.childForFieldName("type");
318
- const typeText = typeNode?.text ?? "<anon>";
319
- const traitText = traitNode?.text;
320
- const body = node.childForFieldName("body");
321
- const methods = [];
322
- if (body) {
323
- for (let i = 0; i < body.namedChildCount; i++) {
324
- const child = body.namedChild(i);
325
- if (child?.type === "function_item") {
326
- const m = child.childForFieldName("name");
327
- if (m) methods.push(m.text);
328
- }
329
- }
330
- }
331
- return {
332
- entry: {
333
- type: typeText,
334
- trait: traitText ?? void 0,
335
- methods,
336
- line: node.startPosition.row + 1,
337
- column: node.startPosition.column
338
- }
339
- };
340
- }
341
- function findUseListNode(argument) {
342
- if (!argument) return null;
343
- if (argument.type === "use_list") return argument;
344
- if (argument.type === "scoped_use_list") {
345
- for (let i = 0; i < argument.namedChildCount; i++) {
346
- const c = argument.namedChild(i);
347
- if (c?.type === "use_list") return c;
348
- }
349
- }
350
- return null;
351
- }
352
- function getField(node, fieldName) {
353
- return node.childForFieldName(fieldName);
354
- }
355
- function collectIdentifiers(node) {
356
- const out = [];
357
- for (let i = 0; i < node.namedChildCount; i++) {
358
- const child = node.namedChild(i);
359
- if (!child) continue;
360
- if (child.type === "identifier" || child.type === "type_identifier") {
361
- out.push(child.text);
362
- } else if (child.type === "scoped_identifier" || child.type === "nested_identifier") {
363
- out.push(...collectIdentifiers(child));
364
- }
365
- }
366
- return out;
367
- }
368
- function hasVisibility(node) {
369
- return node.namedChild(0)?.type === "visibility_modifier";
370
- }
371
- function isInImplBlock(node) {
372
- for (let p = node.parent; p; p = p.parent) {
373
- if (p.type === "impl_item") return true;
374
- if (p.type === "function_item" || p.type === "source_file") return false;
375
- }
376
- return false;
377
- }
378
- function findSelfParameter(params) {
379
- for (let i = 0; i < params.namedChildCount; i++) {
380
- const child = params.namedChild(i);
381
- if (child?.type === "self_parameter") return child;
382
- }
383
- return null;
384
- }
385
- function isFunctionAttrTest(node) {
386
- void node;
387
- return false;
388
- }
389
- var SERVICE_SUFFIXES, SERVICE_SUFFIX_GROUP, RUST_SERVICE_STRUCT_RE, RUST_SERVICE_IMPL_RE;
390
- var init_rust = __esm({
391
- "src/engine/visitors/rust.ts"() {
392
- "use strict";
393
- init_parser_rust();
394
- SERVICE_SUFFIXES = [
395
- "Service",
396
- "Manager",
397
- "Handler",
398
- "Repository",
399
- "Controller",
400
- "Helper",
401
- "Factory",
402
- "Provider",
403
- "Store",
404
- "API",
405
- "Client",
406
- "Adapter",
407
- "Resolver",
408
- "Mapper",
409
- "Transformer",
410
- "Serializer",
411
- "Validator",
412
- "Strategy",
413
- "Facade",
414
- "Decorator",
415
- "Observer",
416
- "Builder",
417
- "Command",
418
- "Processor",
419
- "Worker",
420
- "Job",
421
- "Actor",
422
- "Executor"
423
- ];
424
- SERVICE_SUFFIX_GROUP = `(?:${SERVICE_SUFFIXES.join("|")})`;
425
- RUST_SERVICE_STRUCT_RE = new RegExp(
426
- `^(?:pub(?:\\(crate\\)|\\(super\\))?\\s+)?struct\\s+(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
427
- "gm"
428
- );
429
- RUST_SERVICE_IMPL_RE = new RegExp(
430
- `^impl(?:<[^>]+>)?\\s+(?:${SERVICE_SUFFIX_GROUP}\\s+for\\s+)?(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
431
- "gm"
432
- );
433
- }
434
- });
435
-
436
31
  // src/engine/worker.ts
437
32
  var worker_exports = {};
438
33
  __export(worker_exports, {
@@ -440,8 +35,8 @@ __export(worker_exports, {
440
35
  });
441
36
  module.exports = __toCommonJS(worker_exports);
442
37
  var import_node_worker_threads = require("worker_threads");
443
- var import_node_path11 = require("path");
444
- var import_node_path12 = require("path");
38
+ var import_node_path9 = require("path");
39
+ var import_node_path10 = require("path");
445
40
 
446
41
  // ../engine/dist/index.js
447
42
  var import_promises = require("fs/promises");
@@ -7262,8 +6857,396 @@ function dispatchNode(node, parent, path, vctx) {
7262
6857
  return false;
7263
6858
  }
7264
6859
 
6860
+ // src/engine/parser-rust.ts
6861
+ var import_tree_sitter = __toESM(require("tree-sitter"), 1);
6862
+ var import_tree_sitter_rust = __toESM(require("tree-sitter-rust"), 1);
6863
+ var cachedParser = null;
6864
+ var rustLanguage = null;
6865
+ var loadError = null;
6866
+ function getRustParser() {
6867
+ if (cachedParser) return { ok: true, parser: cachedParser };
6868
+ if (loadError) return { ok: false, error: loadError };
6869
+ try {
6870
+ rustLanguage = import_tree_sitter_rust.default;
6871
+ if (!rustLanguage || typeof rustLanguage !== "object") {
6872
+ throw new Error("tree-sitter-rust: language export is missing or malformed");
6873
+ }
6874
+ const parser = new import_tree_sitter.default();
6875
+ parser.setLanguage(rustLanguage);
6876
+ cachedParser = parser;
6877
+ return { ok: true, parser };
6878
+ } catch (err) {
6879
+ loadError = err instanceof Error ? err : new Error(String(err));
6880
+ return { ok: false, error: loadError };
6881
+ }
6882
+ }
6883
+ var forcedFailure = null;
6884
+ function effectiveParser() {
6885
+ if (forcedFailure) return { ok: false, error: forcedFailure };
6886
+ return getRustParser();
6887
+ }
6888
+ function parseRust(source) {
6889
+ const result = effectiveParser();
6890
+ if (!result.ok) return null;
6891
+ if (!source || source.trim() === "") return null;
6892
+ try {
6893
+ const rawTree = result.parser.parse(source);
6894
+ if (!rawTree) return null;
6895
+ const tree = rawTree;
6896
+ if (!tree || !tree.rootNode) return null;
6897
+ if (tree.rootNode.type === "ERROR" && tree.rootNode.childCount === 0) {
6898
+ return null;
6899
+ }
6900
+ return tree;
6901
+ } catch {
6902
+ return null;
6903
+ }
6904
+ }
6905
+ function isRustParserAvailable() {
6906
+ const result = getRustParser();
6907
+ return result.ok;
6908
+ }
6909
+
6910
+ // src/engine/visitors/rust.ts
6911
+ var SERVICE_SUFFIXES = [
6912
+ "Service",
6913
+ "Manager",
6914
+ "Handler",
6915
+ "Repository",
6916
+ "Controller",
6917
+ "Helper",
6918
+ "Factory",
6919
+ "Provider",
6920
+ "Store",
6921
+ "API",
6922
+ "Client",
6923
+ "Adapter",
6924
+ "Resolver",
6925
+ "Mapper",
6926
+ "Transformer",
6927
+ "Serializer",
6928
+ "Validator",
6929
+ "Strategy",
6930
+ "Facade",
6931
+ "Decorator",
6932
+ "Observer",
6933
+ "Builder",
6934
+ "Command",
6935
+ "Processor",
6936
+ "Worker",
6937
+ "Job",
6938
+ "Actor",
6939
+ "Executor"
6940
+ ];
6941
+ var SERVICE_SUFFIX_GROUP = `(?:${SERVICE_SUFFIXES.join("|")})`;
6942
+ var RUST_SERVICE_STRUCT_RE = new RegExp(
6943
+ `^(?:pub(?:\\(crate\\)|\\(super\\))?\\s+)?struct\\s+(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
6944
+ "gm"
6945
+ );
6946
+ var RUST_SERVICE_IMPL_RE = new RegExp(
6947
+ `^impl(?:<[^>]+>)?\\s+(?:${SERVICE_SUFFIX_GROUP}\\s+for\\s+)?(\\w+?)${SERVICE_SUFFIX_GROUP}?\\b`,
6948
+ "gm"
6949
+ );
6950
+ function parseRustFile(filePath, source, options = {}) {
6951
+ const empty = {
6952
+ imports: [],
6953
+ functions: [],
6954
+ structs: [],
6955
+ traits: [],
6956
+ impls: []
6957
+ };
6958
+ if (!isRustParserAvailable() || options.forceFallback) {
6959
+ return empty;
6960
+ }
6961
+ const tree = parseRust(source);
6962
+ if (!tree) return empty;
6963
+ return walkRustTree(tree);
6964
+ }
6965
+ function walkRustTree(tree) {
6966
+ const ctx = { inTestConfig: false };
6967
+ const out = {
6968
+ imports: [],
6969
+ functions: [],
6970
+ structs: [],
6971
+ traits: [],
6972
+ impls: []
6973
+ };
6974
+ walkChildren(tree.rootNode, ctx, out);
6975
+ return out;
6976
+ }
6977
+ function walkChildren(node, ctx, out) {
6978
+ for (let i = 0; i < node.namedChildCount; i++) {
6979
+ const child = node.namedChild(i);
6980
+ if (!child) continue;
6981
+ visitNode(child, ctx, out);
6982
+ }
6983
+ }
6984
+ function visitNode(node, ctx, out) {
6985
+ const innerCtx = { ...ctx };
6986
+ const attrState = readPrecedingAttributes(node);
6987
+ switch (node.type) {
6988
+ case "use_declaration": {
6989
+ out.imports.push(extractUse(node));
6990
+ return;
6991
+ }
6992
+ case "function_item": {
6993
+ const isMethod = isInImplBlock(node);
6994
+ out.functions.push(extractFunction(node, attrState, innerCtx.inTestConfig, isMethod));
6995
+ walkChildren(getField(node, "body") ?? node, innerCtx, out);
6996
+ return;
6997
+ }
6998
+ case "struct_item": {
6999
+ out.structs.push(extractStruct(node, attrState));
7000
+ const body = getField(node, "body");
7001
+ if (body) walkChildren(body, innerCtx, out);
7002
+ return;
7003
+ }
7004
+ case "trait_item": {
7005
+ out.traits.push(extractTrait(node, attrState));
7006
+ const body = getField(node, "body");
7007
+ if (body) walkChildren(body, innerCtx, out);
7008
+ return;
7009
+ }
7010
+ case "impl_item": {
7011
+ const implEntry = extractImpl(node, attrState, innerCtx);
7012
+ out.impls.push(implEntry.entry);
7013
+ const body = getField(node, "body");
7014
+ if (body) walkChildren(body, innerCtx, out);
7015
+ return;
7016
+ }
7017
+ case "mod_item": {
7018
+ const modIsTest = innerCtx.inTestConfig || attrState.isTestCfg;
7019
+ const modCtx = { ...innerCtx, inTestConfig: modIsTest };
7020
+ const body = getField(node, "body");
7021
+ if (body) walkChildren(body, modCtx, out);
7022
+ return;
7023
+ }
7024
+ default: {
7025
+ for (let i = 0; i < node.namedChildCount; i++) {
7026
+ const child = node.namedChild(i);
7027
+ if (child) visitNode(child, innerCtx, out);
7028
+ }
7029
+ }
7030
+ }
7031
+ }
7032
+ function readPrecedingAttributes(node) {
7033
+ const state = {
7034
+ isTestCfg: false,
7035
+ isTest: false,
7036
+ isPub: false,
7037
+ derives: []
7038
+ };
7039
+ const parent = node.parent;
7040
+ if (!parent) return state;
7041
+ for (let i = 0; i < parent.namedChildCount; i++) {
7042
+ const sibling = parent.namedChild(i);
7043
+ if (!sibling || sibling === node) continue;
7044
+ if (sibling.endIndex > node.startIndex) continue;
7045
+ if (sibling.type !== "attribute_item") continue;
7046
+ decodeAttribute(sibling, state);
7047
+ }
7048
+ return state;
7049
+ }
7050
+ function decodeAttribute(attr, state) {
7051
+ for (let i = 0; i < attr.namedChildCount; i++) {
7052
+ const child = attr.namedChild(i);
7053
+ if (!child || child.type !== "attribute") continue;
7054
+ const name = firstIdentifier(child);
7055
+ if (!name) continue;
7056
+ if (name === "cfg") {
7057
+ const cfgText = child.text;
7058
+ if (/\btest\b/.test(cfgText) && !/\bnot\(test\)/.test(cfgText)) {
7059
+ state.isTestCfg = true;
7060
+ }
7061
+ } else if (name === "test") {
7062
+ state.isTest = true;
7063
+ } else if (name === "derive") {
7064
+ for (let j = 0; j < child.namedChildCount; j++) {
7065
+ const inner = child.namedChild(j);
7066
+ if (inner) {
7067
+ const matches = inner.text.matchAll(/\b([A-Z][A-Za-z0-9_]*)\b/g);
7068
+ for (const m of matches) state.derives.push(m[1]);
7069
+ }
7070
+ }
7071
+ }
7072
+ }
7073
+ }
7074
+ function firstIdentifier(node) {
7075
+ const text = node.text;
7076
+ const idMatch = text.match(/^([a-zA-Z_][a-zA-Z0-9_]*)/);
7077
+ return idMatch ? idMatch[1] : null;
7078
+ }
7079
+ function extractUse(node) {
7080
+ const text = node.text;
7081
+ const argument = node.namedChild(0);
7082
+ const argField = node.childForFieldName("argument");
7083
+ const path = argField?.text ?? argument?.text ?? "";
7084
+ const names = [];
7085
+ let isGlob = false;
7086
+ const useListNode = findUseListNode(argument);
7087
+ if (useListNode) {
7088
+ for (let i = 0; i < useListNode.namedChildCount; i++) {
7089
+ const item = useListNode.namedChild(i);
7090
+ if (item.type === "use_wildcard") {
7091
+ isGlob = true;
7092
+ } else if (item.type === "identifier") {
7093
+ names.push({ name: item.text });
7094
+ } else if (item.type === "use_as_clause") {
7095
+ const alias = item.childForFieldName("alias");
7096
+ const binding = item.childForFieldName("path");
7097
+ if (binding) {
7098
+ names.push({ name: binding.text, alias: alias?.text });
7099
+ } else {
7100
+ names.push({ name: item.text });
7101
+ }
7102
+ } else if (item.type === "scoped_identifier") {
7103
+ const idents = collectIdentifiers(item);
7104
+ names.push({ name: idents[idents.length - 1] ?? item.text });
7105
+ } else if (item.text.includes(" as ")) {
7106
+ const [head, alias] = item.text.split(/\s+as\s+/);
7107
+ names.push({ name: (head ?? "").trim(), alias: alias?.trim() });
7108
+ } else {
7109
+ names.push({ name: item.text });
7110
+ }
7111
+ }
7112
+ } else if (argument?.type === "use_wildcard") {
7113
+ isGlob = true;
7114
+ } else if (argument) {
7115
+ const idents = collectIdentifiers(argument);
7116
+ const last = idents[idents.length - 1] ?? argument.text;
7117
+ names.push({ name: last });
7118
+ const asMatch = text.match(/\s+as\s+(\w+)\s*;?\s*$/);
7119
+ if (asMatch) names[0].alias = asMatch[1];
7120
+ }
7121
+ return {
7122
+ path: path.trim(),
7123
+ names,
7124
+ isGlob,
7125
+ line: node.startPosition.row + 1,
7126
+ column: node.startPosition.column
7127
+ };
7128
+ }
7129
+ function extractFunction(node, attrs, inheritedTestConfig, isMethod) {
7130
+ const nameNode = node.childForFieldName("name");
7131
+ const name = nameNode?.text ?? "<anon>";
7132
+ const params = node.childForFieldName("parameters");
7133
+ const body = node.childForFieldName("body");
7134
+ const visibilityMod = node.namedChild(0);
7135
+ const isPublic = visibilityMod?.type === "visibility_modifier";
7136
+ let receiver;
7137
+ if (isMethod && params) {
7138
+ const selfParam = findSelfParameter(params);
7139
+ if (selfParam) receiver = selfParam.text;
7140
+ }
7141
+ const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
7142
+ const inTestConfig = inheritedTestConfig || attrs.isTest || attrs.isTestCfg || isFunctionAttrTest(node);
7143
+ return {
7144
+ name,
7145
+ line: node.startPosition.row + 1,
7146
+ column: node.startPosition.column,
7147
+ isPublic,
7148
+ isMethod,
7149
+ receiver,
7150
+ bodyLines,
7151
+ inTestConfig
7152
+ };
7153
+ }
7154
+ function extractStruct(node, attrs) {
7155
+ const nameNode = node.childForFieldName("name");
7156
+ return {
7157
+ name: nameNode?.text ?? "<anon>",
7158
+ line: node.startPosition.row + 1,
7159
+ column: node.startPosition.column,
7160
+ isPublic: hasVisibility(node),
7161
+ isDerive: attrs.derives.length > 0,
7162
+ derives: [...attrs.derives]
7163
+ };
7164
+ }
7165
+ function extractTrait(node, _attrs) {
7166
+ const nameNode = node.childForFieldName("name");
7167
+ return {
7168
+ name: nameNode?.text ?? "<anon>",
7169
+ line: node.startPosition.row + 1,
7170
+ column: node.startPosition.column,
7171
+ isPublic: hasVisibility(node)
7172
+ };
7173
+ }
7174
+ function extractImpl(node, _attrs, _ctx) {
7175
+ const traitNode = node.childForFieldName("trait");
7176
+ const typeNode = node.childForFieldName("type");
7177
+ const typeText = typeNode?.text ?? "<anon>";
7178
+ const traitText = traitNode?.text;
7179
+ const body = node.childForFieldName("body");
7180
+ const methods = [];
7181
+ if (body) {
7182
+ for (let i = 0; i < body.namedChildCount; i++) {
7183
+ const child = body.namedChild(i);
7184
+ if (child?.type === "function_item") {
7185
+ const m = child.childForFieldName("name");
7186
+ if (m) methods.push(m.text);
7187
+ }
7188
+ }
7189
+ }
7190
+ return {
7191
+ entry: {
7192
+ type: typeText,
7193
+ trait: traitText ?? void 0,
7194
+ methods,
7195
+ line: node.startPosition.row + 1,
7196
+ column: node.startPosition.column
7197
+ }
7198
+ };
7199
+ }
7200
+ function findUseListNode(argument) {
7201
+ if (!argument) return null;
7202
+ if (argument.type === "use_list") return argument;
7203
+ if (argument.type === "scoped_use_list") {
7204
+ for (let i = 0; i < argument.namedChildCount; i++) {
7205
+ const c = argument.namedChild(i);
7206
+ if (c?.type === "use_list") return c;
7207
+ }
7208
+ }
7209
+ return null;
7210
+ }
7211
+ function getField(node, fieldName) {
7212
+ return node.childForFieldName(fieldName);
7213
+ }
7214
+ function collectIdentifiers(node) {
7215
+ const out = [];
7216
+ for (let i = 0; i < node.namedChildCount; i++) {
7217
+ const child = node.namedChild(i);
7218
+ if (!child) continue;
7219
+ if (child.type === "identifier" || child.type === "type_identifier") {
7220
+ out.push(child.text);
7221
+ } else if (child.type === "scoped_identifier" || child.type === "nested_identifier") {
7222
+ out.push(...collectIdentifiers(child));
7223
+ }
7224
+ }
7225
+ return out;
7226
+ }
7227
+ function hasVisibility(node) {
7228
+ return node.namedChild(0)?.type === "visibility_modifier";
7229
+ }
7230
+ function isInImplBlock(node) {
7231
+ for (let p = node.parent; p; p = p.parent) {
7232
+ if (p.type === "impl_item") return true;
7233
+ if (p.type === "function_item" || p.type === "source_file") return false;
7234
+ }
7235
+ return false;
7236
+ }
7237
+ function findSelfParameter(params) {
7238
+ for (let i = 0; i < params.namedChildCount; i++) {
7239
+ const child = params.namedChild(i);
7240
+ if (child?.type === "self_parameter") return child;
7241
+ }
7242
+ return null;
7243
+ }
7244
+ function isFunctionAttrTest(node) {
7245
+ void node;
7246
+ return false;
7247
+ }
7248
+
7265
7249
  // src/engine/visitors/v2-build.ts
7266
- init_rust();
7267
7250
  var TAILWIND_COLOR_RE = /^(?:bg|text|border|ring|from|to|via|fill|stroke)-([a-z]+-\d+|white|black|transparent|current|\[.+?\])$/;
7268
7251
  var TAILWIND_SPACING_RE = /^(?:[pm][xytrbl]?|gap|space-[xy])-(\d+(?:\.\d+)?)$/;
7269
7252
  var TAILWIND_RADIUS_RE = /^(?:rounded(?:-[a-z]+)?)-(.+)$/;
@@ -32899,8 +32882,8 @@ function getCorpusBaselines() {
32899
32882
 
32900
32883
  // src/rules/ai/comment-ratio.ts
32901
32884
  var MIN_FILE_LINES = 20;
32902
- var FALLBACK_LOW = 0.05;
32903
- var FALLBACK_HIGH = 0.45;
32885
+ var FALLBACK_LOW = 0.02;
32886
+ var FALLBACK_HIGH = 0.55;
32904
32887
  var LINE_COMMENT_RE = /^\s*(?:\/\/|#|--|;|%)/;
32905
32888
  var BLOCK_COMMENT_OPEN_RE = /\/\*/;
32906
32889
  var aiCommentRatioRule = createRule({
@@ -33029,9 +33012,9 @@ function std(xs) {
33029
33012
 
33030
33013
  // src/rules/ai/compression-profile.ts
33031
33014
  var MIN_BYTES = 2e3;
33032
- var GZIP_RATIO_AI_THRESHOLD = 0.5;
33033
- var MEAN_LINE_NCD_HUMAN_MAX = 0.3;
33034
- var NCD_CV_AI_MAX = 0.5;
33015
+ var GZIP_RATIO_AI_THRESHOLD = 0.6;
33016
+ var MEAN_LINE_NCD_HUMAN_MAX = 0.25;
33017
+ var NCD_CV_AI_MAX = 0.4;
33035
33018
  var aiCompressionProfileRule = createRule({
33036
33019
  id: "ai/compression-profile",
33037
33020
  category: "ai",
@@ -33186,6 +33169,13 @@ var aiDefaultReactStackRule = createRule({
33186
33169
  category: "ai",
33187
33170
  severity: "low",
33188
33171
  aiSpecific: true,
33172
+ // v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.001
33173
+ // (fires 1 time per 1,000 files). The "≥3 of default stack"
33174
+ // threshold catches almost nothing on real codebases — the
33175
+ // default stack is now ubiquitous, so the signal is near-zero.
33176
+ // Disable until the threshold is lowered or the rule is split
33177
+ // into per-library detectors.
33178
+ defaultOff: true,
33189
33179
  description: "File uses \u22653 of the default React stack (Next.js + TanStack Query + Zustand + shadcn/ui) \u2014 Sascha 2025 + Nam et al. MSR 2026",
33190
33180
  create(context) {
33191
33181
  return context;
@@ -33438,6 +33428,10 @@ var aiLibraryReinventionRule = createRule({
33438
33428
  category: "ai",
33439
33429
  severity: "medium",
33440
33430
  aiSpecific: true,
33431
+ // v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.000
33432
+ // (fires never on the corpus). The "≥2 reinvented patterns"
33433
+ // threshold is too strict. Disable until rewritten.
33434
+ defaultOff: true,
33441
33435
  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",
33442
33436
  create(context) {
33443
33437
  return context;
@@ -33624,6 +33618,12 @@ var aiMarkdownLeakageRule = createRule({
33624
33618
  category: "ai",
33625
33619
  severity: "high",
33626
33620
  aiSpecific: true,
33621
+ // v0.20.0 calibration: v8.5 verdict = INVERTED (lift 65,504× but
33622
+ // recall 0.000 — fires never on the corpus, and when it does fire
33623
+ // it's wrong). The rule was on by default, contributing false
33624
+ // positives to the self-scan. Disable until the rule is rewritten
33625
+ // or the v9 corpus shows it works.
33626
+ defaultOff: true,
33627
33627
  description: "Stray Markdown fence or bare language name at top of file \u2014 AI tools leak the fenced-block format into code",
33628
33628
  create(context) {
33629
33629
  return context;
@@ -34049,6 +34049,11 @@ var aiTextLikeRatioRule = createRule({
34049
34049
  category: "ai",
34050
34050
  severity: "low",
34051
34051
  aiSpecific: true,
34052
+ // v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.000
34053
+ // (fires never on the corpus). The rule is too specific to ever
34054
+ // fire on real code — the prose-detection heuristic is too
34055
+ // conservative. Disable until rewritten.
34056
+ defaultOff: true,
34052
34057
  description: "High ratio of prose-like lines embedded in code \u2014 AI tools often leave natural-language explanations in source files (Yotkova 2026)",
34053
34058
  create(context) {
34054
34059
  return context;
@@ -35107,125 +35112,17 @@ var unusedParameterRule = createRule({
35107
35112
  });
35108
35113
 
35109
35114
  // src/rules/docs/broken-link.ts
35110
- var import_node_fs6 = require("fs");
35111
- var import_node_path6 = require("path");
35112
-
35113
- // src/engine/doc-freshness.ts
35114
- var import_node_fs5 = require("fs");
35115
- var import_node_path5 = require("path");
35116
- var import_globby2 = require("globby");
35117
-
35118
- // src/mcp/patterns.ts
35119
35115
  var import_node_fs3 = require("fs");
35120
35116
  var import_node_path3 = require("path");
35121
35117
 
35122
- // src/engine/discover.ts
35123
- var import_globby = require("globby");
35124
- var import_minimatch = require("minimatch");
35125
- var import_node_path = require("path");
35126
- var import_node_fs = require("fs");
35127
- var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte", ".astro", ".html"]);
35128
- var BACKEND_EXTENSIONS = /* @__PURE__ */ new Set([
35129
- ".py",
35130
- ".go",
35131
- // v0.14.0
35132
- ".swift",
35133
- ".kt",
35134
- ".kts",
35135
- ".dart",
35136
- ".rs",
35137
- ".cpp",
35138
- ".cc",
35139
- ".cxx",
35140
- ".c",
35141
- ".h",
35142
- ".hpp",
35143
- ".hxx",
35144
- ".java",
35145
- ".rb",
35146
- ".php"
35147
- ]);
35148
- var ALL_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
35149
- ...SOURCE_EXTENSIONS,
35150
- ...BACKEND_EXTENSIONS
35151
- ]);
35152
-
35153
- // src/config/conventions.ts
35118
+ // src/engine/doc-freshness.ts
35154
35119
  var import_node_fs2 = require("fs");
35155
35120
  var import_node_path2 = require("path");
35156
-
35157
- // src/mcp/patterns.ts
35158
- 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;
35159
- var DYNAMIC_IMPORT_RE = /import\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
35160
- var COMMONJS_REQUIRE_RE = /require\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
35161
- function extractImports(source) {
35162
- const seen = /* @__PURE__ */ new Set();
35163
- const out = [];
35164
- const push = (spec) => {
35165
- if (spec.startsWith(".") || spec.startsWith("/")) return;
35166
- if (seen.has(spec)) return;
35167
- seen.add(spec);
35168
- out.push(spec);
35169
- };
35170
- for (const re of [ESM_IMPORT_RE, DYNAMIC_IMPORT_RE, COMMONJS_REQUIRE_RE]) {
35171
- re.lastIndex = 0;
35172
- let m;
35173
- while ((m = re.exec(source)) !== null) {
35174
- push(m[2]);
35175
- }
35176
- }
35177
- return out;
35178
- }
35179
-
35180
- // src/rules/docs/expired-code-example.ts
35181
- var CODE_LANGS = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "javascript", "typescript"]);
35182
- function stripSubpath(spec) {
35183
- if (spec.startsWith("@")) return spec.split("/").slice(0, 2).join("/");
35184
- return spec.split("/")[0] ?? spec;
35185
- }
35186
- var expiredCodeExampleRule = createRule({
35187
- id: "docs/expired-code-example",
35188
- category: "docs",
35189
- severity: "medium",
35190
- aiSpecific: false,
35191
- description: "A fenced code example imports a package that is not declared in package.json.",
35192
- create(context) {
35193
- return { ...context, packages: declaredPackages(context.cwd) };
35194
- },
35195
- analyze(context, facts) {
35196
- const issues = [];
35197
- const source = facts.v2?._source;
35198
- if (!source) return issues;
35199
- const packages = declaredPackages(context.cwd);
35200
- const packageName = context.packageName;
35201
- if (packageName) packages.add(packageName);
35202
- const blocks = extractFencedCodeBlocks(source);
35203
- for (const block of blocks) {
35204
- if (!CODE_LANGS.has(block.lang)) continue;
35205
- if (block.body.split("\n").length < 2) continue;
35206
- const imports = extractImports(block.body);
35207
- for (const imp of imports) {
35208
- const pkgName = stripSubpath(imp);
35209
- if (packages.has(pkgName)) continue;
35210
- issues.push({
35211
- ruleId: "docs/expired-code-example",
35212
- category: "docs",
35213
- severity: "medium",
35214
- aiSpecific: false,
35215
- message: `Code example imports \`${imp}\` but \`${pkgName}\` is not in package.json.`,
35216
- line: block.line,
35217
- column: block.column,
35218
- advice: `Add \`${pkgName}\` to package.json or update the example.`
35219
- });
35220
- }
35221
- }
35222
- return issues;
35223
- }
35224
- });
35121
+ var import_globby = require("globby");
35225
35122
 
35226
35123
  // src/rules/docs/stale-function-reference.ts
35227
- var import_node_fs4 = require("fs");
35228
- var import_node_path4 = require("path");
35124
+ var import_node_fs = require("fs");
35125
+ var import_node_path = require("path");
35229
35126
  var RESERVED = /* @__PURE__ */ new Set([
35230
35127
  // JS reserved words
35231
35128
  "true",
@@ -35798,29 +35695,29 @@ var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs",
35798
35695
  var SOURCE_ROOTS = ["src", "lib", "app", "components"];
35799
35696
  var CAP = 200;
35800
35697
  function walk(dir, out, cap) {
35801
- if (!(0, import_node_fs4.existsSync)(dir) || out.length >= cap) return;
35698
+ if (!(0, import_node_fs.existsSync)(dir) || out.length >= cap) return;
35802
35699
  let entries;
35803
35700
  try {
35804
- entries = (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true });
35701
+ entries = (0, import_node_fs.readdirSync)(dir, { withFileTypes: true });
35805
35702
  } catch {
35806
35703
  return;
35807
35704
  }
35808
35705
  for (const entry of entries) {
35809
35706
  if (out.length >= cap) return;
35810
35707
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
35811
- const full = (0, import_node_path4.join)(dir, entry.name);
35708
+ const full = (0, import_node_path.join)(dir, entry.name);
35812
35709
  if (entry.isDirectory()) walk(full, out, cap);
35813
- else if (entry.isFile() && SOURCE_EXTS.has((0, import_node_path4.extname)(entry.name))) out.push(full);
35710
+ else if (entry.isFile() && SOURCE_EXTS.has((0, import_node_path.extname)(entry.name))) out.push(full);
35814
35711
  }
35815
35712
  }
35816
35713
  function collectExports(cwd) {
35817
35714
  const out = /* @__PURE__ */ new Set();
35818
35715
  const files = [];
35819
- for (const root of SOURCE_ROOTS) walk((0, import_node_path4.join)(cwd, root), files, CAP);
35716
+ for (const root of SOURCE_ROOTS) walk((0, import_node_path.join)(cwd, root), files, CAP);
35820
35717
  for (const file of files) {
35821
35718
  let source;
35822
35719
  try {
35823
- source = (0, import_node_fs4.readFileSync)(file, "utf-8");
35720
+ source = (0, import_node_fs.readFileSync)(file, "utf-8");
35824
35721
  } catch {
35825
35722
  continue;
35826
35723
  }
@@ -35833,7 +35730,7 @@ function collectExports(cwd) {
35833
35730
  /\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g,
35834
35731
  // v0.18.6: also collect field names from `export interface` and
35835
35732
  // `export type` declarations. Without this, fields like
35836
- // `crossFileDrift`, `aiQuality`, `engineeringHygiene` are
35733
+ // `crossFileDrift`, `aiSlopScore`, `engineeringHygiene` are
35837
35734
  // flagged as stale even though they're valid type fields.
35838
35735
  /^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\s*[?:]/gm
35839
35736
  ]) {
@@ -36083,38 +35980,6 @@ function extractInlineCodeSpans(source) {
36083
35980
  }
36084
35981
  return hits;
36085
35982
  }
36086
- function extractFencedCodeBlocks(source) {
36087
- const blocks = [];
36088
- const lines = source.split("\n");
36089
- let i = 0;
36090
- while (i < lines.length) {
36091
- const line = lines[i] ?? "";
36092
- const fenceMatch = /^```(\w*)\s*$/.exec(line);
36093
- if (!fenceMatch) {
36094
- i++;
36095
- continue;
36096
- }
36097
- const lang = fenceMatch[1] ?? "";
36098
- const startLine = i + 1;
36099
- const bodyLines = [];
36100
- i++;
36101
- while (i < lines.length) {
36102
- if (/^```\s*$/.test(lines[i] ?? "")) {
36103
- i++;
36104
- break;
36105
- }
36106
- bodyLines.push(lines[i] ?? "");
36107
- i++;
36108
- }
36109
- blocks.push({
36110
- lang,
36111
- body: bodyLines.join("\n"),
36112
- line: startLine,
36113
- column: 1
36114
- });
36115
- }
36116
- return blocks;
36117
- }
36118
35983
  function extractMarkdownLinks(source) {
36119
35984
  const hits = [];
36120
35985
  const re = /(?<!\!)\[([^\]]*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
@@ -36131,10 +35996,10 @@ function extractMarkdownLinks(source) {
36131
35996
  }
36132
35997
  function declaredPackages(cwd) {
36133
35998
  const out = /* @__PURE__ */ new Set();
36134
- const pkgPath = (0, import_node_path5.join)(cwd, "package.json");
36135
- if (!(0, import_node_fs5.existsSync)(pkgPath)) return out;
35999
+ const pkgPath = (0, import_node_path2.join)(cwd, "package.json");
36000
+ if (!(0, import_node_fs2.existsSync)(pkgPath)) return out;
36136
36001
  try {
36137
- const raw = (0, import_node_fs5.readFileSync)(pkgPath, "utf-8");
36002
+ const raw = (0, import_node_fs2.readFileSync)(pkgPath, "utf-8");
36138
36003
  const pkg = JSON.parse(raw);
36139
36004
  for (const k of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
36140
36005
  const v = pkg[k];
@@ -36164,7 +36029,7 @@ var brokenLinkRule = createRule({
36164
36029
  const source = facts.v2?._source;
36165
36030
  if (!source) return issues;
36166
36031
  const links = extractMarkdownLinks(source);
36167
- const docDir = (0, import_node_path6.dirname)((0, import_node_path6.resolve)(context.cwd, context.filePath));
36032
+ const docDir = (0, import_node_path3.dirname)((0, import_node_path3.resolve)(context.cwd, context.filePath));
36168
36033
  for (const link of links) {
36169
36034
  const target = link.target;
36170
36035
  if (target.startsWith("http://") || target.startsWith("https://")) continue;
@@ -36174,8 +36039,8 @@ var brokenLinkRule = createRule({
36174
36039
  if (target.startsWith("/")) continue;
36175
36040
  const filePart = target.split("#")[0] ?? target;
36176
36041
  if (filePart === "") continue;
36177
- const resolved = (0, import_node_path6.join)(docDir, filePart);
36178
- if ((0, import_node_fs6.existsSync)(resolved)) continue;
36042
+ const resolved = (0, import_node_path3.join)(docDir, filePart);
36043
+ if ((0, import_node_fs3.existsSync)(resolved)) continue;
36179
36044
  issues.push({
36180
36045
  ruleId: "docs/broken-link",
36181
36046
  category: "docs",
@@ -36404,10 +36269,261 @@ var goStructTagInconsistencyRule = createRule({
36404
36269
  }
36405
36270
  });
36406
36271
 
36272
+ // src/rules/java/arraylist-vs-linkedlist.ts
36273
+ var NEW_LINKED_LIST_REGEX = /new\s+LinkedList\s*</g;
36274
+ var javaArraylistVsLinkedlistRule = createRule({
36275
+ id: "java/arraylist-vs-linkedlist",
36276
+ category: "typo",
36277
+ severity: "low",
36278
+ aiSpecific: true,
36279
+ description: "new LinkedList<>() \u2014 use ArrayList (Effective Java, Item 28)",
36280
+ create(_context) {
36281
+ return {};
36282
+ },
36283
+ analyze(_context, facts) {
36284
+ const issues = [];
36285
+ const source = facts.v2?._source;
36286
+ if (!source) return issues;
36287
+ let m;
36288
+ NEW_LINKED_LIST_REGEX.lastIndex = 0;
36289
+ while ((m = NEW_LINKED_LIST_REGEX.exec(source)) !== null) {
36290
+ const line = source.slice(0, m.index).split("\n").length;
36291
+ issues.push({
36292
+ ruleId: "java/arraylist-vs-linkedlist",
36293
+ category: "typo",
36294
+ severity: "low",
36295
+ aiSpecific: true,
36296
+ message: `new LinkedList at line ${line} \u2014 use ArrayList instead`,
36297
+ line,
36298
+ column: 1,
36299
+ 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."
36300
+ });
36301
+ }
36302
+ return issues;
36303
+ }
36304
+ });
36305
+
36306
+ // src/rules/java/empty-catch-block.ts
36307
+ var SINGLE_LINE_EMPTY_CATCH_REGEX = /catch\s*\([^)]*\)\s*\{\s*\}/g;
36308
+ var javaEmptyCatchBlockRule = createRule({
36309
+ id: "java/empty-catch-block",
36310
+ category: "logic",
36311
+ severity: "medium",
36312
+ aiSpecific: true,
36313
+ description: "Empty catch block \u2014 silently swallows exceptions",
36314
+ create(_context) {
36315
+ return {};
36316
+ },
36317
+ analyze(_context, facts) {
36318
+ const issues = [];
36319
+ const source = facts.v2?._source;
36320
+ if (!source) return issues;
36321
+ let m;
36322
+ SINGLE_LINE_EMPTY_CATCH_REGEX.lastIndex = 0;
36323
+ while ((m = SINGLE_LINE_EMPTY_CATCH_REGEX.exec(source)) !== null) {
36324
+ const line = source.slice(0, m.index).split("\n").length;
36325
+ issues.push({
36326
+ ruleId: "java/empty-catch-block",
36327
+ category: "logic",
36328
+ severity: "medium",
36329
+ aiSpecific: true,
36330
+ message: `Empty catch block at line ${line} \u2014 exception is silently swallowed`,
36331
+ line,
36332
+ column: 1,
36333
+ 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.'
36334
+ });
36335
+ }
36336
+ return issues;
36337
+ }
36338
+ });
36339
+
36340
+ // src/rules/java/legacy-date-api.ts
36341
+ var LEGACY_IMPORT_REGEX = /^import\s+(?:static\s+)?java\.(?:util|sql)\.(?:Date|Calendar|GregorianCalendar)\s*;/gm;
36342
+ var LEGACY_USAGE_REGEX = /\bnew\s+(?:Date|GregorianCalendar)\s*\(/g;
36343
+ var CALENDAR_GET_INSTANCE_REGEX = /Calendar\.getInstance\s*\(/g;
36344
+ var javaLegacyDateApiRule = createRule({
36345
+ id: "java/legacy-date-api",
36346
+ category: "typo",
36347
+ severity: "low",
36348
+ aiSpecific: true,
36349
+ description: "Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310) from Java 8+",
36350
+ create(_context) {
36351
+ return {};
36352
+ },
36353
+ analyze(_context, facts) {
36354
+ const issues = [];
36355
+ const source = facts.v2?._source;
36356
+ if (!source) return issues;
36357
+ const flagged = /* @__PURE__ */ new Set();
36358
+ let m;
36359
+ LEGACY_IMPORT_REGEX.lastIndex = 0;
36360
+ while ((m = LEGACY_IMPORT_REGEX.exec(source)) !== null) {
36361
+ const line = source.slice(0, m.index).split("\n").length;
36362
+ flagged.add(line);
36363
+ issues.push({
36364
+ ruleId: "java/legacy-date-api",
36365
+ category: "typo",
36366
+ severity: "low",
36367
+ aiSpecific: true,
36368
+ message: `Legacy date import at line ${line} \u2014 use java.time (JSR-310) from Java 8+`,
36369
+ line,
36370
+ column: 1,
36371
+ 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."
36372
+ });
36373
+ }
36374
+ LEGACY_USAGE_REGEX.lastIndex = 0;
36375
+ while ((m = LEGACY_USAGE_REGEX.exec(source)) !== null) {
36376
+ const line = source.slice(0, m.index).split("\n").length;
36377
+ if (flagged.has(line)) continue;
36378
+ issues.push({
36379
+ ruleId: "java/legacy-date-api",
36380
+ category: "typo",
36381
+ severity: "low",
36382
+ aiSpecific: true,
36383
+ message: `new Date() / GregorianCalendar at line ${line} \u2014 use java.time`,
36384
+ line,
36385
+ column: 1,
36386
+ advice: "Replace with `LocalDate.now()`, `Instant.now()`, or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
36387
+ });
36388
+ }
36389
+ CALENDAR_GET_INSTANCE_REGEX.lastIndex = 0;
36390
+ while ((m = CALENDAR_GET_INSTANCE_REGEX.exec(source)) !== null) {
36391
+ const line = source.slice(0, m.index).split("\n").length;
36392
+ if (flagged.has(line)) continue;
36393
+ issues.push({
36394
+ ruleId: "java/legacy-date-api",
36395
+ category: "typo",
36396
+ severity: "low",
36397
+ aiSpecific: true,
36398
+ message: `Calendar.getInstance() at line ${line} \u2014 use java.time`,
36399
+ line,
36400
+ column: 1,
36401
+ advice: "Replace with `LocalDate.now()` (date-only) or `ZonedDateTime.now()`. Reference: java/legacy-date-api v0.20."
36402
+ });
36403
+ }
36404
+ return issues;
36405
+ }
36406
+ });
36407
+
36408
+ // src/rules/java/raw-type-overuse.ts
36409
+ var RAW_TYPE_REGEX = /\b(List|Map|Set|Collection|Iterable)\s+(?![<A-Z])(\w+)/g;
36410
+ var javaRawTypeOveruseRule = createRule({
36411
+ id: "java/raw-type-overuse",
36412
+ category: "typo",
36413
+ severity: "low",
36414
+ aiSpecific: true,
36415
+ description: "Raw type usage (List, Map, Set) \u2014 use generics (Effective Java, Item 23)",
36416
+ create(_context) {
36417
+ return {};
36418
+ },
36419
+ analyze(_context, facts) {
36420
+ const issues = [];
36421
+ const source = facts.v2?._source;
36422
+ if (!source) return issues;
36423
+ let m;
36424
+ RAW_TYPE_REGEX.lastIndex = 0;
36425
+ while ((m = RAW_TYPE_REGEX.exec(source)) !== null) {
36426
+ const typeName = m[1];
36427
+ const line = source.slice(0, m.index).split("\n").length;
36428
+ issues.push({
36429
+ ruleId: "java/raw-type-overuse",
36430
+ category: "typo",
36431
+ severity: "low",
36432
+ aiSpecific: true,
36433
+ message: `Raw type ${typeName} at line ${line} \u2014 add type parameters`,
36434
+ line,
36435
+ column: 1,
36436
+ 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.`
36437
+ });
36438
+ }
36439
+ return issues;
36440
+ }
36441
+ });
36442
+
36443
+ // src/rules/java/string-concat-loop.ts
36444
+ var STRING_CONCAT_REGEX = /(\b\w+)\s*=\s*\1\s*\+\s*[^;]+;|(\b\w+)\s*\+=\s*['"`]/g;
36445
+ var javaStringConcatLoopRule = createRule({
36446
+ id: "java/string-concat-loop",
36447
+ category: "perf",
36448
+ severity: "low",
36449
+ aiSpecific: true,
36450
+ description: "String concatenation in a loop \u2014 use StringBuilder",
36451
+ create(_context) {
36452
+ return {};
36453
+ },
36454
+ analyze(_context, facts) {
36455
+ const issues = [];
36456
+ const source = facts.v2?._source;
36457
+ if (!source) return issues;
36458
+ if (!/\b(for|while|do)\b/.test(source)) return issues;
36459
+ let m;
36460
+ STRING_CONCAT_REGEX.lastIndex = 0;
36461
+ while ((m = STRING_CONCAT_REGEX.exec(source)) !== null) {
36462
+ const line = source.slice(0, m.index).split("\n").length;
36463
+ issues.push({
36464
+ ruleId: "java/string-concat-loop",
36465
+ category: "perf",
36466
+ severity: "low",
36467
+ aiSpecific: true,
36468
+ message: `String concatenation in a loop at line ${line} \u2014 use StringBuilder`,
36469
+ line,
36470
+ column: 1,
36471
+ 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."
36472
+ });
36473
+ }
36474
+ return issues;
36475
+ }
36476
+ });
36477
+
36478
+ // src/rules/java/system-out-println.ts
36479
+ var PRINTLN_REGEX = /System\.out\.println\s*\(/g;
36480
+ var DEFAULT_THRESHOLD = 1;
36481
+ var javaSystemOutPrintlnRule = createRule({
36482
+ id: "java/system-out-println",
36483
+ category: "typo",
36484
+ severity: "low",
36485
+ aiSpecific: true,
36486
+ description: "System.out.println in production code \u2014 use a logger (SLF4J, Log4j, etc.)",
36487
+ create(_context) {
36488
+ return { threshold: DEFAULT_THRESHOLD };
36489
+ },
36490
+ analyze(context, facts) {
36491
+ const issues = [];
36492
+ const source = facts.v2?._source;
36493
+ if (!source) return issues;
36494
+ const matches = [];
36495
+ let m;
36496
+ PRINTLN_REGEX.lastIndex = 0;
36497
+ while ((m = PRINTLN_REGEX.exec(source)) !== null) {
36498
+ matches.push(m.index);
36499
+ }
36500
+ if (matches.length <= context.threshold) return issues;
36501
+ const cap = Math.min(matches.length, 10);
36502
+ for (let i = 0; i < cap; i++) {
36503
+ const idx = matches[i];
36504
+ const line = source.slice(0, idx).split("\n").length;
36505
+ issues.push({
36506
+ ruleId: "java/system-out-println",
36507
+ category: "typo",
36508
+ severity: "low",
36509
+ aiSpecific: true,
36510
+ message: `System.out.println at line ${line} \u2014 use a logger for production output`,
36511
+ line,
36512
+ column: 1,
36513
+ 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."
36514
+ });
36515
+ }
36516
+ return issues;
36517
+ }
36518
+ });
36519
+
36407
36520
  // src/rules/layout/gap-monopoly.ts
36408
36521
  var GAP_RE = /\bgap(?:-x|-y)?-(\d+)\b/g;
36409
36522
  var gapMonopolyRule = createRule({
36410
36523
  id: "layout/gap-monopoly",
36524
+ // v0.20.0 calibration: recall 0.000, fires never. Disable
36525
+ // until rewritten.
36526
+ defaultOff: true,
36411
36527
  category: "layout",
36412
36528
  severity: "medium",
36413
36529
  aiSpecific: false,
@@ -36504,6 +36620,9 @@ var mathElementUniformityRule = createRule({
36504
36620
  var GRID_COLS_RE = /\bgrid-cols-(\d+)\b/g;
36505
36621
  var mathGridUniformityRule = createRule({
36506
36622
  id: "layout/math-grid-uniformity",
36623
+ // v0.20.0 calibration: recall 0.000, fires never. Disable
36624
+ // until rewritten.
36625
+ defaultOff: true,
36507
36626
  category: "layout",
36508
36627
  severity: "high",
36509
36628
  aiSpecific: true,
@@ -36756,16 +36875,16 @@ var DEFAULT_CONFIG = {
36756
36875
  };
36757
36876
 
36758
36877
  // src/config/detect/monorepo.ts
36759
- var import_node_fs7 = require("fs");
36760
- var import_node_path7 = require("path");
36878
+ var import_node_fs4 = require("fs");
36879
+ var import_node_path4 = require("path");
36761
36880
 
36762
36881
  // src/config/detect/styling.ts
36763
- var import_node_fs8 = require("fs");
36764
- var import_node_path8 = require("path");
36882
+ var import_node_fs5 = require("fs");
36883
+ var import_node_path5 = require("path");
36765
36884
 
36766
36885
  // src/config/detect/stack.ts
36767
- var import_node_fs9 = require("fs");
36768
- var import_node_path9 = require("path");
36886
+ var import_node_fs6 = require("fs");
36887
+ var import_node_path6 = require("path");
36769
36888
 
36770
36889
  // src/config/presets.ts
36771
36890
  var REACT_ONLY_RULES = {
@@ -36814,8 +36933,8 @@ var FRAMEWORK_PRESETS = {
36814
36933
  };
36815
36934
 
36816
36935
  // src/config/load.ts
36817
- var import_node_fs10 = require("fs");
36818
- var import_node_path10 = require("path");
36936
+ var import_node_fs8 = require("fs");
36937
+ var import_node_path8 = require("path");
36819
36938
  var import_node_module = require("module");
36820
36939
 
36821
36940
  // src/engine/logger.ts
@@ -36837,6 +36956,10 @@ function setLoggerQuiet(quiet) {
36837
36956
  logger = createLogger(quiet);
36838
36957
  }
36839
36958
 
36959
+ // src/config/conventions.ts
36960
+ var import_node_fs7 = require("fs");
36961
+ var import_node_path7 = require("path");
36962
+
36840
36963
  // src/config/init.ts
36841
36964
  var STRICTNESS_PRESETS = {
36842
36965
  strict: {
@@ -36951,6 +37074,9 @@ function checkInlineValue(value, scale) {
36951
37074
  }
36952
37075
  var spacingGridRule = createRule({
36953
37076
  id: "layout/spacing-grid",
37077
+ // v0.20.0 calibration: recall 0.000, fires never. Disable
37078
+ // until rewritten.
37079
+ defaultOff: true,
36954
37080
  category: "layout",
36955
37081
  severity: "medium",
36956
37082
  aiSpecific: false,
@@ -37060,15 +37186,9 @@ var boundaryViolationRule = createRule({
37060
37186
  if (context.supportsRsc === false) return [];
37061
37187
  if (!facts.v2) return [];
37062
37188
  const issues = [];
37063
- const hookLines = /* @__PURE__ */ new Map();
37064
- for (const h of facts.v2.logic.hooks) {
37065
- const arr = hookLines.get(h.name) ?? [];
37066
- arr.push({ line: h.line, column: h.column });
37067
- hookLines.set(h.name, arr);
37068
- }
37069
37189
  for (const component of facts.v2.components) {
37070
37190
  if (!component.isServerComponent) continue;
37071
- for (const hook of facts.v2.logic.hooks) {
37191
+ for (const hook of component.hookCalls) {
37072
37192
  if (!context.clientHooks.has(hook.name)) continue;
37073
37193
  issues.push({
37074
37194
  ruleId: "logic/boundary-violation",
@@ -37076,8 +37196,8 @@ var boundaryViolationRule = createRule({
37076
37196
  severity: "high",
37077
37197
  aiSpecific: true,
37078
37198
  message: `'${hook.name}' only works in interactive client components. This component runs on the server.`,
37079
- line: component.line,
37080
- column: component.column,
37199
+ line: hook.line,
37200
+ column: hook.column,
37081
37201
  advice: "Add the 'use client' directive at the top of this file, or move the stateful part to a separate client component.",
37082
37202
  fix: {
37083
37203
  kind: "insert",
@@ -38202,7 +38322,6 @@ function scanForStringlyParams(paramText) {
38202
38322
  }
38203
38323
 
38204
38324
  // src/rules/rust/todo-macro.ts
38205
- init_parser_rust();
38206
38325
  var TODO_MACROS = /* @__PURE__ */ new Set(["todo", "unimplemented", "todo_unimplemented"]);
38207
38326
  var rustTodoMacroRule = createRule({
38208
38327
  id: "rust/todo-macro",
@@ -38338,7 +38457,6 @@ function collectReferencedNames(source) {
38338
38457
  }
38339
38458
 
38340
38459
  // src/rules/rust/unwrap-in-production.ts
38341
- init_parser_rust();
38342
38460
  var UNWRAP_METHODS = /* @__PURE__ */ new Set(["unwrap", "expect", "unwrap_or_else"]);
38343
38461
  var rustUnwrapInProductionRule = createRule({
38344
38462
  id: "rust/unwrap-in-production",
@@ -39475,7 +39593,7 @@ function looksRealistic(value) {
39475
39593
 
39476
39594
  // src/rules/test/missing-edge-case.ts
39477
39595
  var import_core4 = require("@swc/core");
39478
- var import_node_fs11 = require("fs");
39596
+ var import_node_fs9 = require("fs");
39479
39597
  var MAX_PER_FILE = 20;
39480
39598
  var missingEdgeCaseRule = createRule({
39481
39599
  id: "test/missing-edge-case",
@@ -39497,7 +39615,7 @@ var missingEdgeCaseRule = createRule({
39497
39615
  const testFileSources = /* @__PURE__ */ new Map();
39498
39616
  for (const testFile of discoverTestFiles(cwd)) {
39499
39617
  try {
39500
- testFileSources.set(testFile, (0, import_node_fs11.readFileSync)(testFile, "utf-8"));
39618
+ testFileSources.set(testFile, (0, import_node_fs9.readFileSync)(testFile, "utf-8"));
39501
39619
  } catch {
39502
39620
  }
39503
39621
  }
@@ -39751,8 +39869,8 @@ function discoverTestFiles(cwd) {
39751
39869
  const found = [];
39752
39870
  for (const root of roots) {
39753
39871
  const abs = `${cwd}/${root}`;
39754
- if (!(0, import_node_fs11.existsSync)(abs)) continue;
39755
- walk2(abs, found, import_node_fs11.readdirSync, import_node_fs11.statSync);
39872
+ if (!(0, import_node_fs9.existsSync)(abs)) continue;
39873
+ walk2(abs, found, import_node_fs9.readdirSync, import_node_fs9.statSync);
39756
39874
  if (found.length > 200) break;
39757
39875
  }
39758
39876
  return found;
@@ -39863,7 +39981,12 @@ var WEAK_MATCHERS = /* @__PURE__ */ new Set([
39863
39981
  "toBeUndefined",
39864
39982
  "toBeTruthy",
39865
39983
  "toBeFalsy",
39866
- "toBeNull"
39984
+ "toBeNull",
39985
+ "toBeGreaterThan",
39986
+ "toBeLessThan",
39987
+ "toContain",
39988
+ "toHaveLength",
39989
+ "toBeInstanceOf"
39867
39990
  ]);
39868
39991
  function isTautologicalAssertion(hit) {
39869
39992
  if (hit.matcherArg && hit.expectArg && hit.expectArg === hit.matcherArg) return true;
@@ -40704,6 +40827,10 @@ function rgbToHue([r, g, b]) {
40704
40827
  }
40705
40828
  var mathColorClusterRule = createRule({
40706
40829
  id: "visual/math-color-cluster",
40830
+ // v0.20.0 calibration: recall 0.000, fires never. The
40831
+ // "math-color cluster" heuristic is too specific to ever fire
40832
+ // on real code. Disable until rewritten.
40833
+ defaultOff: true,
40707
40834
  category: "visual",
40708
40835
  severity: "high",
40709
40836
  aiSpecific: true,
@@ -40758,6 +40885,9 @@ var FONT_IMPORT_HINT_RE = /next\/font|@import\s+url|fonts\.googleapis|fonts\.gst
40758
40885
  var NEXT_FONT_IMPORT_RE = /import\s+.*from\s+['"]next\/font\/(?:google|local)['"]/;
40759
40886
  var mathDefaultFontRule = createRule({
40760
40887
  id: "visual/math-default-font",
40888
+ // v0.20.0 calibration: recall 0.001, fires 1 per 1,000 files.
40889
+ // Too specific to be useful. Disable until rewritten.
40890
+ defaultOff: true,
40761
40891
  category: "visual",
40762
40892
  severity: "high",
40763
40893
  aiSpecific: true,
@@ -40824,6 +40954,9 @@ var TEXT_SIZE_MAP = {
40824
40954
  var TEXT_SIZE_RE = /\btext-(?:xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl)\b/g;
40825
40955
  var mathFontEntropyRule = createRule({
40826
40956
  id: "visual/math-font-entropy",
40957
+ // v0.20.0 calibration: recall 0.004, fires 0 times on self-scan
40958
+ // (verified). Disable until rewritten.
40959
+ defaultOff: true,
40827
40960
  category: "visual",
40828
40961
  severity: "high",
40829
40962
  aiSpecific: true,
@@ -40946,6 +41079,10 @@ var ROUNDED_MAP = {
40946
41079
  var ROUNDED_RE = /\brounded(?:-none|-sm|-md|-lg|-xl|-2xl|-3xl|-full)?\b/g;
40947
41080
  var mathRoundedEntropyRule = createRule({
40948
41081
  id: "visual/math-rounded-entropy",
41082
+ // v0.20.0 calibration: recall 0.004, fires 0 times on self-scan
41083
+ // (verified). The entropy heuristic is too specific to ever fire
41084
+ // on real code. Disable until rewritten.
41085
+ defaultOff: true,
40949
41086
  category: "visual",
40950
41087
  severity: "high",
40951
41088
  aiSpecific: true,
@@ -40986,6 +41123,9 @@ var mathRoundedEntropyRule = createRule({
40986
41123
  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;
40987
41124
  var mathSpacingEntropyRule = createRule({
40988
41125
  id: "visual/math-spacing-entropy",
41126
+ // v0.20.0 calibration: recall 0.002, fires 0 times on self-scan
41127
+ // (verified). Disable until rewritten.
41128
+ defaultOff: true,
40989
41129
  category: "visual",
40990
41130
  severity: "medium",
40991
41131
  aiSpecific: true,
@@ -41023,7 +41163,7 @@ var mathSpacingEntropyRule = createRule({
41023
41163
 
41024
41164
  // src/rules/visual/naturalness-anomaly.ts
41025
41165
  var MIN_LENGTH = 50;
41026
- var DISTINCT_RATIO_FLOOR = 0.3;
41166
+ var DISTINCT_RATIO_FLOOR = 0.2;
41027
41167
  var naturalnessAnomalyRule = createRule({
41028
41168
  id: "visual/naturalness-anomaly",
41029
41169
  category: "visual",
@@ -41068,6 +41208,9 @@ var naturalnessAnomalyRule = createRule({
41068
41208
  var SCALE_TOLERANCE = 1e-3;
41069
41209
  var radiusScaleViolationRule = createRule({
41070
41210
  id: "visual/radius-scale-violation",
41211
+ // v0.20.0 calibration: recall 0.000, fires never. Disable
41212
+ // until rewritten.
41213
+ defaultOff: true,
41071
41214
  category: "visual",
41072
41215
  severity: "medium",
41073
41216
  aiSpecific: false,
@@ -41252,6 +41395,10 @@ var ALT_HANDLER_RE = /^(?:onClick|onPointerDown|onKey(?:Down|Up|Press))$/;
41252
41395
  var ALT_ROLE_RE = /^(?:button|application|tab|menuitem)$/;
41253
41396
  var draggingMovementsRule = createRule({
41254
41397
  id: "wcag/dragging-movements",
41398
+ // v0.20.0 calibration: v8.5 = DORMANT but defaultOff: false
41399
+ // was a bug — DORMANT rules should be off by default. The
41400
+ // rule was contributing 0 useful fires. Disable.
41401
+ defaultOff: true,
41255
41402
  category: "wcag",
41256
41403
  severity: "medium",
41257
41404
  aiSpecific: false,
@@ -41398,6 +41545,11 @@ function scanForMissingAlt(source) {
41398
41545
  }
41399
41546
  var missingAltRule = createRule({
41400
41547
  id: "wcag/missing-alt",
41548
+ // v0.20.0 calibration: lift 1.0, recall 0.000. Lift of 1.0
41549
+ // means the rule has zero discriminative power — it fires
41550
+ // equally on positive and negative examples. Dead rule.
41551
+ // Disable until rewritten.
41552
+ defaultOff: true,
41401
41553
  category: "wcag",
41402
41554
  severity: "medium",
41403
41555
  aiSpecific: false,
@@ -41618,13 +41770,18 @@ var builtinRules = [
41618
41770
  unusedLocalRule,
41619
41771
  unusedParameterRule,
41620
41772
  brokenLinkRule,
41621
- expiredCodeExampleRule,
41622
41773
  staleFunctionReferenceRule,
41623
41774
  stalePackageReferenceRule,
41624
41775
  dupIdenticalBlockRule,
41625
41776
  goErrorWrapWithoutContextRule,
41626
41777
  goNilSliceVsEmptyRule,
41627
41778
  goStructTagInconsistencyRule,
41779
+ javaArraylistVsLinkedlistRule,
41780
+ javaEmptyCatchBlockRule,
41781
+ javaLegacyDateApiRule,
41782
+ javaRawTypeOveruseRule,
41783
+ javaStringConcatLoopRule,
41784
+ javaSystemOutPrintlnRule,
41628
41785
  gapMonopolyRule,
41629
41786
  mathElementUniformityRule,
41630
41787
  mathGridUniformityRule,
@@ -42338,24 +42495,6 @@ var signal_strength_default = {
42338
42495
  _v8Verdict: "OK",
42339
42496
  _v8Lift: 48.52
42340
42497
  },
42341
- "docs/expired-code-example": {
42342
- recall: 0,
42343
- fpRate: 0,
42344
- ratio: 0,
42345
- precision: 0,
42346
- lastCalibratedAt: "2026-07-01T00:00:00Z",
42347
- verdict: "INVERTED",
42348
- _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).",
42349
- aiSpecific: false,
42350
- _v7Verdict: "DORMANT",
42351
- _v7Lift: 1,
42352
- _v7Recall: 0,
42353
- _v7FpRate: 0,
42354
- _v7Precision: 0,
42355
- _v8Verdict: "INVERTED",
42356
- _v8Lift: 0,
42357
- defaultOff: true
42358
- },
42359
42498
  "docs/stale-function-reference": {
42360
42499
  recall: 26e-4,
42361
42500
  fpRate: 5e-4,
@@ -42462,6 +42601,114 @@ var signal_strength_default = {
42462
42601
  _v8Lift: 1,
42463
42602
  defaultOff: true
42464
42603
  },
42604
+ "java/arraylist-vs-linkedlist": {
42605
+ recall: 0,
42606
+ fpRate: 0,
42607
+ ratio: 1,
42608
+ precision: 0,
42609
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42610
+ verdict: "DORMANT",
42611
+ _calibrationNote: "v0.20: new rule (new LinkedList<>() \u2014 use ArrayList (Effective Java Item 28)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
42612
+ aiSpecific: true,
42613
+ _v7Verdict: "DORMANT",
42614
+ _v7Lift: 1,
42615
+ _v7Recall: 0,
42616
+ _v7FpRate: 0,
42617
+ _v7Precision: 0,
42618
+ _v8Verdict: "DORMANT",
42619
+ _v8Lift: 1,
42620
+ defaultOff: true
42621
+ },
42622
+ "java/empty-catch-block": {
42623
+ recall: 0,
42624
+ fpRate: 0,
42625
+ ratio: 1,
42626
+ precision: 0,
42627
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42628
+ verdict: "DORMANT",
42629
+ _calibrationNote: "v0.20: new rule (Empty catch block \u2014 silently swallows exceptions). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
42630
+ aiSpecific: true,
42631
+ _v7Verdict: "DORMANT",
42632
+ _v7Lift: 1,
42633
+ _v7Recall: 0,
42634
+ _v7FpRate: 0,
42635
+ _v7Precision: 0,
42636
+ _v8Verdict: "DORMANT",
42637
+ _v8Lift: 1,
42638
+ defaultOff: true
42639
+ },
42640
+ "java/legacy-date-api": {
42641
+ recall: 0,
42642
+ fpRate: 0,
42643
+ ratio: 1,
42644
+ precision: 0,
42645
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42646
+ verdict: "DORMANT",
42647
+ _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.",
42648
+ aiSpecific: true,
42649
+ _v7Verdict: "DORMANT",
42650
+ _v7Lift: 1,
42651
+ _v7Recall: 0,
42652
+ _v7FpRate: 0,
42653
+ _v7Precision: 0,
42654
+ _v8Verdict: "DORMANT",
42655
+ _v8Lift: 1,
42656
+ defaultOff: true
42657
+ },
42658
+ "java/raw-type-overuse": {
42659
+ recall: 0,
42660
+ fpRate: 0,
42661
+ ratio: 1,
42662
+ precision: 0,
42663
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42664
+ verdict: "DORMANT",
42665
+ _calibrationNote: "v0.20: new rule (Raw type usage \u2014 use generics). DORMANT until v9 Java corpus calibration.",
42666
+ aiSpecific: true,
42667
+ _v7Verdict: "DORMANT",
42668
+ _v7Lift: 1,
42669
+ _v7Recall: 0,
42670
+ _v7FpRate: 0,
42671
+ _v7Precision: 0,
42672
+ _v8Verdict: "DORMANT",
42673
+ _v8Lift: 1,
42674
+ defaultOff: true
42675
+ },
42676
+ "java/string-concat-loop": {
42677
+ recall: 0,
42678
+ fpRate: 0,
42679
+ ratio: 1,
42680
+ precision: 0,
42681
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42682
+ verdict: "DORMANT",
42683
+ _calibrationNote: "v0.20: new rule (String concat in loop \u2014 use StringBuilder). DORMANT until v9 Java corpus calibration.",
42684
+ aiSpecific: true,
42685
+ _v7Verdict: "DORMANT",
42686
+ _v7Lift: 1,
42687
+ _v7Recall: 0,
42688
+ _v7FpRate: 0,
42689
+ _v7Precision: 0,
42690
+ _v8Verdict: "DORMANT",
42691
+ _v8Lift: 1,
42692
+ defaultOff: true
42693
+ },
42694
+ "java/system-out-println": {
42695
+ recall: 0,
42696
+ fpRate: 0,
42697
+ ratio: 1,
42698
+ precision: 0,
42699
+ lastCalibratedAt: "2026-07-01T00:00:00Z",
42700
+ verdict: "DORMANT",
42701
+ _calibrationNote: "v0.20: new rule (System.out.println in production code \u2014 use a logger). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
42702
+ aiSpecific: true,
42703
+ _v7Verdict: "DORMANT",
42704
+ _v7Lift: 1,
42705
+ _v7Recall: 0,
42706
+ _v7FpRate: 0,
42707
+ _v7Precision: 0,
42708
+ _v8Verdict: "DORMANT",
42709
+ _v8Lift: 1,
42710
+ defaultOff: true
42711
+ },
42465
42712
  "layout/forced-layout": {
42466
42713
  recall: 0,
42467
42714
  fpRate: 0,
@@ -43718,7 +43965,7 @@ function loadSignalStrength() {
43718
43965
  function buildParserCacheConfig(cwd) {
43719
43966
  const envVal = process.env.SLOP_AUDIT_CACHE;
43720
43967
  const enabled = envVal === "1" || envVal === "true";
43721
- const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? (0, import_node_path12.join)(cwd, ".slopbrick", "cache", "ast");
43968
+ const root = process.env.SLOP_AUDIT_CACHE_ROOT ?? (0, import_node_path10.join)(cwd, ".slopbrick", "cache", "ast");
43722
43969
  return { enabled, root };
43723
43970
  }
43724
43971
  function applyRuleOverrides(issues, rules) {
@@ -43736,7 +43983,7 @@ function applyRuleOverrides(issues, rules) {
43736
43983
  }
43737
43984
  async function scanFile(filePath, config, registry, cwd = process.cwd()) {
43738
43985
  const cache = buildParserCacheConfig(cwd);
43739
- const ext = (0, import_node_path11.extname)(filePath).toLowerCase();
43986
+ const ext = (0, import_node_path9.extname)(filePath).toLowerCase();
43740
43987
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
43741
43988
  ".swift",
43742
43989
  ".kt",