slopbrick 0.18.3 → 0.18.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.5";
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
@@ -29920,9 +30173,9 @@ function declaredPackages(cwd) {
29920
30173
  if (!existsSync6(pkgPath)) return out;
29921
30174
  try {
29922
30175
  const raw = readFileSync8(pkgPath, "utf-8");
29923
- const pkg2 = JSON.parse(raw);
30176
+ const pkg = JSON.parse(raw);
29924
30177
  for (const k of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
29925
- const v = pkg2[k];
30178
+ const v = pkg[k];
29926
30179
  if (v && typeof v === "object") {
29927
30180
  for (const name of Object.keys(v)) {
29928
30181
  out.add(name);
@@ -30349,7 +30602,7 @@ var init_spacing_grid = __esm({
30349
30602
  "use strict";
30350
30603
  init_rule();
30351
30604
  init_utils();
30352
- init_config();
30605
+ init_config2();
30353
30606
  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
30607
  ARBITRARY_VALUE_RE2 = /^(-?\d+(?:\.\d+)?)(px|rem)$/;
30355
30608
  SKIP_VALUES = /* @__PURE__ */ new Set(["auto", "full", "screen", "min", "max", "fit", "none"]);
@@ -42437,6 +42690,78 @@ function extractStateBinding(node, lineOffsets) {
42437
42690
  setterReferenced: false
42438
42691
  };
42439
42692
  }
42693
+ function findUnreachableStatements(ast, source, lineOffsets) {
42694
+ const out = [];
42695
+ function isTerminator(node) {
42696
+ if (node.type === "ReturnStatement") return "return";
42697
+ if (node.type === "ThrowStatement") return "throw";
42698
+ if (node.type === "BreakStatement") return "break";
42699
+ if (node.type === "ContinueStatement") return "continue";
42700
+ return null;
42701
+ }
42702
+ function snippetFor(node, src) {
42703
+ const span = node.span;
42704
+ if (!span || typeof span.start !== "number" || typeof span.end !== "number") {
42705
+ return "<unreachable>";
42706
+ }
42707
+ const text = src.slice(span.start, Math.min(span.end, span.start + 60));
42708
+ return text.replace(/\s+/g, " ").trim() || "<unreachable>";
42709
+ }
42710
+ function lineColumn(offset) {
42711
+ let lo = 0;
42712
+ let hi = lineOffsets.length - 1;
42713
+ while (lo < hi) {
42714
+ const mid = lo + hi + 1 >>> 1;
42715
+ const midVal = lineOffsets[mid];
42716
+ const loVal = lineOffsets[lo];
42717
+ if (midVal === void 0) break;
42718
+ if (midVal <= offset) lo = mid;
42719
+ else hi = mid - 1;
42720
+ void loVal;
42721
+ }
42722
+ const baseOffset = lineOffsets[lo] ?? 0;
42723
+ return { line: lo + 1, column: offset - baseOffset };
42724
+ }
42725
+ function visitBody(body) {
42726
+ if (!Array.isArray(body)) return;
42727
+ let lastTerminator = null;
42728
+ for (const stmt of body) {
42729
+ if (!isObject(stmt)) continue;
42730
+ if (lastTerminator && stmt.type !== "EmptyStatement") {
42731
+ const span = stmt.span;
42732
+ const offset = typeof span?.start === "number" ? span.start : 0;
42733
+ const { line, column } = lineColumn(offset);
42734
+ out.push({
42735
+ terminator: lastTerminator,
42736
+ line,
42737
+ column,
42738
+ snippet: snippetFor(stmt, source)
42739
+ });
42740
+ }
42741
+ const t = isTerminator(stmt);
42742
+ if (t) lastTerminator = t;
42743
+ }
42744
+ }
42745
+ function walk3(node) {
42746
+ if (!isObject(node)) return;
42747
+ if (node.type === "BlockStatement") {
42748
+ visitBody(node.stmts);
42749
+ } else if (node.type === "Module") {
42750
+ visitBody(node.body);
42751
+ }
42752
+ for (const key of Object.keys(node)) {
42753
+ if (key === "parent" || key === "span" || key === "ctxt") continue;
42754
+ const child = node[key];
42755
+ if (Array.isArray(child)) {
42756
+ for (const c of child) walk3(c);
42757
+ } else {
42758
+ walk3(child);
42759
+ }
42760
+ }
42761
+ }
42762
+ walk3(ast);
42763
+ return out;
42764
+ }
42440
42765
  var init_scan_helpers = __esm({
42441
42766
  "src/engine/visitors/scan-helpers.ts"() {
42442
42767
  "use strict";
@@ -42522,15 +42847,42 @@ function handleImportDeclaration(node, _parent, _path, vctx) {
42522
42847
  if (specifier.type === "ImportDefaultSpecifier" || specifier.type === "ImportNamespaceSpecifier") {
42523
42848
  const local = specifier.local;
42524
42849
  if (isObject(local) && local.type === "Identifier" && typeof local.value === "string") {
42525
- importedNames.push(local.value);
42850
+ const name = local.value;
42851
+ importedNames.push(name);
42852
+ vctx.facts.deadCode.bindings.push({
42853
+ name,
42854
+ kind: specifier.type === "ImportDefaultSpecifier" ? "import-default" : "import-namespace",
42855
+ line,
42856
+ column,
42857
+ source,
42858
+ isReferenced: false
42859
+ });
42526
42860
  }
42527
42861
  } else if (specifier.type === "ImportSpecifier") {
42528
42862
  const imported = specifier.imported;
42529
42863
  const local = specifier.local;
42530
42864
  if (isObject(imported) && typeof imported.value === "string" && imported.value.length > 0) {
42531
- importedNames.push(imported.value);
42865
+ const name = imported.value;
42866
+ importedNames.push(name);
42867
+ vctx.facts.deadCode.bindings.push({
42868
+ name,
42869
+ kind: "import-specifier",
42870
+ line,
42871
+ column,
42872
+ source,
42873
+ isReferenced: false
42874
+ });
42532
42875
  } else if (isObject(local) && local.type === "Identifier" && typeof local.value === "string") {
42533
- importedNames.push(local.value);
42876
+ const name = local.value;
42877
+ importedNames.push(name);
42878
+ vctx.facts.deadCode.bindings.push({
42879
+ name,
42880
+ kind: "import-specifier",
42881
+ line,
42882
+ column,
42883
+ source,
42884
+ isReferenced: false
42885
+ });
42534
42886
  }
42535
42887
  }
42536
42888
  }
@@ -42620,6 +42972,13 @@ function isBindingSite(node, parent) {
42620
42972
  if (params.some((param) => containsNode(param, node))) return true;
42621
42973
  }
42622
42974
  }
42975
+ if (parent.type === "ImportSpecifier" || parent.type === "ImportDefaultSpecifier" || parent.type === "ImportNamespaceSpecifier") {
42976
+ if (parent.type === "ImportSpecifier") {
42977
+ if (parent.imported === node || parent.local === node) return true;
42978
+ } else {
42979
+ if (parent.local === node) return true;
42980
+ }
42981
+ }
42623
42982
  return false;
42624
42983
  }
42625
42984
  function isNonComputedMemberProperty(node, parent) {
@@ -42686,6 +43045,9 @@ function handleIdentifier(node, parent, path, vctx) {
42686
43045
  if (typeof node.value === "string" && !isBindingSite(node, parent) && !isNonComputedMemberProperty(node, parent)) {
42687
43046
  markStateReference(node.value, vctx);
42688
43047
  trackPropUsage(node, parent, path, vctx);
43048
+ vctx.facts.referencedNames.add(node.value);
43049
+ const top = vctx.ctx.stack[vctx.ctx.stack.length - 1];
43050
+ if (top) top.references.add(node.value);
42689
43051
  }
42690
43052
  return false;
42691
43053
  }
@@ -42826,6 +43188,20 @@ function handleVariableDeclarator(node, _parent, path, vctx) {
42826
43188
  frame.bindings.add(bindingName);
42827
43189
  }
42828
43190
  }
43191
+ if (bindingNames.length > 0) {
43192
+ const parent = node.parent;
43193
+ const kind = isObject(parent) && parent.type === "VariableDeclaration" ? String(parent.kind ?? "var") : "var";
43194
+ const { line, column } = positionFrom(id, vctx.lineOffsets);
43195
+ for (const bindingName of bindingNames) {
43196
+ vctx.facts.deadCode.bindings.push({
43197
+ name: bindingName,
43198
+ kind,
43199
+ line,
43200
+ column,
43201
+ isReferenced: false
43202
+ });
43203
+ }
43204
+ }
42829
43205
  if (isUseStateDeclarator(node)) {
42830
43206
  const binding = extractStateBinding(node, vctx.lineOffsets);
42831
43207
  if (binding) {
@@ -42845,6 +43221,36 @@ function handleVariableDeclarator(node, _parent, path, vctx) {
42845
43221
  }
42846
43222
  return true;
42847
43223
  }
43224
+ function handleIfStatement(node, _parent, _path, vctx) {
43225
+ if (!isObject(node)) return false;
43226
+ const test = node.test;
43227
+ if (!isObject(test)) return false;
43228
+ if (test.type === "BooleanLiteral" && typeof test.value === "boolean") {
43229
+ const { line, column } = positionFrom(test, vctx.lineOffsets);
43230
+ vctx.facts.deadCode.constantConditions.push({
43231
+ kind: test.value ? "if-true" : "if-false",
43232
+ condition: String(test.value),
43233
+ line,
43234
+ column
43235
+ });
43236
+ }
43237
+ return false;
43238
+ }
43239
+ function handleWhileStatement(node, _parent, _path, vctx) {
43240
+ if (!isObject(node)) return false;
43241
+ const test = node.test;
43242
+ if (!isObject(test)) return false;
43243
+ if (test.type === "BooleanLiteral" && typeof test.value === "boolean") {
43244
+ const { line, column } = positionFrom(test, vctx.lineOffsets);
43245
+ vctx.facts.deadCode.constantConditions.push({
43246
+ kind: test.value ? "while-true" : "while-false",
43247
+ condition: String(test.value),
43248
+ line,
43249
+ column
43250
+ });
43251
+ }
43252
+ return false;
43253
+ }
42848
43254
  function dispatchNode(node, parent, path, vctx) {
42849
43255
  if (!isObject(node)) return false;
42850
43256
  const type = getNodeType(node);
@@ -42869,7 +43275,9 @@ var init_dispatch = __esm({
42869
43275
  MemberExpression: handleMemberExpression,
42870
43276
  JSXAttribute: handleJSXAttribute,
42871
43277
  JSXOpeningElement: handleJSXOpeningElement,
42872
- VariableDeclarator: handleVariableDeclarator
43278
+ VariableDeclarator: handleVariableDeclarator,
43279
+ IfStatement: handleIfStatement,
43280
+ WhileStatement: handleWhileStatement
42873
43281
  };
42874
43282
  }
42875
43283
  });
@@ -43020,6 +43428,17 @@ function buildV2Facts(facts, source, ext, framework, config, templateClassNames
43020
43428
  },
43021
43429
  logic: buildLogicBlock(facts),
43022
43430
  designTokens: scanDesignTokens(facts.staticClassNames),
43431
+ // dead-code detector. Copy the internal accumulator
43432
+ // into the v2 shape, marking each binding as referenced
43433
+ // iff the file-level referenced-name set contains its name.
43434
+ deadCode: {
43435
+ bindings: facts.deadCode.bindings.map((b) => ({
43436
+ ...b,
43437
+ isReferenced: facts.referencedNames.has(b.name)
43438
+ })),
43439
+ constantConditions: facts.deadCode.constantConditions,
43440
+ unreachableStatements: facts.deadCode.unreachableStatements
43441
+ },
43023
43442
  componentSizes: facts.componentSizes.map((cs) => ({
43024
43443
  name: cs.name,
43025
43444
  lineCount: cs.lineCount,
@@ -43101,7 +43520,17 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
43101
43520
  componentSizes: [],
43102
43521
  astroComponents: [],
43103
43522
  fetchCalls: [],
43104
- optimisticUpdates: []
43523
+ optimisticUpdates: [],
43524
+ // dead-code detector. The visitor's identifier walk + import/
43525
+ // branch/return handlers populate these. The v2 builder at the
43526
+ // bottom of extractFacts() reads them and produces
43527
+ // `facts.v2.deadCode`.
43528
+ deadCode: {
43529
+ bindings: [],
43530
+ constantConditions: [],
43531
+ unreachableStatements: []
43532
+ },
43533
+ referencedNames: /* @__PURE__ */ new Set()
43105
43534
  };
43106
43535
  const ctx = {
43107
43536
  stack: [],
@@ -43188,6 +43617,16 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
43188
43617
  propBindingSet.add(bindingName);
43189
43618
  }
43190
43619
  }
43620
+ const { line: pLine, column: pCol } = positionFrom(param, lineOffsets);
43621
+ for (const bindingName of collectBindingNames2(param)) {
43622
+ facts.deadCode.bindings.push({
43623
+ name: bindingName,
43624
+ kind: "parameter",
43625
+ line: pLine,
43626
+ column: pCol,
43627
+ isReferenced: false
43628
+ });
43629
+ }
43191
43630
  }
43192
43631
  }
43193
43632
  ctx.stack.push({
@@ -43203,6 +43642,12 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
43203
43642
  propUsages: [],
43204
43643
  isComponent,
43205
43644
  bindings,
43645
+ // dead-code detector: per-frame referenced-name set.
43646
+ // Identifiers encountered inside the frame are added to this
43647
+ // set; the deadCode builder unions it with parent frames at
43648
+ // pop time so a binding is considered used if any reachable
43649
+ // scope references it.
43650
+ references: /* @__PURE__ */ new Set(),
43206
43651
  propBindingSet,
43207
43652
  propUsageSet: /* @__PURE__ */ new Set(),
43208
43653
  node
@@ -43298,6 +43743,11 @@ function extractFacts(filePath, ast, source, supportsRsc = true, framework = "re
43298
43743
  mergeTemplateClassNames(filePath, source, facts, templateClassNames);
43299
43744
  const { ext } = splitFilePath(filePath);
43300
43745
  facts._source = source;
43746
+ facts.deadCode.unreachableStatements = findUnreachableStatements(
43747
+ ast,
43748
+ source,
43749
+ lineOffsets
43750
+ );
43301
43751
  const v2 = buildV2Facts(facts, source, ext, framework, config, templateClassNames);
43302
43752
  return envelopeScanFacts(filePath, v2);
43303
43753
  }
@@ -43980,6 +44430,11 @@ var init_builtins = __esm({
43980
44430
  init_missing_not_null();
43981
44431
  init_naming_inconsistency();
43982
44432
  init_sql_concat();
44433
+ init_dead_branch();
44434
+ init_unreachable();
44435
+ init_unused_import();
44436
+ init_unused_local();
44437
+ init_unused_parameter();
43983
44438
  init_broken_link();
43984
44439
  init_expired_code_example();
43985
44440
  init_stale_function_reference();
@@ -44076,6 +44531,11 @@ var init_builtins = __esm({
44076
44531
  missingNotNullRule,
44077
44532
  namingInconsistencyRule,
44078
44533
  sqlConcatRule,
44534
+ deadBranchRule,
44535
+ unreachableRule,
44536
+ unusedImportRule,
44537
+ unusedLocalRule,
44538
+ unusedParameterRule,
44079
44539
  brokenLinkRule,
44080
44540
  expiredCodeExampleRule,
44081
44541
  staleFunctionReferenceRule,
@@ -44614,7 +45074,7 @@ var init_validation = __esm({
44614
45074
  // src/config/load.ts
44615
45075
  import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
44616
45076
  import { dirname as dirname7, extname as extname5, join as join11, resolve as resolve6 } from "path";
44617
- import { createRequire as createRequire2 } from "module";
45077
+ import { createRequire } from "module";
44618
45078
  function deepMerge(target, source) {
44619
45079
  const out = { ...target };
44620
45080
  for (const key of Object.keys(source)) {
@@ -44650,8 +45110,8 @@ function detectJsLoader(configPath) {
44650
45110
  const pkgPath = join11(current, "package.json");
44651
45111
  if (existsSync10(pkgPath)) {
44652
45112
  try {
44653
- const pkg2 = JSON.parse(readFileSync11(pkgPath, "utf-8"));
44654
- return pkg2.type === "module" ? "import" : "require";
45113
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
45114
+ return pkg.type === "module" ? "import" : "require";
44655
45115
  } catch {
44656
45116
  return "require";
44657
45117
  }
@@ -44665,7 +45125,7 @@ function detectJsLoader(configPath) {
44665
45125
  async function loadConfigFile(path) {
44666
45126
  const loader = detectJsLoader(path);
44667
45127
  if (loader === "require") {
44668
- const req = createRequire2(import.meta.url);
45128
+ const req = createRequire(import.meta.url);
44669
45129
  const mod2 = req(path);
44670
45130
  return mod2.default ?? mod2;
44671
45131
  }
@@ -44846,7 +45306,7 @@ var init_init = __esm({
44846
45306
  });
44847
45307
 
44848
45308
  // src/config/index.ts
44849
- var init_config = __esm({
45309
+ var init_config2 = __esm({
44850
45310
  "src/config/index.ts"() {
44851
45311
  "use strict";
44852
45312
  init_defaults();
@@ -46991,6 +47451,61 @@ var init_signal_strength = __esm({
46991
47451
  _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
47452
  aiSpecific: true
46993
47453
  },
47454
+ "dead/unused-import": {
47455
+ recall: 0,
47456
+ fpRate: 0,
47457
+ ratio: 0,
47458
+ precision: 0,
47459
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
47460
+ verdict: "DORMANT",
47461
+ defaultOff: true,
47462
+ _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.",
47463
+ aiSpecific: true
47464
+ },
47465
+ "dead/unused-local": {
47466
+ recall: 0,
47467
+ fpRate: 0,
47468
+ ratio: 0,
47469
+ precision: 0,
47470
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
47471
+ verdict: "DORMANT",
47472
+ defaultOff: true,
47473
+ _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).",
47474
+ aiSpecific: true
47475
+ },
47476
+ "dead/unused-parameter": {
47477
+ recall: 0,
47478
+ fpRate: 0,
47479
+ ratio: 0,
47480
+ precision: 0,
47481
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
47482
+ verdict: "DORMANT",
47483
+ defaultOff: true,
47484
+ _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.",
47485
+ aiSpecific: true
47486
+ },
47487
+ "dead/dead-branch": {
47488
+ recall: 0,
47489
+ fpRate: 0,
47490
+ ratio: 0,
47491
+ precision: 0,
47492
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
47493
+ verdict: "DORMANT",
47494
+ defaultOff: true,
47495
+ _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.",
47496
+ aiSpecific: true
47497
+ },
47498
+ "dead/unreachable": {
47499
+ recall: 0,
47500
+ fpRate: 0,
47501
+ ratio: 0,
47502
+ precision: 0,
47503
+ lastCalibratedAt: "2026-06-30T00:00:00Z",
47504
+ verdict: "DORMANT",
47505
+ defaultOff: true,
47506
+ _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.",
47507
+ aiSpecific: true
47508
+ },
46994
47509
  "docs/stale-package-reference": {
46995
47510
  recall: 0,
46996
47511
  fpRate: 0,
@@ -47951,7 +48466,7 @@ var init_cache = __esm({
47951
48466
  "src/engine/cache.ts"() {
47952
48467
  "use strict";
47953
48468
  init_types();
47954
- init_config();
48469
+ init_config2();
47955
48470
  BASELINE_VERSION = VERSION;
47956
48471
  BASELINE_HASH_KEYS = /* @__PURE__ */ new Set([
47957
48472
  "framework",
@@ -50442,7 +50957,7 @@ var init_finalizeReport = __esm({
50442
50957
  init_dist2();
50443
50958
  init_logger();
50444
50959
  init_memory_io();
50445
- init_config();
50960
+ init_config2();
50446
50961
  init_enrichReport();
50447
50962
  init_assembleScanReport();
50448
50963
  init_persistRun();
@@ -52366,11 +52881,11 @@ var init_watch = __esm({
52366
52881
  init_worker();
52367
52882
  init_threshold();
52368
52883
  init_cache();
52369
- init_config();
52884
+ init_config2();
52370
52885
  init_render();
52371
52886
  init_logger();
52372
52887
  init_error();
52373
- init_scan();
52888
+ init_scan2();
52374
52889
  init_renderOutput();
52375
52890
  init_types();
52376
52891
  }
@@ -52652,12 +53167,12 @@ async function scanProject(options) {
52652
53167
  const { report } = await runScan({ ...options, workspace: options.cwd });
52653
53168
  return report;
52654
53169
  }
52655
- var init_scan = __esm({
53170
+ var init_scan2 = __esm({
52656
53171
  "src/cli/scan.ts"() {
52657
53172
  "use strict";
52658
53173
  init_render();
52659
53174
  init_threshold();
52660
- init_config();
53175
+ init_config2();
52661
53176
  init_discover();
52662
53177
  init_git();
52663
53178
  init_cache_incremental();
@@ -52829,7 +53344,7 @@ async function runSuggest(args, ctx) {
52829
53344
  }
52830
53345
  async function runGovernance(args, ctx) {
52831
53346
  try {
52832
- const { runScan: runScan3 } = await Promise.resolve().then(() => (init_scan(), scan_exports));
53347
+ const { runScan: runScan3 } = await Promise.resolve().then(() => (init_scan2(), scan_exports));
52833
53348
  const maxFiles = typeof args.maxFiles === "number" && Number.isFinite(args.maxFiles) && args.maxFiles > 0 ? Math.floor(args.maxFiles) : 500;
52834
53349
  const { report } = await runScan3({
52835
53350
  workspace: ctx.cwd,
@@ -53229,7 +53744,7 @@ var init_tools = __esm({
53229
53744
 
53230
53745
  // src/index.ts
53231
53746
  init_types();
53232
- init_config();
53747
+ init_config2();
53233
53748
  init_dist2();
53234
53749
 
53235
53750
  // src/cli/program.ts
@@ -53273,11 +53788,11 @@ function parseThreshold(value) {
53273
53788
  // src/cli/program.ts
53274
53789
  init_render();
53275
53790
  init_threshold();
53276
- init_scan();
53277
- init_scan();
53791
+ init_scan2();
53792
+ init_scan2();
53278
53793
 
53279
53794
  // src/cli/init.ts
53280
- init_config();
53795
+ init_config2();
53281
53796
  init_discover();
53282
53797
  init_git();
53283
53798
  init_cache();
@@ -53769,7 +54284,7 @@ async function runDoctor(cwd) {
53769
54284
  // src/cli/commands/badge.ts
53770
54285
  init_render();
53771
54286
  init_logger();
53772
- init_scan();
54287
+ init_scan2();
53773
54288
  import { resolve as resolve13 } from "path";
53774
54289
  function registerBadge(program) {
53775
54290
  program.command("badge").description(
@@ -53796,7 +54311,7 @@ function registerBadge(program) {
53796
54311
  init_advice();
53797
54312
  init_unified_diff();
53798
54313
  init_logger();
53799
- init_scan();
54314
+ init_scan2();
53800
54315
  import { resolve as resolve14 } from "path";
53801
54316
  function registerSuggest(program) {
53802
54317
  program.command("suggest").description("print remediation advice").action(async (_cmdOptions, command) => {
@@ -53924,6 +54439,11 @@ var RULE_HINTS = {
53924
54439
  "logic/qwik-hook-leak": "Use Qwik primitives ($state, $effect, useSignal) instead of React hooks (useState, useEffect).",
53925
54440
  "logic/reactive-hook-soup": "Coordinate state via a single derived value (useMemo) or a state machine. Avoid chained useEffects that sync local state.",
53926
54441
  "logic/zombie-state": "Remove unused useState or wire it into the component. Don't leave declared-but-never-read state bindings.",
54442
+ "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.",
54443
+ "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.",
54444
+ "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.",
54445
+ "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.",
54446
+ "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
54447
  "perf/cls-image": "Add width/height attributes or an aspect-ratio utility to prevent layout shift.",
53928
54448
  "perf/css-bloat": "Extract to a CSS variable (`--surface-card`) or a component prop when a class string repeats 5+ times.",
53929
54449
  "perf/halstead-anomaly": "Introduce domain-specific identifiers and varied operations. Low vocabulary per line is a strong AI signature (Halstead 1977 \xA73).",
@@ -54184,7 +54704,7 @@ init_logger();
54184
54704
 
54185
54705
  // src/mcp/server.ts
54186
54706
  init_builtins();
54187
- init_config();
54707
+ init_config2();
54188
54708
  init_tools();
54189
54709
  var SERVER_INFO = {
54190
54710
  name: "slopbrick",
@@ -55613,7 +56133,7 @@ function registerTrend(program) {
55613
56133
 
55614
56134
  // src/cli/commands/drift.ts
55615
56135
  init_logger();
55616
- init_scan();
56136
+ init_scan2();
55617
56137
  import { resolve as resolve26 } from "path";
55618
56138
 
55619
56139
  // src/cli/drift.ts
@@ -55754,7 +56274,7 @@ function registerDrift(program) {
55754
56274
 
55755
56275
  // src/cli/commands/pr.ts
55756
56276
  init_logger();
55757
- init_scan();
56277
+ init_scan2();
55758
56278
  import { resolve as resolve28 } from "path";
55759
56279
 
55760
56280
  // src/cli/pr.ts
@@ -56043,7 +56563,7 @@ function registerPr(program) {
56043
56563
 
56044
56564
  // src/cli/commands/security.ts
56045
56565
  init_logger();
56046
- init_scan();
56566
+ init_scan2();
56047
56567
  init_ai_security_risk();
56048
56568
  import { resolve as resolve29 } from "path";
56049
56569
  function registerSecurity(program) {
@@ -56102,11 +56622,11 @@ function registerSecurity(program) {
56102
56622
 
56103
56623
  // src/cli/commands/test.ts
56104
56624
  init_logger();
56105
- init_scan();
56625
+ init_scan2();
56106
56626
  import { resolve as resolve31 } from "path";
56107
56627
 
56108
56628
  // src/cli/test.ts
56109
- init_scan();
56629
+ init_scan2();
56110
56630
  init_test_quality();
56111
56631
  init_logger();
56112
56632
  import { resolve as resolve30 } from "path";
@@ -56221,7 +56741,7 @@ function registerTest(program) {
56221
56741
 
56222
56742
  // src/cli/commands/architecture.ts
56223
56743
  init_logger();
56224
- init_scan();
56744
+ init_scan2();
56225
56745
  init_architecture_score();
56226
56746
  import { resolve as resolve32 } from "path";
56227
56747
  function registerArchitecture(program) {
@@ -56249,7 +56769,7 @@ function registerArchitecture(program) {
56249
56769
 
56250
56770
  // src/cli/commands/business-logic.ts
56251
56771
  init_logger();
56252
- init_scan();
56772
+ init_scan2();
56253
56773
  import { resolve as resolve33 } from "path";
56254
56774
 
56255
56775
  // src/cli/business-logic.ts
@@ -56395,11 +56915,11 @@ function registerBusinessLogic(program) {
56395
56915
 
56396
56916
  // src/cli/commands/maintenance-cost.ts
56397
56917
  init_logger();
56398
- init_scan();
56918
+ init_scan2();
56399
56919
  import { resolve as resolve34 } from "path";
56400
56920
 
56401
56921
  // src/cli/maintenance-cost.ts
56402
- init_scan();
56922
+ init_scan2();
56403
56923
  init_maintenance_cost();
56404
56924
  init_logger();
56405
56925
  async function runMaintenanceCostScan(cwd, config, options = {}) {
@@ -56538,11 +57058,11 @@ function registerMaintenanceCost(program) {
56538
57058
 
56539
57059
  // src/cli/commands/docs.ts
56540
57060
  init_logger();
56541
- init_scan();
57061
+ init_scan2();
56542
57062
  import { resolve as resolve35 } from "path";
56543
57063
 
56544
57064
  // src/cli/docs.ts
56545
- init_scan();
57065
+ init_scan2();
56546
57066
  init_doc_freshness();
56547
57067
  init_logger();
56548
57068
  async function runDocsScan(cwd, config, options = {}) {
@@ -56687,11 +57207,11 @@ function registerDocs(program) {
56687
57207
 
56688
57208
  // src/cli/commands/db.ts
56689
57209
  init_logger();
56690
- init_scan();
57210
+ init_scan2();
56691
57211
  import { resolve as resolve36 } from "path";
56692
57212
 
56693
57213
  // src/cli/db.ts
56694
- init_scan();
57214
+ init_scan2();
56695
57215
  init_db_health();
56696
57216
  init_logger();
56697
57217
  async function runDbScan(cwd, config, options = {}) {
@@ -56830,7 +57350,7 @@ function registerDb(program) {
56830
57350
 
56831
57351
  // src/cli/commands/patterns.ts
56832
57352
  init_logger();
56833
- init_scan();
57353
+ init_scan2();
56834
57354
  import { resolve as resolve37 } from "path";
56835
57355
 
56836
57356
  // src/engine/patterns.ts
@@ -57178,7 +57698,7 @@ function registerPatterns(program) {
57178
57698
 
57179
57699
  // src/cli/commands/research.ts
57180
57700
  init_logger();
57181
- init_config();
57701
+ init_config2();
57182
57702
  import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync34, writeFileSync as writeFileSync17 } from "fs";
57183
57703
  import { dirname as dirname18, resolve as resolve38 } from "path";
57184
57704
  function registerResearch(program) {
@@ -57261,8 +57781,8 @@ init_logger();
57261
57781
  init_builtins();
57262
57782
  import { existsSync as existsSync28, mkdirSync as mkdirSync15, readFileSync as readFileSync35, writeFileSync as writeFileSync18 } from "fs";
57263
57783
  import { dirname as dirname19, join as join29, resolve as resolve39 } from "path";
57264
- init_scan();
57265
- init_config();
57784
+ init_scan2();
57785
+ init_config2();
57266
57786
  init_threshold();
57267
57787
  init_git();
57268
57788
  init_cache();
@@ -57743,7 +58263,7 @@ function registerScan(program, scanAction) {
57743
58263
  }
57744
58264
 
57745
58265
  // src/cli/program.ts
57746
- init_config();
58266
+ init_config2();
57747
58267
  init_git();
57748
58268
  init_logger();
57749
58269
  init_unified_diff();
@@ -58176,7 +58696,140 @@ async function applyFixes(report, config, scannedFiles) {
58176
58696
  // src/cli/program.ts
58177
58697
  init_cache();
58178
58698
  init_types();
58179
- init_scan();
58699
+
58700
+ // src/cli/help.ts
58701
+ var CATEGORY_LABELS3 = {
58702
+ file: "File selection",
58703
+ filter: "Filter",
58704
+ output: "Output & display",
58705
+ perf: "Performance",
58706
+ fix: "Auto-fix",
58707
+ ci: "CI / threshold",
58708
+ watch: "Watch & diagnose",
58709
+ tokens: "Tokens",
58710
+ telemetry: "Telemetry",
58711
+ other: "Other"
58712
+ };
58713
+ var OPTION_CATEGORY = {
58714
+ // File selection
58715
+ "--include": "file",
58716
+ "--exclude": "file",
58717
+ "--since": "file",
58718
+ "--diff": "file",
58719
+ "--staged": "file",
58720
+ "--changed": "file",
58721
+ "--workspace": "file",
58722
+ // Filter
58723
+ "--ai-only": "filter",
58724
+ "--human-only": "filter",
58725
+ "--security-only": "filter",
58726
+ "--ignore-wcag22": "filter",
58727
+ "--framework": "filter",
58728
+ // Output & display
58729
+ "--format": "output",
58730
+ "--brief": "output",
58731
+ "--full": "output",
58732
+ "--json": "output",
58733
+ "--html": "output",
58734
+ "--no-color": "output",
58735
+ "--quiet": "output",
58736
+ "--verbose": "output",
58737
+ "--heatmap": "output",
58738
+ "--trend": "output",
58739
+ "--why-failing": "output",
58740
+ // Performance
58741
+ "--threads": "perf",
58742
+ "--incremental": "perf",
58743
+ "--cache": "perf",
58744
+ "--cache-path": "perf",
58745
+ // Auto-fix
58746
+ "--fix": "fix",
58747
+ "--dry-run": "fix",
58748
+ "--show-fixes-diff": "fix",
58749
+ // CI / threshold
58750
+ "--strict": "ci",
58751
+ "--no-increase": "ci",
58752
+ "--baseline": "ci",
58753
+ "--threshold": "ci",
58754
+ // Watch & diagnose
58755
+ "--watch": "watch",
58756
+ "--doctor": "watch",
58757
+ "--suggest": "watch",
58758
+ "--tighten": "watch",
58759
+ // Tokens
58760
+ "--tokens": "tokens",
58761
+ // Telemetry
58762
+ "--no-telemetry": "telemetry"
58763
+ };
58764
+ var CATEGORY_ORDER2 = [
58765
+ "file",
58766
+ "filter",
58767
+ "output",
58768
+ "perf",
58769
+ "fix",
58770
+ "ci",
58771
+ "watch",
58772
+ "tokens",
58773
+ "telemetry",
58774
+ "other"
58775
+ ];
58776
+ function groupOptions(cmd) {
58777
+ const grouped = /* @__PURE__ */ new Map();
58778
+ for (const category of CATEGORY_ORDER2) {
58779
+ grouped.set(category, []);
58780
+ }
58781
+ for (const opt of cmd.options) {
58782
+ if (opt.hidden) continue;
58783
+ const longFlag = opt.long ?? opt.short ?? "";
58784
+ const category = longFlag ? OPTION_CATEGORY[longFlag] ?? "other" : "other";
58785
+ grouped.get(category).push({
58786
+ category,
58787
+ flags: opt.flags,
58788
+ description: opt.description ?? ""
58789
+ });
58790
+ }
58791
+ return grouped;
58792
+ }
58793
+ function renderOption(opt, flagWidth) {
58794
+ const padded = opt.flags.padEnd(flagWidth, " ");
58795
+ return ` ${padded} ${opt.description}`;
58796
+ }
58797
+ function formatGroupedHelp(cmd) {
58798
+ const lines = [];
58799
+ const usage = cmd.usage ? cmd.usage() : `Usage: ${cmd.name()} [options]`;
58800
+ lines.push(usage);
58801
+ lines.push("");
58802
+ if (cmd.description()) {
58803
+ lines.push(cmd.description());
58804
+ lines.push("");
58805
+ }
58806
+ const grouped = groupOptions(cmd);
58807
+ let flagWidth = 0;
58808
+ for (const options of grouped.values()) {
58809
+ for (const opt of options) {
58810
+ if (opt.flags.length > flagWidth) flagWidth = opt.flags.length;
58811
+ }
58812
+ }
58813
+ let firstCategory = true;
58814
+ for (const category of CATEGORY_ORDER2) {
58815
+ const options = grouped.get(category);
58816
+ if (options.length === 0) continue;
58817
+ if (!firstCategory) lines.push("");
58818
+ firstCategory = false;
58819
+ lines.push(` ${CATEGORY_LABELS3[category]}:`);
58820
+ for (const opt of options) {
58821
+ lines.push(renderOption(opt, flagWidth));
58822
+ }
58823
+ }
58824
+ lines.push("");
58825
+ lines.push(
58826
+ "Use `--help-flat` for the standard un-grouped list. See `https://usebrick.dev/docs/scan-options` for full docs."
58827
+ );
58828
+ return lines.join("\n");
58829
+ }
58830
+
58831
+ // src/cli/program.ts
58832
+ init_scan2();
58180
58833
  process.on("uncaughtException", (err) => {
58181
58834
  logger.error(`Unexpected error: ${err instanceof Error ? err.message : String(err)}`);
58182
58835
  process.exit(3);
@@ -58184,6 +58837,7 @@ process.on("uncaughtException", (err) => {
58184
58837
  async function runCli({ start }) {
58185
58838
  try {
58186
58839
  const program = new Command().name("slopbrick").description("Repository Coherence Scanner \u2014 surface AI-induced pattern drift, secret leaks, and design-token violations").version(VERSION).option("--framework <name>", "framework multiplier to apply").option("--include <glob>", "include pattern (repeatable)", collectGlob, []).option("--exclude <glob>", "exclude pattern (repeatable)", collectGlob, []).option("--ai-only", "only report AI-specific issues").option("--human-only", "only report human-facing issues").option("--ignore-wcag22", "ignore WCAG 2.2 related issues").option("--format <pretty|json|sarif|html>", "output format", "pretty").option("--threads <n>", "number of worker threads", parseThreads).option("--since <ref>", "only scan files changed since git ref").option("--diff <ref>", "alias for --since <ref>; also adds PR Slop Score to the report").option("--workspace <path>", "workspace/project path", process.cwd()).option("--tighten", "tighten baseline allowances").option("--fix", "apply auto-fixes").option("--dry-run", "with --fix: print what would change without writing").option("--show-fixes-diff", "print unified diff of proposed auto-fixes").option("--doctor", "run diagnostics").option("--watch", "watch files and re-run").option("--suggest", "print remediation advice").option("--why-failing", "print the top 5 rules dragging the score down").option("--brief", "terse output (verdict + headline + threshold + delta only)").option("--heatmap", "print migration ROI heatmap").option("--quiet", "suppress non-error output").option("--verbose", "enable debug logging (file paths, timings, rule-fire counts)").option("--strict", "exit 2 if any high-severity issue remains").option("--no-increase", "exit 2 if slop index increased since last run").option("--baseline", "save a baseline after this scan").option("--trend [n]", "print a sparkline of the last n runs", parseTrend).option("--json [path]", "write JSON report to path or stdout").option("--html [path]", "write HTML report to path or stdout").option("--staged", "scan only changed files (staged and unstaged)").option("--changed", "scan working-tree changes (staged + unstaged + untracked)").option("--incremental", "skip unchanged files using the persisted hash cache").option("--cache-path <path>", "path to the incremental-scan cache (default: .slopbrick-cache.json)").option("--tokens <path>", "merge tokens.json layout values into the arbitrary-value allowlist").option("--cache", "cache parsed AST results locally").option("--no-color", "suppress ANSI color codes in output").option("--security-only", "run only the security/* rules").option("--full", "show the complete report (all issues, all categories)");
58840
+ program.helpInformation = () => formatGroupedHelp(program);
58187
58841
  registerInit(program);
58188
58842
  registerInstall(program);
58189
58843
  registerUninstall(program);
@@ -58347,6 +59001,11 @@ async function runCli({ start }) {
58347
59001
  registerTokens(program);
58348
59002
  registerReport(program);
58349
59003
  registerScan(program, scanAction);
59004
+ if (process.argv.includes("--help-flat")) {
59005
+ delete program.helpInformation;
59006
+ program.outputHelp();
59007
+ process.exit(0);
59008
+ }
58350
59009
  await program.parseAsync(process.argv);
58351
59010
  } catch (err) {
58352
59011
  if (err instanceof ConfigValidationError) {