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.
package/dist/index.cjs CHANGED
@@ -31,15 +31,41 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  ));
32
32
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
33
 
34
- // src/types.ts
35
- var import_node_module, require2, pkg, VERSION, AI_SECURITY_NUMERIC, REPOSITORY_HEALTH_WEIGHTS;
36
- var init_types = __esm({
37
- "src/types.ts"() {
34
+ // src/types/_header.ts
35
+ var VERSION;
36
+ var init_header = __esm({
37
+ "src/types/_header.ts"() {
38
+ "use strict";
39
+ VERSION = "0.18.7";
40
+ }
41
+ });
42
+
43
+ // src/types/primitives.ts
44
+ var init_primitives = __esm({
45
+ "src/types/primitives.ts"() {
46
+ "use strict";
47
+ }
48
+ });
49
+
50
+ // src/types/scan.ts
51
+ var init_scan = __esm({
52
+ "src/types/scan.ts"() {
53
+ "use strict";
54
+ }
55
+ });
56
+
57
+ // src/types/config.ts
58
+ var init_config = __esm({
59
+ "src/types/config.ts"() {
60
+ "use strict";
61
+ }
62
+ });
63
+
64
+ // src/types/report.ts
65
+ var AI_SECURITY_NUMERIC, REPOSITORY_HEALTH_WEIGHTS;
66
+ var init_report = __esm({
67
+ "src/types/report.ts"() {
38
68
  "use strict";
39
- import_node_module = require("module");
40
- require2 = (0, import_node_module.createRequire)(__importMetaUrl);
41
- pkg = require2("../package.json");
42
- VERSION = pkg.version;
43
69
  AI_SECURITY_NUMERIC = {
44
70
  low: 100,
45
71
  medium: 75,
@@ -59,6 +85,34 @@ var init_types = __esm({
59
85
  }
60
86
  });
61
87
 
88
+ // src/types/project-report.ts
89
+ var init_project_report = __esm({
90
+ "src/types/project-report.ts"() {
91
+ "use strict";
92
+ }
93
+ });
94
+
95
+ // src/types/baseline.ts
96
+ var init_baseline = __esm({
97
+ "src/types/baseline.ts"() {
98
+ "use strict";
99
+ }
100
+ });
101
+
102
+ // src/types/index.ts
103
+ var init_types = __esm({
104
+ "src/types/index.ts"() {
105
+ "use strict";
106
+ init_header();
107
+ init_primitives();
108
+ init_scan();
109
+ init_config();
110
+ init_report();
111
+ init_project_report();
112
+ init_baseline();
113
+ }
114
+ });
115
+
62
116
  // src/config/defaults.ts
63
117
  var DEFAULT_SPACING_SCALE, DEFAULT_RADIUS_SCALE, DEFAULT_RULE_CONFIG, DEFAULT_CONFIG;
64
118
  var init_defaults = __esm({
@@ -310,9 +364,9 @@ function findWorkspacePackages(cwd) {
310
364
  const pkgPath = (0, import_node_path.join)(root, "package.json");
311
365
  if ((0, import_node_fs.existsSync)(pkgPath)) {
312
366
  try {
313
- const pkg2 = JSON.parse((0, import_node_fs.readFileSync)(pkgPath, "utf-8"));
314
- if (Array.isArray(pkg2.workspaces)) {
315
- for (const pattern of pkg2.workspaces) {
367
+ const pkg = JSON.parse((0, import_node_fs.readFileSync)(pkgPath, "utf-8"));
368
+ if (Array.isArray(pkg.workspaces)) {
369
+ for (const pattern of pkg.workspaces) {
316
370
  packages.push(...expandWorkspacePattern(root, pattern));
317
371
  }
318
372
  }
@@ -370,11 +424,11 @@ function detectStylingSolution(cwd) {
370
424
  let deps = {};
371
425
  if ((0, import_node_fs2.existsSync)(pkgPath)) {
372
426
  try {
373
- const pkg2 = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf-8"));
427
+ const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf-8"));
374
428
  deps = {
375
- ...pkg2.dependencies,
376
- ...pkg2.devDependencies,
377
- ...pkg2.peerDependencies
429
+ ...pkg.dependencies,
430
+ ...pkg.devDependencies,
431
+ ...pkg.peerDependencies
378
432
  };
379
433
  } catch {
380
434
  }
@@ -573,11 +627,11 @@ function detectStack(cwd) {
573
627
  return {};
574
628
  }
575
629
  try {
576
- const pkg2 = JSON.parse((0, import_node_fs3.readFileSync)(pkgPath, "utf-8"));
630
+ const pkg = JSON.parse((0, import_node_fs3.readFileSync)(pkgPath, "utf-8"));
577
631
  const deps = {
578
- ...pkg2.dependencies,
579
- ...pkg2.devDependencies,
580
- ...pkg2.peerDependencies
632
+ ...pkg.dependencies,
633
+ ...pkg.devDependencies,
634
+ ...pkg.peerDependencies
581
635
  };
582
636
  const names = Object.keys(deps).map((name) => name.toLowerCase());
583
637
  const result = {};
@@ -26213,13 +26267,13 @@ var init_default_react_stack = __esm({
26213
26267
  for (const m of source.matchAll(IMPORT_LINE_RE)) {
26214
26268
  const spec = m[1];
26215
26269
  if (!spec) continue;
26216
- const pkg2 = spec.startsWith("@/") ? spec : spec.startsWith("@") ? spec.split("/").slice(0, 2).join("/") : spec.split("/")[0];
26217
- if (pkg2) importedPackages.add(pkg2);
26270
+ const pkg = spec.startsWith("@/") ? spec : spec.startsWith("@") ? spec.split("/").slice(0, 2).join("/") : spec.split("/")[0];
26271
+ if (pkg) importedPackages.add(pkg);
26218
26272
  }
26219
26273
  const hits = [];
26220
- for (const pkg2 of importedPackages) {
26221
- if (DEFAULT_STACK_PACKAGES.some((p) => p === pkg2 || p.startsWith(pkg2 + "/"))) {
26222
- hits.push(pkg2);
26274
+ for (const pkg of importedPackages) {
26275
+ if (DEFAULT_STACK_PACKAGES.some((p) => p === pkg || p.startsWith(pkg + "/"))) {
26276
+ hits.push(pkg);
26223
26277
  }
26224
26278
  }
26225
26279
  if (hits.length < MIN_HITS) return [];
@@ -28109,6 +28163,205 @@ var init_sql_concat = __esm({
28109
28163
  }
28110
28164
  });
28111
28165
 
28166
+ // src/rules/dead/dead-branch.ts
28167
+ var deadBranchRule;
28168
+ var init_dead_branch = __esm({
28169
+ "src/rules/dead/dead-branch.ts"() {
28170
+ "use strict";
28171
+ init_rule();
28172
+ deadBranchRule = createRule({
28173
+ id: "dead/dead-branch",
28174
+ category: "logic",
28175
+ severity: "medium",
28176
+ aiSpecific: true,
28177
+ description: "Literal boolean condition makes one branch statically dead",
28178
+ create(_context) {
28179
+ return {};
28180
+ },
28181
+ analyze(_context, facts) {
28182
+ const issues = [];
28183
+ if (!facts.v2) return issues;
28184
+ for (const cond of facts.v2.deadCode.constantConditions) {
28185
+ const isWhileTrue = cond.kind === "while-true";
28186
+ issues.push({
28187
+ ruleId: "dead/dead-branch",
28188
+ category: "logic",
28189
+ severity: isWhileTrue ? "low" : "medium",
28190
+ aiSpecific: true,
28191
+ message: isWhileTrue ? `Infinite loop with literal condition (${cond.kind})` : `Dead branch: condition is always ${cond.condition}`,
28192
+ line: cond.line,
28193
+ column: cond.column,
28194
+ 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.`
28195
+ });
28196
+ }
28197
+ return issues;
28198
+ }
28199
+ });
28200
+ }
28201
+ });
28202
+
28203
+ // src/rules/dead/unreachable.ts
28204
+ var unreachableRule;
28205
+ var init_unreachable = __esm({
28206
+ "src/rules/dead/unreachable.ts"() {
28207
+ "use strict";
28208
+ init_rule();
28209
+ unreachableRule = createRule({
28210
+ id: "dead/unreachable",
28211
+ category: "logic",
28212
+ severity: "high",
28213
+ aiSpecific: true,
28214
+ description: "Statement is unreachable after an unconditional return/throw/break/continue",
28215
+ create(_context) {
28216
+ return {};
28217
+ },
28218
+ analyze(_context, facts) {
28219
+ const issues = [];
28220
+ if (!facts.v2) return issues;
28221
+ for (const u of facts.v2.deadCode.unreachableStatements) {
28222
+ if (u.snippet === "<unreachable>") continue;
28223
+ issues.push({
28224
+ ruleId: "dead/unreachable",
28225
+ category: "logic",
28226
+ severity: "high",
28227
+ aiSpecific: true,
28228
+ message: `Unreachable after ${u.terminator}: ${u.snippet}`,
28229
+ line: u.line,
28230
+ column: u.column,
28231
+ 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.`
28232
+ });
28233
+ }
28234
+ return issues;
28235
+ }
28236
+ });
28237
+ }
28238
+ });
28239
+
28240
+ // src/rules/dead/unused-import.ts
28241
+ var unusedImportRule;
28242
+ var init_unused_import = __esm({
28243
+ "src/rules/dead/unused-import.ts"() {
28244
+ "use strict";
28245
+ init_rule();
28246
+ unusedImportRule = createRule({
28247
+ id: "dead/unused-import",
28248
+ category: "logic",
28249
+ severity: "low",
28250
+ aiSpecific: true,
28251
+ description: "ES module import is never referenced in the file",
28252
+ create(_context) {
28253
+ return {};
28254
+ },
28255
+ analyze(_context, facts) {
28256
+ const issues = [];
28257
+ if (!facts.v2) return issues;
28258
+ for (const binding of facts.v2.deadCode.bindings) {
28259
+ if (binding.kind !== "import-specifier" && binding.kind !== "import-default" && binding.kind !== "import-namespace") {
28260
+ continue;
28261
+ }
28262
+ if (binding.isReferenced) continue;
28263
+ if (!binding.name) continue;
28264
+ const source = binding.source ? ` from '${binding.source}'` : "";
28265
+ issues.push({
28266
+ ruleId: "dead/unused-import",
28267
+ category: "logic",
28268
+ severity: "low",
28269
+ aiSpecific: true,
28270
+ message: `Unused import: '${binding.name}'${source}`,
28271
+ line: binding.line,
28272
+ column: binding.column,
28273
+ 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.`
28274
+ });
28275
+ }
28276
+ return issues;
28277
+ }
28278
+ });
28279
+ }
28280
+ });
28281
+
28282
+ // src/rules/dead/unused-local.ts
28283
+ var SKIP_NAMES, unusedLocalRule;
28284
+ var init_unused_local = __esm({
28285
+ "src/rules/dead/unused-local.ts"() {
28286
+ "use strict";
28287
+ init_rule();
28288
+ SKIP_NAMES = /* @__PURE__ */ new Set(["React", "_"]);
28289
+ unusedLocalRule = createRule({
28290
+ id: "dead/unused-local",
28291
+ category: "logic",
28292
+ severity: "low",
28293
+ aiSpecific: true,
28294
+ description: "Variable is declared but never read after declaration",
28295
+ create(_context) {
28296
+ return {};
28297
+ },
28298
+ analyze(_context, facts) {
28299
+ const issues = [];
28300
+ if (!facts.v2) return issues;
28301
+ for (const binding of facts.v2.deadCode.bindings) {
28302
+ 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") {
28303
+ continue;
28304
+ }
28305
+ if (binding.isReferenced) continue;
28306
+ if (SKIP_NAMES.has(binding.name)) continue;
28307
+ if (binding.name.startsWith("_")) continue;
28308
+ issues.push({
28309
+ ruleId: "dead/unused-local",
28310
+ category: "logic",
28311
+ severity: "low",
28312
+ aiSpecific: true,
28313
+ message: `Unused ${binding.kind}: '${binding.name}'`,
28314
+ line: binding.line,
28315
+ column: binding.column,
28316
+ 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.`
28317
+ });
28318
+ }
28319
+ return issues;
28320
+ }
28321
+ });
28322
+ }
28323
+ });
28324
+
28325
+ // src/rules/dead/unused-parameter.ts
28326
+ var unusedParameterRule;
28327
+ var init_unused_parameter = __esm({
28328
+ "src/rules/dead/unused-parameter.ts"() {
28329
+ "use strict";
28330
+ init_rule();
28331
+ unusedParameterRule = createRule({
28332
+ id: "dead/unused-parameter",
28333
+ category: "logic",
28334
+ severity: "low",
28335
+ aiSpecific: true,
28336
+ description: "Function parameter is declared but never read",
28337
+ create(_context) {
28338
+ return {};
28339
+ },
28340
+ analyze(_context, facts) {
28341
+ const issues = [];
28342
+ if (!facts.v2) return issues;
28343
+ for (const binding of facts.v2.deadCode.bindings) {
28344
+ if (binding.kind !== "parameter") continue;
28345
+ if (binding.isReferenced) continue;
28346
+ if (binding.name.startsWith("_")) continue;
28347
+ if (binding.name === "props") continue;
28348
+ issues.push({
28349
+ ruleId: "dead/unused-parameter",
28350
+ category: "logic",
28351
+ severity: "low",
28352
+ aiSpecific: true,
28353
+ message: `Unused parameter: '${binding.name}'`,
28354
+ line: binding.line,
28355
+ column: binding.column,
28356
+ 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.`
28357
+ });
28358
+ }
28359
+ return issues;
28360
+ }
28361
+ });
28362
+ }
28363
+ });
28364
+
28112
28365
  // src/engine/discover.ts
28113
28366
  var discover_exports = {};
28114
28367
  __export(discover_exports, {
@@ -28218,14 +28471,14 @@ var init_discover = __esm({
28218
28471
  function readDeps(cwd) {
28219
28472
  const pkgPath = (0, import_node_path5.join)(cwd, "package.json");
28220
28473
  if (!(0, import_node_fs5.existsSync)(pkgPath)) return /* @__PURE__ */ new Set();
28221
- let pkg2;
28474
+ let pkg;
28222
28475
  try {
28223
- pkg2 = JSON.parse((0, import_node_fs5.readFileSync)(pkgPath, "utf-8"));
28476
+ pkg = JSON.parse((0, import_node_fs5.readFileSync)(pkgPath, "utf-8"));
28224
28477
  } catch {
28225
28478
  return /* @__PURE__ */ new Set();
28226
28479
  }
28227
28480
  const out = /* @__PURE__ */ new Set();
28228
- for (const source of [pkg2.dependencies, pkg2.devDependencies]) {
28481
+ for (const source of [pkg.dependencies, pkg.devDependencies]) {
28229
28482
  if (source && typeof source === "object") {
28230
28483
  for (const name of Object.keys(source)) out.add(name);
28231
28484
  }
@@ -28241,8 +28494,8 @@ function detectConstitution(cwd) {
28241
28494
  const deps = readDeps(cwd);
28242
28495
  if (deps.size === 0) return {};
28243
28496
  const out = {};
28244
- for (const [pkg2, { field, signal }] of Object.entries(CONSTITUTION_SIGNALS)) {
28245
- if (deps.has(pkg2)) {
28497
+ for (const [pkg, { field, signal }] of Object.entries(CONSTITUTION_SIGNALS)) {
28498
+ if (deps.has(pkg)) {
28246
28499
  out[field] = pushUnique(
28247
28500
  out[field],
28248
28501
  signal
@@ -29512,6 +29765,9 @@ var init_expired_code_example = __esm({
29512
29765
  const issues = [];
29513
29766
  const source = facts.v2?._source;
29514
29767
  if (!source) return issues;
29768
+ const packages = declaredPackages(context.cwd);
29769
+ const packageName = context.packageName;
29770
+ if (packageName) packages.add(packageName);
29515
29771
  const blocks = extractFencedCodeBlocks(source);
29516
29772
  for (const block of blocks) {
29517
29773
  if (!CODE_LANGS.has(block.lang)) continue;
@@ -29519,7 +29775,7 @@ var init_expired_code_example = __esm({
29519
29775
  const imports = extractImports(block.body);
29520
29776
  for (const imp of imports) {
29521
29777
  const pkgName = stripSubpath(imp);
29522
- if (context.packages.has(pkgName)) continue;
29778
+ if (packages.has(pkgName)) continue;
29523
29779
  issues.push({
29524
29780
  ruleId: "docs/expired-code-example",
29525
29781
  category: "docs",
@@ -29572,7 +29828,12 @@ function collectExports(cwd) {
29572
29828
  /\bexport\s+class\s+([A-Za-z_$][\w$]*)/g,
29573
29829
  /\bexport\s+interface\s+([A-Za-z_$][\w$]*)/g,
29574
29830
  /\bexport\s+type\s+([A-Za-z_$][\w$]*)/g,
29575
- /\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g
29831
+ /\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g,
29832
+ // v0.18.6: also collect field names from `export interface` and
29833
+ // `export type` declarations. Without this, fields like
29834
+ // `crossFileDrift`, `aiQuality`, `engineeringHygiene` are
29835
+ // flagged as stale even though they're valid type fields.
29836
+ /^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\s*[?:]/gm
29576
29837
  ]) {
29577
29838
  let m;
29578
29839
  while ((m = re.exec(source)) !== null) {
@@ -29583,6 +29844,21 @@ function collectExports(cwd) {
29583
29844
  }
29584
29845
  return out;
29585
29846
  }
29847
+ function looksLikeProseLabel(inside) {
29848
+ const trimmed = inside.trim();
29849
+ if (trimmed.length === 0) return false;
29850
+ if (trimmed.startsWith("`") || trimmed.startsWith("/")) return true;
29851
+ if (trimmed.includes("`")) return true;
29852
+ if (/\d+\s+[a-z]/i.test(trimmed)) return true;
29853
+ const parts = trimmed.split(",").map((s) => s.trim());
29854
+ if (parts.length >= 3) {
29855
+ const allNumeric = parts.every((p) => /^\d+\.?\d*$/.test(p));
29856
+ if (!allNumeric) return true;
29857
+ }
29858
+ if (trimmed.length > 40 && !trimmed.includes(",")) return true;
29859
+ if (trimmed.includes("\u2014") || trimmed.includes("\u2013")) return true;
29860
+ return false;
29861
+ }
29586
29862
  var import_node_fs7, import_node_path7, RESERVED, SOURCE_EXTS, SOURCE_ROOTS, CAP, staleFunctionReferenceRule;
29587
29863
  var init_stale_function_reference = __esm({
29588
29864
  "src/rules/docs/stale-function-reference.ts"() {
@@ -29592,6 +29868,7 @@ var init_stale_function_reference = __esm({
29592
29868
  init_rule();
29593
29869
  init_doc_freshness();
29594
29870
  RESERVED = /* @__PURE__ */ new Set([
29871
+ // JS reserved words
29595
29872
  "true",
29596
29873
  "false",
29597
29874
  "null",
@@ -29691,7 +29968,472 @@ var init_stale_function_reference = __esm({
29691
29968
  "next",
29692
29969
  "vue",
29693
29970
  "angular",
29694
- "svelte"
29971
+ "svelte",
29972
+ // Framework / runtime names
29973
+ "html",
29974
+ "astro",
29975
+ "python",
29976
+ "jvm",
29977
+ "kotlin",
29978
+ "swift",
29979
+ "dart",
29980
+ "ruby",
29981
+ "rust",
29982
+ "cpp",
29983
+ "go",
29984
+ "java",
29985
+ "php",
29986
+ "php-html",
29987
+ "csharp",
29988
+ "typescript",
29989
+ "javascript",
29990
+ "jsx",
29991
+ "tsx",
29992
+ "mjs",
29993
+ "cjs",
29994
+ "esnext",
29995
+ "es6",
29996
+ "es2022",
29997
+ "es2023",
29998
+ "esm",
29999
+ "cjs",
30000
+ "umd",
30001
+ "amd",
30002
+ "commonjs",
30003
+ "require",
30004
+ "module",
30005
+ "exports",
30006
+ "define",
30007
+ "global",
30008
+ "window",
30009
+ "document",
30010
+ "process",
30011
+ "console",
30012
+ "buffer",
30013
+ "stream",
30014
+ "fetch",
30015
+ "axios",
30016
+ "express",
30017
+ "fastify",
30018
+ "koa",
30019
+ "hapi",
30020
+ "nextjs",
30021
+ "nuxt",
30022
+ "remix",
30023
+ "gatsby",
30024
+ "sveltekit",
30025
+ "solid",
30026
+ "preact",
30027
+ "qwik",
30028
+ "lit",
30029
+ "stencil",
30030
+ "marko",
30031
+ "alpine",
30032
+ "stimulus",
30033
+ "turbo",
30034
+ "hotwire",
30035
+ // Models / providers
30036
+ "gpt",
30037
+ "claude",
30038
+ "gpt-3",
30039
+ "gpt-3.5",
30040
+ "gpt-4",
30041
+ "gpt-oss",
30042
+ "haiku",
30043
+ "sonnet",
30044
+ "opus",
30045
+ "aider",
30046
+ "tabby",
30047
+ "copilot",
30048
+ "cursor",
30049
+ "windsurf",
30050
+ "devin",
30051
+ "claude-code",
30052
+ // LLM-detection lingo
30053
+ "heuristic",
30054
+ "heuristics",
30055
+ "calibrate",
30056
+ "calibration",
30057
+ "calibrator",
30058
+ "corpus",
30059
+ "baseline",
30060
+ "baselines",
30061
+ "corpus-baselines",
30062
+ "lift",
30063
+ "recall",
30064
+ "precision",
30065
+ "fpRate",
30066
+ "ratio",
30067
+ "verdict",
30068
+ "USEFUL",
30069
+ "NOISY",
30070
+ "INVERTED",
30071
+ "HYGIENE",
30072
+ "DORMANT",
30073
+ "OK",
30074
+ "aiSpecific",
30075
+ "defaultOff",
30076
+ // Common slop-audit verbs/nouns
30077
+ "commit",
30078
+ "push",
30079
+ "reset",
30080
+ "rebase",
30081
+ "merge",
30082
+ "cherry-pick",
30083
+ "revert",
30084
+ "scan",
30085
+ "parse",
30086
+ "build",
30087
+ "test",
30088
+ "lint",
30089
+ "format",
30090
+ "check",
30091
+ "audit",
30092
+ "fix",
30093
+ "patch",
30094
+ "diff",
30095
+ "pr",
30096
+ "ci",
30097
+ "cd",
30098
+ "gh",
30099
+ "npm",
30100
+ "npx",
30101
+ "pnpm",
30102
+ "yaml",
30103
+ "json",
30104
+ "toml",
30105
+ "csv",
30106
+ "md",
30107
+ "mdx",
30108
+ "sh",
30109
+ "bash",
30110
+ "zsh",
30111
+ "fish",
30112
+ "ascii",
30113
+ "utf8",
30114
+ "utf-8",
30115
+ "base64",
30116
+ "hex",
30117
+ "binary",
30118
+ "text",
30119
+ // Common design / ui terms
30120
+ "flex",
30121
+ "grid",
30122
+ "auto",
30123
+ "min",
30124
+ "max",
30125
+ "fill",
30126
+ "stretch",
30127
+ "wrap",
30128
+ "nowrap",
30129
+ "inline",
30130
+ "block",
30131
+ "hidden",
30132
+ "visible",
30133
+ "static",
30134
+ "fixed",
30135
+ "absolute",
30136
+ "relative",
30137
+ "sticky",
30138
+ "pointer",
30139
+ "cursor",
30140
+ "focus",
30141
+ "hover",
30142
+ "active",
30143
+ "disabled",
30144
+ "readonly",
30145
+ "primary",
30146
+ "secondary",
30147
+ "tertiary",
30148
+ "success",
30149
+ "warning",
30150
+ "danger",
30151
+ "info",
30152
+ "muted",
30153
+ "sm",
30154
+ "md",
30155
+ "lg",
30156
+ "xl",
30157
+ "xxl",
30158
+ "xs",
30159
+ "2xl",
30160
+ "3xl",
30161
+ "4xl",
30162
+ // Math / types
30163
+ "array",
30164
+ "map",
30165
+ "set",
30166
+ "weakmap",
30167
+ "weakset",
30168
+ "object",
30169
+ "string",
30170
+ "number",
30171
+ "boolean",
30172
+ "bigint",
30173
+ "symbol",
30174
+ "null",
30175
+ "undefined",
30176
+ "any",
30177
+ "unknown",
30178
+ "never",
30179
+ "void",
30180
+ "readonly",
30181
+ "private",
30182
+ "public",
30183
+ "protected",
30184
+ "static",
30185
+ "abstract",
30186
+ "async",
30187
+ "generator",
30188
+ "iterator",
30189
+ "iterable",
30190
+ "promise",
30191
+ "observable",
30192
+ // Auth / domain
30193
+ "admin",
30194
+ "user",
30195
+ "guest",
30196
+ "anonymous",
30197
+ "authenticated",
30198
+ "unauthenticated",
30199
+ "jwt",
30200
+ "oauth",
30201
+ "oidc",
30202
+ "saml",
30203
+ "csrf",
30204
+ "xss",
30205
+ "sql",
30206
+ "nosql",
30207
+ "orm",
30208
+ "prisma",
30209
+ "drizzle",
30210
+ "sequelize",
30211
+ "mongoose",
30212
+ "redis",
30213
+ "postgres",
30214
+ "mysql",
30215
+ "sqlite",
30216
+ "kafka",
30217
+ "rabbitmq",
30218
+ "graphql",
30219
+ "rest",
30220
+ "grpc",
30221
+ "websocket",
30222
+ // slop-audit specific
30223
+ "slopbrick",
30224
+ "usebrick",
30225
+ "deadcode",
30226
+ "unused",
30227
+ "orphan",
30228
+ "zombie",
30229
+ "blocker",
30230
+ "warning",
30231
+ "info",
30232
+ "error",
30233
+ "verbose",
30234
+ "debug",
30235
+ "silly",
30236
+ "p50",
30237
+ "p90",
30238
+ "p95",
30239
+ "p99",
30240
+ "min",
30241
+ "max",
30242
+ "avg",
30243
+ "mean",
30244
+ "median",
30245
+ "ratchet",
30246
+ "tier",
30247
+ "composite",
30248
+ "fitness",
30249
+ "fpr",
30250
+ "tpr",
30251
+ "roc",
30252
+ "should",
30253
+ "could",
30254
+ "would",
30255
+ "might",
30256
+ "must",
30257
+ "shall",
30258
+ "may",
30259
+ "can",
30260
+ "todo",
30261
+ "fixme",
30262
+ "xxx",
30263
+ "hack",
30264
+ "note",
30265
+ "warning",
30266
+ "attention",
30267
+ "h1",
30268
+ "h2",
30269
+ "h3",
30270
+ "h4",
30271
+ "h5",
30272
+ "h6",
30273
+ "strong",
30274
+ "em",
30275
+ "b",
30276
+ "i",
30277
+ "u",
30278
+ "true",
30279
+ "false",
30280
+ "yes",
30281
+ "no",
30282
+ "on",
30283
+ "off",
30284
+ "enable",
30285
+ "disable",
30286
+ "ltr",
30287
+ "rtl",
30288
+ "auto",
30289
+ "start",
30290
+ "end",
30291
+ "center",
30292
+ "baseline",
30293
+ "stretch",
30294
+ "rounded",
30295
+ "sharp",
30296
+ "outline",
30297
+ "ghost",
30298
+ "link",
30299
+ "filled",
30300
+ "row",
30301
+ "col",
30302
+ "gap",
30303
+ "pad",
30304
+ "margin",
30305
+ "padding",
30306
+ "border",
30307
+ "shadow",
30308
+ "transparent",
30309
+ "currentcolor",
30310
+ "inherit",
30311
+ "initial",
30312
+ "unset",
30313
+ "revert",
30314
+ "hover",
30315
+ "focus",
30316
+ "active",
30317
+ "disabled",
30318
+ "checked",
30319
+ "indeterminate",
30320
+ "open",
30321
+ "close",
30322
+ "expanded",
30323
+ "collapsed",
30324
+ "selected",
30325
+ "pressed",
30326
+ // Web/CSS
30327
+ "div",
30328
+ "span",
30329
+ "p",
30330
+ "a",
30331
+ "img",
30332
+ "ul",
30333
+ "ol",
30334
+ "li",
30335
+ "table",
30336
+ "tr",
30337
+ "td",
30338
+ "th",
30339
+ "thead",
30340
+ "tbody",
30341
+ "tfoot",
30342
+ "caption",
30343
+ "figure",
30344
+ "figcaption",
30345
+ "main",
30346
+ "section",
30347
+ "article",
30348
+ "aside",
30349
+ "header",
30350
+ "footer",
30351
+ "nav",
30352
+ "form",
30353
+ "input",
30354
+ "button",
30355
+ "select",
30356
+ "option",
30357
+ "textarea",
30358
+ "label",
30359
+ "fieldset",
30360
+ "legend",
30361
+ "details",
30362
+ "summary",
30363
+ "dialog",
30364
+ "menu",
30365
+ "menuitem",
30366
+ "template",
30367
+ "slot",
30368
+ "picture",
30369
+ "source",
30370
+ "track",
30371
+ "video",
30372
+ "audio",
30373
+ "canvas",
30374
+ "svg",
30375
+ "iframe",
30376
+ "embed",
30377
+ "object",
30378
+ "portal",
30379
+ // Common business terms
30380
+ "api",
30381
+ "cli",
30382
+ "ui",
30383
+ "ux",
30384
+ "sdk",
30385
+ "ide",
30386
+ "cli",
30387
+ "docs",
30388
+ "doc",
30389
+ "blog",
30390
+ "post",
30391
+ "page",
30392
+ "view",
30393
+ "tab",
30394
+ "panel",
30395
+ "card",
30396
+ "list",
30397
+ "grid",
30398
+ "form",
30399
+ "modal",
30400
+ "menu",
30401
+ "button",
30402
+ "icon",
30403
+ "avatar",
30404
+ "badge",
30405
+ "chip",
30406
+ "tooltip",
30407
+ "popover",
30408
+ "dropdown",
30409
+ "banner",
30410
+ "alert",
30411
+ "toast",
30412
+ "notification",
30413
+ "drawer",
30414
+ "sidebar",
30415
+ "navbar",
30416
+ "header",
30417
+ "footer",
30418
+ "hero",
30419
+ "cta",
30420
+ "cta-primary",
30421
+ "cta-secondary",
30422
+ "pricing",
30423
+ "price",
30424
+ "cost",
30425
+ "rate",
30426
+ "percent",
30427
+ "pct",
30428
+ "count",
30429
+ "total",
30430
+ "small",
30431
+ "medium",
30432
+ "large",
30433
+ "xl",
30434
+ "xxl",
30435
+ "tiny",
30436
+ "huge"
29695
30437
  ]);
29696
30438
  SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
29697
30439
  SOURCE_ROOTS = ["src", "lib", "app", "components"];
@@ -29715,8 +30457,39 @@ var init_stale_function_reference = __esm({
29715
30457
  if (text.length < 3) continue;
29716
30458
  if (RESERVED.has(text.toLowerCase())) continue;
29717
30459
  if (context.exports.has(text)) continue;
29718
- const end = Math.min(source.length, span.index + text.length + 50);
29719
- if (!/\(/.test(source.slice(span.index, end))) continue;
30460
+ const lineEnd = source.indexOf("\n", span.index);
30461
+ const restOfLine = source.slice(
30462
+ span.index,
30463
+ lineEnd === -1 ? source.length : lineEnd
30464
+ );
30465
+ const closeTick = restOfLine.indexOf("`", 1);
30466
+ if (closeTick === -1) continue;
30467
+ const afterTick = restOfLine.slice(closeTick + 1);
30468
+ const directCall = /^\s*\(/.test(afterTick);
30469
+ let identifierRepeats = false;
30470
+ if (!directCall) {
30471
+ const afterSpan = restOfLine.slice(closeTick + 1);
30472
+ const needle = text + "(";
30473
+ identifierRepeats = afterSpan.indexOf(needle) !== -1;
30474
+ }
30475
+ if (!directCall && !identifierRepeats) continue;
30476
+ const beforeTickIdx = span.index - 1;
30477
+ const beforeChar = beforeTickIdx >= 0 ? source[beforeTickIdx] : "";
30478
+ if (beforeChar === "." || beforeChar === "|") continue;
30479
+ const parenStart = restOfLine.indexOf("(", closeTick);
30480
+ const parenEnd = restOfLine.indexOf(")", parenStart);
30481
+ if (parenStart !== -1 && parenEnd !== -1) {
30482
+ const inside = restOfLine.slice(parenStart + 1, parenEnd);
30483
+ const trimmed = inside.trim();
30484
+ if (looksLikeProseLabel(inside)) continue;
30485
+ 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(
30486
+ inside
30487
+ ) || // Short single-word label (≤ 24 chars, no `,`,
30488
+ // doesn't look like a function arg). Real function
30489
+ // calls are usually longer or contain commas.
30490
+ trimmed.length > 0 && trimmed.length <= 24 && !trimmed.includes(",") && /[a-zA-Z]/.test(trimmed));
30491
+ if (looksLikeTypeAnnotation) continue;
30492
+ }
29720
30493
  issues.push({
29721
30494
  ruleId: "docs/stale-function-reference",
29722
30495
  category: "docs",
@@ -29796,7 +30569,33 @@ var init_stale_package_reference = __esm({
29796
30569
  "jsx",
29797
30570
  "ok",
29798
30571
  "no",
29799
- "yes"
30572
+ "yes",
30573
+ // v0.18.6: common English adjectives / adverbs that frequently
30574
+ // appear in backticked prose but are not package names.
30575
+ "aspirational",
30576
+ "concrete",
30577
+ "abstract",
30578
+ "inline",
30579
+ "exposed",
30580
+ "deprecated",
30581
+ "experimental",
30582
+ "stable",
30583
+ "beta",
30584
+ "alpha",
30585
+ "wip",
30586
+ "draft",
30587
+ "final",
30588
+ "shim",
30589
+ "polyfill",
30590
+ "stub",
30591
+ "mock",
30592
+ "fake",
30593
+ "real",
30594
+ "false",
30595
+ "true",
30596
+ "optional",
30597
+ "required",
30598
+ "default"
29800
30599
  ]);
29801
30600
  stalePackageReferenceRule = createRule({
29802
30601
  id: "docs/stale-package-reference",
@@ -29935,9 +30734,9 @@ function declaredPackages(cwd) {
29935
30734
  if (!(0, import_node_fs8.existsSync)(pkgPath)) return out;
29936
30735
  try {
29937
30736
  const raw = (0, import_node_fs8.readFileSync)(pkgPath, "utf-8");
29938
- const pkg2 = JSON.parse(raw);
30737
+ const pkg = JSON.parse(raw);
29939
30738
  for (const k of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
29940
- const v = pkg2[k];
30739
+ const v = pkg[k];
29941
30740
  if (v && typeof v === "object") {
29942
30741
  for (const name of Object.keys(v)) {
29943
30742
  out.add(name);
@@ -30018,7 +30817,15 @@ async function buildDocFreshness(cwd, config, options = {}) {
30018
30817
  continue;
30019
30818
  }
30020
30819
  const relPath = (0, import_node_path8.relative)(cwd, abs);
30021
- const context = { config, filePath: relPath, cwd };
30820
+ let packageName;
30821
+ try {
30822
+ const pkg = JSON.parse(
30823
+ (0, import_node_fs8.readFileSync)((0, import_node_path8.join)(cwd, "package.json"), "utf-8")
30824
+ );
30825
+ packageName = pkg.name;
30826
+ } catch {
30827
+ }
30828
+ const context = { config, filePath: relPath, cwd, packageName };
30022
30829
  const facts = { filePath: relPath, v2: { _source: source } };
30023
30830
  const ruleConfigs = [
30024
30831
  { rule: stalePackageReferenceRule, ruleId: "docs/stale-package-reference" },
@@ -30134,7 +30941,9 @@ var init_broken_link = __esm({
30134
30941
  if (target.startsWith("#")) continue;
30135
30942
  if (target.startsWith("//")) continue;
30136
30943
  if (target.startsWith("/")) continue;
30137
- const resolved = (0, import_node_path9.join)(docDir, target);
30944
+ const filePart = target.split("#")[0] ?? target;
30945
+ if (filePart === "") continue;
30946
+ const resolved = (0, import_node_path9.join)(docDir, filePart);
30138
30947
  if ((0, import_node_fs9.existsSync)(resolved)) continue;
30139
30948
  issues.push({
30140
30949
  ruleId: "docs/broken-link",
@@ -30367,7 +31176,7 @@ var init_spacing_grid = __esm({
30367
31176
  "use strict";
30368
31177
  init_rule();
30369
31178
  init_utils();
30370
- init_config();
31179
+ init_config2();
30371
31180
  SPACING_PREFIX_RE = /^-?(p|px|py|pt|pr|pb|pl|m|mx|my|mt|mr|mb|ml|gap|gap-x|gap-y|space-x|space-y)-(.+)$/;
30372
31181
  ARBITRARY_VALUE_RE2 = /^(-?\d+(?:\.\d+)?)(px|rem)$/;
30373
31182
  SKIP_VALUES = /* @__PURE__ */ new Set(["auto", "full", "screen", "min", "max", "fit", "none"]);
@@ -42455,6 +43264,78 @@ function extractStateBinding(node, lineOffsets) {
42455
43264
  setterReferenced: false
42456
43265
  };
42457
43266
  }
43267
+ function findUnreachableStatements(ast, source, lineOffsets) {
43268
+ const out = [];
43269
+ function isTerminator(node) {
43270
+ if (node.type === "ReturnStatement") return "return";
43271
+ if (node.type === "ThrowStatement") return "throw";
43272
+ if (node.type === "BreakStatement") return "break";
43273
+ if (node.type === "ContinueStatement") return "continue";
43274
+ return null;
43275
+ }
43276
+ function snippetFor(node, src) {
43277
+ const span = node.span;
43278
+ if (!span || typeof span.start !== "number" || typeof span.end !== "number") {
43279
+ return "<unreachable>";
43280
+ }
43281
+ const text = src.slice(span.start, Math.min(span.end, span.start + 60));
43282
+ return text.replace(/\s+/g, " ").trim() || "<unreachable>";
43283
+ }
43284
+ function lineColumn(offset) {
43285
+ let lo = 0;
43286
+ let hi = lineOffsets.length - 1;
43287
+ while (lo < hi) {
43288
+ const mid = lo + hi + 1 >>> 1;
43289
+ const midVal = lineOffsets[mid];
43290
+ const loVal = lineOffsets[lo];
43291
+ if (midVal === void 0) break;
43292
+ if (midVal <= offset) lo = mid;
43293
+ else hi = mid - 1;
43294
+ void loVal;
43295
+ }
43296
+ const baseOffset = lineOffsets[lo] ?? 0;
43297
+ return { line: lo + 1, column: offset - baseOffset };
43298
+ }
43299
+ function visitBody(body) {
43300
+ if (!Array.isArray(body)) return;
43301
+ let lastTerminator = null;
43302
+ for (const stmt of body) {
43303
+ if (!isObject(stmt)) continue;
43304
+ if (lastTerminator && stmt.type !== "EmptyStatement") {
43305
+ const span = stmt.span;
43306
+ const offset = typeof span?.start === "number" ? span.start : 0;
43307
+ const { line, column } = lineColumn(offset);
43308
+ out.push({
43309
+ terminator: lastTerminator,
43310
+ line,
43311
+ column,
43312
+ snippet: snippetFor(stmt, source)
43313
+ });
43314
+ }
43315
+ const t = isTerminator(stmt);
43316
+ if (t) lastTerminator = t;
43317
+ }
43318
+ }
43319
+ function walk3(node) {
43320
+ if (!isObject(node)) return;
43321
+ if (node.type === "BlockStatement") {
43322
+ visitBody(node.stmts);
43323
+ } else if (node.type === "Module") {
43324
+ visitBody(node.body);
43325
+ }
43326
+ for (const key of Object.keys(node)) {
43327
+ if (key === "parent" || key === "span" || key === "ctxt") continue;
43328
+ const child = node[key];
43329
+ if (Array.isArray(child)) {
43330
+ for (const c of child) walk3(c);
43331
+ } else {
43332
+ walk3(child);
43333
+ }
43334
+ }
43335
+ }
43336
+ walk3(ast);
43337
+ return out;
43338
+ }
42458
43339
  var init_scan_helpers = __esm({
42459
43340
  "src/engine/visitors/scan-helpers.ts"() {
42460
43341
  "use strict";
@@ -42540,15 +43421,42 @@ function handleImportDeclaration(node, _parent, _path, vctx) {
42540
43421
  if (specifier.type === "ImportDefaultSpecifier" || specifier.type === "ImportNamespaceSpecifier") {
42541
43422
  const local = specifier.local;
42542
43423
  if (isObject(local) && local.type === "Identifier" && typeof local.value === "string") {
42543
- importedNames.push(local.value);
43424
+ const name = local.value;
43425
+ importedNames.push(name);
43426
+ vctx.facts.deadCode.bindings.push({
43427
+ name,
43428
+ kind: specifier.type === "ImportDefaultSpecifier" ? "import-default" : "import-namespace",
43429
+ line,
43430
+ column,
43431
+ source,
43432
+ isReferenced: false
43433
+ });
42544
43434
  }
42545
43435
  } else if (specifier.type === "ImportSpecifier") {
42546
43436
  const imported = specifier.imported;
42547
43437
  const local = specifier.local;
42548
43438
  if (isObject(imported) && typeof imported.value === "string" && imported.value.length > 0) {
42549
- importedNames.push(imported.value);
43439
+ const name = imported.value;
43440
+ importedNames.push(name);
43441
+ vctx.facts.deadCode.bindings.push({
43442
+ name,
43443
+ kind: "import-specifier",
43444
+ line,
43445
+ column,
43446
+ source,
43447
+ isReferenced: false
43448
+ });
42550
43449
  } else if (isObject(local) && local.type === "Identifier" && typeof local.value === "string") {
42551
- importedNames.push(local.value);
43450
+ const name = local.value;
43451
+ importedNames.push(name);
43452
+ vctx.facts.deadCode.bindings.push({
43453
+ name,
43454
+ kind: "import-specifier",
43455
+ line,
43456
+ column,
43457
+ source,
43458
+ isReferenced: false
43459
+ });
42552
43460
  }
42553
43461
  }
42554
43462
  }
@@ -42638,6 +43546,13 @@ function isBindingSite(node, parent) {
42638
43546
  if (params.some((param) => containsNode(param, node))) return true;
42639
43547
  }
42640
43548
  }
43549
+ if (parent.type === "ImportSpecifier" || parent.type === "ImportDefaultSpecifier" || parent.type === "ImportNamespaceSpecifier") {
43550
+ if (parent.type === "ImportSpecifier") {
43551
+ if (parent.imported === node || parent.local === node) return true;
43552
+ } else {
43553
+ if (parent.local === node) return true;
43554
+ }
43555
+ }
42641
43556
  return false;
42642
43557
  }
42643
43558
  function isNonComputedMemberProperty(node, parent) {
@@ -42704,6 +43619,9 @@ function handleIdentifier(node, parent, path, vctx) {
42704
43619
  if (typeof node.value === "string" && !isBindingSite(node, parent) && !isNonComputedMemberProperty(node, parent)) {
42705
43620
  markStateReference(node.value, vctx);
42706
43621
  trackPropUsage(node, parent, path, vctx);
43622
+ vctx.facts.referencedNames.add(node.value);
43623
+ const top = vctx.ctx.stack[vctx.ctx.stack.length - 1];
43624
+ if (top) top.references.add(node.value);
42707
43625
  }
42708
43626
  return false;
42709
43627
  }
@@ -42844,6 +43762,20 @@ function handleVariableDeclarator(node, _parent, path, vctx) {
42844
43762
  frame.bindings.add(bindingName);
42845
43763
  }
42846
43764
  }
43765
+ if (bindingNames.length > 0) {
43766
+ const parent = node.parent;
43767
+ const kind = isObject(parent) && parent.type === "VariableDeclaration" ? String(parent.kind ?? "var") : "var";
43768
+ const { line, column } = positionFrom(id, vctx.lineOffsets);
43769
+ for (const bindingName of bindingNames) {
43770
+ vctx.facts.deadCode.bindings.push({
43771
+ name: bindingName,
43772
+ kind,
43773
+ line,
43774
+ column,
43775
+ isReferenced: false
43776
+ });
43777
+ }
43778
+ }
42847
43779
  if (isUseStateDeclarator(node)) {
42848
43780
  const binding = extractStateBinding(node, vctx.lineOffsets);
42849
43781
  if (binding) {
@@ -42863,6 +43795,36 @@ function handleVariableDeclarator(node, _parent, path, vctx) {
42863
43795
  }
42864
43796
  return true;
42865
43797
  }
43798
+ function handleIfStatement(node, _parent, _path, vctx) {
43799
+ if (!isObject(node)) return false;
43800
+ const test = node.test;
43801
+ if (!isObject(test)) return false;
43802
+ if (test.type === "BooleanLiteral" && typeof test.value === "boolean") {
43803
+ const { line, column } = positionFrom(test, vctx.lineOffsets);
43804
+ vctx.facts.deadCode.constantConditions.push({
43805
+ kind: test.value ? "if-true" : "if-false",
43806
+ condition: String(test.value),
43807
+ line,
43808
+ column
43809
+ });
43810
+ }
43811
+ return false;
43812
+ }
43813
+ function handleWhileStatement(node, _parent, _path, vctx) {
43814
+ if (!isObject(node)) return false;
43815
+ const test = node.test;
43816
+ if (!isObject(test)) return false;
43817
+ if (test.type === "BooleanLiteral" && typeof test.value === "boolean") {
43818
+ const { line, column } = positionFrom(test, vctx.lineOffsets);
43819
+ vctx.facts.deadCode.constantConditions.push({
43820
+ kind: test.value ? "while-true" : "while-false",
43821
+ condition: String(test.value),
43822
+ line,
43823
+ column
43824
+ });
43825
+ }
43826
+ return false;
43827
+ }
42866
43828
  function dispatchNode(node, parent, path, vctx) {
42867
43829
  if (!isObject(node)) return false;
42868
43830
  const type = getNodeType(node);
@@ -42887,7 +43849,9 @@ var init_dispatch = __esm({
42887
43849
  MemberExpression: handleMemberExpression,
42888
43850
  JSXAttribute: handleJSXAttribute,
42889
43851
  JSXOpeningElement: handleJSXOpeningElement,
42890
- VariableDeclarator: handleVariableDeclarator
43852
+ VariableDeclarator: handleVariableDeclarator,
43853
+ IfStatement: handleIfStatement,
43854
+ WhileStatement: handleWhileStatement
42891
43855
  };
42892
43856
  }
42893
43857
  });
@@ -43038,6 +44002,17 @@ function buildV2Facts(facts, source, ext, framework, config, templateClassNames
43038
44002
  },
43039
44003
  logic: buildLogicBlock(facts),
43040
44004
  designTokens: scanDesignTokens(facts.staticClassNames),
44005
+ // dead-code detector. Copy the internal accumulator
44006
+ // into the v2 shape, marking each binding as referenced
44007
+ // iff the file-level referenced-name set contains its name.
44008
+ deadCode: {
44009
+ bindings: facts.deadCode.bindings.map((b) => ({
44010
+ ...b,
44011
+ isReferenced: facts.referencedNames.has(b.name)
44012
+ })),
44013
+ constantConditions: facts.deadCode.constantConditions,
44014
+ unreachableStatements: facts.deadCode.unreachableStatements
44015
+ },
43041
44016
  componentSizes: facts.componentSizes.map((cs) => ({
43042
44017
  name: cs.name,
43043
44018
  lineCount: cs.lineCount,
@@ -43119,7 +44094,17 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
43119
44094
  componentSizes: [],
43120
44095
  astroComponents: [],
43121
44096
  fetchCalls: [],
43122
- optimisticUpdates: []
44097
+ optimisticUpdates: [],
44098
+ // dead-code detector. The visitor's identifier walk + import/
44099
+ // branch/return handlers populate these. The v2 builder at the
44100
+ // bottom of extractFacts() reads them and produces
44101
+ // `facts.v2.deadCode`.
44102
+ deadCode: {
44103
+ bindings: [],
44104
+ constantConditions: [],
44105
+ unreachableStatements: []
44106
+ },
44107
+ referencedNames: /* @__PURE__ */ new Set()
43123
44108
  };
43124
44109
  const ctx = {
43125
44110
  stack: [],
@@ -43206,6 +44191,16 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
43206
44191
  propBindingSet.add(bindingName);
43207
44192
  }
43208
44193
  }
44194
+ const { line: pLine, column: pCol } = positionFrom(param, lineOffsets);
44195
+ for (const bindingName of collectBindingNames2(param)) {
44196
+ facts.deadCode.bindings.push({
44197
+ name: bindingName,
44198
+ kind: "parameter",
44199
+ line: pLine,
44200
+ column: pCol,
44201
+ isReferenced: false
44202
+ });
44203
+ }
43209
44204
  }
43210
44205
  }
43211
44206
  ctx.stack.push({
@@ -43221,6 +44216,12 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
43221
44216
  propUsages: [],
43222
44217
  isComponent,
43223
44218
  bindings,
44219
+ // dead-code detector: per-frame referenced-name set.
44220
+ // Identifiers encountered inside the frame are added to this
44221
+ // set; the deadCode builder unions it with parent frames at
44222
+ // pop time so a binding is considered used if any reachable
44223
+ // scope references it.
44224
+ references: /* @__PURE__ */ new Set(),
43224
44225
  propBindingSet,
43225
44226
  propUsageSet: /* @__PURE__ */ new Set(),
43226
44227
  node
@@ -43316,6 +44317,11 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
43316
44317
  mergeTemplateClassNames(filePath, source, facts, templateClassNames);
43317
44318
  const { ext } = splitFilePath(filePath);
43318
44319
  facts._source = source;
44320
+ facts.deadCode.unreachableStatements = findUnreachableStatements(
44321
+ ast,
44322
+ source,
44323
+ lineOffsets
44324
+ );
43319
44325
  const v2 = buildV2Facts(facts, source, ext, framework, config, templateClassNames);
43320
44326
  return envelopeScanFacts(filePath, v2);
43321
44327
  }
@@ -43998,6 +45004,11 @@ var init_builtins = __esm({
43998
45004
  init_missing_not_null();
43999
45005
  init_naming_inconsistency();
44000
45006
  init_sql_concat();
45007
+ init_dead_branch();
45008
+ init_unreachable();
45009
+ init_unused_import();
45010
+ init_unused_local();
45011
+ init_unused_parameter();
44001
45012
  init_broken_link();
44002
45013
  init_expired_code_example();
44003
45014
  init_stale_function_reference();
@@ -44094,6 +45105,11 @@ var init_builtins = __esm({
44094
45105
  missingNotNullRule,
44095
45106
  namingInconsistencyRule,
44096
45107
  sqlConcatRule,
45108
+ deadBranchRule,
45109
+ unreachableRule,
45110
+ unusedImportRule,
45111
+ unusedLocalRule,
45112
+ unusedParameterRule,
44097
45113
  brokenLinkRule,
44098
45114
  expiredCodeExampleRule,
44099
45115
  staleFunctionReferenceRule,
@@ -44665,8 +45681,8 @@ function detectJsLoader(configPath) {
44665
45681
  const pkgPath = (0, import_node_path10.join)(current, "package.json");
44666
45682
  if ((0, import_node_fs11.existsSync)(pkgPath)) {
44667
45683
  try {
44668
- const pkg2 = JSON.parse((0, import_node_fs11.readFileSync)(pkgPath, "utf-8"));
44669
- return pkg2.type === "module" ? "import" : "require";
45684
+ const pkg = JSON.parse((0, import_node_fs11.readFileSync)(pkgPath, "utf-8"));
45685
+ return pkg.type === "module" ? "import" : "require";
44670
45686
  } catch {
44671
45687
  return "require";
44672
45688
  }
@@ -44680,7 +45696,7 @@ function detectJsLoader(configPath) {
44680
45696
  async function loadConfigFile(path) {
44681
45697
  const loader = detectJsLoader(path);
44682
45698
  if (loader === "require") {
44683
- const req = (0, import_node_module2.createRequire)(__importMetaUrl);
45699
+ const req = (0, import_node_module.createRequire)(__importMetaUrl);
44684
45700
  const mod2 = req(path);
44685
45701
  return mod2.default ?? mod2;
44686
45702
  }
@@ -44712,13 +45728,13 @@ async function loadConfig(cwd) {
44712
45728
  );
44713
45729
  return merged;
44714
45730
  }
44715
- var import_node_fs11, import_node_path10, import_node_module2;
45731
+ var import_node_fs11, import_node_path10, import_node_module;
44716
45732
  var init_load = __esm({
44717
45733
  "src/config/load.ts"() {
44718
45734
  "use strict";
44719
45735
  import_node_fs11 = require("fs");
44720
45736
  import_node_path10 = require("path");
44721
- import_node_module2 = require("module");
45737
+ import_node_module = require("module");
44722
45738
  init_logger();
44723
45739
  init_validation();
44724
45740
  init_defaults();
@@ -44865,7 +45881,7 @@ var init_init = __esm({
44865
45881
  });
44866
45882
 
44867
45883
  // src/config/index.ts
44868
- var init_config = __esm({
45884
+ var init_config2 = __esm({
44869
45885
  "src/config/index.ts"() {
44870
45886
  "use strict";
44871
45887
  init_defaults();
@@ -47012,6 +48028,61 @@ var init_signal_strength = __esm({
47012
48028
  _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.)",
47013
48029
  aiSpecific: true
47014
48030
  },
48031
+ "dead/unused-import": {
48032
+ recall: 0,
48033
+ fpRate: 0,
48034
+ ratio: 0,
48035
+ precision: 0,
48036
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
48037
+ verdict: "DORMANT",
48038
+ defaultOff: true,
48039
+ _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.",
48040
+ aiSpecific: true
48041
+ },
48042
+ "dead/unused-local": {
48043
+ recall: 0,
48044
+ fpRate: 0,
48045
+ ratio: 0,
48046
+ precision: 0,
48047
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
48048
+ verdict: "DORMANT",
48049
+ defaultOff: true,
48050
+ _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).",
48051
+ aiSpecific: true
48052
+ },
48053
+ "dead/unused-parameter": {
48054
+ recall: 0,
48055
+ fpRate: 0,
48056
+ ratio: 0,
48057
+ precision: 0,
48058
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
48059
+ verdict: "DORMANT",
48060
+ defaultOff: true,
48061
+ _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.",
48062
+ aiSpecific: true
48063
+ },
48064
+ "dead/dead-branch": {
48065
+ recall: 0,
48066
+ fpRate: 0,
48067
+ ratio: 0,
48068
+ precision: 0,
48069
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
48070
+ verdict: "DORMANT",
48071
+ defaultOff: true,
48072
+ _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.",
48073
+ aiSpecific: true
48074
+ },
48075
+ "dead/unreachable": {
48076
+ recall: 0,
48077
+ fpRate: 0,
48078
+ ratio: 0,
48079
+ precision: 0,
48080
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
48081
+ verdict: "DORMANT",
48082
+ defaultOff: true,
48083
+ _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.",
48084
+ aiSpecific: true
48085
+ },
47015
48086
  "docs/stale-package-reference": {
47016
48087
  recall: 0,
47017
48088
  fpRate: 0,
@@ -47973,7 +49044,7 @@ var init_cache = __esm({
47973
49044
  import_node_fs16 = require("fs");
47974
49045
  import_node_path17 = require("path");
47975
49046
  init_types();
47976
- init_config();
49047
+ init_config2();
47977
49048
  BASELINE_VERSION = VERSION;
47978
49049
  BASELINE_HASH_KEYS = /* @__PURE__ */ new Set([
47979
49050
  "framework",
@@ -50457,7 +51528,7 @@ var init_finalizeReport = __esm({
50457
51528
  init_dist2();
50458
51529
  init_logger();
50459
51530
  init_memory_io();
50460
- init_config();
51531
+ init_config2();
50461
51532
  init_enrichReport();
50462
51533
  init_assembleScanReport();
50463
51534
  init_persistRun();
@@ -52382,11 +53453,11 @@ var init_watch = __esm({
52382
53453
  init_worker();
52383
53454
  init_threshold();
52384
53455
  init_cache();
52385
- init_config();
53456
+ init_config2();
52386
53457
  init_render();
52387
53458
  init_logger();
52388
53459
  init_error();
52389
- init_scan();
53460
+ init_scan2();
52390
53461
  init_renderOutput();
52391
53462
  init_types();
52392
53463
  }
@@ -52667,14 +53738,14 @@ async function scanProject(options) {
52667
53738
  return report;
52668
53739
  }
52669
53740
  var import_node_fs28, import_node_path30;
52670
- var init_scan = __esm({
53741
+ var init_scan2 = __esm({
52671
53742
  "src/cli/scan.ts"() {
52672
53743
  "use strict";
52673
53744
  import_node_fs28 = require("fs");
52674
53745
  import_node_path30 = require("path");
52675
53746
  init_render();
52676
53747
  init_threshold();
52677
- init_config();
53748
+ init_config2();
52678
53749
  init_discover();
52679
53750
  init_git();
52680
53751
  init_cache_incremental();
@@ -52844,7 +53915,7 @@ async function runSuggest(args, ctx) {
52844
53915
  }
52845
53916
  async function runGovernance(args, ctx) {
52846
53917
  try {
52847
- const { runScan: runScan3 } = await Promise.resolve().then(() => (init_scan(), scan_exports));
53918
+ const { runScan: runScan3 } = await Promise.resolve().then(() => (init_scan2(), scan_exports));
52848
53919
  const maxFiles = typeof args.maxFiles === "number" && Number.isFinite(args.maxFiles) && args.maxFiles > 0 ? Math.floor(args.maxFiles) : 500;
52849
53920
  const { report } = await runScan3({
52850
53921
  workspace: ctx.cwd,
@@ -53326,7 +54397,7 @@ __export(src_exports, {
53326
54397
  });
53327
54398
  module.exports = __toCommonJS(src_exports);
53328
54399
  init_types();
53329
- init_config();
54400
+ init_config2();
53330
54401
  init_dist2();
53331
54402
 
53332
54403
  // src/cli/program.ts
@@ -53370,14 +54441,14 @@ function parseThreshold(value) {
53370
54441
  // src/cli/program.ts
53371
54442
  init_render();
53372
54443
  init_threshold();
53373
- init_scan();
53374
- init_scan();
54444
+ init_scan2();
54445
+ init_scan2();
53375
54446
 
53376
54447
  // src/cli/init.ts
53377
54448
  var import_node_fs30 = require("fs");
53378
54449
  var import_node_path32 = require("path");
53379
54450
  var import_node_readline = require("readline");
53380
- init_config();
54451
+ init_config2();
53381
54452
  init_discover();
53382
54453
  init_git();
53383
54454
  init_cache();
@@ -53867,7 +54938,7 @@ async function runDoctor(cwd) {
53867
54938
  var import_node_path33 = require("path");
53868
54939
  init_render();
53869
54940
  init_logger();
53870
- init_scan();
54941
+ init_scan2();
53871
54942
  function registerBadge(program) {
53872
54943
  program.command("badge").description(
53873
54944
  "print a shields.io slop-index badge. Reads .slopbrick/health.json if present (no re-scan); falls back to a fresh scan."
@@ -53894,7 +54965,7 @@ var import_node_path34 = require("path");
53894
54965
  init_advice();
53895
54966
  init_unified_diff();
53896
54967
  init_logger();
53897
- init_scan();
54968
+ init_scan2();
53898
54969
  function registerSuggest(program) {
53899
54970
  program.command("suggest").description("print remediation advice").action(async (_cmdOptions, command) => {
53900
54971
  const options = command.optsWithGlobals();
@@ -54021,6 +55092,11 @@ var RULE_HINTS = {
54021
55092
  "logic/qwik-hook-leak": "Use Qwik primitives ($state, $effect, useSignal) instead of React hooks (useState, useEffect).",
54022
55093
  "logic/reactive-hook-soup": "Coordinate state via a single derived value (useMemo) or a state machine. Avoid chained useEffects that sync local state.",
54023
55094
  "logic/zombie-state": "Remove unused useState or wire it into the component. Don't leave declared-but-never-read state bindings.",
55095
+ "dead/unused-import": "Remove the import or use the symbol somewhere. Unused imports are the most common AI-iteration signature \u2014 the model added it for a feature, then rewrote the function without cleaning up.",
55096
+ "dead/unused-local": "Remove the declaration or use the variable. AI-iteration signature: the model declared a binding for a feature, then rewrote the function without cleaning up.",
55097
+ "dead/unused-parameter": "Remove the parameter (and update every call site) or use it in the function body. AI-iteration signature: the model added a parameter for a feature, then rewrote the function without removing parameters the new code does not need.",
55098
+ "dead/dead-branch": "Replace the literal boolean with a real condition, or remove the dead branch. AI-iteration signature: a feature flag toggled to a constant, or a wrapper from a previous refactor that was never cleaned up.",
55099
+ "dead/unreachable": "Remove this statement \u2014 code after a return/throw/break/continue is unreachable. AI-iteration signature: the model added an early return for a new error path, then forgot the rest of the function body was still sitting below it.",
54024
55100
  "perf/cls-image": "Add width/height attributes or an aspect-ratio utility to prevent layout shift.",
54025
55101
  "perf/css-bloat": "Extract to a CSS variable (`--surface-card`) or a component prop when a class string repeats 5+ times.",
54026
55102
  "perf/halstead-anomaly": "Introduce domain-specific identifiers and varied operations. Low vocabulary per line is a strong AI signature (Halstead 1977 \xA73).",
@@ -54275,7 +55351,7 @@ init_logger();
54275
55351
 
54276
55352
  // src/mcp/server.ts
54277
55353
  init_builtins();
54278
- init_config();
55354
+ init_config2();
54279
55355
  init_tools();
54280
55356
  var SERVER_INFO = {
54281
55357
  name: "slopbrick",
@@ -55705,7 +56781,7 @@ function registerTrend(program) {
55705
56781
  // src/cli/commands/drift.ts
55706
56782
  var import_node_path51 = require("path");
55707
56783
  init_logger();
55708
- init_scan();
56784
+ init_scan2();
55709
56785
 
55710
56786
  // src/cli/drift.ts
55711
56787
  var import_node_fs38 = require("fs");
@@ -55846,7 +56922,7 @@ function registerDrift(program) {
55846
56922
  // src/cli/commands/pr.ts
55847
56923
  var import_node_path53 = require("path");
55848
56924
  init_logger();
55849
- init_scan();
56925
+ init_scan2();
55850
56926
 
55851
56927
  // src/cli/pr.ts
55852
56928
  var import_node_fs39 = require("fs");
@@ -56135,7 +57211,7 @@ function registerPr(program) {
56135
57211
  // src/cli/commands/security.ts
56136
57212
  var import_node_path54 = require("path");
56137
57213
  init_logger();
56138
- init_scan();
57214
+ init_scan2();
56139
57215
  init_ai_security_risk();
56140
57216
  function registerSecurity(program) {
56141
57217
  program.command("security").description(
@@ -56194,11 +57270,11 @@ function registerSecurity(program) {
56194
57270
  // src/cli/commands/test.ts
56195
57271
  var import_node_path56 = require("path");
56196
57272
  init_logger();
56197
- init_scan();
57273
+ init_scan2();
56198
57274
 
56199
57275
  // src/cli/test.ts
56200
57276
  var import_node_path55 = require("path");
56201
- init_scan();
57277
+ init_scan2();
56202
57278
  init_test_quality();
56203
57279
  init_logger();
56204
57280
  var TEST_INCLUDE_GLOBS = [
@@ -56313,7 +57389,7 @@ function registerTest(program) {
56313
57389
  // src/cli/commands/architecture.ts
56314
57390
  var import_node_path57 = require("path");
56315
57391
  init_logger();
56316
- init_scan();
57392
+ init_scan2();
56317
57393
  init_architecture_score();
56318
57394
  function registerArchitecture(program) {
56319
57395
  program.command("architecture").description(
@@ -56341,7 +57417,7 @@ function registerArchitecture(program) {
56341
57417
  // src/cli/commands/business-logic.ts
56342
57418
  var import_node_path59 = require("path");
56343
57419
  init_logger();
56344
- init_scan();
57420
+ init_scan2();
56345
57421
 
56346
57422
  // src/cli/business-logic.ts
56347
57423
  var import_node_fs40 = require("fs");
@@ -56487,10 +57563,10 @@ function registerBusinessLogic(program) {
56487
57563
  // src/cli/commands/maintenance-cost.ts
56488
57564
  var import_node_path60 = require("path");
56489
57565
  init_logger();
56490
- init_scan();
57566
+ init_scan2();
56491
57567
 
56492
57568
  // src/cli/maintenance-cost.ts
56493
- init_scan();
57569
+ init_scan2();
56494
57570
  init_maintenance_cost();
56495
57571
  init_logger();
56496
57572
  async function runMaintenanceCostScan(cwd, config, options = {}) {
@@ -56630,10 +57706,10 @@ function registerMaintenanceCost(program) {
56630
57706
  // src/cli/commands/docs.ts
56631
57707
  var import_node_path61 = require("path");
56632
57708
  init_logger();
56633
- init_scan();
57709
+ init_scan2();
56634
57710
 
56635
57711
  // src/cli/docs.ts
56636
- init_scan();
57712
+ init_scan2();
56637
57713
  init_doc_freshness();
56638
57714
  init_logger();
56639
57715
  async function runDocsScan(cwd, config, options = {}) {
@@ -56779,10 +57855,10 @@ function registerDocs(program) {
56779
57855
  // src/cli/commands/db.ts
56780
57856
  var import_node_path62 = require("path");
56781
57857
  init_logger();
56782
- init_scan();
57858
+ init_scan2();
56783
57859
 
56784
57860
  // src/cli/db.ts
56785
- init_scan();
57861
+ init_scan2();
56786
57862
  init_db_health();
56787
57863
  init_logger();
56788
57864
  async function runDbScan(cwd, config, options = {}) {
@@ -56922,7 +57998,7 @@ function registerDb(program) {
56922
57998
  // src/cli/commands/patterns.ts
56923
57999
  var import_node_path64 = require("path");
56924
58000
  init_logger();
56925
- init_scan();
58001
+ init_scan2();
56926
58002
 
56927
58003
  // src/engine/patterns.ts
56928
58004
  var import_node_fs41 = require("fs");
@@ -57271,7 +58347,7 @@ function registerPatterns(program) {
57271
58347
  var import_node_fs42 = require("fs");
57272
58348
  var import_node_path65 = require("path");
57273
58349
  init_logger();
57274
- init_config();
58350
+ init_config2();
57275
58351
  function registerResearch(program) {
57276
58352
  const research = program.command("research").description("research commands for the AI UI learning loop");
57277
58353
  research.command("generate").description("generate synthetic UI samples").requiredOption("--count <n>", "number of samples", parseCount).requiredOption("--framework <name>", "target framework").requiredOption("--component-type <type>", "component type").requiredOption("--provider <name>", "AI provider (openai)").option("--api-key <key>", "API key for provider").option("--model <name>", "model name").option("--temperature <n>", "sampling temperature", parseFloat, 0.7).option("--output-dir <path>", "output directory", ".slopbrick/corpus/generated").action(async (cmdOptions) => {
@@ -57352,8 +58428,8 @@ var import_node_fs43 = require("fs");
57352
58428
  var import_node_path67 = require("path");
57353
58429
  init_logger();
57354
58430
  init_builtins();
57355
- init_scan();
57356
- init_config();
58431
+ init_scan2();
58432
+ init_config2();
57357
58433
  init_threshold();
57358
58434
  init_git();
57359
58435
  init_cache();
@@ -57834,7 +58910,7 @@ function registerScan(program, scanAction) {
57834
58910
  }
57835
58911
 
57836
58912
  // src/cli/program.ts
57837
- init_config();
58913
+ init_config2();
57838
58914
  init_git();
57839
58915
  init_logger();
57840
58916
  init_unified_diff();
@@ -58400,7 +59476,7 @@ function formatGroupedHelp(cmd) {
58400
59476
  }
58401
59477
 
58402
59478
  // src/cli/program.ts
58403
- init_scan();
59479
+ init_scan2();
58404
59480
  process.on("uncaughtException", (err) => {
58405
59481
  logger.error(`Unexpected error: ${err instanceof Error ? err.message : String(err)}`);
58406
59482
  process.exit(3);