slopbrick 0.18.4 → 0.18.7

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");
@@ -34396,6 +34757,9 @@ var expiredCodeExampleRule = createRule({
34396
34757
  const issues = [];
34397
34758
  const source = facts.v2?._source;
34398
34759
  if (!source) return issues;
34760
+ const packages = declaredPackages(context.cwd);
34761
+ const packageName = context.packageName;
34762
+ if (packageName) packages.add(packageName);
34399
34763
  const blocks = extractFencedCodeBlocks(source);
34400
34764
  for (const block of blocks) {
34401
34765
  if (!CODE_LANGS.has(block.lang)) continue;
@@ -34403,7 +34767,7 @@ var expiredCodeExampleRule = createRule({
34403
34767
  const imports = extractImports(block.body);
34404
34768
  for (const imp of imports) {
34405
34769
  const pkgName = stripSubpath(imp);
34406
- if (context.packages.has(pkgName)) continue;
34770
+ if (packages.has(pkgName)) continue;
34407
34771
  issues.push({
34408
34772
  ruleId: "docs/expired-code-example",
34409
34773
  category: "docs",
@@ -34424,6 +34788,7 @@ var expiredCodeExampleRule = createRule({
34424
34788
  var import_node_fs4 = require("fs");
34425
34789
  var import_node_path4 = require("path");
34426
34790
  var RESERVED = /* @__PURE__ */ new Set([
34791
+ // JS reserved words
34427
34792
  "true",
34428
34793
  "false",
34429
34794
  "null",
@@ -34523,7 +34888,472 @@ var RESERVED = /* @__PURE__ */ new Set([
34523
34888
  "next",
34524
34889
  "vue",
34525
34890
  "angular",
34526
- "svelte"
34891
+ "svelte",
34892
+ // Framework / runtime names
34893
+ "html",
34894
+ "astro",
34895
+ "python",
34896
+ "jvm",
34897
+ "kotlin",
34898
+ "swift",
34899
+ "dart",
34900
+ "ruby",
34901
+ "rust",
34902
+ "cpp",
34903
+ "go",
34904
+ "java",
34905
+ "php",
34906
+ "php-html",
34907
+ "csharp",
34908
+ "typescript",
34909
+ "javascript",
34910
+ "jsx",
34911
+ "tsx",
34912
+ "mjs",
34913
+ "cjs",
34914
+ "esnext",
34915
+ "es6",
34916
+ "es2022",
34917
+ "es2023",
34918
+ "esm",
34919
+ "cjs",
34920
+ "umd",
34921
+ "amd",
34922
+ "commonjs",
34923
+ "require",
34924
+ "module",
34925
+ "exports",
34926
+ "define",
34927
+ "global",
34928
+ "window",
34929
+ "document",
34930
+ "process",
34931
+ "console",
34932
+ "buffer",
34933
+ "stream",
34934
+ "fetch",
34935
+ "axios",
34936
+ "express",
34937
+ "fastify",
34938
+ "koa",
34939
+ "hapi",
34940
+ "nextjs",
34941
+ "nuxt",
34942
+ "remix",
34943
+ "gatsby",
34944
+ "sveltekit",
34945
+ "solid",
34946
+ "preact",
34947
+ "qwik",
34948
+ "lit",
34949
+ "stencil",
34950
+ "marko",
34951
+ "alpine",
34952
+ "stimulus",
34953
+ "turbo",
34954
+ "hotwire",
34955
+ // Models / providers
34956
+ "gpt",
34957
+ "claude",
34958
+ "gpt-3",
34959
+ "gpt-3.5",
34960
+ "gpt-4",
34961
+ "gpt-oss",
34962
+ "haiku",
34963
+ "sonnet",
34964
+ "opus",
34965
+ "aider",
34966
+ "tabby",
34967
+ "copilot",
34968
+ "cursor",
34969
+ "windsurf",
34970
+ "devin",
34971
+ "claude-code",
34972
+ // LLM-detection lingo
34973
+ "heuristic",
34974
+ "heuristics",
34975
+ "calibrate",
34976
+ "calibration",
34977
+ "calibrator",
34978
+ "corpus",
34979
+ "baseline",
34980
+ "baselines",
34981
+ "corpus-baselines",
34982
+ "lift",
34983
+ "recall",
34984
+ "precision",
34985
+ "fpRate",
34986
+ "ratio",
34987
+ "verdict",
34988
+ "USEFUL",
34989
+ "NOISY",
34990
+ "INVERTED",
34991
+ "HYGIENE",
34992
+ "DORMANT",
34993
+ "OK",
34994
+ "aiSpecific",
34995
+ "defaultOff",
34996
+ // Common slop-audit verbs/nouns
34997
+ "commit",
34998
+ "push",
34999
+ "reset",
35000
+ "rebase",
35001
+ "merge",
35002
+ "cherry-pick",
35003
+ "revert",
35004
+ "scan",
35005
+ "parse",
35006
+ "build",
35007
+ "test",
35008
+ "lint",
35009
+ "format",
35010
+ "check",
35011
+ "audit",
35012
+ "fix",
35013
+ "patch",
35014
+ "diff",
35015
+ "pr",
35016
+ "ci",
35017
+ "cd",
35018
+ "gh",
35019
+ "npm",
35020
+ "npx",
35021
+ "pnpm",
35022
+ "yaml",
35023
+ "json",
35024
+ "toml",
35025
+ "csv",
35026
+ "md",
35027
+ "mdx",
35028
+ "sh",
35029
+ "bash",
35030
+ "zsh",
35031
+ "fish",
35032
+ "ascii",
35033
+ "utf8",
35034
+ "utf-8",
35035
+ "base64",
35036
+ "hex",
35037
+ "binary",
35038
+ "text",
35039
+ // Common design / ui terms
35040
+ "flex",
35041
+ "grid",
35042
+ "auto",
35043
+ "min",
35044
+ "max",
35045
+ "fill",
35046
+ "stretch",
35047
+ "wrap",
35048
+ "nowrap",
35049
+ "inline",
35050
+ "block",
35051
+ "hidden",
35052
+ "visible",
35053
+ "static",
35054
+ "fixed",
35055
+ "absolute",
35056
+ "relative",
35057
+ "sticky",
35058
+ "pointer",
35059
+ "cursor",
35060
+ "focus",
35061
+ "hover",
35062
+ "active",
35063
+ "disabled",
35064
+ "readonly",
35065
+ "primary",
35066
+ "secondary",
35067
+ "tertiary",
35068
+ "success",
35069
+ "warning",
35070
+ "danger",
35071
+ "info",
35072
+ "muted",
35073
+ "sm",
35074
+ "md",
35075
+ "lg",
35076
+ "xl",
35077
+ "xxl",
35078
+ "xs",
35079
+ "2xl",
35080
+ "3xl",
35081
+ "4xl",
35082
+ // Math / types
35083
+ "array",
35084
+ "map",
35085
+ "set",
35086
+ "weakmap",
35087
+ "weakset",
35088
+ "object",
35089
+ "string",
35090
+ "number",
35091
+ "boolean",
35092
+ "bigint",
35093
+ "symbol",
35094
+ "null",
35095
+ "undefined",
35096
+ "any",
35097
+ "unknown",
35098
+ "never",
35099
+ "void",
35100
+ "readonly",
35101
+ "private",
35102
+ "public",
35103
+ "protected",
35104
+ "static",
35105
+ "abstract",
35106
+ "async",
35107
+ "generator",
35108
+ "iterator",
35109
+ "iterable",
35110
+ "promise",
35111
+ "observable",
35112
+ // Auth / domain
35113
+ "admin",
35114
+ "user",
35115
+ "guest",
35116
+ "anonymous",
35117
+ "authenticated",
35118
+ "unauthenticated",
35119
+ "jwt",
35120
+ "oauth",
35121
+ "oidc",
35122
+ "saml",
35123
+ "csrf",
35124
+ "xss",
35125
+ "sql",
35126
+ "nosql",
35127
+ "orm",
35128
+ "prisma",
35129
+ "drizzle",
35130
+ "sequelize",
35131
+ "mongoose",
35132
+ "redis",
35133
+ "postgres",
35134
+ "mysql",
35135
+ "sqlite",
35136
+ "kafka",
35137
+ "rabbitmq",
35138
+ "graphql",
35139
+ "rest",
35140
+ "grpc",
35141
+ "websocket",
35142
+ // slop-audit specific
35143
+ "slopbrick",
35144
+ "usebrick",
35145
+ "deadcode",
35146
+ "unused",
35147
+ "orphan",
35148
+ "zombie",
35149
+ "blocker",
35150
+ "warning",
35151
+ "info",
35152
+ "error",
35153
+ "verbose",
35154
+ "debug",
35155
+ "silly",
35156
+ "p50",
35157
+ "p90",
35158
+ "p95",
35159
+ "p99",
35160
+ "min",
35161
+ "max",
35162
+ "avg",
35163
+ "mean",
35164
+ "median",
35165
+ "ratchet",
35166
+ "tier",
35167
+ "composite",
35168
+ "fitness",
35169
+ "fpr",
35170
+ "tpr",
35171
+ "roc",
35172
+ "should",
35173
+ "could",
35174
+ "would",
35175
+ "might",
35176
+ "must",
35177
+ "shall",
35178
+ "may",
35179
+ "can",
35180
+ "todo",
35181
+ "fixme",
35182
+ "xxx",
35183
+ "hack",
35184
+ "note",
35185
+ "warning",
35186
+ "attention",
35187
+ "h1",
35188
+ "h2",
35189
+ "h3",
35190
+ "h4",
35191
+ "h5",
35192
+ "h6",
35193
+ "strong",
35194
+ "em",
35195
+ "b",
35196
+ "i",
35197
+ "u",
35198
+ "true",
35199
+ "false",
35200
+ "yes",
35201
+ "no",
35202
+ "on",
35203
+ "off",
35204
+ "enable",
35205
+ "disable",
35206
+ "ltr",
35207
+ "rtl",
35208
+ "auto",
35209
+ "start",
35210
+ "end",
35211
+ "center",
35212
+ "baseline",
35213
+ "stretch",
35214
+ "rounded",
35215
+ "sharp",
35216
+ "outline",
35217
+ "ghost",
35218
+ "link",
35219
+ "filled",
35220
+ "row",
35221
+ "col",
35222
+ "gap",
35223
+ "pad",
35224
+ "margin",
35225
+ "padding",
35226
+ "border",
35227
+ "shadow",
35228
+ "transparent",
35229
+ "currentcolor",
35230
+ "inherit",
35231
+ "initial",
35232
+ "unset",
35233
+ "revert",
35234
+ "hover",
35235
+ "focus",
35236
+ "active",
35237
+ "disabled",
35238
+ "checked",
35239
+ "indeterminate",
35240
+ "open",
35241
+ "close",
35242
+ "expanded",
35243
+ "collapsed",
35244
+ "selected",
35245
+ "pressed",
35246
+ // Web/CSS
35247
+ "div",
35248
+ "span",
35249
+ "p",
35250
+ "a",
35251
+ "img",
35252
+ "ul",
35253
+ "ol",
35254
+ "li",
35255
+ "table",
35256
+ "tr",
35257
+ "td",
35258
+ "th",
35259
+ "thead",
35260
+ "tbody",
35261
+ "tfoot",
35262
+ "caption",
35263
+ "figure",
35264
+ "figcaption",
35265
+ "main",
35266
+ "section",
35267
+ "article",
35268
+ "aside",
35269
+ "header",
35270
+ "footer",
35271
+ "nav",
35272
+ "form",
35273
+ "input",
35274
+ "button",
35275
+ "select",
35276
+ "option",
35277
+ "textarea",
35278
+ "label",
35279
+ "fieldset",
35280
+ "legend",
35281
+ "details",
35282
+ "summary",
35283
+ "dialog",
35284
+ "menu",
35285
+ "menuitem",
35286
+ "template",
35287
+ "slot",
35288
+ "picture",
35289
+ "source",
35290
+ "track",
35291
+ "video",
35292
+ "audio",
35293
+ "canvas",
35294
+ "svg",
35295
+ "iframe",
35296
+ "embed",
35297
+ "object",
35298
+ "portal",
35299
+ // Common business terms
35300
+ "api",
35301
+ "cli",
35302
+ "ui",
35303
+ "ux",
35304
+ "sdk",
35305
+ "ide",
35306
+ "cli",
35307
+ "docs",
35308
+ "doc",
35309
+ "blog",
35310
+ "post",
35311
+ "page",
35312
+ "view",
35313
+ "tab",
35314
+ "panel",
35315
+ "card",
35316
+ "list",
35317
+ "grid",
35318
+ "form",
35319
+ "modal",
35320
+ "menu",
35321
+ "button",
35322
+ "icon",
35323
+ "avatar",
35324
+ "badge",
35325
+ "chip",
35326
+ "tooltip",
35327
+ "popover",
35328
+ "dropdown",
35329
+ "banner",
35330
+ "alert",
35331
+ "toast",
35332
+ "notification",
35333
+ "drawer",
35334
+ "sidebar",
35335
+ "navbar",
35336
+ "header",
35337
+ "footer",
35338
+ "hero",
35339
+ "cta",
35340
+ "cta-primary",
35341
+ "cta-secondary",
35342
+ "pricing",
35343
+ "price",
35344
+ "cost",
35345
+ "rate",
35346
+ "percent",
35347
+ "pct",
35348
+ "count",
35349
+ "total",
35350
+ "small",
35351
+ "medium",
35352
+ "large",
35353
+ "xl",
35354
+ "xxl",
35355
+ "tiny",
35356
+ "huge"
34527
35357
  ]);
34528
35358
  var SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
34529
35359
  var SOURCE_ROOTS = ["src", "lib", "app", "components"];
@@ -34561,7 +35391,12 @@ function collectExports(cwd) {
34561
35391
  /\bexport\s+class\s+([A-Za-z_$][\w$]*)/g,
34562
35392
  /\bexport\s+interface\s+([A-Za-z_$][\w$]*)/g,
34563
35393
  /\bexport\s+type\s+([A-Za-z_$][\w$]*)/g,
34564
- /\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g
35394
+ /\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g,
35395
+ // v0.18.6: also collect field names from `export interface` and
35396
+ // `export type` declarations. Without this, fields like
35397
+ // `crossFileDrift`, `aiQuality`, `engineeringHygiene` are
35398
+ // flagged as stale even though they're valid type fields.
35399
+ /^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\s*[?:]/gm
34565
35400
  ]) {
34566
35401
  let m;
34567
35402
  while ((m = re.exec(source)) !== null) {
@@ -34572,6 +35407,21 @@ function collectExports(cwd) {
34572
35407
  }
34573
35408
  return out;
34574
35409
  }
35410
+ function looksLikeProseLabel(inside) {
35411
+ const trimmed = inside.trim();
35412
+ if (trimmed.length === 0) return false;
35413
+ if (trimmed.startsWith("`") || trimmed.startsWith("/")) return true;
35414
+ if (trimmed.includes("`")) return true;
35415
+ if (/\d+\s+[a-z]/i.test(trimmed)) return true;
35416
+ const parts = trimmed.split(",").map((s) => s.trim());
35417
+ if (parts.length >= 3) {
35418
+ const allNumeric = parts.every((p) => /^\d+\.?\d*$/.test(p));
35419
+ if (!allNumeric) return true;
35420
+ }
35421
+ if (trimmed.length > 40 && !trimmed.includes(",")) return true;
35422
+ if (trimmed.includes("\u2014") || trimmed.includes("\u2013")) return true;
35423
+ return false;
35424
+ }
34575
35425
  var staleFunctionReferenceRule = createRule({
34576
35426
  id: "docs/stale-function-reference",
34577
35427
  category: "docs",
@@ -34591,8 +35441,39 @@ var staleFunctionReferenceRule = createRule({
34591
35441
  if (text.length < 3) continue;
34592
35442
  if (RESERVED.has(text.toLowerCase())) continue;
34593
35443
  if (context.exports.has(text)) continue;
34594
- const end = Math.min(source.length, span.index + text.length + 50);
34595
- if (!/\(/.test(source.slice(span.index, end))) continue;
35444
+ const lineEnd = source.indexOf("\n", span.index);
35445
+ const restOfLine = source.slice(
35446
+ span.index,
35447
+ lineEnd === -1 ? source.length : lineEnd
35448
+ );
35449
+ const closeTick = restOfLine.indexOf("`", 1);
35450
+ if (closeTick === -1) continue;
35451
+ const afterTick = restOfLine.slice(closeTick + 1);
35452
+ const directCall = /^\s*\(/.test(afterTick);
35453
+ let identifierRepeats = false;
35454
+ if (!directCall) {
35455
+ const afterSpan = restOfLine.slice(closeTick + 1);
35456
+ const needle = text + "(";
35457
+ identifierRepeats = afterSpan.indexOf(needle) !== -1;
35458
+ }
35459
+ if (!directCall && !identifierRepeats) continue;
35460
+ const beforeTickIdx = span.index - 1;
35461
+ const beforeChar = beforeTickIdx >= 0 ? source[beforeTickIdx] : "";
35462
+ if (beforeChar === "." || beforeChar === "|") continue;
35463
+ const parenStart = restOfLine.indexOf("(", closeTick);
35464
+ const parenEnd = restOfLine.indexOf(")", parenStart);
35465
+ if (parenStart !== -1 && parenEnd !== -1) {
35466
+ const inside = restOfLine.slice(parenStart + 1, parenEnd);
35467
+ const trimmed = inside.trim();
35468
+ if (looksLikeProseLabel(inside)) continue;
35469
+ const looksLikeTypeAnnotation = !inside.includes(":") && (/\b(string|number|boolean|null|undefined|object|array|required|optional|categorical|direct|n\/a|\bmapped\b|0[\-–][0-9]+|v[0-9]|higher is better|lower is better|added in|deprecated|pr-[0-9])\b/i.test(
35470
+ inside
35471
+ ) || // Short single-word label (≤ 24 chars, no `,`,
35472
+ // doesn't look like a function arg). Real function
35473
+ // calls are usually longer or contain commas.
35474
+ trimmed.length > 0 && trimmed.length <= 24 && !trimmed.includes(",") && /[a-zA-Z]/.test(trimmed));
35475
+ if (looksLikeTypeAnnotation) continue;
35476
+ }
34596
35477
  issues.push({
34597
35478
  ruleId: "docs/stale-function-reference",
34598
35479
  category: "docs",
@@ -34664,7 +35545,33 @@ var ENGLISH_WORD_DENYLIST = /* @__PURE__ */ new Set([
34664
35545
  "jsx",
34665
35546
  "ok",
34666
35547
  "no",
34667
- "yes"
35548
+ "yes",
35549
+ // v0.18.6: common English adjectives / adverbs that frequently
35550
+ // appear in backticked prose but are not package names.
35551
+ "aspirational",
35552
+ "concrete",
35553
+ "abstract",
35554
+ "inline",
35555
+ "exposed",
35556
+ "deprecated",
35557
+ "experimental",
35558
+ "stable",
35559
+ "beta",
35560
+ "alpha",
35561
+ "wip",
35562
+ "draft",
35563
+ "final",
35564
+ "shim",
35565
+ "polyfill",
35566
+ "stub",
35567
+ "mock",
35568
+ "fake",
35569
+ "real",
35570
+ "false",
35571
+ "true",
35572
+ "optional",
35573
+ "required",
35574
+ "default"
34668
35575
  ]);
34669
35576
  var stalePackageReferenceRule = createRule({
34670
35577
  id: "docs/stale-package-reference",
@@ -34826,7 +35733,9 @@ var brokenLinkRule = createRule({
34826
35733
  if (target.startsWith("#")) continue;
34827
35734
  if (target.startsWith("//")) continue;
34828
35735
  if (target.startsWith("/")) continue;
34829
- const resolved = (0, import_node_path6.join)(docDir, target);
35736
+ const filePart = target.split("#")[0] ?? target;
35737
+ if (filePart === "") continue;
35738
+ const resolved = (0, import_node_path6.join)(docDir, filePart);
34830
35739
  if ((0, import_node_fs6.existsSync)(resolved)) continue;
34831
35740
  issues.push({
34832
35741
  ruleId: "docs/broken-link",
@@ -39598,6 +40507,11 @@ var builtinRules = [
39598
40507
  missingNotNullRule,
39599
40508
  namingInconsistencyRule,
39600
40509
  sqlConcatRule,
40510
+ deadBranchRule,
40511
+ unreachableRule,
40512
+ unusedImportRule,
40513
+ unusedLocalRule,
40514
+ unusedParameterRule,
39601
40515
  brokenLinkRule,
39602
40516
  expiredCodeExampleRule,
39603
40517
  staleFunctionReferenceRule,
@@ -40605,6 +41519,61 @@ var signal_strength_default = {
40605
41519
  _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
41520
  aiSpecific: true
40607
41521
  },
41522
+ "dead/unused-import": {
41523
+ recall: 0,
41524
+ fpRate: 0,
41525
+ ratio: 0,
41526
+ precision: 0,
41527
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
41528
+ verdict: "DORMANT",
41529
+ defaultOff: true,
41530
+ _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.",
41531
+ aiSpecific: true
41532
+ },
41533
+ "dead/unused-local": {
41534
+ recall: 0,
41535
+ fpRate: 0,
41536
+ ratio: 0,
41537
+ precision: 0,
41538
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
41539
+ verdict: "DORMANT",
41540
+ defaultOff: true,
41541
+ _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).",
41542
+ aiSpecific: true
41543
+ },
41544
+ "dead/unused-parameter": {
41545
+ recall: 0,
41546
+ fpRate: 0,
41547
+ ratio: 0,
41548
+ precision: 0,
41549
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
41550
+ verdict: "DORMANT",
41551
+ defaultOff: true,
41552
+ _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.",
41553
+ aiSpecific: true
41554
+ },
41555
+ "dead/dead-branch": {
41556
+ recall: 0,
41557
+ fpRate: 0,
41558
+ ratio: 0,
41559
+ precision: 0,
41560
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
41561
+ verdict: "DORMANT",
41562
+ defaultOff: true,
41563
+ _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.",
41564
+ aiSpecific: true
41565
+ },
41566
+ "dead/unreachable": {
41567
+ recall: 0,
41568
+ fpRate: 0,
41569
+ ratio: 0,
41570
+ precision: 0,
41571
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
41572
+ verdict: "DORMANT",
41573
+ defaultOff: true,
41574
+ _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.",
41575
+ aiSpecific: true
41576
+ },
40608
41577
  "docs/stale-package-reference": {
40609
41578
  recall: 0,
40610
41579
  fpRate: 0,