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.
@@ -6335,6 +6335,78 @@ function extractStateBinding(node, lineOffsets) {
6335
6335
  setterReferenced: false
6336
6336
  };
6337
6337
  }
6338
+ function findUnreachableStatements(ast, source, lineOffsets) {
6339
+ const out = [];
6340
+ function isTerminator(node) {
6341
+ if (node.type === "ReturnStatement") return "return";
6342
+ if (node.type === "ThrowStatement") return "throw";
6343
+ if (node.type === "BreakStatement") return "break";
6344
+ if (node.type === "ContinueStatement") return "continue";
6345
+ return null;
6346
+ }
6347
+ function snippetFor(node, src) {
6348
+ const span = node.span;
6349
+ if (!span || typeof span.start !== "number" || typeof span.end !== "number") {
6350
+ return "<unreachable>";
6351
+ }
6352
+ const text = src.slice(span.start, Math.min(span.end, span.start + 60));
6353
+ return text.replace(/\s+/g, " ").trim() || "<unreachable>";
6354
+ }
6355
+ function lineColumn(offset) {
6356
+ let lo = 0;
6357
+ let hi = lineOffsets.length - 1;
6358
+ while (lo < hi) {
6359
+ const mid = lo + hi + 1 >>> 1;
6360
+ const midVal = lineOffsets[mid];
6361
+ const loVal = lineOffsets[lo];
6362
+ if (midVal === void 0) break;
6363
+ if (midVal <= offset) lo = mid;
6364
+ else hi = mid - 1;
6365
+ void loVal;
6366
+ }
6367
+ const baseOffset = lineOffsets[lo] ?? 0;
6368
+ return { line: lo + 1, column: offset - baseOffset };
6369
+ }
6370
+ function visitBody(body) {
6371
+ if (!Array.isArray(body)) return;
6372
+ let lastTerminator = null;
6373
+ for (const stmt of body) {
6374
+ if (!isObject(stmt)) continue;
6375
+ if (lastTerminator && stmt.type !== "EmptyStatement") {
6376
+ const span = stmt.span;
6377
+ const offset = typeof span?.start === "number" ? span.start : 0;
6378
+ const { line, column } = lineColumn(offset);
6379
+ out.push({
6380
+ terminator: lastTerminator,
6381
+ line,
6382
+ column,
6383
+ snippet: snippetFor(stmt, source)
6384
+ });
6385
+ }
6386
+ const t = isTerminator(stmt);
6387
+ if (t) lastTerminator = t;
6388
+ }
6389
+ }
6390
+ function walk3(node) {
6391
+ if (!isObject(node)) return;
6392
+ if (node.type === "BlockStatement") {
6393
+ visitBody(node.stmts);
6394
+ } else if (node.type === "Module") {
6395
+ visitBody(node.body);
6396
+ }
6397
+ for (const key of Object.keys(node)) {
6398
+ if (key === "parent" || key === "span" || key === "ctxt") continue;
6399
+ const child = node[key];
6400
+ if (Array.isArray(child)) {
6401
+ for (const c of child) walk3(c);
6402
+ } else {
6403
+ walk3(child);
6404
+ }
6405
+ }
6406
+ }
6407
+ walk3(ast);
6408
+ return out;
6409
+ }
6338
6410
 
6339
6411
  // src/engine/visitors/dispatch.ts
6340
6412
  function nearestComponent(vctx) {
@@ -6411,15 +6483,42 @@ function handleImportDeclaration(node, _parent, _path, vctx) {
6411
6483
  if (specifier.type === "ImportDefaultSpecifier" || specifier.type === "ImportNamespaceSpecifier") {
6412
6484
  const local = specifier.local;
6413
6485
  if (isObject(local) && local.type === "Identifier" && typeof local.value === "string") {
6414
- importedNames.push(local.value);
6486
+ const name = local.value;
6487
+ importedNames.push(name);
6488
+ vctx.facts.deadCode.bindings.push({
6489
+ name,
6490
+ kind: specifier.type === "ImportDefaultSpecifier" ? "import-default" : "import-namespace",
6491
+ line,
6492
+ column,
6493
+ source,
6494
+ isReferenced: false
6495
+ });
6415
6496
  }
6416
6497
  } else if (specifier.type === "ImportSpecifier") {
6417
6498
  const imported = specifier.imported;
6418
6499
  const local = specifier.local;
6419
6500
  if (isObject(imported) && typeof imported.value === "string" && imported.value.length > 0) {
6420
- importedNames.push(imported.value);
6501
+ const name = imported.value;
6502
+ importedNames.push(name);
6503
+ vctx.facts.deadCode.bindings.push({
6504
+ name,
6505
+ kind: "import-specifier",
6506
+ line,
6507
+ column,
6508
+ source,
6509
+ isReferenced: false
6510
+ });
6421
6511
  } else if (isObject(local) && local.type === "Identifier" && typeof local.value === "string") {
6422
- importedNames.push(local.value);
6512
+ const name = local.value;
6513
+ importedNames.push(name);
6514
+ vctx.facts.deadCode.bindings.push({
6515
+ name,
6516
+ kind: "import-specifier",
6517
+ line,
6518
+ column,
6519
+ source,
6520
+ isReferenced: false
6521
+ });
6423
6522
  }
6424
6523
  }
6425
6524
  }
@@ -6509,6 +6608,13 @@ function isBindingSite(node, parent) {
6509
6608
  if (params.some((param) => containsNode(param, node))) return true;
6510
6609
  }
6511
6610
  }
6611
+ if (parent.type === "ImportSpecifier" || parent.type === "ImportDefaultSpecifier" || parent.type === "ImportNamespaceSpecifier") {
6612
+ if (parent.type === "ImportSpecifier") {
6613
+ if (parent.imported === node || parent.local === node) return true;
6614
+ } else {
6615
+ if (parent.local === node) return true;
6616
+ }
6617
+ }
6512
6618
  return false;
6513
6619
  }
6514
6620
  function isNonComputedMemberProperty(node, parent) {
@@ -6575,6 +6681,9 @@ function handleIdentifier(node, parent, path, vctx) {
6575
6681
  if (typeof node.value === "string" && !isBindingSite(node, parent) && !isNonComputedMemberProperty(node, parent)) {
6576
6682
  markStateReference(node.value, vctx);
6577
6683
  trackPropUsage(node, parent, path, vctx);
6684
+ vctx.facts.referencedNames.add(node.value);
6685
+ const top = vctx.ctx.stack[vctx.ctx.stack.length - 1];
6686
+ if (top) top.references.add(node.value);
6578
6687
  }
6579
6688
  return false;
6580
6689
  }
@@ -6715,6 +6824,20 @@ function handleVariableDeclarator(node, _parent, path, vctx) {
6715
6824
  frame.bindings.add(bindingName);
6716
6825
  }
6717
6826
  }
6827
+ if (bindingNames.length > 0) {
6828
+ const parent = node.parent;
6829
+ const kind = isObject(parent) && parent.type === "VariableDeclaration" ? String(parent.kind ?? "var") : "var";
6830
+ const { line, column } = positionFrom(id, vctx.lineOffsets);
6831
+ for (const bindingName of bindingNames) {
6832
+ vctx.facts.deadCode.bindings.push({
6833
+ name: bindingName,
6834
+ kind,
6835
+ line,
6836
+ column,
6837
+ isReferenced: false
6838
+ });
6839
+ }
6840
+ }
6718
6841
  if (isUseStateDeclarator(node)) {
6719
6842
  const binding = extractStateBinding(node, vctx.lineOffsets);
6720
6843
  if (binding) {
@@ -6734,6 +6857,36 @@ function handleVariableDeclarator(node, _parent, path, vctx) {
6734
6857
  }
6735
6858
  return true;
6736
6859
  }
6860
+ function handleIfStatement(node, _parent, _path, vctx) {
6861
+ if (!isObject(node)) return false;
6862
+ const test = node.test;
6863
+ if (!isObject(test)) return false;
6864
+ if (test.type === "BooleanLiteral" && typeof test.value === "boolean") {
6865
+ const { line, column } = positionFrom(test, vctx.lineOffsets);
6866
+ vctx.facts.deadCode.constantConditions.push({
6867
+ kind: test.value ? "if-true" : "if-false",
6868
+ condition: String(test.value),
6869
+ line,
6870
+ column
6871
+ });
6872
+ }
6873
+ return false;
6874
+ }
6875
+ function handleWhileStatement(node, _parent, _path, vctx) {
6876
+ if (!isObject(node)) return false;
6877
+ const test = node.test;
6878
+ if (!isObject(test)) return false;
6879
+ if (test.type === "BooleanLiteral" && typeof test.value === "boolean") {
6880
+ const { line, column } = positionFrom(test, vctx.lineOffsets);
6881
+ vctx.facts.deadCode.constantConditions.push({
6882
+ kind: test.value ? "while-true" : "while-false",
6883
+ condition: String(test.value),
6884
+ line,
6885
+ column
6886
+ });
6887
+ }
6888
+ return false;
6889
+ }
6737
6890
  var HANDLERS = {
6738
6891
  ExpressionStatement: handleExpressionStatement,
6739
6892
  ImportDeclaration: handleImportDeclaration,
@@ -6743,7 +6896,9 @@ var HANDLERS = {
6743
6896
  MemberExpression: handleMemberExpression,
6744
6897
  JSXAttribute: handleJSXAttribute,
6745
6898
  JSXOpeningElement: handleJSXOpeningElement,
6746
- VariableDeclarator: handleVariableDeclarator
6899
+ VariableDeclarator: handleVariableDeclarator,
6900
+ IfStatement: handleIfStatement,
6901
+ WhileStatement: handleWhileStatement
6747
6902
  };
6748
6903
  function dispatchNode(node, parent, path, vctx) {
6749
6904
  if (!isObject(node)) return false;
@@ -6908,6 +7063,17 @@ function buildV2Facts(facts, source, ext, framework, config, templateClassNames
6908
7063
  },
6909
7064
  logic: buildLogicBlock(facts),
6910
7065
  designTokens: scanDesignTokens(facts.staticClassNames),
7066
+ // dead-code detector. Copy the internal accumulator
7067
+ // into the v2 shape, marking each binding as referenced
7068
+ // iff the file-level referenced-name set contains its name.
7069
+ deadCode: {
7070
+ bindings: facts.deadCode.bindings.map((b) => ({
7071
+ ...b,
7072
+ isReferenced: facts.referencedNames.has(b.name)
7073
+ })),
7074
+ constantConditions: facts.deadCode.constantConditions,
7075
+ unreachableStatements: facts.deadCode.unreachableStatements
7076
+ },
6911
7077
  componentSizes: facts.componentSizes.map((cs) => ({
6912
7078
  name: cs.name,
6913
7079
  lineCount: cs.lineCount,
@@ -6975,7 +7141,17 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
6975
7141
  componentSizes: [],
6976
7142
  astroComponents: [],
6977
7143
  fetchCalls: [],
6978
- optimisticUpdates: []
7144
+ optimisticUpdates: [],
7145
+ // dead-code detector. The visitor's identifier walk + import/
7146
+ // branch/return handlers populate these. The v2 builder at the
7147
+ // bottom of extractFacts() reads them and produces
7148
+ // `facts.v2.deadCode`.
7149
+ deadCode: {
7150
+ bindings: [],
7151
+ constantConditions: [],
7152
+ unreachableStatements: []
7153
+ },
7154
+ referencedNames: /* @__PURE__ */ new Set()
6979
7155
  };
6980
7156
  const ctx = {
6981
7157
  stack: [],
@@ -7062,6 +7238,16 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
7062
7238
  propBindingSet.add(bindingName);
7063
7239
  }
7064
7240
  }
7241
+ const { line: pLine, column: pCol } = positionFrom(param, lineOffsets);
7242
+ for (const bindingName of collectBindingNames2(param)) {
7243
+ facts.deadCode.bindings.push({
7244
+ name: bindingName,
7245
+ kind: "parameter",
7246
+ line: pLine,
7247
+ column: pCol,
7248
+ isReferenced: false
7249
+ });
7250
+ }
7065
7251
  }
7066
7252
  }
7067
7253
  ctx.stack.push({
@@ -7077,6 +7263,12 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
7077
7263
  propUsages: [],
7078
7264
  isComponent,
7079
7265
  bindings,
7266
+ // dead-code detector: per-frame referenced-name set.
7267
+ // Identifiers encountered inside the frame are added to this
7268
+ // set; the deadCode builder unions it with parent frames at
7269
+ // pop time so a binding is considered used if any reachable
7270
+ // scope references it.
7271
+ references: /* @__PURE__ */ new Set(),
7080
7272
  propBindingSet,
7081
7273
  propUsageSet: /* @__PURE__ */ new Set(),
7082
7274
  node
@@ -7172,6 +7364,11 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
7172
7364
  mergeTemplateClassNames(filePath, source, facts, templateClassNames);
7173
7365
  const { ext } = splitFilePath(filePath);
7174
7366
  facts._source = source;
7367
+ facts.deadCode.unreachableStatements = findUnreachableStatements(
7368
+ ast,
7369
+ source,
7370
+ lineOffsets
7371
+ );
7175
7372
  const v2 = buildV2Facts(facts, source, ext, framework, config, templateClassNames);
7176
7373
  return envelopeScanFacts(filePath, v2);
7177
7374
  }
@@ -34306,6 +34503,170 @@ var sqlConcatRule = createRule({
34306
34503
  }
34307
34504
  });
34308
34505
 
34506
+ // src/rules/dead/dead-branch.ts
34507
+ var deadBranchRule = createRule({
34508
+ id: "dead/dead-branch",
34509
+ category: "logic",
34510
+ severity: "medium",
34511
+ aiSpecific: true,
34512
+ description: "Literal boolean condition makes one branch statically dead",
34513
+ create(_context) {
34514
+ return {};
34515
+ },
34516
+ analyze(_context, facts) {
34517
+ const issues = [];
34518
+ if (!facts.v2) return issues;
34519
+ for (const cond of facts.v2.deadCode.constantConditions) {
34520
+ const isWhileTrue = cond.kind === "while-true";
34521
+ issues.push({
34522
+ ruleId: "dead/dead-branch",
34523
+ category: "logic",
34524
+ severity: isWhileTrue ? "low" : "medium",
34525
+ aiSpecific: true,
34526
+ message: isWhileTrue ? `Infinite loop with literal condition (${cond.kind})` : `Dead branch: condition is always ${cond.condition}`,
34527
+ line: cond.line,
34528
+ column: cond.column,
34529
+ 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.`
34530
+ });
34531
+ }
34532
+ return issues;
34533
+ }
34534
+ });
34535
+
34536
+ // src/rules/dead/unreachable.ts
34537
+ var unreachableRule = createRule({
34538
+ id: "dead/unreachable",
34539
+ category: "logic",
34540
+ severity: "high",
34541
+ aiSpecific: true,
34542
+ description: "Statement is unreachable after an unconditional return/throw/break/continue",
34543
+ create(_context) {
34544
+ return {};
34545
+ },
34546
+ analyze(_context, facts) {
34547
+ const issues = [];
34548
+ if (!facts.v2) return issues;
34549
+ for (const u of facts.v2.deadCode.unreachableStatements) {
34550
+ if (u.snippet === "<unreachable>") continue;
34551
+ issues.push({
34552
+ ruleId: "dead/unreachable",
34553
+ category: "logic",
34554
+ severity: "high",
34555
+ aiSpecific: true,
34556
+ message: `Unreachable after ${u.terminator}: ${u.snippet}`,
34557
+ line: u.line,
34558
+ column: u.column,
34559
+ 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.`
34560
+ });
34561
+ }
34562
+ return issues;
34563
+ }
34564
+ });
34565
+
34566
+ // src/rules/dead/unused-import.ts
34567
+ var unusedImportRule = createRule({
34568
+ id: "dead/unused-import",
34569
+ category: "logic",
34570
+ severity: "low",
34571
+ aiSpecific: true,
34572
+ description: "ES module import is never referenced in the file",
34573
+ create(_context) {
34574
+ return {};
34575
+ },
34576
+ analyze(_context, facts) {
34577
+ const issues = [];
34578
+ if (!facts.v2) return issues;
34579
+ for (const binding of facts.v2.deadCode.bindings) {
34580
+ if (binding.kind !== "import-specifier" && binding.kind !== "import-default" && binding.kind !== "import-namespace") {
34581
+ continue;
34582
+ }
34583
+ if (binding.isReferenced) continue;
34584
+ if (!binding.name) continue;
34585
+ const source = binding.source ? ` from '${binding.source}'` : "";
34586
+ issues.push({
34587
+ ruleId: "dead/unused-import",
34588
+ category: "logic",
34589
+ severity: "low",
34590
+ aiSpecific: true,
34591
+ message: `Unused import: '${binding.name}'${source}`,
34592
+ line: binding.line,
34593
+ column: binding.column,
34594
+ 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.`
34595
+ });
34596
+ }
34597
+ return issues;
34598
+ }
34599
+ });
34600
+
34601
+ // src/rules/dead/unused-local.ts
34602
+ var SKIP_NAMES = /* @__PURE__ */ new Set(["React", "_"]);
34603
+ var unusedLocalRule = createRule({
34604
+ id: "dead/unused-local",
34605
+ category: "logic",
34606
+ severity: "low",
34607
+ aiSpecific: true,
34608
+ description: "Variable is declared but never read after declaration",
34609
+ create(_context) {
34610
+ return {};
34611
+ },
34612
+ analyze(_context, facts) {
34613
+ const issues = [];
34614
+ if (!facts.v2) return issues;
34615
+ for (const binding of facts.v2.deadCode.bindings) {
34616
+ 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") {
34617
+ continue;
34618
+ }
34619
+ if (binding.isReferenced) continue;
34620
+ if (SKIP_NAMES.has(binding.name)) continue;
34621
+ if (binding.name.startsWith("_")) continue;
34622
+ issues.push({
34623
+ ruleId: "dead/unused-local",
34624
+ category: "logic",
34625
+ severity: "low",
34626
+ aiSpecific: true,
34627
+ message: `Unused ${binding.kind}: '${binding.name}'`,
34628
+ line: binding.line,
34629
+ column: binding.column,
34630
+ 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.`
34631
+ });
34632
+ }
34633
+ return issues;
34634
+ }
34635
+ });
34636
+
34637
+ // src/rules/dead/unused-parameter.ts
34638
+ var unusedParameterRule = createRule({
34639
+ id: "dead/unused-parameter",
34640
+ category: "logic",
34641
+ severity: "low",
34642
+ aiSpecific: true,
34643
+ description: "Function parameter is declared but never read",
34644
+ create(_context) {
34645
+ return {};
34646
+ },
34647
+ analyze(_context, facts) {
34648
+ const issues = [];
34649
+ if (!facts.v2) return issues;
34650
+ for (const binding of facts.v2.deadCode.bindings) {
34651
+ if (binding.kind !== "parameter") continue;
34652
+ if (binding.isReferenced) continue;
34653
+ if (binding.name.startsWith("_")) continue;
34654
+ if (binding.name === "props") continue;
34655
+ issues.push({
34656
+ ruleId: "dead/unused-parameter",
34657
+ category: "logic",
34658
+ severity: "low",
34659
+ aiSpecific: true,
34660
+ message: `Unused parameter: '${binding.name}'`,
34661
+ line: binding.line,
34662
+ column: binding.column,
34663
+ 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.`
34664
+ });
34665
+ }
34666
+ return issues;
34667
+ }
34668
+ });
34669
+
34309
34670
  // src/rules/docs/broken-link.ts
34310
34671
  var import_node_fs6 = require("fs");
34311
34672
  var import_node_path6 = require("path");
@@ -39598,6 +39959,11 @@ var builtinRules = [
39598
39959
  missingNotNullRule,
39599
39960
  namingInconsistencyRule,
39600
39961
  sqlConcatRule,
39962
+ deadBranchRule,
39963
+ unreachableRule,
39964
+ unusedImportRule,
39965
+ unusedLocalRule,
39966
+ unusedParameterRule,
39601
39967
  brokenLinkRule,
39602
39968
  expiredCodeExampleRule,
39603
39969
  staleFunctionReferenceRule,
@@ -40605,6 +40971,61 @@ var signal_strength_default = {
40605
40971
  _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.)",
40606
40972
  aiSpecific: true
40607
40973
  },
40974
+ "dead/unused-import": {
40975
+ recall: 0,
40976
+ fpRate: 0,
40977
+ ratio: 0,
40978
+ precision: 0,
40979
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
40980
+ verdict: "DORMANT",
40981
+ defaultOff: true,
40982
+ _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.",
40983
+ aiSpecific: true
40984
+ },
40985
+ "dead/unused-local": {
40986
+ recall: 0,
40987
+ fpRate: 0,
40988
+ ratio: 0,
40989
+ precision: 0,
40990
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
40991
+ verdict: "DORMANT",
40992
+ defaultOff: true,
40993
+ _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).",
40994
+ aiSpecific: true
40995
+ },
40996
+ "dead/unused-parameter": {
40997
+ recall: 0,
40998
+ fpRate: 0,
40999
+ ratio: 0,
41000
+ precision: 0,
41001
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
41002
+ verdict: "DORMANT",
41003
+ defaultOff: true,
41004
+ _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.",
41005
+ aiSpecific: true
41006
+ },
41007
+ "dead/dead-branch": {
41008
+ recall: 0,
41009
+ fpRate: 0,
41010
+ ratio: 0,
41011
+ precision: 0,
41012
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
41013
+ verdict: "DORMANT",
41014
+ defaultOff: true,
41015
+ _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.",
41016
+ aiSpecific: true
41017
+ },
41018
+ "dead/unreachable": {
41019
+ recall: 0,
41020
+ fpRate: 0,
41021
+ ratio: 0,
41022
+ precision: 0,
41023
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
41024
+ verdict: "DORMANT",
41025
+ defaultOff: true,
41026
+ _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.",
41027
+ aiSpecific: true
41028
+ },
40608
41029
  "docs/stale-package-reference": {
40609
41030
  recall: 0,
40610
41031
  fpRate: 0,