slopbrick 0.21.2 → 0.25.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.
@@ -37,6 +37,7 @@ module.exports = __toCommonJS(worker_exports);
37
37
  var import_node_worker_threads = require("worker_threads");
38
38
  var import_node_path9 = require("path");
39
39
  var import_node_path10 = require("path");
40
+ var import_minimatch = require("minimatch");
40
41
 
41
42
  // ../engine/dist/index.js
42
43
  var import_promises = require("fs/promises");
@@ -4364,6 +4365,7 @@ function parseSource(source, filePath) {
4364
4365
  case "py":
4365
4366
  case "go":
4366
4367
  case "rs":
4368
+ case "java":
4367
4369
  return parseBlankModule(source);
4368
4370
  default:
4369
4371
  return parseWithSwc(source, filePath);
@@ -34405,6 +34407,232 @@ var importPathMismatchRule = createRule({
34405
34407
  }
34406
34408
  });
34407
34409
 
34410
+ // src/rules/cpp/c-style-cast.ts
34411
+ var C_STYLE_CAST_REGEX = /\(\s*(?:int|long|short|char|float|double|bool|unsigned\s+\w+|signed\s+\w+|size_t|[A-Za-z_]\w*(?:\s*[*,&][^)]*)?)\s*\)\s*(\w+|[^a-zA-Z_])/g;
34412
+ var NAMED_CAST_PREFIX_REGEX = /\b(?:static|reinterpret|const|dynamic)_cast\s*<[^>]*>\s*\($/;
34413
+ var cppCStyleCastRule = createRule({
34414
+ id: "cpp/c-style-cast",
34415
+ category: "typo",
34416
+ severity: "low",
34417
+ aiSpecific: true,
34418
+ description: "C-style cast `(int)x` \u2014 use static_cast / reinterpret_cast / const_cast so the kind is greppable.",
34419
+ create(_context) {
34420
+ return {};
34421
+ },
34422
+ analyze(_context, facts) {
34423
+ const issues = [];
34424
+ const source = facts.v2?._source;
34425
+ if (!source) return issues;
34426
+ if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
34427
+ let m;
34428
+ C_STYLE_CAST_REGEX.lastIndex = 0;
34429
+ while ((m = C_STYLE_CAST_REGEX.exec(source)) !== null) {
34430
+ const innerMatch = /\(\s*([^)]+?)\s*\)/.exec(m[0]) ?? [];
34431
+ const inner = (innerMatch[1] ?? "").trim();
34432
+ const looksLikeCast = /\b(?:int|long|short|char|float|double|bool|unsigned|signed|size_t)\b/.test(inner) || /[*&]/.test(inner);
34433
+ if (!looksLikeCast) continue;
34434
+ const before = source.slice(Math.max(0, m.index - 40), m.index);
34435
+ if (NAMED_CAST_PREFIX_REGEX.test(before)) continue;
34436
+ const line = source.slice(0, m.index).split("\n").length;
34437
+ issues.push({
34438
+ ruleId: "cpp/c-style-cast",
34439
+ category: "typo",
34440
+ severity: "low",
34441
+ aiSpecific: true,
34442
+ message: `C-style cast at line ${line} \u2014 use static_cast / reinterpret_cast / const_cast`,
34443
+ line,
34444
+ column: 1,
34445
+ advice: "Use the named cast that matches the intent: `static_cast<int>(x)`, `reinterpret_cast<MyClass*>(p)`, `const_cast<...>(ref)`, or `dynamic_cast<Derived*>(base)` for runtime-checked downcasts. C-style casts silently pick whichever the compiler needs \u2014 `static_cast`, `reinterpret_cast`, OR `const_cast` \u2014 which makes them impossible to grep for and impossible to review. The C++ Core Guidelines (ES.49) call this out by name. AI agents reach for `(int)x` because of training-data C textbooks. Reference: cpp/c-style-cast v0.24."
34446
+ });
34447
+ }
34448
+ return issues;
34449
+ }
34450
+ });
34451
+
34452
+ // src/rules/cpp/magic-numbers.ts
34453
+ var MAGIC_NUMBER_REGEX = /\b(\d+(?:\.\d+)?)\b/g;
34454
+ var COMPARE_OR_RETURN_REGEX = /\b(?:if|while|return|for)\s*[\(\s]?[^;]*[<>=!][^;]*/;
34455
+ var cppMagicNumbersRule = createRule({
34456
+ id: "cpp/magic-numbers",
34457
+ category: "typo",
34458
+ severity: "low",
34459
+ aiSpecific: true,
34460
+ description: "Magic-number literal in a comparison / return \u2014 name it: `constexpr int MAX = 1024;`.",
34461
+ create(_context) {
34462
+ return {};
34463
+ },
34464
+ analyze(_context, facts) {
34465
+ const issues = [];
34466
+ const source = facts.v2?._source;
34467
+ if (!source) return issues;
34468
+ if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
34469
+ const lines = source.split("\n");
34470
+ const allowSet = /* @__PURE__ */ new Set([
34471
+ "1024",
34472
+ "65535",
34473
+ "65536",
34474
+ "86400",
34475
+ "3600",
34476
+ "1000",
34477
+ "100.0",
34478
+ "60",
34479
+ "24",
34480
+ "7",
34481
+ "365",
34482
+ "256",
34483
+ "255",
34484
+ "0",
34485
+ "1",
34486
+ "2",
34487
+ "3",
34488
+ "4",
34489
+ "5"
34490
+ ]);
34491
+ for (let i = 0; i < lines.length; i++) {
34492
+ const line = lines[i] ?? "";
34493
+ if (!COMPARE_OR_RETURN_REGEX.test(line)) continue;
34494
+ let m;
34495
+ MAGIC_NUMBER_REGEX.lastIndex = 0;
34496
+ while ((m = MAGIC_NUMBER_REGEX.exec(line)) !== null) {
34497
+ const literal = m[0] ?? "";
34498
+ if (allowSet.has(literal)) continue;
34499
+ if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(line.slice(0, m.index).trimEnd())) continue;
34500
+ const prevLine = i > 0 ? lines[i - 1] ?? "" : "";
34501
+ if (/\b(?:constexpr|const)\s+\w+\s*=\s*$/.test(prevLine.trim())) continue;
34502
+ issues.push({
34503
+ ruleId: "cpp/magic-numbers",
34504
+ category: "typo",
34505
+ severity: "low",
34506
+ aiSpecific: true,
34507
+ message: `magic number ${literal} at line ${i + 1} \u2014 name it: constexpr int MAX = ${literal};`,
34508
+ line: i + 1,
34509
+ column: 1,
34510
+ advice: 'Replace the bare literal with a named constant declared as `constexpr int MAX_SIZE = 1024;` (or `static constexpr`). The named constant lives next to its value, can be searched, and forces the reader to mean what they say. Magic numbers in comparisons hide intent ("7" against what?). AI agents produce magic-number-heavy code because their training-data examples rarely bother to name the constant. Reference: cpp/magic-numbers v0.24.'
34511
+ });
34512
+ }
34513
+ }
34514
+ return issues;
34515
+ }
34516
+ });
34517
+
34518
+ // src/rules/cpp/printf-debug.ts
34519
+ var PRINTF_FAMILY_REGEX = /\b(?:printf|fprintf|sprintf|snprintf)\s*\(/g;
34520
+ var COUT_LITERAL_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*"[^"]*"/g;
34521
+ var STD_COUT_BARE_REGEX = /std\s*::\s*(?:cout|cerr|clog)\s*<<\s*'[^']*'/g;
34522
+ var THRESHOLD_DEFAULT = 1;
34523
+ var cppPrintfDebugRule = createRule({
34524
+ id: "cpp/printf-debug",
34525
+ category: "typo",
34526
+ severity: "low",
34527
+ aiSpecific: true,
34528
+ description: "printf family or std::cout debug printing \u2014 use spdlog / glog / AbslLog for level-controlled output.",
34529
+ create(_context) {
34530
+ return { threshold: THRESHOLD_DEFAULT };
34531
+ },
34532
+ analyze(context, facts) {
34533
+ const issues = [];
34534
+ const source = facts.v2?._source;
34535
+ if (!source) return issues;
34536
+ if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
34537
+ const printfCount = (source.match(PRINTF_FAMILY_REGEX) ?? []).length;
34538
+ let viaPrintf = false;
34539
+ if (printfCount > context.threshold) {
34540
+ const line = source.indexOf("printf");
34541
+ if (line !== -1) viaPrintf = true;
34542
+ }
34543
+ const coutHit = COUT_LITERAL_REGEX.test(source) || STD_COUT_BARE_REGEX.test(source);
34544
+ if (!viaPrintf && !coutHit) return issues;
34545
+ issues.push({
34546
+ ruleId: "cpp/printf-debug",
34547
+ category: "typo",
34548
+ severity: "low",
34549
+ aiSpecific: true,
34550
+ message: viaPrintf ? `${printfCount} printf-family calls \u2014 use spdlog / glog / AbslLog` : "std::cout/cerr/clog with a string literal \u2014 use spdlog / glog / AbslLog",
34551
+ line: 1,
34552
+ column: 1,
34553
+ advice: "Replace with `spdlog::info(...)`, `LOG(INFO) << ...` (glog), or `ABSL_LOG(INFO) << ...` (Abseil). All of these are level-aware and have a configurable sink, and they route to stderr by default. `printf` / `std::cout` have no levels, no redaction, no sink routing, and can't be silenced in release builds. AI agents reach for these because their training data has countless C++ textbook examples with them. Reference: cpp/printf-debug v0.24."
34554
+ });
34555
+ return issues;
34556
+ }
34557
+ });
34558
+
34559
+ // src/rules/cpp/raw-new-delete.ts
34560
+ var NEW_TYPE_REGEX = /\bnew\s+[A-Za-z_]\w*\s*\(/g;
34561
+ var DELETE_REGEX = /\bdelete\s+/g;
34562
+ var MIN_PAIRS_DEFAULT = 1;
34563
+ var cppRawNewDeleteRule = createRule({
34564
+ id: "cpp/raw-new-delete",
34565
+ category: "typo",
34566
+ severity: "low",
34567
+ aiSpecific: true,
34568
+ description: "Manual `new` / `delete` pairing \u2014 use std::make_unique / std::unique_ptr instead.",
34569
+ create(_context) {
34570
+ return { minPairs: MIN_PAIRS_DEFAULT };
34571
+ },
34572
+ analyze(context, facts) {
34573
+ const issues = [];
34574
+ const source = facts.v2?._source;
34575
+ if (!source) return issues;
34576
+ if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
34577
+ let newCount = 0;
34578
+ let m;
34579
+ NEW_TYPE_REGEX.lastIndex = 0;
34580
+ while ((m = NEW_TYPE_REGEX.exec(source)) !== null) newCount++;
34581
+ let deleteCount = 0;
34582
+ DELETE_REGEX.lastIndex = 0;
34583
+ while ((m = DELETE_REGEX.exec(source)) !== null) deleteCount++;
34584
+ if (newCount <= context.minPairs) return issues;
34585
+ if (deleteCount <= context.minPairs) return issues;
34586
+ issues.push({
34587
+ ruleId: "cpp/raw-new-delete",
34588
+ category: "typo",
34589
+ severity: "low",
34590
+ aiSpecific: true,
34591
+ message: `manual new/delete pairing (${newCount} new, ${deleteCount} delete) \u2014 wrap in std::unique_ptr`,
34592
+ line: 1,
34593
+ column: 1,
34594
+ advice: "Replace `T* p = new T(...); ... delete p;` with `auto p = std::make_unique<T>(...);`. Smart pointers delete on scope exit, never leak on early return / exception, never double-free on copy. The C++ Core Guidelines R.20-R.23 call out raw `new` / `delete` as something to avoid in application code. AI agents reach for `new` / `delete` because their training data was written for pre-C++11 idioms. Reference: cpp/raw-new-delete v0.24."
34595
+ });
34596
+ return issues;
34597
+ }
34598
+ });
34599
+
34600
+ // src/rules/cpp/using-namespace-std.ts
34601
+ var USING_NAMESPACE_STD_REGEX = /\busing\s+namespace\s+std\s*;/g;
34602
+ var cppUsingNamespaceStdRule = createRule({
34603
+ id: "cpp/using-namespace-std",
34604
+ category: "logic",
34605
+ severity: "medium",
34606
+ aiSpecific: true,
34607
+ description: "`using namespace std;` in a header \u2014 pollutes every consumer. Use `using std::foo;` or qualify names.",
34608
+ create(_context) {
34609
+ return {};
34610
+ },
34611
+ analyze(_context, facts) {
34612
+ const issues = [];
34613
+ const source = facts.v2?._source;
34614
+ if (!source) return issues;
34615
+ if (!/\.(cpp|cc|cxx|h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
34616
+ if (!/\.(h|hpp|hh|hxx|H)$/i.test(facts.filePath)) return issues;
34617
+ let m;
34618
+ USING_NAMESPACE_STD_REGEX.lastIndex = 0;
34619
+ while ((m = USING_NAMESPACE_STD_REGEX.exec(source)) !== null) {
34620
+ const line = source.slice(0, m.index).split("\n").length;
34621
+ issues.push({
34622
+ ruleId: "cpp/using-namespace-std",
34623
+ category: "logic",
34624
+ severity: "medium",
34625
+ aiSpecific: true,
34626
+ message: `using namespace std; at line ${line} \u2014 pollutes every consumer of this header`,
34627
+ line,
34628
+ column: 1,
34629
+ advice: "Remove the directive and qualify names with `std::`, or use a targeted `using std::cout;` / `using std::string;` for the few names that recur. The C++ Core Guidelines (SF.6 / SF.7) explicitly forbid `using namespace` in header files. A single `using namespace std;` in a widely-included header pulls 200+ names into every translation unit \u2014 downstream code, name-lookup, and ADL silently change. AI agents default to `using namespace std;` because their training snippets include countless copy-pasteable textbook examples that start with it. Reference: cpp/using-namespace-std v0.24."
34630
+ });
34631
+ }
34632
+ return issues;
34633
+ }
34634
+ });
34635
+
34408
34636
  // src/rules/db/duplicate-index.ts
34409
34637
  var import_pgsql_parser = require("pgsql-parser");
34410
34638
  var moduleReady = (0, import_pgsql_parser.loadModule)();
@@ -36137,6 +36365,480 @@ var dupIdenticalBlockRule = createRule({
36137
36365
  }
36138
36366
  });
36139
36367
 
36368
+ // src/engine/dedup/tokenize.ts
36369
+ var import_node_crypto = require("crypto");
36370
+ var DEFAULT_CONFIG = { k: 5 };
36371
+ function stripComments(source) {
36372
+ let out = "";
36373
+ let i = 0;
36374
+ while (i < source.length) {
36375
+ const ch = source[i];
36376
+ const next = source[i + 1];
36377
+ if (ch === "/" && next === "/") {
36378
+ while (i < source.length && source[i] !== "\n") i++;
36379
+ continue;
36380
+ }
36381
+ if (ch === "/" && next === "*") {
36382
+ i += 2;
36383
+ while (i < source.length && !(source[i] === "*" && source[i + 1] === "/")) i++;
36384
+ i += 2;
36385
+ continue;
36386
+ }
36387
+ out += ch;
36388
+ i++;
36389
+ }
36390
+ return out;
36391
+ }
36392
+ function tokenize(source) {
36393
+ return source.toLowerCase().split(/[^a-z0-9_]+/u).filter((t) => t.length > 0);
36394
+ }
36395
+ function shingleHash(shingle) {
36396
+ const h = (0, import_node_crypto.createHash)("sha1").update(shingle).digest();
36397
+ return (h[0] ?? 0) << 24 | (h[1] ?? 0) << 16 | (h[2] ?? 0) << 8 | (h[3] ?? 0);
36398
+ }
36399
+ function shingleSet(source, config = {}) {
36400
+ const { k } = { ...DEFAULT_CONFIG, ...config };
36401
+ const tokens = tokenize(stripComments(source));
36402
+ if (tokens.length === 0) return /* @__PURE__ */ new Set();
36403
+ if (tokens.length < k) {
36404
+ return /* @__PURE__ */ new Set([shingleHash(tokens.join(" "))]);
36405
+ }
36406
+ const out = /* @__PURE__ */ new Set();
36407
+ for (let i = 0; i + k <= tokens.length; i++) {
36408
+ out.add(shingleHash(tokens.slice(i, i + k).join(" ")));
36409
+ }
36410
+ return out;
36411
+ }
36412
+
36413
+ // src/engine/dedup/minhash.ts
36414
+ var import_node_crypto2 = require("crypto");
36415
+ var PRIME = (1n << 61n) - 1n;
36416
+ function mix64(x) {
36417
+ x ^= x >> 33n;
36418
+ x = x * 0xff51afd7ed558ccdn & 0xffffffffffffffffn;
36419
+ x ^= x >> 33n;
36420
+ x = x * 0xc4ceb9fe1a85ec53n & 0xffffffffffffffffn;
36421
+ x ^= x >> 33n;
36422
+ return x;
36423
+ }
36424
+ function bmod(a, m) {
36425
+ const r = a % m;
36426
+ return r < 0n ? r + m : r;
36427
+ }
36428
+ function hashParams(i) {
36429
+ const a = mix64(BigInt(i) * 0x9e3779b97f4a7c15n) & 0xffffffffffffffffn | 1n;
36430
+ const b = mix64(BigInt(i) * 0xbf58476d1ce4e5b9n) & 0xffffffffffffffffn;
36431
+ return [a, b];
36432
+ }
36433
+ function hashAt(i, x) {
36434
+ const [a, b] = hashParams(i);
36435
+ const xb = BigInt(x >>> 0);
36436
+ const combined = a * xb + b;
36437
+ const h = Number(bmod(combined, PRIME) & 0xffffffffn);
36438
+ return h >>> 0;
36439
+ }
36440
+ var DEFAULT_CONFIG2 = {
36441
+ numHashes: 128
36442
+ };
36443
+ function minHash(tokens, config = {}) {
36444
+ const { numHashes } = { ...DEFAULT_CONFIG2, ...config };
36445
+ const sig = new Uint32Array(numHashes);
36446
+ sig.fill(4294967295);
36447
+ const tokenIter = tokens instanceof Set ? tokens : Array.from(tokens);
36448
+ for (const t of tokenIter) {
36449
+ for (let i = 0; i < numHashes; i++) {
36450
+ const h = hashAt(i, t);
36451
+ if (h < (sig[i] ?? 4294967295)) sig[i] = h;
36452
+ }
36453
+ }
36454
+ return sig;
36455
+ }
36456
+ function minHashSimilarity(a, b) {
36457
+ if (a.length !== b.length) {
36458
+ throw new Error(`minHashSimilarity: length mismatch ${a.length} vs ${b.length}`);
36459
+ }
36460
+ if (a.length === 0) return 1;
36461
+ let matches = 0;
36462
+ for (let i = 0; i < a.length; i++) {
36463
+ if (a[i] === b[i]) matches++;
36464
+ }
36465
+ return matches / a.length;
36466
+ }
36467
+
36468
+ // src/rules/dup/near-duplicate.ts
36469
+ var DEFAULT_CONTEXT = {
36470
+ threshold: 0.7,
36471
+ k: 5
36472
+ };
36473
+ var signatureCache = /* @__PURE__ */ new Map();
36474
+ var nearDuplicateRule = createRule({
36475
+ id: "dup/near-duplicate",
36476
+ category: "logic",
36477
+ severity: "medium",
36478
+ aiSpecific: true,
36479
+ description: "Near-duplicate file (Type-2 clone) \u2014 token-set Jaccard similarity > 0.7 after MinHash. Catches AI whitespace/comment refactors.",
36480
+ create(_context) {
36481
+ return { ...DEFAULT_CONTEXT };
36482
+ },
36483
+ analyze(context, facts) {
36484
+ const ctx = { ...DEFAULT_CONTEXT, ...context };
36485
+ const issues = [];
36486
+ const source = facts.v2?._source;
36487
+ if (!source) return issues;
36488
+ const filePath = facts.filePath;
36489
+ if (!filePath) return issues;
36490
+ const shingles = shingleSet(source, { k: ctx.k });
36491
+ if (shingles.size === 0) return issues;
36492
+ const sig = minHash(shingles);
36493
+ const matched = [];
36494
+ for (const [prevPath, prevSig] of signatureCache.entries()) {
36495
+ const sim = minHashSimilarity(sig, prevSig);
36496
+ if (sim >= ctx.threshold) {
36497
+ matched.push({ path: prevPath, similarity: sim });
36498
+ }
36499
+ }
36500
+ if (matched.length > 0) {
36501
+ matched.sort((a, b) => b.similarity - a.similarity);
36502
+ const best = matched[0];
36503
+ issues.push({
36504
+ ruleId: "dup/near-duplicate",
36505
+ category: "logic",
36506
+ severity: "medium",
36507
+ aiSpecific: true,
36508
+ message: `Near-duplicate of ${best.path} (Jaccard ${(best.similarity * 100).toFixed(1)}%, threshold ${(ctx.threshold * 100).toFixed(0)}%)`,
36509
+ line: 1,
36510
+ column: 1,
36511
+ advice: "Refactor to share the implementation, or rename/restructure to make the difference intentional. AI agents produce near-duplicates when iterating \u2014 this is the v0.23.0 Type-2 clone detector. Reference: dup/near-duplicate v0.23."
36512
+ });
36513
+ }
36514
+ signatureCache.set(filePath, sig);
36515
+ return issues;
36516
+ }
36517
+ });
36518
+
36519
+ // src/engine/dedup/lsh.ts
36520
+ var import_node_crypto3 = require("crypto");
36521
+
36522
+ // src/engine/dedup/structural-clone.ts
36523
+ function stripComments2(source) {
36524
+ let out = "";
36525
+ let i = 0;
36526
+ while (i < source.length) {
36527
+ const ch = source[i];
36528
+ const next = source[i + 1];
36529
+ if (ch === "/" && next === "/") {
36530
+ while (i < source.length && source[i] !== "\n") i++;
36531
+ continue;
36532
+ }
36533
+ if (ch === "/" && next === "*") {
36534
+ i += 2;
36535
+ while (i < source.length && !(source[i] === "*" && source[i + 1] === "/")) i++;
36536
+ i += 2;
36537
+ continue;
36538
+ }
36539
+ out += ch;
36540
+ i++;
36541
+ }
36542
+ return out;
36543
+ }
36544
+ var TOKEN_REGEX = /([A-Za-z_$][A-Za-z0-9_$]*)|(0[xX][0-9A-Fa-f]+|\d+(?:\.\d+)?(?:[eE][+-]?\d+)?[fFdDlL]?)|([(){}\[\];,.<>=+\-*/%!&|^?:])|"((?:[^"\\\n]|\\.)*)"|'((?:[^'\\\n]|\\.)*)'|`((?:[^`\\]|\\.)*)`/g;
36545
+ var BOOL_TOKENS = /* @__PURE__ */ new Set(["true", "false", "null", "none", "nil"]);
36546
+ var KEYWORDS = /* @__PURE__ */ new Set([
36547
+ // Control flow
36548
+ "if",
36549
+ "else",
36550
+ "for",
36551
+ "while",
36552
+ "do",
36553
+ "switch",
36554
+ "case",
36555
+ "default",
36556
+ "break",
36557
+ "continue",
36558
+ "return",
36559
+ "goto",
36560
+ // Declarations
36561
+ "function",
36562
+ "class",
36563
+ "interface",
36564
+ "struct",
36565
+ "enum",
36566
+ "trait",
36567
+ "def",
36568
+ "fn",
36569
+ "fun",
36570
+ "func",
36571
+ "var",
36572
+ "let",
36573
+ "const",
36574
+ "val",
36575
+ "mod",
36576
+ "use",
36577
+ "pub",
36578
+ "impl",
36579
+ "package",
36580
+ "module",
36581
+ "lambda",
36582
+ // Modifiers / visibility
36583
+ "public",
36584
+ "private",
36585
+ "protected",
36586
+ "static",
36587
+ "abstract",
36588
+ "final",
36589
+ "sealed",
36590
+ "override",
36591
+ "virtual",
36592
+ "readonly",
36593
+ "async",
36594
+ "await",
36595
+ "synchronized",
36596
+ "volatile",
36597
+ "transient",
36598
+ "native",
36599
+ "strictfp",
36600
+ "inline",
36601
+ "external",
36602
+ "export",
36603
+ "import",
36604
+ // Type system
36605
+ "type",
36606
+ "interface",
36607
+ "record",
36608
+ "object",
36609
+ "enum",
36610
+ "data",
36611
+ // Pattern / dispatch
36612
+ "match",
36613
+ "select",
36614
+ "when",
36615
+ "where",
36616
+ // Error handling
36617
+ "try",
36618
+ "catch",
36619
+ "finally",
36620
+ "throw",
36621
+ "throws",
36622
+ "raise",
36623
+ "rescue",
36624
+ "ensure",
36625
+ "except",
36626
+ // Logical connectives and tests
36627
+ "and",
36628
+ "or",
36629
+ "not",
36630
+ "in",
36631
+ "is",
36632
+ "as",
36633
+ "instanceof",
36634
+ "typeof",
36635
+ "delete",
36636
+ "void",
36637
+ "new",
36638
+ "this",
36639
+ "super",
36640
+ "self",
36641
+ "yield",
36642
+ // Misc
36643
+ "with",
36644
+ "without",
36645
+ "pass",
36646
+ "assert",
36647
+ "mut",
36648
+ "move",
36649
+ "ref",
36650
+ "out",
36651
+ "params",
36652
+ "operator",
36653
+ "sizeof",
36654
+ "typedef",
36655
+ "union",
36656
+ "register",
36657
+ "extern",
36658
+ "volatile",
36659
+ "goto",
36660
+ // Python-specific
36661
+ "elif",
36662
+ "global",
36663
+ "nonlocal",
36664
+ "del",
36665
+ "with",
36666
+ // Ruby-specific
36667
+ "unless",
36668
+ "until",
36669
+ "begin",
36670
+ "next",
36671
+ "then",
36672
+ "end",
36673
+ "alias",
36674
+ "defined",
36675
+ "super",
36676
+ "yield",
36677
+ "self",
36678
+ // Kotlin-specific
36679
+ "suspend",
36680
+ "lateinit",
36681
+ "companion",
36682
+ "internal",
36683
+ "open",
36684
+ // Rust-specific
36685
+ "unsafe",
36686
+ "dyn",
36687
+ "box",
36688
+ "crate",
36689
+ "where",
36690
+ // Swift-specific
36691
+ "guard",
36692
+ "defer",
36693
+ "repeat",
36694
+ "init",
36695
+ "deinit",
36696
+ "extension",
36697
+ "protocol",
36698
+ "throws",
36699
+ "rethrows",
36700
+ "weak",
36701
+ "unowned",
36702
+ "lazy"
36703
+ ]);
36704
+ function canonicalTokens(source) {
36705
+ const stripped = stripComments2(source);
36706
+ const out = [];
36707
+ TOKEN_REGEX.lastIndex = 0;
36708
+ let m;
36709
+ while ((m = TOKEN_REGEX.exec(stripped)) !== null) {
36710
+ const [, ident, num, punct, dq, sq, bq] = m;
36711
+ if (ident !== void 0) {
36712
+ const lower = ident.toLowerCase();
36713
+ if (BOOL_TOKENS.has(lower)) {
36714
+ out.push("bool");
36715
+ } else if (KEYWORDS.has(lower)) {
36716
+ out.push(lower);
36717
+ } else {
36718
+ out.push("id");
36719
+ }
36720
+ continue;
36721
+ }
36722
+ if (num !== void 0) {
36723
+ out.push("num");
36724
+ continue;
36725
+ }
36726
+ if (punct !== void 0) {
36727
+ out.push(punct);
36728
+ continue;
36729
+ }
36730
+ if (dq !== void 0 || sq !== void 0 || bq !== void 0) {
36731
+ out.push("str");
36732
+ continue;
36733
+ }
36734
+ }
36735
+ return out;
36736
+ }
36737
+ function shingleHash2(shingle) {
36738
+ let h = 2166136261;
36739
+ for (let i = 0; i < shingle.length; i++) {
36740
+ h ^= shingle.charCodeAt(i);
36741
+ h = Math.imul(h, 16777619) >>> 0;
36742
+ }
36743
+ return h >>> 0;
36744
+ }
36745
+ var DEFAULT_STRUCTURAL_CONFIG = {
36746
+ k: 8,
36747
+ minTokens: 60
36748
+ };
36749
+ function structuralShingles(source, config = {}) {
36750
+ const { k, minTokens } = { ...DEFAULT_STRUCTURAL_CONFIG, ...config };
36751
+ const tokens = canonicalTokens(source);
36752
+ if (tokens.length < minTokens) return /* @__PURE__ */ new Set();
36753
+ if (tokens.length < k) {
36754
+ return /* @__PURE__ */ new Set([shingleHash2(tokens.join(" "))]);
36755
+ }
36756
+ const out = /* @__PURE__ */ new Set();
36757
+ for (let i = 0; i + k <= tokens.length; i++) {
36758
+ out.add(shingleHash2(tokens.slice(i, i + k).join(" ")));
36759
+ }
36760
+ return out;
36761
+ }
36762
+ function structuralSignature(source, config = {}) {
36763
+ return minHash(structuralShingles(source, config));
36764
+ }
36765
+
36766
+ // src/rules/dup/structural-clone.ts
36767
+ var DEFAULT_CONTEXT2 = {
36768
+ structuralThreshold: 0.55,
36769
+ verifyThreshold: 0.45,
36770
+ kStruct: 8,
36771
+ kIdent: 5,
36772
+ minTokens: 60,
36773
+ minHits: 1
36774
+ };
36775
+ var structuralCache = /* @__PURE__ */ new Map();
36776
+ var identifierCache = /* @__PURE__ */ new Map();
36777
+ var structuralCloneRule = createRule({
36778
+ id: "dup/structural-clone",
36779
+ category: "logic",
36780
+ severity: "medium",
36781
+ aiSpecific: true,
36782
+ defaultOff: true,
36783
+ description: "Structural clone (Type-3) \u2014 same shape after identifier canonicalization with possible added/removed statements. Two-stage MinHash: canonical + identifier.",
36784
+ create(_context) {
36785
+ return { ...DEFAULT_CONTEXT2 };
36786
+ },
36787
+ analyze(context, facts) {
36788
+ const ctx = { ...DEFAULT_CONTEXT2, ...context };
36789
+ const issues = [];
36790
+ const source = facts.v2?._source;
36791
+ if (!source) return issues;
36792
+ const filePath = facts.filePath;
36793
+ if (!filePath) return issues;
36794
+ const sigStruct = structuralSignature(source, {
36795
+ k: ctx.kStruct,
36796
+ minTokens: ctx.minTokens
36797
+ });
36798
+ if (sigStruct.every((v) => v === 4294967295)) return issues;
36799
+ const identShingles = shingleSet(source, { k: ctx.kIdent });
36800
+ if (identShingles.size === 0) return issues;
36801
+ const sigIdent = minHash(identShingles);
36802
+ const matched = [];
36803
+ for (const [prevPath, prevStruct] of structuralCache.entries()) {
36804
+ if (prevPath === filePath) continue;
36805
+ const simStruct = minHashSimilarity(sigStruct, prevStruct);
36806
+ if (simStruct < ctx.structuralThreshold) continue;
36807
+ const prevIdent = identifierCache.get(prevPath);
36808
+ if (!prevIdent) continue;
36809
+ const simIdent = minHashSimilarity(sigIdent, prevIdent);
36810
+ if (simIdent <= 0) continue;
36811
+ const score = 2 * simStruct * simIdent / (simStruct + simIdent + 1e-9);
36812
+ if (score >= ctx.verifyThreshold) {
36813
+ matched.push({ path: prevPath, score, simStruct, simIdent });
36814
+ }
36815
+ }
36816
+ if (matched.length >= ctx.minHits) {
36817
+ matched.sort((a, b) => b.score - a.score);
36818
+ const best = matched[0];
36819
+ issues.push({
36820
+ ruleId: "dup/structural-clone",
36821
+ category: "logic",
36822
+ severity: "medium",
36823
+ aiSpecific: true,
36824
+ message: `Structural clone of ${best.path} (canonical Jaccard ${(best.simStruct * 100).toFixed(1)}%, identifier Jaccard ${(best.simIdent * 100).toFixed(1)}%, blend ${(best.score * 100).toFixed(1)}% \u2014 threshold ${(ctx.verifyThreshold * 100).toFixed(0)}%)`,
36825
+ line: 1,
36826
+ column: 1,
36827
+ advice: "Refactor to a shared helper. This is a Type-3 clone \u2014 two files share canonical shape after identifier normalization and likely a few added/removed statements. AI agents produce Type-3 clones when iterating on a function: same control flow, different names, a few lines added. (v0.24.0 \u2014 two-stage MinHash on canonical k=8 + identifier k=5 shingles.)",
36828
+ extras: {
36829
+ structuralDuplicateOf: {
36830
+ file: best.path,
36831
+ score: best.score
36832
+ }
36833
+ }
36834
+ });
36835
+ }
36836
+ structuralCache.set(filePath, sigStruct);
36837
+ identifierCache.set(filePath, sigIdent);
36838
+ return issues;
36839
+ }
36840
+ });
36841
+
36140
36842
  // src/rules/go/error-wrap-without-context.ts
36141
36843
  var ERR_WRAP_REGEX = /fmt\.Errorf\(\s*"((?:[^"\\]|\\.)*)"\s*,\s*([^)]+)\)/g;
36142
36844
  var GENERIC_PREFIX_REGEX = /^\s*(?:error|err|failed|wrap(?:ping)?|invalid)\s*(?::\s*)?%w\b/i;
@@ -36543,6 +37245,229 @@ var javaSystemOutPrintlnRule = createRule({
36543
37245
  }
36544
37246
  });
36545
37247
 
37248
+ // src/rules/kotlin/coroutine-global-scope.ts
37249
+ var GLOBAL_SCOPE_REGEX = /GlobalScope\s*\.\s*(?:launch|async|runBlocking)\s*[({]/g;
37250
+ var kotlinCoroutineGlobalScopeRule = createRule({
37251
+ id: "kotlin/coroutine-global-scope",
37252
+ category: "logic",
37253
+ severity: "medium",
37254
+ aiSpecific: true,
37255
+ description: "GlobalScope.launch/async/runBlocking \u2014 bypasses structured concurrency. Use viewModelScope / lifecycleScope / coroutineScope {}.",
37256
+ create(_context) {
37257
+ return {};
37258
+ },
37259
+ analyze(_context, facts) {
37260
+ const issues = [];
37261
+ const source = facts.v2?._source;
37262
+ if (!source) return issues;
37263
+ if (!/\.kt$/i.test(facts.filePath)) return issues;
37264
+ let m;
37265
+ GLOBAL_SCOPE_REGEX.lastIndex = 0;
37266
+ while ((m = GLOBAL_SCOPE_REGEX.exec(source)) !== null) {
37267
+ const line = source.slice(0, m.index).split("\n").length;
37268
+ issues.push({
37269
+ ruleId: "kotlin/coroutine-global-scope",
37270
+ category: "logic",
37271
+ severity: "medium",
37272
+ aiSpecific: true,
37273
+ message: `GlobalScope.${m[0].split(".")[1]?.split("(")[0]}() at line ${line} \u2014 bypasses structured concurrency`,
37274
+ line,
37275
+ column: 1,
37276
+ advice: 'Replace with `viewModelScope.launch { ... }` (Android view models), `lifecycleScope.launch { ... }` (Android activities / fragments), or `coroutineScope { ... }` inside a suspending function. GlobalScope bypasses cancellation, exception propagation, and lifetime binding. The kotlinx.coroutines docs explicitly call GlobalScope "delicate API". AI agents default to it for "fire-and-forget" work because of training-data snippets. Reference: kotlin/coroutine-global-scope v0.24.'
37277
+ });
37278
+ }
37279
+ return issues;
37280
+ }
37281
+ });
37282
+
37283
+ // src/rules/kotlin/data-class-defaults-overuse.ts
37284
+ var DATA_CLASS_HEAD_REGEX = /\bdata\s+class\s+\w+\s*\(/g;
37285
+ var DEFAULT_PARAM_REGEX = /=\s*(?:null|true|false|"[^"]*"|'[^']*'|[+-]?\d+(?:\.\d+)?\w*|emptyList\(\)|emptyMap\(\)|emptySet\(\)|listOf\(\)|mapOf\(\)|setOf\(\))/g;
37286
+ var DEFAULT_MIN_DEFAULTS = 3;
37287
+ var kotlinDataClassDefaultsOveruseRule = createRule({
37288
+ id: "kotlin/data-class-defaults-overuse",
37289
+ category: "typo",
37290
+ severity: "low",
37291
+ aiSpecific: true,
37292
+ description: "data class with 3+ literal defaults \u2014 AI defaults every field rather than only the genuinely optional ones",
37293
+ create(_context) {
37294
+ return { minDefaults: DEFAULT_MIN_DEFAULTS };
37295
+ },
37296
+ analyze(context, facts) {
37297
+ const issues = [];
37298
+ const source = facts.v2?._source;
37299
+ if (!source) return issues;
37300
+ if (!/\.kt$/i.test(facts.filePath)) return issues;
37301
+ let m;
37302
+ DATA_CLASS_HEAD_REGEX.lastIndex = 0;
37303
+ while ((m = DATA_CLASS_HEAD_REGEX.exec(source)) !== null) {
37304
+ const headStart = m.index;
37305
+ const openParen = source.indexOf("(", headStart);
37306
+ if (openParen === -1) continue;
37307
+ let depth = 1;
37308
+ let i = openParen + 1;
37309
+ while (i < source.length && depth > 0) {
37310
+ const ch = source.charAt(i);
37311
+ if (ch === "(") depth++;
37312
+ else if (ch === ")") depth--;
37313
+ i++;
37314
+ }
37315
+ if (depth !== 0) continue;
37316
+ const paramBlock = source.slice(openParen + 1, i - 1);
37317
+ DEFAULT_PARAM_REGEX.lastIndex = 0;
37318
+ let defaults = 0;
37319
+ let dm;
37320
+ while ((dm = DEFAULT_PARAM_REGEX.exec(paramBlock)) !== null) {
37321
+ defaults++;
37322
+ }
37323
+ if (defaults < context.minDefaults) continue;
37324
+ const line = source.slice(0, headStart).split("\n").length;
37325
+ issues.push({
37326
+ ruleId: "kotlin/data-class-defaults-overuse",
37327
+ category: "typo",
37328
+ severity: "low",
37329
+ aiSpecific: true,
37330
+ message: `data class with ${defaults} literal defaults at line ${line} \u2014 only optional fields should have defaults`,
37331
+ line,
37332
+ column: 1,
37333
+ advice: "Audit which fields are genuinely optional. Real Kotlin uses defaults sparingly (1-2 callbacks, the rest required). Defaulting every field makes the no-arg constructor callable for every data class and obscures intent. AI agents default every field because they imitate Java/Swift optional patterns. Reference: kotlin/data-class-defaults-overuse v0.24."
37334
+ });
37335
+ }
37336
+ return issues;
37337
+ }
37338
+ });
37339
+
37340
+ // src/rules/kotlin/object-singleton-misuse.ts
37341
+ var OBJECT_DECL_REGEX = /\bobject\s+(?!\w*Companion\b)(\w+)\s*\{/g;
37342
+ var kotlinObjectSingletonMisuseRule = createRule({
37343
+ id: "kotlin/object-singleton-misuse",
37344
+ category: "logic",
37345
+ severity: "medium",
37346
+ aiSpecific: true,
37347
+ description: "Kotlin object singleton holding mutable `var` state \u2014 hidden shared state. Use DI or a repository.",
37348
+ create(_context) {
37349
+ return {};
37350
+ },
37351
+ analyze(_context, facts) {
37352
+ const issues = [];
37353
+ const source = facts.v2?._source;
37354
+ if (!source) return issues;
37355
+ if (!/\.kt$/i.test(facts.filePath)) return issues;
37356
+ let m;
37357
+ OBJECT_DECL_REGEX.lastIndex = 0;
37358
+ while ((m = OBJECT_DECL_REGEX.exec(source)) !== null) {
37359
+ const declStart = m.index;
37360
+ const openBrace = source.indexOf("{", declStart);
37361
+ if (openBrace === -1) continue;
37362
+ let depth = 1;
37363
+ let i = openBrace + 1;
37364
+ while (i < source.length && depth > 0) {
37365
+ const ch = source.charAt(i);
37366
+ if (ch === "{") depth++;
37367
+ else if (ch === "}") depth--;
37368
+ i++;
37369
+ }
37370
+ if (depth !== 0) continue;
37371
+ const body = source.slice(openBrace + 1, i - 1);
37372
+ if (!/\bvar\s+\w+\s*[:=]/m.test(body)) continue;
37373
+ const line = source.slice(0, declStart).split("\n").length;
37374
+ const name = m[1] ?? "<unknown>";
37375
+ issues.push({
37376
+ ruleId: "kotlin/object-singleton-misuse",
37377
+ category: "logic",
37378
+ severity: "medium",
37379
+ aiSpecific: true,
37380
+ message: `object ${name} at line ${line} holds mutable state \u2014 singleton + var = hidden shared state`,
37381
+ line,
37382
+ column: 1,
37383
+ advice: "Replace with a class injected via DI, a Repository, or a `MutableStateFlow` with explicit synchronization. The Kotlin `object` keyword gives you a thread-safe singleton handle, but it does NOT make `var` fields inside that singleton thread-safe \u2014 mutable singleton state is a classic source of race conditions. AI agents reach for `object Cache { var data = ... }` because their training snippets make it look natural. Reference: kotlin/object-singleton-misuse v0.24."
37384
+ });
37385
+ }
37386
+ return issues;
37387
+ }
37388
+ });
37389
+
37390
+ // src/rules/kotlin/println-debug.ts
37391
+ var PRINTLN_REGEX2 = /^\s*println\s*\(/gm;
37392
+ var DEFAULT_THRESHOLD2 = 1;
37393
+ var kotlinPrintlnDebugRule = createRule({
37394
+ id: "kotlin/println-debug",
37395
+ category: "typo",
37396
+ severity: "low",
37397
+ aiSpecific: true,
37398
+ description: "println(...) in production code \u2014 use Timber, android.util.Log, or an SLF4J facade",
37399
+ create(_context) {
37400
+ return { threshold: DEFAULT_THRESHOLD2 };
37401
+ },
37402
+ analyze(context, facts) {
37403
+ const issues = [];
37404
+ const source = facts.v2?._source;
37405
+ if (!source) return issues;
37406
+ if (!/\.kt$/i.test(facts.filePath)) return issues;
37407
+ const matches = [];
37408
+ let m;
37409
+ PRINTLN_REGEX2.lastIndex = 0;
37410
+ while ((m = PRINTLN_REGEX2.exec(source)) !== null) {
37411
+ matches.push(m.index);
37412
+ }
37413
+ if (matches.length <= context.threshold) return issues;
37414
+ const cap = Math.min(matches.length, 10);
37415
+ for (let i = 0; i < cap; i++) {
37416
+ const idx = matches[i];
37417
+ const line = source.slice(0, idx).split("\n").length;
37418
+ issues.push({
37419
+ ruleId: "kotlin/println-debug",
37420
+ category: "typo",
37421
+ severity: "low",
37422
+ aiSpecific: true,
37423
+ message: `println(...) at line ${line} \u2014 use Timber or android.util.Log for production output`,
37424
+ line,
37425
+ column: 1,
37426
+ advice: "Replace with `Timber.plant(Timber.DebugTree())` then `Timber.d(...)`, or with an SLF4J facade. `println` writes to stdout with no level / no redaction and cannot be silenced in production. Multiple `println` calls in one file is an AI fingerprint \u2014 agents default to it because their training data has many copy-pasteable Kotlin examples. A single println in a `main` function is fine; 2+ is not. Reference: kotlin/println-debug v0.24."
37427
+ });
37428
+ }
37429
+ return issues;
37430
+ }
37431
+ });
37432
+
37433
+ // src/rules/kotlin/string-concat-loop.ts
37434
+ var STRING_CONCAT_REGEX2 = /\b(\w+)\s*=\s*\1\s*\+\s*[^;}]+[;}\n]/g;
37435
+ var kotlinStringConcatLoopRule = createRule({
37436
+ id: "kotlin/string-concat-loop",
37437
+ category: "perf",
37438
+ severity: "low",
37439
+ aiSpecific: true,
37440
+ description: "String concatenation in a loop \u2014 use buildString { ... } or a StringBuilder",
37441
+ create(_context) {
37442
+ return {};
37443
+ },
37444
+ analyze(_context, facts) {
37445
+ const issues = [];
37446
+ const source = facts.v2?._source;
37447
+ if (!source) return issues;
37448
+ if (!/\.kt$/i.test(facts.filePath)) return issues;
37449
+ if (!/\b(?:for|while|repeat|forEach)\b/.test(source)) return issues;
37450
+ let m;
37451
+ STRING_CONCAT_REGEX2.lastIndex = 0;
37452
+ while ((m = STRING_CONCAT_REGEX2.exec(source)) !== null) {
37453
+ const line = source.slice(0, m.index).split("\n").length;
37454
+ const lineText = source.slice(0, m.index).split("\n").pop() ?? "";
37455
+ if (/\.append\s*\(/.test(lineText)) continue;
37456
+ issues.push({
37457
+ ruleId: "kotlin/string-concat-loop",
37458
+ category: "perf",
37459
+ severity: "low",
37460
+ aiSpecific: true,
37461
+ message: `String concatenation in a loop at line ${line} \u2014 use buildString { ... }`,
37462
+ line,
37463
+ column: 1,
37464
+ advice: "Wrap the loop in `buildString { append(...) }` or declare a `StringBuilder` outside the loop. Kotlin cannot optimize `s = s + x` inside a loop \u2014 each iteration allocates a new StringBuilder, making the whole thing O(n\xB2) for n iterations. AI agents concatenate inside loops because of training-data examples. Reference: kotlin/string-concat-loop v0.24."
37465
+ });
37466
+ }
37467
+ return issues;
37468
+ }
37469
+ });
37470
+
36546
37471
  // src/rules/layout/gap-monopoly.ts
36547
37472
  var GAP_RE = /\bgap(?:-x|-y)?-(\d+)\b/g;
36548
37473
  var gapMonopolyRule = createRule({
@@ -36755,7 +37680,7 @@ var DEFAULT_RULE_CONFIG = {
36755
37680
  },
36756
37681
  genericCenteringMaxInstances: 1
36757
37682
  };
36758
- var DEFAULT_CONFIG = {
37683
+ var DEFAULT_CONFIG3 = {
36759
37684
  include: [
36760
37685
  // v0.9.2 — Repository Coherence is language-agnostic. Default include
36761
37686
  // covers frontend (TS/JS/Vue/Svelte/Astro) AND backend (Python/Go).
@@ -36897,6 +37822,38 @@ var DEFAULT_CONFIG = {
36897
37822
  */
36898
37823
  testIntelligence: {
36899
37824
  missingEdgeCase: false
37825
+ },
37826
+ /**
37827
+ * v0.25.0: self-scan exclude paths. Defaults cover the three paths
37828
+ * that are always false positives when scanning the slopbrick repo
37829
+ * itself:
37830
+ *
37831
+ * - `src/rules/**` — rule definitions contain example patterns
37832
+ * the rules themselves detect (self-fire). E.g. a
37833
+ * `security/sql-construction` rule's source file has a SQL
37834
+ * concat string literal that fires its own regex.
37835
+ * - `tests/fixtures/**` — test fixtures contain intentional bad
37836
+ * code that the rules must fire on to be useful (each fixture
37837
+ * is a positive test case). Scanning them in a self-scan
37838
+ * produces ~70 false-positive "issues" that are really just
37839
+ * test data.
37840
+ * - `tests/rules/**` — rule test files contain expected-issue
37841
+ * assertions, also meta-code.
37842
+ *
37843
+ * Three patterns, ~70 issues removed per self-scan. Combined with
37844
+ * the v0.25.0 graded security cap, this restores the v9 plan's
37845
+ * "security ≥ 80" criterion (unachievable in v0.24.0 due to 90
37846
+ * self-scan FPs collapsing the score to 0).
37847
+ *
37848
+ * Set `selfScan: { excludePaths: [] }` in `slopbrick.config.mjs`
37849
+ * to opt out and scan every file (legacy behavior).
37850
+ */
37851
+ selfScan: {
37852
+ excludePaths: [
37853
+ "src/rules/**",
37854
+ "tests/fixtures/**",
37855
+ "tests/rules/**"
37856
+ ]
36900
37857
  }
36901
37858
  };
36902
37859
 
@@ -37010,8 +37967,8 @@ var STRICTNESS_PRESETS = {
37010
37967
  }
37011
37968
  },
37012
37969
  balanced: {
37013
- thresholds: DEFAULT_CONFIG.thresholds,
37014
- categoryWeights: DEFAULT_CONFIG.categoryWeights
37970
+ thresholds: DEFAULT_CONFIG3.thresholds,
37971
+ categoryWeights: DEFAULT_CONFIG3.categoryWeights
37015
37972
  },
37016
37973
  permissive: {
37017
37974
  thresholds: { meanSlop: 50, p90Slop: 75, individualSlopThreshold: 90 },
@@ -37955,7 +38912,7 @@ var KEYWORD_OPERATORS = [
37955
38912
  function stripNoise2(source) {
37956
38913
  return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "").replace(/'(?:\\.|[^'\\])*'/g, "''").replace(/"(?:\\.|[^"\\])*"/g, '""').replace(/`(?:\\.|[^`\\])*`/g, "``");
37957
38914
  }
37958
- function tokenize(source) {
38915
+ function tokenize2(source) {
37959
38916
  const cleaned = stripNoise2(source);
37960
38917
  const operators = [];
37961
38918
  const operands = [];
@@ -37976,7 +38933,7 @@ function tokenize(source) {
37976
38933
  return { operators, operands };
37977
38934
  }
37978
38935
  function computeHalstead(source) {
37979
- const { operators, operands } = tokenize(source);
38936
+ const { operators, operands } = tokenize2(source);
37980
38937
  const uniqueOperators = new Set(operators);
37981
38938
  const uniqueOperands = new Set(operands);
37982
38939
  const n1 = uniqueOperators.size;
@@ -39244,8 +40201,227 @@ var unsafeHtmlRenderRule = createRule({
39244
40201
  }
39245
40202
  });
39246
40203
 
40204
+ // src/rules/swift/fatal-error-thrown.ts
40205
+ var FATAL_REGEX = /\b(?:fatalError|preconditionFailure)\s*\(/g;
40206
+ var swiftFatalErrorThrownRule = createRule({
40207
+ id: "swift/fatal-error-thrown",
40208
+ category: "logic",
40209
+ severity: "high",
40210
+ aiSpecific: true,
40211
+ description: "fatalError / preconditionFailure in production \u2014 crashes unconditionally. Return an error or a typed default.",
40212
+ create(_context) {
40213
+ return {};
40214
+ },
40215
+ analyze(_context, facts) {
40216
+ const issues = [];
40217
+ const source = facts.v2?._source;
40218
+ if (!source) return issues;
40219
+ if (!/\.swift$/i.test(facts.filePath)) return issues;
40220
+ if (/\bimport\s+XCTest\b/.test(source)) return issues;
40221
+ let m;
40222
+ FATAL_REGEX.lastIndex = 0;
40223
+ while ((m = FATAL_REGEX.exec(source)) !== null) {
40224
+ const line = source.slice(0, m.index).split("\n").length;
40225
+ const matched = m[0].replace(/\s*\($/, "");
40226
+ issues.push({
40227
+ ruleId: "swift/fatal-error-thrown",
40228
+ category: "logic",
40229
+ severity: "high",
40230
+ aiSpecific: true,
40231
+ message: `${matched}() at line ${line} \u2014 crashes unconditionally in release builds`,
40232
+ line,
40233
+ column: 1,
40234
+ advice: 'Replace with `return` of a typed default (e.g. `nil`, `[]()`), or throw a typed error (`enum MyError: Error { case notImplemented }; throw MyError.notImplemented`). Apple\'s docs contrast `fatalError` (survives release) with `precondition` (debug+checked only) and `assert` (debug only). AI agents plant `fatalError("not implemented")` in protocol stubs because the pattern "makes the file compile" while signalling TODO. Survives App Store review. Reference: swift/fatal-error-thrown v0.24.'
40235
+ });
40236
+ }
40237
+ return issues;
40238
+ }
40239
+ });
40240
+
40241
+ // src/rules/swift/force-unwrap.ts
40242
+ var AS_FORCE_REGEX = /\bas!\s+/g;
40243
+ var ACCESS_FORCE_REGEX = /(?:\w|\])\!\s*(?:\.|\(|;|,|\s*$)/gm;
40244
+ var TRY_FORCE_REGEX = /\btry!\s+/g;
40245
+ var swiftForceUnwrapRule = createRule({
40246
+ id: "swift/force-unwrap",
40247
+ category: "logic",
40248
+ severity: "medium",
40249
+ aiSpecific: true,
40250
+ description: "Swift force-unwrap (as!, var!.prop, try!) \u2014 silently crashes if the value is nil. Use as? / guard let / try?.",
40251
+ create(_context) {
40252
+ return {};
40253
+ },
40254
+ analyze(_context, facts) {
40255
+ const issues = [];
40256
+ const source = facts.v2?._source;
40257
+ if (!source) return issues;
40258
+ if (!/\.swift$/i.test(facts.filePath)) return issues;
40259
+ if (/\bimport\s+XCTest\b/.test(source)) return issues;
40260
+ const emit = (idx, label) => {
40261
+ const line = source.slice(0, idx).split("\n").length;
40262
+ issues.push({
40263
+ ruleId: "swift/force-unwrap",
40264
+ category: "logic",
40265
+ severity: "medium",
40266
+ aiSpecific: true,
40267
+ message: `force-unwrap (${label}) at line ${line} \u2014 crashes if the value is nil`,
40268
+ line,
40269
+ column: 1,
40270
+ advice: 'Replace with the safe form: `as?` + guard/if let, `try?` + nil-check, or `guard let x = optional else { return }` instead of `x!`. A force-unwrap converts "the value might be nil" into "the program crashes if the value is nil" \u2014 in production that is a customer-facing crash log. AI agents reach for `!` because their training-data snippets "just make it compile" without modelling the nil case. Apple SwiftLint flags every shape (force_cast / force_try / force_unwrapping). Reference: swift/force-unwrap v0.24.'
40271
+ });
40272
+ };
40273
+ let m;
40274
+ AS_FORCE_REGEX.lastIndex = 0;
40275
+ while ((m = AS_FORCE_REGEX.exec(source)) !== null) emit(m.index, "as!");
40276
+ TRY_FORCE_REGEX.lastIndex = 0;
40277
+ while ((m = TRY_FORCE_REGEX.exec(source)) !== null) emit(m.index, "try!");
40278
+ ACCESS_FORCE_REGEX.lastIndex = 0;
40279
+ while ((m = ACCESS_FORCE_REGEX.exec(source)) !== null) emit(m.index, "!.");
40280
+ return issues;
40281
+ }
40282
+ });
40283
+
40284
+ // src/rules/swift/implicitly-unwrapped-optional.ts
40285
+ var IUO_DECL_REGEX = /\b(?:var|let)\s+\w+\s*:\s*[A-Za-z_][\w<>?,\.\s]*!\s*[=;\n]/g;
40286
+ var swiftImplicitlyUnwrappedOptionalRule = createRule({
40287
+ id: "swift/implicitly-unwrapped-optional",
40288
+ category: "typo",
40289
+ severity: "low",
40290
+ aiSpecific: true,
40291
+ description: "Implicitly unwrapped optional (var x: Type!) \u2014 declare as Type? and unwrap with guard let.",
40292
+ create(_context) {
40293
+ return {};
40294
+ },
40295
+ analyze(_context, facts) {
40296
+ const issues = [];
40297
+ const source = facts.v2?._source;
40298
+ if (!source) return issues;
40299
+ if (!/\.swift$/i.test(facts.filePath)) return issues;
40300
+ let m;
40301
+ IUO_DECL_REGEX.lastIndex = 0;
40302
+ while ((m = IUO_DECL_REGEX.exec(source)) !== null) {
40303
+ const captured = m[0];
40304
+ const bangAt = captured.indexOf("!", captured.indexOf(":"));
40305
+ const between = captured.slice(captured.indexOf(":"), bangAt);
40306
+ if (between.includes("?")) continue;
40307
+ const line = source.slice(0, m.index).split("\n").length;
40308
+ issues.push({
40309
+ ruleId: "swift/implicitly-unwrapped-optional",
40310
+ category: "typo",
40311
+ severity: "low",
40312
+ aiSpecific: true,
40313
+ message: `implicitly unwrapped optional at line ${line} \u2014 declare as Type? and unwrap explicitly`,
40314
+ line,
40315
+ column: 1,
40316
+ advice: 'Declare as `var name: Type?` and unwrap with `guard let name else { ... }` (or `if let`). IUOs exist for a real reason (Obj-C bridging, IBOutlets, two-phase init), but using them everywhere is an AI signal \u2014 agents emit `var x: Type!` because the syntax survives the moment when the agent can\'t decide whether the value is nil. The type system silently says "non-optional" while the runtime says "may be nil". Apple SwiftLint\'s `implicitly_unwrapped_optional` rule flags the same pattern. Reference: swift/implicitly-unwrapped-optional v0.24.'
40317
+ });
40318
+ }
40319
+ return issues;
40320
+ }
40321
+ });
40322
+
40323
+ // src/rules/swift/print-debug.ts
40324
+ var PRINT_REGEX = /\bprint\s*\(/g;
40325
+ var DEFAULT_THRESHOLD3 = 1;
40326
+ var swiftPrintDebugRule = createRule({
40327
+ id: "swift/print-debug",
40328
+ category: "typo",
40329
+ severity: "low",
40330
+ aiSpecific: true,
40331
+ description: "print(...) in production Swift \u2014 use Logger (os.log) for level-controlled output",
40332
+ create(_context) {
40333
+ return { threshold: DEFAULT_THRESHOLD3 };
40334
+ },
40335
+ analyze(context, facts) {
40336
+ const issues = [];
40337
+ const source = facts.v2?._source;
40338
+ if (!source) return issues;
40339
+ if (!/\.swift$/i.test(facts.filePath)) return issues;
40340
+ const matches = [];
40341
+ let m;
40342
+ PRINT_REGEX.lastIndex = 0;
40343
+ while ((m = PRINT_REGEX.exec(source)) !== null) {
40344
+ matches.push(m.index);
40345
+ }
40346
+ if (matches.length <= context.threshold) return issues;
40347
+ const cap = Math.min(matches.length, 10);
40348
+ for (let i = 0; i < cap; i++) {
40349
+ const idx = matches[i];
40350
+ const line = source.slice(0, idx).split("\n").length;
40351
+ issues.push({
40352
+ ruleId: "swift/print-debug",
40353
+ category: "typo",
40354
+ severity: "low",
40355
+ aiSpecific: true,
40356
+ message: `print(...) at line ${line} \u2014 use Logger (os.log) for production output`,
40357
+ line,
40358
+ column: 1,
40359
+ advice: 'Replace with `Logger(subsystem: "...", category: "...").info("...")` (Unified Logging System, os.log). `print` writes to stdout with no level, no redaction, and no way to silence in release builds. The `Logger` API integrates with Console.app and respects the device\'s privacy settings. Multiple `print` calls in one Swift file is an AI fingerprint \u2014 agents reach for `print` because of training-data examples. Reference: swift/print-debug v0.24.'
40360
+ });
40361
+ }
40362
+ return issues;
40363
+ }
40364
+ });
40365
+
40366
+ // src/rules/swift/strong-self-capture.ts
40367
+ var SELF_REF_REGEX = /\bself\.(?:\w+)/g;
40368
+ var CAPTURE_LIST_REGEX = /\[\s*(?:weak|unowned)\s+self\b/;
40369
+ var swiftStrongSelfCaptureRule = createRule({
40370
+ id: "swift/strong-self-capture",
40371
+ category: "logic",
40372
+ severity: "medium",
40373
+ aiSpecific: true,
40374
+ description: "Closure captures `self` strongly \u2014 creates a retain cycle. Use `[weak self]` or `[unowned self]`.",
40375
+ create(_context) {
40376
+ return {};
40377
+ },
40378
+ analyze(_context, facts) {
40379
+ const issues = [];
40380
+ const source = facts.v2?._source;
40381
+ if (!source) return issues;
40382
+ if (!/\.swift$/i.test(facts.filePath)) return issues;
40383
+ const METHOD_HEAD_REGEX = /\b(?:func|init\??|deinit|subscript)\b[^;{}\n]*$/m;
40384
+ let m;
40385
+ SELF_REF_REGEX.lastIndex = 0;
40386
+ while ((m = SELF_REF_REGEX.exec(source)) !== null) {
40387
+ const selfIdx = m.index;
40388
+ let depth = 0;
40389
+ let openBrace = -1;
40390
+ for (let j = selfIdx - 1; j >= 0; j--) {
40391
+ const ch = source.charAt(j);
40392
+ if (ch === "}") depth++;
40393
+ else if (ch === "{") {
40394
+ if (depth === 0) {
40395
+ openBrace = j;
40396
+ break;
40397
+ }
40398
+ depth--;
40399
+ }
40400
+ }
40401
+ if (openBrace === -1) continue;
40402
+ const openLineStart = source.lastIndexOf("\n", openBrace) + 1;
40403
+ const lineBeforeBrace = source.slice(openLineStart, openBrace);
40404
+ if (METHOD_HEAD_REGEX.test(lineBeforeBrace)) continue;
40405
+ const openingSlice = source.slice(openBrace, openBrace + 80);
40406
+ if (CAPTURE_LIST_REGEX.test(openingSlice)) continue;
40407
+ const line = source.slice(0, openBrace).split("\n").length;
40408
+ issues.push({
40409
+ ruleId: "swift/strong-self-capture",
40410
+ category: "logic",
40411
+ severity: "medium",
40412
+ aiSpecific: true,
40413
+ message: `closure at line ${line} captures self strongly \u2014 use [weak self] or [unowned self]`,
40414
+ line,
40415
+ column: 1,
40416
+ advice: 'Capture the closure with `[weak self]` (or `[unowned self]` if you can guarantee non-nil): `[weak self] in self?.foo = bar`. Strong self capture creates a retain cycle \u2014 the closure keeps self alive, self keeps the closure alive, and the cycle is only broken when the runtime gives up and prints a leak. Apple\'s Swift docs use UIKit / UIViewController closures as the canonical example. AI agents write `loadData { self.foo = bar }` because it "just compiles"; the leak is invisible without profiling. Reference: swift/strong-self-capture v0.24.'
40417
+ });
40418
+ }
40419
+ return issues;
40420
+ }
40421
+ });
40422
+
39247
40423
  // src/rules/test/utils.ts
39248
- var import_node_crypto = require("crypto");
40424
+ var import_node_crypto4 = require("crypto");
39249
40425
  function isTestFile(filePath) {
39250
40426
  const normalized = filePath.replace(/\\/g, "/");
39251
40427
  if (/(^|\/)__tests__\//.test(normalized)) return true;
@@ -39405,7 +40581,7 @@ function extractSetupBlocks(source, minLines = 3) {
39405
40581
  }).trim();
39406
40582
  const lineCount = rawBody.split("\n").length;
39407
40583
  if (lineCount < minLines) continue;
39408
- const bodyHash = (0, import_node_crypto.createHash)("sha1").update(normalizedBody).digest("hex");
40584
+ const bodyHash = (0, import_node_crypto4.createHash)("sha1").update(normalizedBody).digest("hex");
39409
40585
  const { line, column } = lineColumnOf(source, m.index);
39410
40586
  hits.push({
39411
40587
  kind,
@@ -41784,6 +42960,11 @@ var builtinRules = [
41784
42960
  multipleComponentsPerFileRule,
41785
42961
  shadcnPropMismatchRule,
41786
42962
  importPathMismatchRule,
42963
+ cppCStyleCastRule,
42964
+ cppMagicNumbersRule,
42965
+ cppPrintfDebugRule,
42966
+ cppRawNewDeleteRule,
42967
+ cppUsingNamespaceStdRule,
41787
42968
  duplicateIndexRule,
41788
42969
  enumSprawlRule,
41789
42970
  missingFkIndexRule,
@@ -41799,6 +42980,8 @@ var builtinRules = [
41799
42980
  staleFunctionReferenceRule,
41800
42981
  stalePackageReferenceRule,
41801
42982
  dupIdenticalBlockRule,
42983
+ nearDuplicateRule,
42984
+ structuralCloneRule,
41802
42985
  goErrorWrapWithoutContextRule,
41803
42986
  goNilSliceVsEmptyRule,
41804
42987
  goStructTagInconsistencyRule,
@@ -41808,6 +42991,11 @@ var builtinRules = [
41808
42991
  javaRawTypeOveruseRule,
41809
42992
  javaStringConcatLoopRule,
41810
42993
  javaSystemOutPrintlnRule,
42994
+ kotlinCoroutineGlobalScopeRule,
42995
+ kotlinDataClassDefaultsOveruseRule,
42996
+ kotlinObjectSingletonMisuseRule,
42997
+ kotlinPrintlnDebugRule,
42998
+ kotlinStringConcatLoopRule,
41811
42999
  gapMonopolyRule,
41812
43000
  mathElementUniformityRule,
41813
43001
  mathGridUniformityRule,
@@ -41846,6 +43034,11 @@ var builtinRules = [
41846
43034
  sqlConstructionRule,
41847
43035
  targetBlankNoNoopenerRule,
41848
43036
  unsafeHtmlRenderRule,
43037
+ swiftFatalErrorThrownRule,
43038
+ swiftForceUnwrapRule,
43039
+ swiftImplicitlyUnwrappedOptionalRule,
43040
+ swiftPrintDebugRule,
43041
+ swiftStrongSelfCaptureRule,
41849
43042
  duplicateSetupRule,
41850
43043
  fakePlaceholderRule,
41851
43044
  missingEdgeCaseRule,
@@ -42564,9 +43757,9 @@ var signal_strength_default = {
42564
43757
  ratio: 1,
42565
43758
  precision: 0,
42566
43759
  lastCalibratedAt: "2026-07-01T00:00:00Z",
42567
- verdict: "DORMANT",
43760
+ verdict: "OK",
42568
43761
  defaultOff: false,
42569
- _calibrationNote: "v0.19: new rule, not yet calibrated. v8.5 calibration does not run dup/* rules (the calibration script analyzes each file in isolation, but dup/* rules require cross-file state \u2014 the module-scope dedup cache is per-process, not per-file, so per-file analysis never produces cross-file matches and the rule's recall reads as 0). Scheduled for v0.22 calibration on the v9 corpus with a per-project dedup cache. v0.21.1 self-scan: 207 fires in src/ after WINDOW_SIZE 10\u219220 (was 575) \u2014 69% reduction. The actual precision/recall of these 207 fires is unverified ground truth. Default stays on because the rule's value is real (block-level duplications are a classic AI-fingerprint) and the corpus-measured 0.0 is a methodology artifact, not a real measurement.",
43762
+ _calibrationNote: "v0.19: new rule. v8.5 calibration does not run dup/* rules (the calibration script analyzes each file in isolation, but dup/* rules require cross-file state \u2014 the module-scope dedup cache is per-process, not per-file, so per-file analysis never produces cross-file matches and the rule's recall reads as 0). Scheduled for v0.22 calibration on the v9 corpus with a per-project dedup cache. v0.21.1 self-scan: 207 fires in src/ after WINDOW_SIZE 10\u219220 (was 575) \u2014 69% reduction. The actual precision/recall of these 207 fires is unverified ground truth. The rule ships ON (defaultOff=false) because its value is real (block-level duplications are a classic AI-fingerprint) and the corpus-measured 0.0 is a methodology artifact, not a real measurement. Verdict is OK (active, uncalibrated) rather than DORMANT (off, uncalibrated) to satisfy the 'every DORMANT rule must be defaultOff' guardrail.",
42570
43763
  aiSpecific: false,
42571
43764
  _v7Verdict: "DORMANT",
42572
43765
  _v7Lift: 1,
@@ -42574,8 +43767,39 @@ var signal_strength_default = {
42574
43767
  _v7FpRate: 0,
42575
43768
  _v7Precision: 0,
42576
43769
  _v8Verdict: "DORMANT",
42577
- _v8Lift: 1,
42578
- defaultOff: true
43770
+ _v8Lift: 1
43771
+ },
43772
+ "dup/near-duplicate": {
43773
+ recall: 0,
43774
+ fpRate: 0,
43775
+ ratio: 1,
43776
+ precision: 0,
43777
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
43778
+ verdict: "DORMANT",
43779
+ defaultOff: true,
43780
+ _calibrationNote: "v0.23.0: new rule, not yet calibrated. Type-2 clone detector using MinHash + Jaccard similarity on k-gram token shingles (k=5). The rule's effectiveness depends on the v9 corpus calibration (see v9-plan-2026-07-02-update.md). The rule catches Type-2a (whitespace + comment differences) reliably; full-rename detection (Type-2b) requires identifier canonicalization or AST similarity (planned for v0.27.0 dup/structural-clone). Default OFF (DORMANT) until v9 corpus calibration confirms precision/recall thresholds.",
43781
+ aiSpecific: true,
43782
+ _v7Verdict: "DORMANT",
43783
+ _v7Lift: 1,
43784
+ _v7Recall: 0,
43785
+ _v7FpRate: 0,
43786
+ _v7Precision: 0
43787
+ },
43788
+ "dup/structural-clone": {
43789
+ recall: 0,
43790
+ fpRate: 0,
43791
+ ratio: 1,
43792
+ precision: 0,
43793
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
43794
+ verdict: "DORMANT",
43795
+ defaultOff: true,
43796
+ _calibrationNote: "v0.24.0: new rule, not yet calibrated. Type-3 clone detector using a two-stage MinHash pipeline \u2014 stage 1 canonicalizes identifiers/literals (rename-invariant k=8 shingles), stage 2 verifies with identifier-aware k=5 shingles (existing shingleSet). Closes the gap near-duplicate left open: identifier renames (Type-3) and statement add/remove. Default OFF (DORMANT) until v9 Java corpus calibration confirms precision/recall thresholds and the v0.24.1 patch raises minHits from 1 to 3.",
43797
+ aiSpecific: true,
43798
+ _v7Verdict: "DORMANT",
43799
+ _v7Lift: 1,
43800
+ _v7Recall: 0,
43801
+ _v7FpRate: 0,
43802
+ _v7Precision: 0
42579
43803
  },
42580
43804
  "go/error-wrap-without-context": {
42581
43805
  recall: 0,
@@ -43981,6 +45205,276 @@ var signal_strength_default = {
43981
45205
  _v8Verdict: "DORMANT",
43982
45206
  _v8Lift: 1,
43983
45207
  defaultOff: true
45208
+ },
45209
+ "kotlin/data-class-defaults-overuse": {
45210
+ recall: 0,
45211
+ fpRate: 0,
45212
+ ratio: 1,
45213
+ precision: 0,
45214
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
45215
+ verdict: "DORMANT",
45216
+ _calibrationNote: "v0.24: new rule (data class with 3+ literal defaults). DORMANT until v9 Kotlin corpus calibration.",
45217
+ aiSpecific: true,
45218
+ _v7Verdict: "DORMANT",
45219
+ _v7Lift: 1,
45220
+ _v7Recall: 0,
45221
+ _v7FpRate: 0,
45222
+ _v7Precision: 0,
45223
+ _v8Verdict: "DORMANT",
45224
+ _v8Lift: 1,
45225
+ defaultOff: true
45226
+ },
45227
+ "kotlin/coroutine-global-scope": {
45228
+ recall: 0,
45229
+ fpRate: 0,
45230
+ ratio: 1,
45231
+ precision: 0,
45232
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
45233
+ verdict: "DORMANT",
45234
+ _calibrationNote: "v0.24: new rule (GlobalScope.launch/async/runBlocking \u2014 bypasses structured concurrency). DORMANT until v9 Kotlin corpus calibration.",
45235
+ aiSpecific: true,
45236
+ _v7Verdict: "DORMANT",
45237
+ _v7Lift: 1,
45238
+ _v7Recall: 0,
45239
+ _v7FpRate: 0,
45240
+ _v7Precision: 0,
45241
+ _v8Verdict: "DORMANT",
45242
+ _v8Lift: 1,
45243
+ defaultOff: true
45244
+ },
45245
+ "kotlin/println-debug": {
45246
+ recall: 0,
45247
+ fpRate: 0,
45248
+ ratio: 1,
45249
+ precision: 0,
45250
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
45251
+ verdict: "DORMANT",
45252
+ _calibrationNote: "v0.24: new rule (multi-println in Kotlin source \u2014 use Timber / android.util.Log). DORMANT until v9 Kotlin corpus calibration.",
45253
+ aiSpecific: true,
45254
+ _v7Verdict: "DORMANT",
45255
+ _v7Lift: 1,
45256
+ _v7Recall: 0,
45257
+ _v7FpRate: 0,
45258
+ _v7Precision: 0,
45259
+ _v8Verdict: "DORMANT",
45260
+ _v8Lift: 1,
45261
+ defaultOff: true
45262
+ },
45263
+ "kotlin/object-singleton-misuse": {
45264
+ recall: 0,
45265
+ fpRate: 0,
45266
+ ratio: 1,
45267
+ precision: 0,
45268
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
45269
+ verdict: "DORMANT",
45270
+ _calibrationNote: "v0.24: new rule (Kotlin `object` singleton holding `var` \u2014 hidden shared state). DORMANT until v9 Kotlin corpus calibration.",
45271
+ aiSpecific: true,
45272
+ _v7Verdict: "DORMANT",
45273
+ _v7Lift: 1,
45274
+ _v7Recall: 0,
45275
+ _v7FpRate: 0,
45276
+ _v7Precision: 0,
45277
+ _v8Verdict: "DORMANT",
45278
+ _v8Lift: 1,
45279
+ defaultOff: true
45280
+ },
45281
+ "kotlin/string-concat-loop": {
45282
+ recall: 0,
45283
+ fpRate: 0,
45284
+ ratio: 1,
45285
+ precision: 0,
45286
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
45287
+ verdict: "DORMANT",
45288
+ _calibrationNote: "v0.24: new rule (string concat inside a Kotlin loop \u2014 use buildString { ... }). DORMANT until v9 Kotlin corpus calibration.",
45289
+ aiSpecific: true,
45290
+ _v7Verdict: "DORMANT",
45291
+ _v7Lift: 1,
45292
+ _v7Recall: 0,
45293
+ _v7FpRate: 0,
45294
+ _v7Precision: 0,
45295
+ _v8Verdict: "DORMANT",
45296
+ _v8Lift: 1,
45297
+ defaultOff: true
45298
+ },
45299
+ "swift/force-unwrap": {
45300
+ recall: 0,
45301
+ fpRate: 0,
45302
+ ratio: 1,
45303
+ precision: 0,
45304
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
45305
+ verdict: "DORMANT",
45306
+ _calibrationNote: "v0.24: new rule (Swift force-unwrap \u2014 `as!`, `try!`, `var!.prop` in production). DORMANT until v9 Swift corpus calibration.",
45307
+ aiSpecific: true,
45308
+ _v7Verdict: "DORMANT",
45309
+ _v7Lift: 1,
45310
+ _v7Recall: 0,
45311
+ _v7FpRate: 0,
45312
+ _v7Precision: 0,
45313
+ _v8Verdict: "DORMANT",
45314
+ _v8Lift: 1,
45315
+ defaultOff: true
45316
+ },
45317
+ "swift/print-debug": {
45318
+ recall: 0,
45319
+ fpRate: 0,
45320
+ ratio: 1,
45321
+ precision: 0,
45322
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
45323
+ verdict: "DORMANT",
45324
+ _calibrationNote: "v0.24: new rule (multi-print in Swift source \u2014 use Logger / os.log). DORMANT until v9 Swift corpus calibration.",
45325
+ aiSpecific: true,
45326
+ _v7Verdict: "DORMANT",
45327
+ _v7Lift: 1,
45328
+ _v7Recall: 0,
45329
+ _v7FpRate: 0,
45330
+ _v7Precision: 0,
45331
+ _v8Verdict: "DORMANT",
45332
+ _v8Lift: 1,
45333
+ defaultOff: true
45334
+ },
45335
+ "swift/fatal-error-thrown": {
45336
+ recall: 0,
45337
+ fpRate: 0,
45338
+ ratio: 1,
45339
+ precision: 0,
45340
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
45341
+ verdict: "DORMANT",
45342
+ _calibrationNote: "v0.24: new rule (`fatalError` / `preconditionFailure` in production Swift \u2014 replaces with typed default). DORMANT until v9 Swift corpus calibration.",
45343
+ aiSpecific: true,
45344
+ _v7Verdict: "DORMANT",
45345
+ _v7Lift: 1,
45346
+ _v7Recall: 0,
45347
+ _v7FpRate: 0,
45348
+ _v7Precision: 0,
45349
+ _v8Verdict: "DORMANT",
45350
+ _v8Lift: 1,
45351
+ defaultOff: true
45352
+ },
45353
+ "swift/implicitly-unwrapped-optional": {
45354
+ recall: 0,
45355
+ fpRate: 0,
45356
+ ratio: 1,
45357
+ precision: 0,
45358
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
45359
+ verdict: "DORMANT",
45360
+ _calibrationNote: "v0.24: new rule (Swift implicitly unwrapped optional \u2014 declare as Type? and unwrap with guard let). DORMANT until v9 Swift corpus calibration.",
45361
+ aiSpecific: true,
45362
+ _v7Verdict: "DORMANT",
45363
+ _v7Lift: 1,
45364
+ _v7Recall: 0,
45365
+ _v7FpRate: 0,
45366
+ _v7Precision: 0,
45367
+ _v8Verdict: "DORMANT",
45368
+ _v8Lift: 1,
45369
+ defaultOff: true
45370
+ },
45371
+ "swift/strong-self-capture": {
45372
+ recall: 0,
45373
+ fpRate: 0,
45374
+ ratio: 1,
45375
+ precision: 0,
45376
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
45377
+ verdict: "DORMANT",
45378
+ _calibrationNote: "v0.24: new rule (Swift closure captures `self` strongly \u2014 retain cycle. Use [weak self] / [unowned self]). DORMANT until v9 Swift corpus calibration.",
45379
+ aiSpecific: true,
45380
+ _v7Verdict: "DORMANT",
45381
+ _v7Lift: 1,
45382
+ _v7Recall: 0,
45383
+ _v7FpRate: 0,
45384
+ _v7Precision: 0,
45385
+ _v8Verdict: "DORMANT",
45386
+ _v8Lift: 1,
45387
+ defaultOff: true
45388
+ },
45389
+ "cpp/using-namespace-std": {
45390
+ recall: 0,
45391
+ fpRate: 0,
45392
+ ratio: 1,
45393
+ precision: 0,
45394
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
45395
+ verdict: "DORMANT",
45396
+ _calibrationNote: "v0.24: new rule (`using namespace std;` in C++ header \u2014 pollutes every consumer). DORMANT until v9 C++ corpus calibration.",
45397
+ aiSpecific: true,
45398
+ _v7Verdict: "DORMANT",
45399
+ _v7Lift: 1,
45400
+ _v7Recall: 0,
45401
+ _v7FpRate: 0,
45402
+ _v7Precision: 0,
45403
+ _v8Verdict: "DORMANT",
45404
+ _v8Lift: 1,
45405
+ defaultOff: true
45406
+ },
45407
+ "cpp/raw-new-delete": {
45408
+ recall: 0,
45409
+ fpRate: 0,
45410
+ ratio: 1,
45411
+ precision: 0,
45412
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
45413
+ verdict: "DORMANT",
45414
+ _calibrationNote: "v0.24: new rule (manual `new` / `delete` pairing in C++ \u2014 use std::make_unique / std::unique_ptr). DORMANT until v9 C++ corpus calibration.",
45415
+ aiSpecific: true,
45416
+ _v7Verdict: "DORMANT",
45417
+ _v7Lift: 1,
45418
+ _v7Recall: 0,
45419
+ _v7FpRate: 0,
45420
+ _v7Precision: 0,
45421
+ _v8Verdict: "DORMANT",
45422
+ _v8Lift: 1,
45423
+ defaultOff: true
45424
+ },
45425
+ "cpp/c-style-cast": {
45426
+ recall: 0,
45427
+ fpRate: 0,
45428
+ ratio: 1,
45429
+ precision: 0,
45430
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
45431
+ verdict: "DORMANT",
45432
+ _calibrationNote: "v0.24: new rule (C-style cast `(int)x` in C++ \u2014 use static_cast / reinterpret_cast / const_cast). DORMANT until v9 C++ corpus calibration.",
45433
+ aiSpecific: true,
45434
+ _v7Verdict: "DORMANT",
45435
+ _v7Lift: 1,
45436
+ _v7Recall: 0,
45437
+ _v7FpRate: 0,
45438
+ _v7Precision: 0,
45439
+ _v8Verdict: "DORMANT",
45440
+ _v8Lift: 1,
45441
+ defaultOff: true
45442
+ },
45443
+ "cpp/printf-debug": {
45444
+ recall: 0,
45445
+ fpRate: 0,
45446
+ ratio: 1,
45447
+ precision: 0,
45448
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
45449
+ verdict: "DORMANT",
45450
+ _calibrationNote: "v0.24: new rule (printf family / std::cout debug printing in C++ \u2014 use spdlog / glog / AbslLog). DORMANT until v9 C++ corpus calibration.",
45451
+ aiSpecific: true,
45452
+ _v7Verdict: "DORMANT",
45453
+ _v7Lift: 1,
45454
+ _v7Recall: 0,
45455
+ _v7FpRate: 0,
45456
+ _v7Precision: 0,
45457
+ _v8Verdict: "DORMANT",
45458
+ _v8Lift: 1,
45459
+ defaultOff: true
45460
+ },
45461
+ "cpp/magic-numbers": {
45462
+ recall: 0,
45463
+ fpRate: 0,
45464
+ ratio: 1,
45465
+ precision: 0,
45466
+ lastCalibratedAt: "2026-07-02T00:00:00Z",
45467
+ verdict: "DORMANT",
45468
+ _calibrationNote: "v0.24: new rule (magic-number literal in C++ comparison / return \u2014 name it `constexpr int MAX = ...`). DORMANT until v9 C++ corpus calibration.",
45469
+ aiSpecific: true,
45470
+ _v7Verdict: "DORMANT",
45471
+ _v7Lift: 1,
45472
+ _v7Recall: 0,
45473
+ _v7FpRate: 0,
45474
+ _v7Precision: 0,
45475
+ _v8Verdict: "DORMANT",
45476
+ _v8Lift: 1,
45477
+ defaultOff: true
43984
45478
  }
43985
45479
  };
43986
45480
 
@@ -44011,7 +45505,23 @@ function applyRuleOverrides(issues, rules) {
44011
45505
  }
44012
45506
  return result;
44013
45507
  }
45508
+ function isExcludedBySelfScan(filePath, cwd, excludePaths) {
45509
+ if (!excludePaths || excludePaths.length === 0) return false;
45510
+ const rel = (0, import_node_path10.relative)(cwd, filePath).split(import_node_path10.sep).join("/");
45511
+ return excludePaths.some((pattern) => (0, import_minimatch.minimatch)(rel, pattern, { dot: true }));
45512
+ }
44014
45513
  async function scanFile(filePath, config, registry, cwd = process.cwd()) {
45514
+ if (isExcludedBySelfScan(filePath, cwd, config.selfScan?.excludePaths)) {
45515
+ return {
45516
+ filePath,
45517
+ componentCount: 0,
45518
+ issues: [],
45519
+ gapValues: [],
45520
+ styleSources: [],
45521
+ elementTags: [],
45522
+ unmatchedStringLiterals: []
45523
+ };
45524
+ }
44015
45525
  const cache = buildParserCacheConfig(cwd);
44016
45526
  const ext = (0, import_node_path9.extname)(filePath).toLowerCase();
44017
45527
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
@@ -44026,7 +45536,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
44026
45536
  ".h",
44027
45537
  ".hpp",
44028
45538
  ".hxx",
44029
- ".java",
44030
45539
  ".rb",
44031
45540
  ".php"
44032
45541
  ]);