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.js CHANGED
@@ -14,15 +14,41 @@ var __export = (target, all) => {
14
14
  __defProp(target, name, { get: all[name], enumerable: true });
15
15
  };
16
16
 
17
- // src/types.ts
18
- import { createRequire } from "module";
19
- var require2, pkg, VERSION, AI_SECURITY_NUMERIC, REPOSITORY_HEALTH_WEIGHTS;
20
- var init_types = __esm({
21
- "src/types.ts"() {
17
+ // src/types/_header.ts
18
+ var VERSION;
19
+ var init_header = __esm({
20
+ "src/types/_header.ts"() {
21
+ "use strict";
22
+ VERSION = "0.18.7";
23
+ }
24
+ });
25
+
26
+ // src/types/primitives.ts
27
+ var init_primitives = __esm({
28
+ "src/types/primitives.ts"() {
29
+ "use strict";
30
+ }
31
+ });
32
+
33
+ // src/types/scan.ts
34
+ var init_scan = __esm({
35
+ "src/types/scan.ts"() {
36
+ "use strict";
37
+ }
38
+ });
39
+
40
+ // src/types/config.ts
41
+ var init_config = __esm({
42
+ "src/types/config.ts"() {
43
+ "use strict";
44
+ }
45
+ });
46
+
47
+ // src/types/report.ts
48
+ var AI_SECURITY_NUMERIC, REPOSITORY_HEALTH_WEIGHTS;
49
+ var init_report = __esm({
50
+ "src/types/report.ts"() {
22
51
  "use strict";
23
- require2 = createRequire(import.meta.url);
24
- pkg = require2("../package.json");
25
- VERSION = pkg.version;
26
52
  AI_SECURITY_NUMERIC = {
27
53
  low: 100,
28
54
  medium: 75,
@@ -42,6 +68,34 @@ var init_types = __esm({
42
68
  }
43
69
  });
44
70
 
71
+ // src/types/project-report.ts
72
+ var init_project_report = __esm({
73
+ "src/types/project-report.ts"() {
74
+ "use strict";
75
+ }
76
+ });
77
+
78
+ // src/types/baseline.ts
79
+ var init_baseline = __esm({
80
+ "src/types/baseline.ts"() {
81
+ "use strict";
82
+ }
83
+ });
84
+
85
+ // src/types/index.ts
86
+ var init_types = __esm({
87
+ "src/types/index.ts"() {
88
+ "use strict";
89
+ init_header();
90
+ init_primitives();
91
+ init_scan();
92
+ init_config();
93
+ init_report();
94
+ init_project_report();
95
+ init_baseline();
96
+ }
97
+ });
98
+
45
99
  // src/config/defaults.ts
46
100
  var DEFAULT_SPACING_SCALE, DEFAULT_RADIUS_SCALE, DEFAULT_RULE_CONFIG, DEFAULT_CONFIG;
47
101
  var init_defaults = __esm({
@@ -295,9 +349,9 @@ function findWorkspacePackages(cwd) {
295
349
  const pkgPath = join(root, "package.json");
296
350
  if (existsSync(pkgPath)) {
297
351
  try {
298
- const pkg2 = JSON.parse(readFileSync(pkgPath, "utf-8"));
299
- if (Array.isArray(pkg2.workspaces)) {
300
- for (const pattern of pkg2.workspaces) {
352
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
353
+ if (Array.isArray(pkg.workspaces)) {
354
+ for (const pattern of pkg.workspaces) {
301
355
  packages.push(...expandWorkspacePattern(root, pattern));
302
356
  }
303
357
  }
@@ -355,11 +409,11 @@ function detectStylingSolution(cwd) {
355
409
  let deps = {};
356
410
  if (existsSync2(pkgPath)) {
357
411
  try {
358
- const pkg2 = JSON.parse(readFileSync2(pkgPath, "utf-8"));
412
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
359
413
  deps = {
360
- ...pkg2.dependencies,
361
- ...pkg2.devDependencies,
362
- ...pkg2.peerDependencies
414
+ ...pkg.dependencies,
415
+ ...pkg.devDependencies,
416
+ ...pkg.peerDependencies
363
417
  };
364
418
  } catch {
365
419
  }
@@ -558,11 +612,11 @@ function detectStack(cwd) {
558
612
  return {};
559
613
  }
560
614
  try {
561
- const pkg2 = JSON.parse(readFileSync3(pkgPath, "utf-8"));
615
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
562
616
  const deps = {
563
- ...pkg2.dependencies,
564
- ...pkg2.devDependencies,
565
- ...pkg2.peerDependencies
617
+ ...pkg.dependencies,
618
+ ...pkg.devDependencies,
619
+ ...pkg.peerDependencies
566
620
  };
567
621
  const names = Object.keys(deps).map((name) => name.toLowerCase());
568
622
  const result = {};
@@ -26195,13 +26249,13 @@ var init_default_react_stack = __esm({
26195
26249
  for (const m of source.matchAll(IMPORT_LINE_RE)) {
26196
26250
  const spec = m[1];
26197
26251
  if (!spec) continue;
26198
- const pkg2 = spec.startsWith("@/") ? spec : spec.startsWith("@") ? spec.split("/").slice(0, 2).join("/") : spec.split("/")[0];
26199
- if (pkg2) importedPackages.add(pkg2);
26252
+ const pkg = spec.startsWith("@/") ? spec : spec.startsWith("@") ? spec.split("/").slice(0, 2).join("/") : spec.split("/")[0];
26253
+ if (pkg) importedPackages.add(pkg);
26200
26254
  }
26201
26255
  const hits = [];
26202
- for (const pkg2 of importedPackages) {
26203
- if (DEFAULT_STACK_PACKAGES.some((p) => p === pkg2 || p.startsWith(pkg2 + "/"))) {
26204
- hits.push(pkg2);
26256
+ for (const pkg of importedPackages) {
26257
+ if (DEFAULT_STACK_PACKAGES.some((p) => p === pkg || p.startsWith(pkg + "/"))) {
26258
+ hits.push(pkg);
26205
26259
  }
26206
26260
  }
26207
26261
  if (hits.length < MIN_HITS) return [];
@@ -28091,6 +28145,205 @@ var init_sql_concat = __esm({
28091
28145
  }
28092
28146
  });
28093
28147
 
28148
+ // src/rules/dead/dead-branch.ts
28149
+ var deadBranchRule;
28150
+ var init_dead_branch = __esm({
28151
+ "src/rules/dead/dead-branch.ts"() {
28152
+ "use strict";
28153
+ init_rule();
28154
+ deadBranchRule = createRule({
28155
+ id: "dead/dead-branch",
28156
+ category: "logic",
28157
+ severity: "medium",
28158
+ aiSpecific: true,
28159
+ description: "Literal boolean condition makes one branch statically dead",
28160
+ create(_context) {
28161
+ return {};
28162
+ },
28163
+ analyze(_context, facts) {
28164
+ const issues = [];
28165
+ if (!facts.v2) return issues;
28166
+ for (const cond of facts.v2.deadCode.constantConditions) {
28167
+ const isWhileTrue = cond.kind === "while-true";
28168
+ issues.push({
28169
+ ruleId: "dead/dead-branch",
28170
+ category: "logic",
28171
+ severity: isWhileTrue ? "low" : "medium",
28172
+ aiSpecific: true,
28173
+ message: isWhileTrue ? `Infinite loop with literal condition (${cond.kind})` : `Dead branch: condition is always ${cond.condition}`,
28174
+ line: cond.line,
28175
+ column: cond.column,
28176
+ 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.`
28177
+ });
28178
+ }
28179
+ return issues;
28180
+ }
28181
+ });
28182
+ }
28183
+ });
28184
+
28185
+ // src/rules/dead/unreachable.ts
28186
+ var unreachableRule;
28187
+ var init_unreachable = __esm({
28188
+ "src/rules/dead/unreachable.ts"() {
28189
+ "use strict";
28190
+ init_rule();
28191
+ unreachableRule = createRule({
28192
+ id: "dead/unreachable",
28193
+ category: "logic",
28194
+ severity: "high",
28195
+ aiSpecific: true,
28196
+ description: "Statement is unreachable after an unconditional return/throw/break/continue",
28197
+ create(_context) {
28198
+ return {};
28199
+ },
28200
+ analyze(_context, facts) {
28201
+ const issues = [];
28202
+ if (!facts.v2) return issues;
28203
+ for (const u of facts.v2.deadCode.unreachableStatements) {
28204
+ if (u.snippet === "<unreachable>") continue;
28205
+ issues.push({
28206
+ ruleId: "dead/unreachable",
28207
+ category: "logic",
28208
+ severity: "high",
28209
+ aiSpecific: true,
28210
+ message: `Unreachable after ${u.terminator}: ${u.snippet}`,
28211
+ line: u.line,
28212
+ column: u.column,
28213
+ 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.`
28214
+ });
28215
+ }
28216
+ return issues;
28217
+ }
28218
+ });
28219
+ }
28220
+ });
28221
+
28222
+ // src/rules/dead/unused-import.ts
28223
+ var unusedImportRule;
28224
+ var init_unused_import = __esm({
28225
+ "src/rules/dead/unused-import.ts"() {
28226
+ "use strict";
28227
+ init_rule();
28228
+ unusedImportRule = createRule({
28229
+ id: "dead/unused-import",
28230
+ category: "logic",
28231
+ severity: "low",
28232
+ aiSpecific: true,
28233
+ description: "ES module import is never referenced in the file",
28234
+ create(_context) {
28235
+ return {};
28236
+ },
28237
+ analyze(_context, facts) {
28238
+ const issues = [];
28239
+ if (!facts.v2) return issues;
28240
+ for (const binding of facts.v2.deadCode.bindings) {
28241
+ if (binding.kind !== "import-specifier" && binding.kind !== "import-default" && binding.kind !== "import-namespace") {
28242
+ continue;
28243
+ }
28244
+ if (binding.isReferenced) continue;
28245
+ if (!binding.name) continue;
28246
+ const source = binding.source ? ` from '${binding.source}'` : "";
28247
+ issues.push({
28248
+ ruleId: "dead/unused-import",
28249
+ category: "logic",
28250
+ severity: "low",
28251
+ aiSpecific: true,
28252
+ message: `Unused import: '${binding.name}'${source}`,
28253
+ line: binding.line,
28254
+ column: binding.column,
28255
+ 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.`
28256
+ });
28257
+ }
28258
+ return issues;
28259
+ }
28260
+ });
28261
+ }
28262
+ });
28263
+
28264
+ // src/rules/dead/unused-local.ts
28265
+ var SKIP_NAMES, unusedLocalRule;
28266
+ var init_unused_local = __esm({
28267
+ "src/rules/dead/unused-local.ts"() {
28268
+ "use strict";
28269
+ init_rule();
28270
+ SKIP_NAMES = /* @__PURE__ */ new Set(["React", "_"]);
28271
+ unusedLocalRule = createRule({
28272
+ id: "dead/unused-local",
28273
+ category: "logic",
28274
+ severity: "low",
28275
+ aiSpecific: true,
28276
+ description: "Variable is declared but never read after declaration",
28277
+ create(_context) {
28278
+ return {};
28279
+ },
28280
+ analyze(_context, facts) {
28281
+ const issues = [];
28282
+ if (!facts.v2) return issues;
28283
+ for (const binding of facts.v2.deadCode.bindings) {
28284
+ 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") {
28285
+ continue;
28286
+ }
28287
+ if (binding.isReferenced) continue;
28288
+ if (SKIP_NAMES.has(binding.name)) continue;
28289
+ if (binding.name.startsWith("_")) continue;
28290
+ issues.push({
28291
+ ruleId: "dead/unused-local",
28292
+ category: "logic",
28293
+ severity: "low",
28294
+ aiSpecific: true,
28295
+ message: `Unused ${binding.kind}: '${binding.name}'`,
28296
+ line: binding.line,
28297
+ column: binding.column,
28298
+ 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.`
28299
+ });
28300
+ }
28301
+ return issues;
28302
+ }
28303
+ });
28304
+ }
28305
+ });
28306
+
28307
+ // src/rules/dead/unused-parameter.ts
28308
+ var unusedParameterRule;
28309
+ var init_unused_parameter = __esm({
28310
+ "src/rules/dead/unused-parameter.ts"() {
28311
+ "use strict";
28312
+ init_rule();
28313
+ unusedParameterRule = createRule({
28314
+ id: "dead/unused-parameter",
28315
+ category: "logic",
28316
+ severity: "low",
28317
+ aiSpecific: true,
28318
+ description: "Function parameter is declared but never read",
28319
+ create(_context) {
28320
+ return {};
28321
+ },
28322
+ analyze(_context, facts) {
28323
+ const issues = [];
28324
+ if (!facts.v2) return issues;
28325
+ for (const binding of facts.v2.deadCode.bindings) {
28326
+ if (binding.kind !== "parameter") continue;
28327
+ if (binding.isReferenced) continue;
28328
+ if (binding.name.startsWith("_")) continue;
28329
+ if (binding.name === "props") continue;
28330
+ issues.push({
28331
+ ruleId: "dead/unused-parameter",
28332
+ category: "logic",
28333
+ severity: "low",
28334
+ aiSpecific: true,
28335
+ message: `Unused parameter: '${binding.name}'`,
28336
+ line: binding.line,
28337
+ column: binding.column,
28338
+ 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.`
28339
+ });
28340
+ }
28341
+ return issues;
28342
+ }
28343
+ });
28344
+ }
28345
+ });
28346
+
28094
28347
  // src/engine/discover.ts
28095
28348
  var discover_exports = {};
28096
28349
  __export(discover_exports, {
@@ -28202,14 +28455,14 @@ import { join as join5 } from "path";
28202
28455
  function readDeps(cwd) {
28203
28456
  const pkgPath = join5(cwd, "package.json");
28204
28457
  if (!existsSync4(pkgPath)) return /* @__PURE__ */ new Set();
28205
- let pkg2;
28458
+ let pkg;
28206
28459
  try {
28207
- pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
28460
+ pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
28208
28461
  } catch {
28209
28462
  return /* @__PURE__ */ new Set();
28210
28463
  }
28211
28464
  const out = /* @__PURE__ */ new Set();
28212
- for (const source of [pkg2.dependencies, pkg2.devDependencies]) {
28465
+ for (const source of [pkg.dependencies, pkg.devDependencies]) {
28213
28466
  if (source && typeof source === "object") {
28214
28467
  for (const name of Object.keys(source)) out.add(name);
28215
28468
  }
@@ -28225,8 +28478,8 @@ function detectConstitution(cwd) {
28225
28478
  const deps = readDeps(cwd);
28226
28479
  if (deps.size === 0) return {};
28227
28480
  const out = {};
28228
- for (const [pkg2, { field, signal }] of Object.entries(CONSTITUTION_SIGNALS)) {
28229
- if (deps.has(pkg2)) {
28481
+ for (const [pkg, { field, signal }] of Object.entries(CONSTITUTION_SIGNALS)) {
28482
+ if (deps.has(pkg)) {
28230
28483
  out[field] = pushUnique(
28231
28484
  out[field],
28232
28485
  signal
@@ -29494,6 +29747,9 @@ var init_expired_code_example = __esm({
29494
29747
  const issues = [];
29495
29748
  const source = facts.v2?._source;
29496
29749
  if (!source) return issues;
29750
+ const packages = declaredPackages(context.cwd);
29751
+ const packageName = context.packageName;
29752
+ if (packageName) packages.add(packageName);
29497
29753
  const blocks = extractFencedCodeBlocks(source);
29498
29754
  for (const block of blocks) {
29499
29755
  if (!CODE_LANGS.has(block.lang)) continue;
@@ -29501,7 +29757,7 @@ var init_expired_code_example = __esm({
29501
29757
  const imports = extractImports(block.body);
29502
29758
  for (const imp of imports) {
29503
29759
  const pkgName = stripSubpath(imp);
29504
- if (context.packages.has(pkgName)) continue;
29760
+ if (packages.has(pkgName)) continue;
29505
29761
  issues.push({
29506
29762
  ruleId: "docs/expired-code-example",
29507
29763
  category: "docs",
@@ -29556,7 +29812,12 @@ function collectExports(cwd) {
29556
29812
  /\bexport\s+class\s+([A-Za-z_$][\w$]*)/g,
29557
29813
  /\bexport\s+interface\s+([A-Za-z_$][\w$]*)/g,
29558
29814
  /\bexport\s+type\s+([A-Za-z_$][\w$]*)/g,
29559
- /\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g
29815
+ /\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g,
29816
+ // v0.18.6: also collect field names from `export interface` and
29817
+ // `export type` declarations. Without this, fields like
29818
+ // `crossFileDrift`, `aiQuality`, `engineeringHygiene` are
29819
+ // flagged as stale even though they're valid type fields.
29820
+ /^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\s*[?:]/gm
29560
29821
  ]) {
29561
29822
  let m;
29562
29823
  while ((m = re.exec(source)) !== null) {
@@ -29567,6 +29828,21 @@ function collectExports(cwd) {
29567
29828
  }
29568
29829
  return out;
29569
29830
  }
29831
+ function looksLikeProseLabel(inside) {
29832
+ const trimmed = inside.trim();
29833
+ if (trimmed.length === 0) return false;
29834
+ if (trimmed.startsWith("`") || trimmed.startsWith("/")) return true;
29835
+ if (trimmed.includes("`")) return true;
29836
+ if (/\d+\s+[a-z]/i.test(trimmed)) return true;
29837
+ const parts = trimmed.split(",").map((s) => s.trim());
29838
+ if (parts.length >= 3) {
29839
+ const allNumeric = parts.every((p) => /^\d+\.?\d*$/.test(p));
29840
+ if (!allNumeric) return true;
29841
+ }
29842
+ if (trimmed.length > 40 && !trimmed.includes(",")) return true;
29843
+ if (trimmed.includes("\u2014") || trimmed.includes("\u2013")) return true;
29844
+ return false;
29845
+ }
29570
29846
  var RESERVED, SOURCE_EXTS, SOURCE_ROOTS, CAP, staleFunctionReferenceRule;
29571
29847
  var init_stale_function_reference = __esm({
29572
29848
  "src/rules/docs/stale-function-reference.ts"() {
@@ -29574,6 +29850,7 @@ var init_stale_function_reference = __esm({
29574
29850
  init_rule();
29575
29851
  init_doc_freshness();
29576
29852
  RESERVED = /* @__PURE__ */ new Set([
29853
+ // JS reserved words
29577
29854
  "true",
29578
29855
  "false",
29579
29856
  "null",
@@ -29673,7 +29950,472 @@ var init_stale_function_reference = __esm({
29673
29950
  "next",
29674
29951
  "vue",
29675
29952
  "angular",
29676
- "svelte"
29953
+ "svelte",
29954
+ // Framework / runtime names
29955
+ "html",
29956
+ "astro",
29957
+ "python",
29958
+ "jvm",
29959
+ "kotlin",
29960
+ "swift",
29961
+ "dart",
29962
+ "ruby",
29963
+ "rust",
29964
+ "cpp",
29965
+ "go",
29966
+ "java",
29967
+ "php",
29968
+ "php-html",
29969
+ "csharp",
29970
+ "typescript",
29971
+ "javascript",
29972
+ "jsx",
29973
+ "tsx",
29974
+ "mjs",
29975
+ "cjs",
29976
+ "esnext",
29977
+ "es6",
29978
+ "es2022",
29979
+ "es2023",
29980
+ "esm",
29981
+ "cjs",
29982
+ "umd",
29983
+ "amd",
29984
+ "commonjs",
29985
+ "require",
29986
+ "module",
29987
+ "exports",
29988
+ "define",
29989
+ "global",
29990
+ "window",
29991
+ "document",
29992
+ "process",
29993
+ "console",
29994
+ "buffer",
29995
+ "stream",
29996
+ "fetch",
29997
+ "axios",
29998
+ "express",
29999
+ "fastify",
30000
+ "koa",
30001
+ "hapi",
30002
+ "nextjs",
30003
+ "nuxt",
30004
+ "remix",
30005
+ "gatsby",
30006
+ "sveltekit",
30007
+ "solid",
30008
+ "preact",
30009
+ "qwik",
30010
+ "lit",
30011
+ "stencil",
30012
+ "marko",
30013
+ "alpine",
30014
+ "stimulus",
30015
+ "turbo",
30016
+ "hotwire",
30017
+ // Models / providers
30018
+ "gpt",
30019
+ "claude",
30020
+ "gpt-3",
30021
+ "gpt-3.5",
30022
+ "gpt-4",
30023
+ "gpt-oss",
30024
+ "haiku",
30025
+ "sonnet",
30026
+ "opus",
30027
+ "aider",
30028
+ "tabby",
30029
+ "copilot",
30030
+ "cursor",
30031
+ "windsurf",
30032
+ "devin",
30033
+ "claude-code",
30034
+ // LLM-detection lingo
30035
+ "heuristic",
30036
+ "heuristics",
30037
+ "calibrate",
30038
+ "calibration",
30039
+ "calibrator",
30040
+ "corpus",
30041
+ "baseline",
30042
+ "baselines",
30043
+ "corpus-baselines",
30044
+ "lift",
30045
+ "recall",
30046
+ "precision",
30047
+ "fpRate",
30048
+ "ratio",
30049
+ "verdict",
30050
+ "USEFUL",
30051
+ "NOISY",
30052
+ "INVERTED",
30053
+ "HYGIENE",
30054
+ "DORMANT",
30055
+ "OK",
30056
+ "aiSpecific",
30057
+ "defaultOff",
30058
+ // Common slop-audit verbs/nouns
30059
+ "commit",
30060
+ "push",
30061
+ "reset",
30062
+ "rebase",
30063
+ "merge",
30064
+ "cherry-pick",
30065
+ "revert",
30066
+ "scan",
30067
+ "parse",
30068
+ "build",
30069
+ "test",
30070
+ "lint",
30071
+ "format",
30072
+ "check",
30073
+ "audit",
30074
+ "fix",
30075
+ "patch",
30076
+ "diff",
30077
+ "pr",
30078
+ "ci",
30079
+ "cd",
30080
+ "gh",
30081
+ "npm",
30082
+ "npx",
30083
+ "pnpm",
30084
+ "yaml",
30085
+ "json",
30086
+ "toml",
30087
+ "csv",
30088
+ "md",
30089
+ "mdx",
30090
+ "sh",
30091
+ "bash",
30092
+ "zsh",
30093
+ "fish",
30094
+ "ascii",
30095
+ "utf8",
30096
+ "utf-8",
30097
+ "base64",
30098
+ "hex",
30099
+ "binary",
30100
+ "text",
30101
+ // Common design / ui terms
30102
+ "flex",
30103
+ "grid",
30104
+ "auto",
30105
+ "min",
30106
+ "max",
30107
+ "fill",
30108
+ "stretch",
30109
+ "wrap",
30110
+ "nowrap",
30111
+ "inline",
30112
+ "block",
30113
+ "hidden",
30114
+ "visible",
30115
+ "static",
30116
+ "fixed",
30117
+ "absolute",
30118
+ "relative",
30119
+ "sticky",
30120
+ "pointer",
30121
+ "cursor",
30122
+ "focus",
30123
+ "hover",
30124
+ "active",
30125
+ "disabled",
30126
+ "readonly",
30127
+ "primary",
30128
+ "secondary",
30129
+ "tertiary",
30130
+ "success",
30131
+ "warning",
30132
+ "danger",
30133
+ "info",
30134
+ "muted",
30135
+ "sm",
30136
+ "md",
30137
+ "lg",
30138
+ "xl",
30139
+ "xxl",
30140
+ "xs",
30141
+ "2xl",
30142
+ "3xl",
30143
+ "4xl",
30144
+ // Math / types
30145
+ "array",
30146
+ "map",
30147
+ "set",
30148
+ "weakmap",
30149
+ "weakset",
30150
+ "object",
30151
+ "string",
30152
+ "number",
30153
+ "boolean",
30154
+ "bigint",
30155
+ "symbol",
30156
+ "null",
30157
+ "undefined",
30158
+ "any",
30159
+ "unknown",
30160
+ "never",
30161
+ "void",
30162
+ "readonly",
30163
+ "private",
30164
+ "public",
30165
+ "protected",
30166
+ "static",
30167
+ "abstract",
30168
+ "async",
30169
+ "generator",
30170
+ "iterator",
30171
+ "iterable",
30172
+ "promise",
30173
+ "observable",
30174
+ // Auth / domain
30175
+ "admin",
30176
+ "user",
30177
+ "guest",
30178
+ "anonymous",
30179
+ "authenticated",
30180
+ "unauthenticated",
30181
+ "jwt",
30182
+ "oauth",
30183
+ "oidc",
30184
+ "saml",
30185
+ "csrf",
30186
+ "xss",
30187
+ "sql",
30188
+ "nosql",
30189
+ "orm",
30190
+ "prisma",
30191
+ "drizzle",
30192
+ "sequelize",
30193
+ "mongoose",
30194
+ "redis",
30195
+ "postgres",
30196
+ "mysql",
30197
+ "sqlite",
30198
+ "kafka",
30199
+ "rabbitmq",
30200
+ "graphql",
30201
+ "rest",
30202
+ "grpc",
30203
+ "websocket",
30204
+ // slop-audit specific
30205
+ "slopbrick",
30206
+ "usebrick",
30207
+ "deadcode",
30208
+ "unused",
30209
+ "orphan",
30210
+ "zombie",
30211
+ "blocker",
30212
+ "warning",
30213
+ "info",
30214
+ "error",
30215
+ "verbose",
30216
+ "debug",
30217
+ "silly",
30218
+ "p50",
30219
+ "p90",
30220
+ "p95",
30221
+ "p99",
30222
+ "min",
30223
+ "max",
30224
+ "avg",
30225
+ "mean",
30226
+ "median",
30227
+ "ratchet",
30228
+ "tier",
30229
+ "composite",
30230
+ "fitness",
30231
+ "fpr",
30232
+ "tpr",
30233
+ "roc",
30234
+ "should",
30235
+ "could",
30236
+ "would",
30237
+ "might",
30238
+ "must",
30239
+ "shall",
30240
+ "may",
30241
+ "can",
30242
+ "todo",
30243
+ "fixme",
30244
+ "xxx",
30245
+ "hack",
30246
+ "note",
30247
+ "warning",
30248
+ "attention",
30249
+ "h1",
30250
+ "h2",
30251
+ "h3",
30252
+ "h4",
30253
+ "h5",
30254
+ "h6",
30255
+ "strong",
30256
+ "em",
30257
+ "b",
30258
+ "i",
30259
+ "u",
30260
+ "true",
30261
+ "false",
30262
+ "yes",
30263
+ "no",
30264
+ "on",
30265
+ "off",
30266
+ "enable",
30267
+ "disable",
30268
+ "ltr",
30269
+ "rtl",
30270
+ "auto",
30271
+ "start",
30272
+ "end",
30273
+ "center",
30274
+ "baseline",
30275
+ "stretch",
30276
+ "rounded",
30277
+ "sharp",
30278
+ "outline",
30279
+ "ghost",
30280
+ "link",
30281
+ "filled",
30282
+ "row",
30283
+ "col",
30284
+ "gap",
30285
+ "pad",
30286
+ "margin",
30287
+ "padding",
30288
+ "border",
30289
+ "shadow",
30290
+ "transparent",
30291
+ "currentcolor",
30292
+ "inherit",
30293
+ "initial",
30294
+ "unset",
30295
+ "revert",
30296
+ "hover",
30297
+ "focus",
30298
+ "active",
30299
+ "disabled",
30300
+ "checked",
30301
+ "indeterminate",
30302
+ "open",
30303
+ "close",
30304
+ "expanded",
30305
+ "collapsed",
30306
+ "selected",
30307
+ "pressed",
30308
+ // Web/CSS
30309
+ "div",
30310
+ "span",
30311
+ "p",
30312
+ "a",
30313
+ "img",
30314
+ "ul",
30315
+ "ol",
30316
+ "li",
30317
+ "table",
30318
+ "tr",
30319
+ "td",
30320
+ "th",
30321
+ "thead",
30322
+ "tbody",
30323
+ "tfoot",
30324
+ "caption",
30325
+ "figure",
30326
+ "figcaption",
30327
+ "main",
30328
+ "section",
30329
+ "article",
30330
+ "aside",
30331
+ "header",
30332
+ "footer",
30333
+ "nav",
30334
+ "form",
30335
+ "input",
30336
+ "button",
30337
+ "select",
30338
+ "option",
30339
+ "textarea",
30340
+ "label",
30341
+ "fieldset",
30342
+ "legend",
30343
+ "details",
30344
+ "summary",
30345
+ "dialog",
30346
+ "menu",
30347
+ "menuitem",
30348
+ "template",
30349
+ "slot",
30350
+ "picture",
30351
+ "source",
30352
+ "track",
30353
+ "video",
30354
+ "audio",
30355
+ "canvas",
30356
+ "svg",
30357
+ "iframe",
30358
+ "embed",
30359
+ "object",
30360
+ "portal",
30361
+ // Common business terms
30362
+ "api",
30363
+ "cli",
30364
+ "ui",
30365
+ "ux",
30366
+ "sdk",
30367
+ "ide",
30368
+ "cli",
30369
+ "docs",
30370
+ "doc",
30371
+ "blog",
30372
+ "post",
30373
+ "page",
30374
+ "view",
30375
+ "tab",
30376
+ "panel",
30377
+ "card",
30378
+ "list",
30379
+ "grid",
30380
+ "form",
30381
+ "modal",
30382
+ "menu",
30383
+ "button",
30384
+ "icon",
30385
+ "avatar",
30386
+ "badge",
30387
+ "chip",
30388
+ "tooltip",
30389
+ "popover",
30390
+ "dropdown",
30391
+ "banner",
30392
+ "alert",
30393
+ "toast",
30394
+ "notification",
30395
+ "drawer",
30396
+ "sidebar",
30397
+ "navbar",
30398
+ "header",
30399
+ "footer",
30400
+ "hero",
30401
+ "cta",
30402
+ "cta-primary",
30403
+ "cta-secondary",
30404
+ "pricing",
30405
+ "price",
30406
+ "cost",
30407
+ "rate",
30408
+ "percent",
30409
+ "pct",
30410
+ "count",
30411
+ "total",
30412
+ "small",
30413
+ "medium",
30414
+ "large",
30415
+ "xl",
30416
+ "xxl",
30417
+ "tiny",
30418
+ "huge"
29677
30419
  ]);
29678
30420
  SOURCE_EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
29679
30421
  SOURCE_ROOTS = ["src", "lib", "app", "components"];
@@ -29697,8 +30439,39 @@ var init_stale_function_reference = __esm({
29697
30439
  if (text.length < 3) continue;
29698
30440
  if (RESERVED.has(text.toLowerCase())) continue;
29699
30441
  if (context.exports.has(text)) continue;
29700
- const end = Math.min(source.length, span.index + text.length + 50);
29701
- if (!/\(/.test(source.slice(span.index, end))) continue;
30442
+ const lineEnd = source.indexOf("\n", span.index);
30443
+ const restOfLine = source.slice(
30444
+ span.index,
30445
+ lineEnd === -1 ? source.length : lineEnd
30446
+ );
30447
+ const closeTick = restOfLine.indexOf("`", 1);
30448
+ if (closeTick === -1) continue;
30449
+ const afterTick = restOfLine.slice(closeTick + 1);
30450
+ const directCall = /^\s*\(/.test(afterTick);
30451
+ let identifierRepeats = false;
30452
+ if (!directCall) {
30453
+ const afterSpan = restOfLine.slice(closeTick + 1);
30454
+ const needle = text + "(";
30455
+ identifierRepeats = afterSpan.indexOf(needle) !== -1;
30456
+ }
30457
+ if (!directCall && !identifierRepeats) continue;
30458
+ const beforeTickIdx = span.index - 1;
30459
+ const beforeChar = beforeTickIdx >= 0 ? source[beforeTickIdx] : "";
30460
+ if (beforeChar === "." || beforeChar === "|") continue;
30461
+ const parenStart = restOfLine.indexOf("(", closeTick);
30462
+ const parenEnd = restOfLine.indexOf(")", parenStart);
30463
+ if (parenStart !== -1 && parenEnd !== -1) {
30464
+ const inside = restOfLine.slice(parenStart + 1, parenEnd);
30465
+ const trimmed = inside.trim();
30466
+ if (looksLikeProseLabel(inside)) continue;
30467
+ 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(
30468
+ inside
30469
+ ) || // Short single-word label (≤ 24 chars, no `,`,
30470
+ // doesn't look like a function arg). Real function
30471
+ // calls are usually longer or contain commas.
30472
+ trimmed.length > 0 && trimmed.length <= 24 && !trimmed.includes(",") && /[a-zA-Z]/.test(trimmed));
30473
+ if (looksLikeTypeAnnotation) continue;
30474
+ }
29702
30475
  issues.push({
29703
30476
  ruleId: "docs/stale-function-reference",
29704
30477
  category: "docs",
@@ -29778,7 +30551,33 @@ var init_stale_package_reference = __esm({
29778
30551
  "jsx",
29779
30552
  "ok",
29780
30553
  "no",
29781
- "yes"
30554
+ "yes",
30555
+ // v0.18.6: common English adjectives / adverbs that frequently
30556
+ // appear in backticked prose but are not package names.
30557
+ "aspirational",
30558
+ "concrete",
30559
+ "abstract",
30560
+ "inline",
30561
+ "exposed",
30562
+ "deprecated",
30563
+ "experimental",
30564
+ "stable",
30565
+ "beta",
30566
+ "alpha",
30567
+ "wip",
30568
+ "draft",
30569
+ "final",
30570
+ "shim",
30571
+ "polyfill",
30572
+ "stub",
30573
+ "mock",
30574
+ "fake",
30575
+ "real",
30576
+ "false",
30577
+ "true",
30578
+ "optional",
30579
+ "required",
30580
+ "default"
29782
30581
  ]);
29783
30582
  stalePackageReferenceRule = createRule({
29784
30583
  id: "docs/stale-package-reference",
@@ -29920,9 +30719,9 @@ function declaredPackages(cwd) {
29920
30719
  if (!existsSync6(pkgPath)) return out;
29921
30720
  try {
29922
30721
  const raw = readFileSync8(pkgPath, "utf-8");
29923
- const pkg2 = JSON.parse(raw);
30722
+ const pkg = JSON.parse(raw);
29924
30723
  for (const k of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
29925
- const v = pkg2[k];
30724
+ const v = pkg[k];
29926
30725
  if (v && typeof v === "object") {
29927
30726
  for (const name of Object.keys(v)) {
29928
30727
  out.add(name);
@@ -30003,7 +30802,15 @@ async function buildDocFreshness(cwd, config, options = {}) {
30003
30802
  continue;
30004
30803
  }
30005
30804
  const relPath = relative2(cwd, abs);
30006
- const context = { config, filePath: relPath, cwd };
30805
+ let packageName;
30806
+ try {
30807
+ const pkg = JSON.parse(
30808
+ readFileSync8(join7(cwd, "package.json"), "utf-8")
30809
+ );
30810
+ packageName = pkg.name;
30811
+ } catch {
30812
+ }
30813
+ const context = { config, filePath: relPath, cwd, packageName };
30007
30814
  const facts = { filePath: relPath, v2: { _source: source } };
30008
30815
  const ruleConfigs = [
30009
30816
  { rule: stalePackageReferenceRule, ruleId: "docs/stale-package-reference" },
@@ -30116,7 +30923,9 @@ var init_broken_link = __esm({
30116
30923
  if (target.startsWith("#")) continue;
30117
30924
  if (target.startsWith("//")) continue;
30118
30925
  if (target.startsWith("/")) continue;
30119
- const resolved = join8(docDir, target);
30926
+ const filePart = target.split("#")[0] ?? target;
30927
+ if (filePart === "") continue;
30928
+ const resolved = join8(docDir, filePart);
30120
30929
  if (existsSync7(resolved)) continue;
30121
30930
  issues.push({
30122
30931
  ruleId: "docs/broken-link",
@@ -30349,7 +31158,7 @@ var init_spacing_grid = __esm({
30349
31158
  "use strict";
30350
31159
  init_rule();
30351
31160
  init_utils();
30352
- init_config();
31161
+ init_config2();
30353
31162
  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)-(.+)$/;
30354
31163
  ARBITRARY_VALUE_RE2 = /^(-?\d+(?:\.\d+)?)(px|rem)$/;
30355
31164
  SKIP_VALUES = /* @__PURE__ */ new Set(["auto", "full", "screen", "min", "max", "fit", "none"]);
@@ -42437,6 +43246,78 @@ function extractStateBinding(node, lineOffsets) {
42437
43246
  setterReferenced: false
42438
43247
  };
42439
43248
  }
43249
+ function findUnreachableStatements(ast, source, lineOffsets) {
43250
+ const out = [];
43251
+ function isTerminator(node) {
43252
+ if (node.type === "ReturnStatement") return "return";
43253
+ if (node.type === "ThrowStatement") return "throw";
43254
+ if (node.type === "BreakStatement") return "break";
43255
+ if (node.type === "ContinueStatement") return "continue";
43256
+ return null;
43257
+ }
43258
+ function snippetFor(node, src) {
43259
+ const span = node.span;
43260
+ if (!span || typeof span.start !== "number" || typeof span.end !== "number") {
43261
+ return "<unreachable>";
43262
+ }
43263
+ const text = src.slice(span.start, Math.min(span.end, span.start + 60));
43264
+ return text.replace(/\s+/g, " ").trim() || "<unreachable>";
43265
+ }
43266
+ function lineColumn(offset) {
43267
+ let lo = 0;
43268
+ let hi = lineOffsets.length - 1;
43269
+ while (lo < hi) {
43270
+ const mid = lo + hi + 1 >>> 1;
43271
+ const midVal = lineOffsets[mid];
43272
+ const loVal = lineOffsets[lo];
43273
+ if (midVal === void 0) break;
43274
+ if (midVal <= offset) lo = mid;
43275
+ else hi = mid - 1;
43276
+ void loVal;
43277
+ }
43278
+ const baseOffset = lineOffsets[lo] ?? 0;
43279
+ return { line: lo + 1, column: offset - baseOffset };
43280
+ }
43281
+ function visitBody(body) {
43282
+ if (!Array.isArray(body)) return;
43283
+ let lastTerminator = null;
43284
+ for (const stmt of body) {
43285
+ if (!isObject(stmt)) continue;
43286
+ if (lastTerminator && stmt.type !== "EmptyStatement") {
43287
+ const span = stmt.span;
43288
+ const offset = typeof span?.start === "number" ? span.start : 0;
43289
+ const { line, column } = lineColumn(offset);
43290
+ out.push({
43291
+ terminator: lastTerminator,
43292
+ line,
43293
+ column,
43294
+ snippet: snippetFor(stmt, source)
43295
+ });
43296
+ }
43297
+ const t = isTerminator(stmt);
43298
+ if (t) lastTerminator = t;
43299
+ }
43300
+ }
43301
+ function walk3(node) {
43302
+ if (!isObject(node)) return;
43303
+ if (node.type === "BlockStatement") {
43304
+ visitBody(node.stmts);
43305
+ } else if (node.type === "Module") {
43306
+ visitBody(node.body);
43307
+ }
43308
+ for (const key of Object.keys(node)) {
43309
+ if (key === "parent" || key === "span" || key === "ctxt") continue;
43310
+ const child = node[key];
43311
+ if (Array.isArray(child)) {
43312
+ for (const c of child) walk3(c);
43313
+ } else {
43314
+ walk3(child);
43315
+ }
43316
+ }
43317
+ }
43318
+ walk3(ast);
43319
+ return out;
43320
+ }
42440
43321
  var init_scan_helpers = __esm({
42441
43322
  "src/engine/visitors/scan-helpers.ts"() {
42442
43323
  "use strict";
@@ -42522,15 +43403,42 @@ function handleImportDeclaration(node, _parent, _path, vctx) {
42522
43403
  if (specifier.type === "ImportDefaultSpecifier" || specifier.type === "ImportNamespaceSpecifier") {
42523
43404
  const local = specifier.local;
42524
43405
  if (isObject(local) && local.type === "Identifier" && typeof local.value === "string") {
42525
- importedNames.push(local.value);
43406
+ const name = local.value;
43407
+ importedNames.push(name);
43408
+ vctx.facts.deadCode.bindings.push({
43409
+ name,
43410
+ kind: specifier.type === "ImportDefaultSpecifier" ? "import-default" : "import-namespace",
43411
+ line,
43412
+ column,
43413
+ source,
43414
+ isReferenced: false
43415
+ });
42526
43416
  }
42527
43417
  } else if (specifier.type === "ImportSpecifier") {
42528
43418
  const imported = specifier.imported;
42529
43419
  const local = specifier.local;
42530
43420
  if (isObject(imported) && typeof imported.value === "string" && imported.value.length > 0) {
42531
- importedNames.push(imported.value);
43421
+ const name = imported.value;
43422
+ importedNames.push(name);
43423
+ vctx.facts.deadCode.bindings.push({
43424
+ name,
43425
+ kind: "import-specifier",
43426
+ line,
43427
+ column,
43428
+ source,
43429
+ isReferenced: false
43430
+ });
42532
43431
  } else if (isObject(local) && local.type === "Identifier" && typeof local.value === "string") {
42533
- importedNames.push(local.value);
43432
+ const name = local.value;
43433
+ importedNames.push(name);
43434
+ vctx.facts.deadCode.bindings.push({
43435
+ name,
43436
+ kind: "import-specifier",
43437
+ line,
43438
+ column,
43439
+ source,
43440
+ isReferenced: false
43441
+ });
42534
43442
  }
42535
43443
  }
42536
43444
  }
@@ -42620,6 +43528,13 @@ function isBindingSite(node, parent) {
42620
43528
  if (params.some((param) => containsNode(param, node))) return true;
42621
43529
  }
42622
43530
  }
43531
+ if (parent.type === "ImportSpecifier" || parent.type === "ImportDefaultSpecifier" || parent.type === "ImportNamespaceSpecifier") {
43532
+ if (parent.type === "ImportSpecifier") {
43533
+ if (parent.imported === node || parent.local === node) return true;
43534
+ } else {
43535
+ if (parent.local === node) return true;
43536
+ }
43537
+ }
42623
43538
  return false;
42624
43539
  }
42625
43540
  function isNonComputedMemberProperty(node, parent) {
@@ -42686,6 +43601,9 @@ function handleIdentifier(node, parent, path, vctx) {
42686
43601
  if (typeof node.value === "string" && !isBindingSite(node, parent) && !isNonComputedMemberProperty(node, parent)) {
42687
43602
  markStateReference(node.value, vctx);
42688
43603
  trackPropUsage(node, parent, path, vctx);
43604
+ vctx.facts.referencedNames.add(node.value);
43605
+ const top = vctx.ctx.stack[vctx.ctx.stack.length - 1];
43606
+ if (top) top.references.add(node.value);
42689
43607
  }
42690
43608
  return false;
42691
43609
  }
@@ -42826,6 +43744,20 @@ function handleVariableDeclarator(node, _parent, path, vctx) {
42826
43744
  frame.bindings.add(bindingName);
42827
43745
  }
42828
43746
  }
43747
+ if (bindingNames.length > 0) {
43748
+ const parent = node.parent;
43749
+ const kind = isObject(parent) && parent.type === "VariableDeclaration" ? String(parent.kind ?? "var") : "var";
43750
+ const { line, column } = positionFrom(id, vctx.lineOffsets);
43751
+ for (const bindingName of bindingNames) {
43752
+ vctx.facts.deadCode.bindings.push({
43753
+ name: bindingName,
43754
+ kind,
43755
+ line,
43756
+ column,
43757
+ isReferenced: false
43758
+ });
43759
+ }
43760
+ }
42829
43761
  if (isUseStateDeclarator(node)) {
42830
43762
  const binding = extractStateBinding(node, vctx.lineOffsets);
42831
43763
  if (binding) {
@@ -42845,6 +43777,36 @@ function handleVariableDeclarator(node, _parent, path, vctx) {
42845
43777
  }
42846
43778
  return true;
42847
43779
  }
43780
+ function handleIfStatement(node, _parent, _path, vctx) {
43781
+ if (!isObject(node)) return false;
43782
+ const test = node.test;
43783
+ if (!isObject(test)) return false;
43784
+ if (test.type === "BooleanLiteral" && typeof test.value === "boolean") {
43785
+ const { line, column } = positionFrom(test, vctx.lineOffsets);
43786
+ vctx.facts.deadCode.constantConditions.push({
43787
+ kind: test.value ? "if-true" : "if-false",
43788
+ condition: String(test.value),
43789
+ line,
43790
+ column
43791
+ });
43792
+ }
43793
+ return false;
43794
+ }
43795
+ function handleWhileStatement(node, _parent, _path, vctx) {
43796
+ if (!isObject(node)) return false;
43797
+ const test = node.test;
43798
+ if (!isObject(test)) return false;
43799
+ if (test.type === "BooleanLiteral" && typeof test.value === "boolean") {
43800
+ const { line, column } = positionFrom(test, vctx.lineOffsets);
43801
+ vctx.facts.deadCode.constantConditions.push({
43802
+ kind: test.value ? "while-true" : "while-false",
43803
+ condition: String(test.value),
43804
+ line,
43805
+ column
43806
+ });
43807
+ }
43808
+ return false;
43809
+ }
42848
43810
  function dispatchNode(node, parent, path, vctx) {
42849
43811
  if (!isObject(node)) return false;
42850
43812
  const type = getNodeType(node);
@@ -42869,7 +43831,9 @@ var init_dispatch = __esm({
42869
43831
  MemberExpression: handleMemberExpression,
42870
43832
  JSXAttribute: handleJSXAttribute,
42871
43833
  JSXOpeningElement: handleJSXOpeningElement,
42872
- VariableDeclarator: handleVariableDeclarator
43834
+ VariableDeclarator: handleVariableDeclarator,
43835
+ IfStatement: handleIfStatement,
43836
+ WhileStatement: handleWhileStatement
42873
43837
  };
42874
43838
  }
42875
43839
  });
@@ -43020,6 +43984,17 @@ function buildV2Facts(facts, source, ext, framework, config, templateClassNames
43020
43984
  },
43021
43985
  logic: buildLogicBlock(facts),
43022
43986
  designTokens: scanDesignTokens(facts.staticClassNames),
43987
+ // dead-code detector. Copy the internal accumulator
43988
+ // into the v2 shape, marking each binding as referenced
43989
+ // iff the file-level referenced-name set contains its name.
43990
+ deadCode: {
43991
+ bindings: facts.deadCode.bindings.map((b) => ({
43992
+ ...b,
43993
+ isReferenced: facts.referencedNames.has(b.name)
43994
+ })),
43995
+ constantConditions: facts.deadCode.constantConditions,
43996
+ unreachableStatements: facts.deadCode.unreachableStatements
43997
+ },
43023
43998
  componentSizes: facts.componentSizes.map((cs) => ({
43024
43999
  name: cs.name,
43025
44000
  lineCount: cs.lineCount,
@@ -43101,7 +44076,17 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
43101
44076
  componentSizes: [],
43102
44077
  astroComponents: [],
43103
44078
  fetchCalls: [],
43104
- optimisticUpdates: []
44079
+ optimisticUpdates: [],
44080
+ // dead-code detector. The visitor's identifier walk + import/
44081
+ // branch/return handlers populate these. The v2 builder at the
44082
+ // bottom of extractFacts() reads them and produces
44083
+ // `facts.v2.deadCode`.
44084
+ deadCode: {
44085
+ bindings: [],
44086
+ constantConditions: [],
44087
+ unreachableStatements: []
44088
+ },
44089
+ referencedNames: /* @__PURE__ */ new Set()
43105
44090
  };
43106
44091
  const ctx = {
43107
44092
  stack: [],
@@ -43188,6 +44173,16 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
43188
44173
  propBindingSet.add(bindingName);
43189
44174
  }
43190
44175
  }
44176
+ const { line: pLine, column: pCol } = positionFrom(param, lineOffsets);
44177
+ for (const bindingName of collectBindingNames2(param)) {
44178
+ facts.deadCode.bindings.push({
44179
+ name: bindingName,
44180
+ kind: "parameter",
44181
+ line: pLine,
44182
+ column: pCol,
44183
+ isReferenced: false
44184
+ });
44185
+ }
43191
44186
  }
43192
44187
  }
43193
44188
  ctx.stack.push({
@@ -43203,6 +44198,12 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
43203
44198
  propUsages: [],
43204
44199
  isComponent,
43205
44200
  bindings,
44201
+ // dead-code detector: per-frame referenced-name set.
44202
+ // Identifiers encountered inside the frame are added to this
44203
+ // set; the deadCode builder unions it with parent frames at
44204
+ // pop time so a binding is considered used if any reachable
44205
+ // scope references it.
44206
+ references: /* @__PURE__ */ new Set(),
43206
44207
  propBindingSet,
43207
44208
  propUsageSet: /* @__PURE__ */ new Set(),
43208
44209
  node
@@ -43298,6 +44299,11 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
43298
44299
  mergeTemplateClassNames(filePath, source, facts, templateClassNames);
43299
44300
  const { ext } = splitFilePath(filePath);
43300
44301
  facts._source = source;
44302
+ facts.deadCode.unreachableStatements = findUnreachableStatements(
44303
+ ast,
44304
+ source,
44305
+ lineOffsets
44306
+ );
43301
44307
  const v2 = buildV2Facts(facts, source, ext, framework, config, templateClassNames);
43302
44308
  return envelopeScanFacts(filePath, v2);
43303
44309
  }
@@ -43980,6 +44986,11 @@ var init_builtins = __esm({
43980
44986
  init_missing_not_null();
43981
44987
  init_naming_inconsistency();
43982
44988
  init_sql_concat();
44989
+ init_dead_branch();
44990
+ init_unreachable();
44991
+ init_unused_import();
44992
+ init_unused_local();
44993
+ init_unused_parameter();
43983
44994
  init_broken_link();
43984
44995
  init_expired_code_example();
43985
44996
  init_stale_function_reference();
@@ -44076,6 +45087,11 @@ var init_builtins = __esm({
44076
45087
  missingNotNullRule,
44077
45088
  namingInconsistencyRule,
44078
45089
  sqlConcatRule,
45090
+ deadBranchRule,
45091
+ unreachableRule,
45092
+ unusedImportRule,
45093
+ unusedLocalRule,
45094
+ unusedParameterRule,
44079
45095
  brokenLinkRule,
44080
45096
  expiredCodeExampleRule,
44081
45097
  staleFunctionReferenceRule,
@@ -44614,7 +45630,7 @@ var init_validation = __esm({
44614
45630
  // src/config/load.ts
44615
45631
  import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
44616
45632
  import { dirname as dirname7, extname as extname5, join as join11, resolve as resolve6 } from "path";
44617
- import { createRequire as createRequire2 } from "module";
45633
+ import { createRequire } from "module";
44618
45634
  function deepMerge(target, source) {
44619
45635
  const out = { ...target };
44620
45636
  for (const key of Object.keys(source)) {
@@ -44650,8 +45666,8 @@ function detectJsLoader(configPath) {
44650
45666
  const pkgPath = join11(current, "package.json");
44651
45667
  if (existsSync10(pkgPath)) {
44652
45668
  try {
44653
- const pkg2 = JSON.parse(readFileSync11(pkgPath, "utf-8"));
44654
- return pkg2.type === "module" ? "import" : "require";
45669
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
45670
+ return pkg.type === "module" ? "import" : "require";
44655
45671
  } catch {
44656
45672
  return "require";
44657
45673
  }
@@ -44665,7 +45681,7 @@ function detectJsLoader(configPath) {
44665
45681
  async function loadConfigFile(path) {
44666
45682
  const loader = detectJsLoader(path);
44667
45683
  if (loader === "require") {
44668
- const req = createRequire2(import.meta.url);
45684
+ const req = createRequire(import.meta.url);
44669
45685
  const mod2 = req(path);
44670
45686
  return mod2.default ?? mod2;
44671
45687
  }
@@ -44846,7 +45862,7 @@ var init_init = __esm({
44846
45862
  });
44847
45863
 
44848
45864
  // src/config/index.ts
44849
- var init_config = __esm({
45865
+ var init_config2 = __esm({
44850
45866
  "src/config/index.ts"() {
44851
45867
  "use strict";
44852
45868
  init_defaults();
@@ -46991,6 +48007,61 @@ var init_signal_strength = __esm({
46991
48007
  _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.)",
46992
48008
  aiSpecific: true
46993
48009
  },
48010
+ "dead/unused-import": {
48011
+ recall: 0,
48012
+ fpRate: 0,
48013
+ ratio: 0,
48014
+ precision: 0,
48015
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
48016
+ verdict: "DORMANT",
48017
+ defaultOff: true,
48018
+ _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.",
48019
+ aiSpecific: true
48020
+ },
48021
+ "dead/unused-local": {
48022
+ recall: 0,
48023
+ fpRate: 0,
48024
+ ratio: 0,
48025
+ precision: 0,
48026
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
48027
+ verdict: "DORMANT",
48028
+ defaultOff: true,
48029
+ _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).",
48030
+ aiSpecific: true
48031
+ },
48032
+ "dead/unused-parameter": {
48033
+ recall: 0,
48034
+ fpRate: 0,
48035
+ ratio: 0,
48036
+ precision: 0,
48037
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
48038
+ verdict: "DORMANT",
48039
+ defaultOff: true,
48040
+ _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.",
48041
+ aiSpecific: true
48042
+ },
48043
+ "dead/dead-branch": {
48044
+ recall: 0,
48045
+ fpRate: 0,
48046
+ ratio: 0,
48047
+ precision: 0,
48048
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
48049
+ verdict: "DORMANT",
48050
+ defaultOff: true,
48051
+ _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.",
48052
+ aiSpecific: true
48053
+ },
48054
+ "dead/unreachable": {
48055
+ recall: 0,
48056
+ fpRate: 0,
48057
+ ratio: 0,
48058
+ precision: 0,
48059
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
48060
+ verdict: "DORMANT",
48061
+ defaultOff: true,
48062
+ _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.",
48063
+ aiSpecific: true
48064
+ },
46994
48065
  "docs/stale-package-reference": {
46995
48066
  recall: 0,
46996
48067
  fpRate: 0,
@@ -47951,7 +49022,7 @@ var init_cache = __esm({
47951
49022
  "src/engine/cache.ts"() {
47952
49023
  "use strict";
47953
49024
  init_types();
47954
- init_config();
49025
+ init_config2();
47955
49026
  BASELINE_VERSION = VERSION;
47956
49027
  BASELINE_HASH_KEYS = /* @__PURE__ */ new Set([
47957
49028
  "framework",
@@ -50442,7 +51513,7 @@ var init_finalizeReport = __esm({
50442
51513
  init_dist2();
50443
51514
  init_logger();
50444
51515
  init_memory_io();
50445
- init_config();
51516
+ init_config2();
50446
51517
  init_enrichReport();
50447
51518
  init_assembleScanReport();
50448
51519
  init_persistRun();
@@ -52366,11 +53437,11 @@ var init_watch = __esm({
52366
53437
  init_worker();
52367
53438
  init_threshold();
52368
53439
  init_cache();
52369
- init_config();
53440
+ init_config2();
52370
53441
  init_render();
52371
53442
  init_logger();
52372
53443
  init_error();
52373
- init_scan();
53444
+ init_scan2();
52374
53445
  init_renderOutput();
52375
53446
  init_types();
52376
53447
  }
@@ -52652,12 +53723,12 @@ async function scanProject(options) {
52652
53723
  const { report } = await runScan({ ...options, workspace: options.cwd });
52653
53724
  return report;
52654
53725
  }
52655
- var init_scan = __esm({
53726
+ var init_scan2 = __esm({
52656
53727
  "src/cli/scan.ts"() {
52657
53728
  "use strict";
52658
53729
  init_render();
52659
53730
  init_threshold();
52660
- init_config();
53731
+ init_config2();
52661
53732
  init_discover();
52662
53733
  init_git();
52663
53734
  init_cache_incremental();
@@ -52829,7 +53900,7 @@ async function runSuggest(args, ctx) {
52829
53900
  }
52830
53901
  async function runGovernance(args, ctx) {
52831
53902
  try {
52832
- const { runScan: runScan3 } = await Promise.resolve().then(() => (init_scan(), scan_exports));
53903
+ const { runScan: runScan3 } = await Promise.resolve().then(() => (init_scan2(), scan_exports));
52833
53904
  const maxFiles = typeof args.maxFiles === "number" && Number.isFinite(args.maxFiles) && args.maxFiles > 0 ? Math.floor(args.maxFiles) : 500;
52834
53905
  const { report } = await runScan3({
52835
53906
  workspace: ctx.cwd,
@@ -53229,7 +54300,7 @@ var init_tools = __esm({
53229
54300
 
53230
54301
  // src/index.ts
53231
54302
  init_types();
53232
- init_config();
54303
+ init_config2();
53233
54304
  init_dist2();
53234
54305
 
53235
54306
  // src/cli/program.ts
@@ -53273,11 +54344,11 @@ function parseThreshold(value) {
53273
54344
  // src/cli/program.ts
53274
54345
  init_render();
53275
54346
  init_threshold();
53276
- init_scan();
53277
- init_scan();
54347
+ init_scan2();
54348
+ init_scan2();
53278
54349
 
53279
54350
  // src/cli/init.ts
53280
- init_config();
54351
+ init_config2();
53281
54352
  init_discover();
53282
54353
  init_git();
53283
54354
  init_cache();
@@ -53769,7 +54840,7 @@ async function runDoctor(cwd) {
53769
54840
  // src/cli/commands/badge.ts
53770
54841
  init_render();
53771
54842
  init_logger();
53772
- init_scan();
54843
+ init_scan2();
53773
54844
  import { resolve as resolve13 } from "path";
53774
54845
  function registerBadge(program) {
53775
54846
  program.command("badge").description(
@@ -53796,7 +54867,7 @@ function registerBadge(program) {
53796
54867
  init_advice();
53797
54868
  init_unified_diff();
53798
54869
  init_logger();
53799
- init_scan();
54870
+ init_scan2();
53800
54871
  import { resolve as resolve14 } from "path";
53801
54872
  function registerSuggest(program) {
53802
54873
  program.command("suggest").description("print remediation advice").action(async (_cmdOptions, command) => {
@@ -53924,6 +54995,11 @@ var RULE_HINTS = {
53924
54995
  "logic/qwik-hook-leak": "Use Qwik primitives ($state, $effect, useSignal) instead of React hooks (useState, useEffect).",
53925
54996
  "logic/reactive-hook-soup": "Coordinate state via a single derived value (useMemo) or a state machine. Avoid chained useEffects that sync local state.",
53926
54997
  "logic/zombie-state": "Remove unused useState or wire it into the component. Don't leave declared-but-never-read state bindings.",
54998
+ "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.",
54999
+ "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.",
55000
+ "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.",
55001
+ "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.",
55002
+ "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.",
53927
55003
  "perf/cls-image": "Add width/height attributes or an aspect-ratio utility to prevent layout shift.",
53928
55004
  "perf/css-bloat": "Extract to a CSS variable (`--surface-card`) or a component prop when a class string repeats 5+ times.",
53929
55005
  "perf/halstead-anomaly": "Introduce domain-specific identifiers and varied operations. Low vocabulary per line is a strong AI signature (Halstead 1977 \xA73).",
@@ -54184,7 +55260,7 @@ init_logger();
54184
55260
 
54185
55261
  // src/mcp/server.ts
54186
55262
  init_builtins();
54187
- init_config();
55263
+ init_config2();
54188
55264
  init_tools();
54189
55265
  var SERVER_INFO = {
54190
55266
  name: "slopbrick",
@@ -55613,7 +56689,7 @@ function registerTrend(program) {
55613
56689
 
55614
56690
  // src/cli/commands/drift.ts
55615
56691
  init_logger();
55616
- init_scan();
56692
+ init_scan2();
55617
56693
  import { resolve as resolve26 } from "path";
55618
56694
 
55619
56695
  // src/cli/drift.ts
@@ -55754,7 +56830,7 @@ function registerDrift(program) {
55754
56830
 
55755
56831
  // src/cli/commands/pr.ts
55756
56832
  init_logger();
55757
- init_scan();
56833
+ init_scan2();
55758
56834
  import { resolve as resolve28 } from "path";
55759
56835
 
55760
56836
  // src/cli/pr.ts
@@ -56043,7 +57119,7 @@ function registerPr(program) {
56043
57119
 
56044
57120
  // src/cli/commands/security.ts
56045
57121
  init_logger();
56046
- init_scan();
57122
+ init_scan2();
56047
57123
  init_ai_security_risk();
56048
57124
  import { resolve as resolve29 } from "path";
56049
57125
  function registerSecurity(program) {
@@ -56102,11 +57178,11 @@ function registerSecurity(program) {
56102
57178
 
56103
57179
  // src/cli/commands/test.ts
56104
57180
  init_logger();
56105
- init_scan();
57181
+ init_scan2();
56106
57182
  import { resolve as resolve31 } from "path";
56107
57183
 
56108
57184
  // src/cli/test.ts
56109
- init_scan();
57185
+ init_scan2();
56110
57186
  init_test_quality();
56111
57187
  init_logger();
56112
57188
  import { resolve as resolve30 } from "path";
@@ -56221,7 +57297,7 @@ function registerTest(program) {
56221
57297
 
56222
57298
  // src/cli/commands/architecture.ts
56223
57299
  init_logger();
56224
- init_scan();
57300
+ init_scan2();
56225
57301
  init_architecture_score();
56226
57302
  import { resolve as resolve32 } from "path";
56227
57303
  function registerArchitecture(program) {
@@ -56249,7 +57325,7 @@ function registerArchitecture(program) {
56249
57325
 
56250
57326
  // src/cli/commands/business-logic.ts
56251
57327
  init_logger();
56252
- init_scan();
57328
+ init_scan2();
56253
57329
  import { resolve as resolve33 } from "path";
56254
57330
 
56255
57331
  // src/cli/business-logic.ts
@@ -56395,11 +57471,11 @@ function registerBusinessLogic(program) {
56395
57471
 
56396
57472
  // src/cli/commands/maintenance-cost.ts
56397
57473
  init_logger();
56398
- init_scan();
57474
+ init_scan2();
56399
57475
  import { resolve as resolve34 } from "path";
56400
57476
 
56401
57477
  // src/cli/maintenance-cost.ts
56402
- init_scan();
57478
+ init_scan2();
56403
57479
  init_maintenance_cost();
56404
57480
  init_logger();
56405
57481
  async function runMaintenanceCostScan(cwd, config, options = {}) {
@@ -56538,11 +57614,11 @@ function registerMaintenanceCost(program) {
56538
57614
 
56539
57615
  // src/cli/commands/docs.ts
56540
57616
  init_logger();
56541
- init_scan();
57617
+ init_scan2();
56542
57618
  import { resolve as resolve35 } from "path";
56543
57619
 
56544
57620
  // src/cli/docs.ts
56545
- init_scan();
57621
+ init_scan2();
56546
57622
  init_doc_freshness();
56547
57623
  init_logger();
56548
57624
  async function runDocsScan(cwd, config, options = {}) {
@@ -56687,11 +57763,11 @@ function registerDocs(program) {
56687
57763
 
56688
57764
  // src/cli/commands/db.ts
56689
57765
  init_logger();
56690
- init_scan();
57766
+ init_scan2();
56691
57767
  import { resolve as resolve36 } from "path";
56692
57768
 
56693
57769
  // src/cli/db.ts
56694
- init_scan();
57770
+ init_scan2();
56695
57771
  init_db_health();
56696
57772
  init_logger();
56697
57773
  async function runDbScan(cwd, config, options = {}) {
@@ -56830,7 +57906,7 @@ function registerDb(program) {
56830
57906
 
56831
57907
  // src/cli/commands/patterns.ts
56832
57908
  init_logger();
56833
- init_scan();
57909
+ init_scan2();
56834
57910
  import { resolve as resolve37 } from "path";
56835
57911
 
56836
57912
  // src/engine/patterns.ts
@@ -57178,7 +58254,7 @@ function registerPatterns(program) {
57178
58254
 
57179
58255
  // src/cli/commands/research.ts
57180
58256
  init_logger();
57181
- init_config();
58257
+ init_config2();
57182
58258
  import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync34, writeFileSync as writeFileSync17 } from "fs";
57183
58259
  import { dirname as dirname18, resolve as resolve38 } from "path";
57184
58260
  function registerResearch(program) {
@@ -57261,8 +58337,8 @@ init_logger();
57261
58337
  init_builtins();
57262
58338
  import { existsSync as existsSync28, mkdirSync as mkdirSync15, readFileSync as readFileSync35, writeFileSync as writeFileSync18 } from "fs";
57263
58339
  import { dirname as dirname19, join as join29, resolve as resolve39 } from "path";
57264
- init_scan();
57265
- init_config();
58340
+ init_scan2();
58341
+ init_config2();
57266
58342
  init_threshold();
57267
58343
  init_git();
57268
58344
  init_cache();
@@ -57743,7 +58819,7 @@ function registerScan(program, scanAction) {
57743
58819
  }
57744
58820
 
57745
58821
  // src/cli/program.ts
57746
- init_config();
58822
+ init_config2();
57747
58823
  init_git();
57748
58824
  init_logger();
57749
58825
  init_unified_diff();
@@ -58309,7 +59385,7 @@ function formatGroupedHelp(cmd) {
58309
59385
  }
58310
59386
 
58311
59387
  // src/cli/program.ts
58312
- init_scan();
59388
+ init_scan2();
58313
59389
  process.on("uncaughtException", (err) => {
58314
59390
  logger.error(`Unexpected error: ${err instanceof Error ? err.message : String(err)}`);
58315
59391
  process.exit(3);