slopbrick 0.18.4 → 0.18.5

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.
@@ -6316,6 +6316,78 @@ function extractStateBinding(node, lineOffsets) {
6316
6316
  setterReferenced: false
6317
6317
  };
6318
6318
  }
6319
+ function findUnreachableStatements(ast, source, lineOffsets) {
6320
+ const out = [];
6321
+ function isTerminator(node) {
6322
+ if (node.type === "ReturnStatement") return "return";
6323
+ if (node.type === "ThrowStatement") return "throw";
6324
+ if (node.type === "BreakStatement") return "break";
6325
+ if (node.type === "ContinueStatement") return "continue";
6326
+ return null;
6327
+ }
6328
+ function snippetFor(node, src) {
6329
+ const span = node.span;
6330
+ if (!span || typeof span.start !== "number" || typeof span.end !== "number") {
6331
+ return "<unreachable>";
6332
+ }
6333
+ const text = src.slice(span.start, Math.min(span.end, span.start + 60));
6334
+ return text.replace(/\s+/g, " ").trim() || "<unreachable>";
6335
+ }
6336
+ function lineColumn(offset) {
6337
+ let lo = 0;
6338
+ let hi = lineOffsets.length - 1;
6339
+ while (lo < hi) {
6340
+ const mid = lo + hi + 1 >>> 1;
6341
+ const midVal = lineOffsets[mid];
6342
+ const loVal = lineOffsets[lo];
6343
+ if (midVal === void 0) break;
6344
+ if (midVal <= offset) lo = mid;
6345
+ else hi = mid - 1;
6346
+ void loVal;
6347
+ }
6348
+ const baseOffset = lineOffsets[lo] ?? 0;
6349
+ return { line: lo + 1, column: offset - baseOffset };
6350
+ }
6351
+ function visitBody(body) {
6352
+ if (!Array.isArray(body)) return;
6353
+ let lastTerminator = null;
6354
+ for (const stmt of body) {
6355
+ if (!isObject(stmt)) continue;
6356
+ if (lastTerminator && stmt.type !== "EmptyStatement") {
6357
+ const span = stmt.span;
6358
+ const offset = typeof span?.start === "number" ? span.start : 0;
6359
+ const { line, column } = lineColumn(offset);
6360
+ out.push({
6361
+ terminator: lastTerminator,
6362
+ line,
6363
+ column,
6364
+ snippet: snippetFor(stmt, source)
6365
+ });
6366
+ }
6367
+ const t = isTerminator(stmt);
6368
+ if (t) lastTerminator = t;
6369
+ }
6370
+ }
6371
+ function walk3(node) {
6372
+ if (!isObject(node)) return;
6373
+ if (node.type === "BlockStatement") {
6374
+ visitBody(node.stmts);
6375
+ } else if (node.type === "Module") {
6376
+ visitBody(node.body);
6377
+ }
6378
+ for (const key of Object.keys(node)) {
6379
+ if (key === "parent" || key === "span" || key === "ctxt") continue;
6380
+ const child = node[key];
6381
+ if (Array.isArray(child)) {
6382
+ for (const c of child) walk3(c);
6383
+ } else {
6384
+ walk3(child);
6385
+ }
6386
+ }
6387
+ }
6388
+ walk3(ast);
6389
+ return out;
6390
+ }
6319
6391
 
6320
6392
  // src/engine/visitors/dispatch.ts
6321
6393
  function nearestComponent(vctx) {
@@ -6392,15 +6464,42 @@ function handleImportDeclaration(node, _parent, _path, vctx) {
6392
6464
  if (specifier.type === "ImportDefaultSpecifier" || specifier.type === "ImportNamespaceSpecifier") {
6393
6465
  const local = specifier.local;
6394
6466
  if (isObject(local) && local.type === "Identifier" && typeof local.value === "string") {
6395
- importedNames.push(local.value);
6467
+ const name = local.value;
6468
+ importedNames.push(name);
6469
+ vctx.facts.deadCode.bindings.push({
6470
+ name,
6471
+ kind: specifier.type === "ImportDefaultSpecifier" ? "import-default" : "import-namespace",
6472
+ line,
6473
+ column,
6474
+ source,
6475
+ isReferenced: false
6476
+ });
6396
6477
  }
6397
6478
  } else if (specifier.type === "ImportSpecifier") {
6398
6479
  const imported = specifier.imported;
6399
6480
  const local = specifier.local;
6400
6481
  if (isObject(imported) && typeof imported.value === "string" && imported.value.length > 0) {
6401
- importedNames.push(imported.value);
6482
+ const name = imported.value;
6483
+ importedNames.push(name);
6484
+ vctx.facts.deadCode.bindings.push({
6485
+ name,
6486
+ kind: "import-specifier",
6487
+ line,
6488
+ column,
6489
+ source,
6490
+ isReferenced: false
6491
+ });
6402
6492
  } else if (isObject(local) && local.type === "Identifier" && typeof local.value === "string") {
6403
- importedNames.push(local.value);
6493
+ const name = local.value;
6494
+ importedNames.push(name);
6495
+ vctx.facts.deadCode.bindings.push({
6496
+ name,
6497
+ kind: "import-specifier",
6498
+ line,
6499
+ column,
6500
+ source,
6501
+ isReferenced: false
6502
+ });
6404
6503
  }
6405
6504
  }
6406
6505
  }
@@ -6490,6 +6589,13 @@ function isBindingSite(node, parent) {
6490
6589
  if (params.some((param) => containsNode(param, node))) return true;
6491
6590
  }
6492
6591
  }
6592
+ if (parent.type === "ImportSpecifier" || parent.type === "ImportDefaultSpecifier" || parent.type === "ImportNamespaceSpecifier") {
6593
+ if (parent.type === "ImportSpecifier") {
6594
+ if (parent.imported === node || parent.local === node) return true;
6595
+ } else {
6596
+ if (parent.local === node) return true;
6597
+ }
6598
+ }
6493
6599
  return false;
6494
6600
  }
6495
6601
  function isNonComputedMemberProperty(node, parent) {
@@ -6556,6 +6662,9 @@ function handleIdentifier(node, parent, path, vctx) {
6556
6662
  if (typeof node.value === "string" && !isBindingSite(node, parent) && !isNonComputedMemberProperty(node, parent)) {
6557
6663
  markStateReference(node.value, vctx);
6558
6664
  trackPropUsage(node, parent, path, vctx);
6665
+ vctx.facts.referencedNames.add(node.value);
6666
+ const top = vctx.ctx.stack[vctx.ctx.stack.length - 1];
6667
+ if (top) top.references.add(node.value);
6559
6668
  }
6560
6669
  return false;
6561
6670
  }
@@ -6696,6 +6805,20 @@ function handleVariableDeclarator(node, _parent, path, vctx) {
6696
6805
  frame.bindings.add(bindingName);
6697
6806
  }
6698
6807
  }
6808
+ if (bindingNames.length > 0) {
6809
+ const parent = node.parent;
6810
+ const kind = isObject(parent) && parent.type === "VariableDeclaration" ? String(parent.kind ?? "var") : "var";
6811
+ const { line, column } = positionFrom(id, vctx.lineOffsets);
6812
+ for (const bindingName of bindingNames) {
6813
+ vctx.facts.deadCode.bindings.push({
6814
+ name: bindingName,
6815
+ kind,
6816
+ line,
6817
+ column,
6818
+ isReferenced: false
6819
+ });
6820
+ }
6821
+ }
6699
6822
  if (isUseStateDeclarator(node)) {
6700
6823
  const binding = extractStateBinding(node, vctx.lineOffsets);
6701
6824
  if (binding) {
@@ -6715,6 +6838,36 @@ function handleVariableDeclarator(node, _parent, path, vctx) {
6715
6838
  }
6716
6839
  return true;
6717
6840
  }
6841
+ function handleIfStatement(node, _parent, _path, vctx) {
6842
+ if (!isObject(node)) return false;
6843
+ const test = node.test;
6844
+ if (!isObject(test)) return false;
6845
+ if (test.type === "BooleanLiteral" && typeof test.value === "boolean") {
6846
+ const { line, column } = positionFrom(test, vctx.lineOffsets);
6847
+ vctx.facts.deadCode.constantConditions.push({
6848
+ kind: test.value ? "if-true" : "if-false",
6849
+ condition: String(test.value),
6850
+ line,
6851
+ column
6852
+ });
6853
+ }
6854
+ return false;
6855
+ }
6856
+ function handleWhileStatement(node, _parent, _path, vctx) {
6857
+ if (!isObject(node)) return false;
6858
+ const test = node.test;
6859
+ if (!isObject(test)) return false;
6860
+ if (test.type === "BooleanLiteral" && typeof test.value === "boolean") {
6861
+ const { line, column } = positionFrom(test, vctx.lineOffsets);
6862
+ vctx.facts.deadCode.constantConditions.push({
6863
+ kind: test.value ? "while-true" : "while-false",
6864
+ condition: String(test.value),
6865
+ line,
6866
+ column
6867
+ });
6868
+ }
6869
+ return false;
6870
+ }
6718
6871
  var HANDLERS = {
6719
6872
  ExpressionStatement: handleExpressionStatement,
6720
6873
  ImportDeclaration: handleImportDeclaration,
@@ -6724,7 +6877,9 @@ var HANDLERS = {
6724
6877
  MemberExpression: handleMemberExpression,
6725
6878
  JSXAttribute: handleJSXAttribute,
6726
6879
  JSXOpeningElement: handleJSXOpeningElement,
6727
- VariableDeclarator: handleVariableDeclarator
6880
+ VariableDeclarator: handleVariableDeclarator,
6881
+ IfStatement: handleIfStatement,
6882
+ WhileStatement: handleWhileStatement
6728
6883
  };
6729
6884
  function dispatchNode(node, parent, path, vctx) {
6730
6885
  if (!isObject(node)) return false;
@@ -6889,6 +7044,17 @@ function buildV2Facts(facts, source, ext, framework, config, templateClassNames
6889
7044
  },
6890
7045
  logic: buildLogicBlock(facts),
6891
7046
  designTokens: scanDesignTokens(facts.staticClassNames),
7047
+ // dead-code detector. Copy the internal accumulator
7048
+ // into the v2 shape, marking each binding as referenced
7049
+ // iff the file-level referenced-name set contains its name.
7050
+ deadCode: {
7051
+ bindings: facts.deadCode.bindings.map((b) => ({
7052
+ ...b,
7053
+ isReferenced: facts.referencedNames.has(b.name)
7054
+ })),
7055
+ constantConditions: facts.deadCode.constantConditions,
7056
+ unreachableStatements: facts.deadCode.unreachableStatements
7057
+ },
6892
7058
  componentSizes: facts.componentSizes.map((cs) => ({
6893
7059
  name: cs.name,
6894
7060
  lineCount: cs.lineCount,
@@ -6956,7 +7122,17 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
6956
7122
  componentSizes: [],
6957
7123
  astroComponents: [],
6958
7124
  fetchCalls: [],
6959
- optimisticUpdates: []
7125
+ optimisticUpdates: [],
7126
+ // dead-code detector. The visitor's identifier walk + import/
7127
+ // branch/return handlers populate these. The v2 builder at the
7128
+ // bottom of extractFacts() reads them and produces
7129
+ // `facts.v2.deadCode`.
7130
+ deadCode: {
7131
+ bindings: [],
7132
+ constantConditions: [],
7133
+ unreachableStatements: []
7134
+ },
7135
+ referencedNames: /* @__PURE__ */ new Set()
6960
7136
  };
6961
7137
  const ctx = {
6962
7138
  stack: [],
@@ -7043,6 +7219,16 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
7043
7219
  propBindingSet.add(bindingName);
7044
7220
  }
7045
7221
  }
7222
+ const { line: pLine, column: pCol } = positionFrom(param, lineOffsets);
7223
+ for (const bindingName of collectBindingNames2(param)) {
7224
+ facts.deadCode.bindings.push({
7225
+ name: bindingName,
7226
+ kind: "parameter",
7227
+ line: pLine,
7228
+ column: pCol,
7229
+ isReferenced: false
7230
+ });
7231
+ }
7046
7232
  }
7047
7233
  }
7048
7234
  ctx.stack.push({
@@ -7058,6 +7244,12 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
7058
7244
  propUsages: [],
7059
7245
  isComponent,
7060
7246
  bindings,
7247
+ // dead-code detector: per-frame referenced-name set.
7248
+ // Identifiers encountered inside the frame are added to this
7249
+ // set; the deadCode builder unions it with parent frames at
7250
+ // pop time so a binding is considered used if any reachable
7251
+ // scope references it.
7252
+ references: /* @__PURE__ */ new Set(),
7061
7253
  propBindingSet,
7062
7254
  propUsageSet: /* @__PURE__ */ new Set(),
7063
7255
  node
@@ -7153,6 +7345,11 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
7153
7345
  mergeTemplateClassNames(filePath, source, facts, templateClassNames);
7154
7346
  const { ext } = splitFilePath(filePath);
7155
7347
  facts._source = source;
7348
+ facts.deadCode.unreachableStatements = findUnreachableStatements(
7349
+ ast,
7350
+ source,
7351
+ lineOffsets
7352
+ );
7156
7353
  const v2 = buildV2Facts(facts, source, ext, framework, config, templateClassNames);
7157
7354
  return envelopeScanFacts(filePath, v2);
7158
7355
  }
@@ -34287,6 +34484,170 @@ var sqlConcatRule = createRule({
34287
34484
  }
34288
34485
  });
34289
34486
 
34487
+ // src/rules/dead/dead-branch.ts
34488
+ var deadBranchRule = createRule({
34489
+ id: "dead/dead-branch",
34490
+ category: "logic",
34491
+ severity: "medium",
34492
+ aiSpecific: true,
34493
+ description: "Literal boolean condition makes one branch statically dead",
34494
+ create(_context) {
34495
+ return {};
34496
+ },
34497
+ analyze(_context, facts) {
34498
+ const issues = [];
34499
+ if (!facts.v2) return issues;
34500
+ for (const cond of facts.v2.deadCode.constantConditions) {
34501
+ const isWhileTrue = cond.kind === "while-true";
34502
+ issues.push({
34503
+ ruleId: "dead/dead-branch",
34504
+ category: "logic",
34505
+ severity: isWhileTrue ? "low" : "medium",
34506
+ aiSpecific: true,
34507
+ message: isWhileTrue ? `Infinite loop with literal condition (${cond.kind})` : `Dead branch: condition is always ${cond.condition}`,
34508
+ line: cond.line,
34509
+ column: cond.column,
34510
+ advice: isWhileTrue ? `If this is an intentional infinite loop (event loop, hot loop with explicit \`break\`), add a \`// slopbrick-disable\` comment. Otherwise, replace the literal with a real condition.` : `Replace the literal with a real condition, or remove the dead branch entirely. This is the AI-iteration signature: the model toggled a feature flag to a constant or left a wrapper from a previous refactor.`
34511
+ });
34512
+ }
34513
+ return issues;
34514
+ }
34515
+ });
34516
+
34517
+ // src/rules/dead/unreachable.ts
34518
+ var unreachableRule = createRule({
34519
+ id: "dead/unreachable",
34520
+ category: "logic",
34521
+ severity: "high",
34522
+ aiSpecific: true,
34523
+ description: "Statement is unreachable after an unconditional return/throw/break/continue",
34524
+ create(_context) {
34525
+ return {};
34526
+ },
34527
+ analyze(_context, facts) {
34528
+ const issues = [];
34529
+ if (!facts.v2) return issues;
34530
+ for (const u of facts.v2.deadCode.unreachableStatements) {
34531
+ if (u.snippet === "<unreachable>") continue;
34532
+ issues.push({
34533
+ ruleId: "dead/unreachable",
34534
+ category: "logic",
34535
+ severity: "high",
34536
+ aiSpecific: true,
34537
+ message: `Unreachable after ${u.terminator}: ${u.snippet}`,
34538
+ line: u.line,
34539
+ column: u.column,
34540
+ advice: `Remove this statement \u2014 code after a ${u.terminator} is unreachable. This is the AI-iteration signature: the model added an early ${u.terminator} for a new error path, then forgot the rest of the function body was still sitting below it.`
34541
+ });
34542
+ }
34543
+ return issues;
34544
+ }
34545
+ });
34546
+
34547
+ // src/rules/dead/unused-import.ts
34548
+ var unusedImportRule = createRule({
34549
+ id: "dead/unused-import",
34550
+ category: "logic",
34551
+ severity: "low",
34552
+ aiSpecific: true,
34553
+ description: "ES module import is never referenced in the file",
34554
+ create(_context) {
34555
+ return {};
34556
+ },
34557
+ analyze(_context, facts) {
34558
+ const issues = [];
34559
+ if (!facts.v2) return issues;
34560
+ for (const binding of facts.v2.deadCode.bindings) {
34561
+ if (binding.kind !== "import-specifier" && binding.kind !== "import-default" && binding.kind !== "import-namespace") {
34562
+ continue;
34563
+ }
34564
+ if (binding.isReferenced) continue;
34565
+ if (!binding.name) continue;
34566
+ const source = binding.source ? ` from '${binding.source}'` : "";
34567
+ issues.push({
34568
+ ruleId: "dead/unused-import",
34569
+ category: "logic",
34570
+ severity: "low",
34571
+ aiSpecific: true,
34572
+ message: `Unused import: '${binding.name}'${source}`,
34573
+ line: binding.line,
34574
+ column: binding.column,
34575
+ advice: `Remove the import or use '${binding.name}' somewhere in the file. This is the most common AI-iteration rot \u2014 the model added the import when it introduced a feature, then rewrote the function without cleaning up.`
34576
+ });
34577
+ }
34578
+ return issues;
34579
+ }
34580
+ });
34581
+
34582
+ // src/rules/dead/unused-local.ts
34583
+ var SKIP_NAMES = /* @__PURE__ */ new Set(["React", "_"]);
34584
+ var unusedLocalRule = createRule({
34585
+ id: "dead/unused-local",
34586
+ category: "logic",
34587
+ severity: "low",
34588
+ aiSpecific: true,
34589
+ description: "Variable is declared but never read after declaration",
34590
+ create(_context) {
34591
+ return {};
34592
+ },
34593
+ analyze(_context, facts) {
34594
+ const issues = [];
34595
+ if (!facts.v2) return issues;
34596
+ for (const binding of facts.v2.deadCode.bindings) {
34597
+ if (binding.kind !== "var" && binding.kind !== "let" && binding.kind !== "const" && binding.kind !== "function" && binding.kind !== "class" && binding.kind !== "type" && binding.kind !== "interface" && binding.kind !== "enum") {
34598
+ continue;
34599
+ }
34600
+ if (binding.isReferenced) continue;
34601
+ if (SKIP_NAMES.has(binding.name)) continue;
34602
+ if (binding.name.startsWith("_")) continue;
34603
+ issues.push({
34604
+ ruleId: "dead/unused-local",
34605
+ category: "logic",
34606
+ severity: "low",
34607
+ aiSpecific: true,
34608
+ message: `Unused ${binding.kind}: '${binding.name}'`,
34609
+ line: binding.line,
34610
+ column: binding.column,
34611
+ advice: `Remove the declaration or use '${binding.name}' somewhere in the file. This is the second-most-common AI-iteration signature \u2014 the model declared the binding when it introduced a feature, then rewrote the function without cleaning up.`
34612
+ });
34613
+ }
34614
+ return issues;
34615
+ }
34616
+ });
34617
+
34618
+ // src/rules/dead/unused-parameter.ts
34619
+ var unusedParameterRule = createRule({
34620
+ id: "dead/unused-parameter",
34621
+ category: "logic",
34622
+ severity: "low",
34623
+ aiSpecific: true,
34624
+ description: "Function parameter is declared but never read",
34625
+ create(_context) {
34626
+ return {};
34627
+ },
34628
+ analyze(_context, facts) {
34629
+ const issues = [];
34630
+ if (!facts.v2) return issues;
34631
+ for (const binding of facts.v2.deadCode.bindings) {
34632
+ if (binding.kind !== "parameter") continue;
34633
+ if (binding.isReferenced) continue;
34634
+ if (binding.name.startsWith("_")) continue;
34635
+ if (binding.name === "props") continue;
34636
+ issues.push({
34637
+ ruleId: "dead/unused-parameter",
34638
+ category: "logic",
34639
+ severity: "low",
34640
+ aiSpecific: true,
34641
+ message: `Unused parameter: '${binding.name}'`,
34642
+ line: binding.line,
34643
+ column: binding.column,
34644
+ advice: `Remove the parameter (and update every call site) or use '${binding.name}' in the function body. This is the AI-iteration signature: the model added the parameter when it introduced a feature, then rewrote the function without removing parameters the new code does not need.`
34645
+ });
34646
+ }
34647
+ return issues;
34648
+ }
34649
+ });
34650
+
34290
34651
  // src/rules/docs/broken-link.ts
34291
34652
  import { existsSync as existsSync4 } from "fs";
34292
34653
  import { dirname as dirname4, join as join7, resolve as resolve2 } from "path";
@@ -39579,6 +39940,11 @@ var builtinRules = [
39579
39940
  missingNotNullRule,
39580
39941
  namingInconsistencyRule,
39581
39942
  sqlConcatRule,
39943
+ deadBranchRule,
39944
+ unreachableRule,
39945
+ unusedImportRule,
39946
+ unusedLocalRule,
39947
+ unusedParameterRule,
39582
39948
  brokenLinkRule,
39583
39949
  expiredCodeExampleRule,
39584
39950
  staleFunctionReferenceRule,
@@ -40586,6 +40952,61 @@ var signal_strength_default = {
40586
40952
  _calibrationNote: "v0.17.0 ship \u2014 not in v7 per-rule table. Default-off until calibration data lands. Backed by: OWASP Foundation (2021), *OWASP Top 10 \u2014 A03:2021 Injection*, https://owasp.org/Top10/A03_2021-Injection/; OWASP Foundation (2017), *SQL Injection Prevention Cheat Sheet*. (Template-literal SQL with ${...} interpolation is the #1 SQL injection vector in AI-generated TypeScript code.)",
40587
40953
  aiSpecific: true
40588
40954
  },
40955
+ "dead/unused-import": {
40956
+ recall: 0,
40957
+ fpRate: 0,
40958
+ ratio: 0,
40959
+ precision: 0,
40960
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
40961
+ verdict: "DORMANT",
40962
+ defaultOff: true,
40963
+ _calibrationNote: "v0.18.5 ship \u2014 not in v7 per-rule table. Default-off until v8 calibration data lands. The first of 5 planned `dead/*` rules (this one + dead/unused-local + dead/unused-parameter + dead/dead-branch + dead/unreachable). The pattern is the canonical AI-iteration rot: the model adds an import when introducing a feature, then rewrites the function later without cleaning up. Most real-world tsconfig.json files have `noUnusedLocals: false`, so tsc never fires.",
40964
+ aiSpecific: true
40965
+ },
40966
+ "dead/unused-local": {
40967
+ recall: 0,
40968
+ fpRate: 0,
40969
+ ratio: 0,
40970
+ precision: 0,
40971
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
40972
+ verdict: "DORMANT",
40973
+ defaultOff: true,
40974
+ _calibrationNote: "v0.18.5b ship \u2014 not in v7 per-rule table. Default-off until v8 calibration data lands. The second of 5 planned `dead/*` rules. Muchnick 1997 Ch. 13 'liveness analysis' (textbook compiler optimization).",
40975
+ aiSpecific: true
40976
+ },
40977
+ "dead/unused-parameter": {
40978
+ recall: 0,
40979
+ fpRate: 0,
40980
+ ratio: 0,
40981
+ precision: 0,
40982
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
40983
+ verdict: "DORMANT",
40984
+ defaultOff: true,
40985
+ _calibrationNote: "v0.18.5b ship \u2014 not in v7 per-rule table. Default-off until v8 calibration data lands. The third of 5 planned `dead/*` rules. AI agents add parameters when introducing features, then rewrite the function without removing parameters the new code does not need.",
40986
+ aiSpecific: true
40987
+ },
40988
+ "dead/dead-branch": {
40989
+ recall: 0,
40990
+ fpRate: 0,
40991
+ ratio: 0,
40992
+ precision: 0,
40993
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
40994
+ verdict: "DORMANT",
40995
+ defaultOff: true,
40996
+ _calibrationNote: "v0.18.5b ship \u2014 not in v7 per-rule table. Default-off until v8 calibration data lands. The fourth of 5 planned `dead/*` rules. AI-iteration signature: feature flag toggled to a constant, or wrapper from a previous refactor.",
40997
+ aiSpecific: true
40998
+ },
40999
+ "dead/unreachable": {
41000
+ recall: 0,
41001
+ fpRate: 0,
41002
+ ratio: 0,
41003
+ precision: 0,
41004
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
41005
+ verdict: "DORMANT",
41006
+ defaultOff: true,
41007
+ _calibrationNote: "v0.18.5b ship \u2014 not in v7 per-rule table. Default-off until v8 calibration data lands. The fifth of 5 planned `dead/*` rules. AI-iteration signature: model added an early return for a new error path, then forgot the rest of the function body was still sitting below it.",
41008
+ aiSpecific: true
41009
+ },
40589
41010
  "docs/stale-package-reference": {
40590
41011
  recall: 0,
40591
41012
  fpRate: 0,