slopbrick 0.11.0 → 0.11.2

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
@@ -30,7 +30,7 @@ var VERSION, AI_SECURITY_NUMERIC, REPOSITORY_HEALTH_WEIGHTS;
30
30
  var init_types = __esm({
31
31
  "src/types.ts"() {
32
32
  "use strict";
33
- VERSION = "0.10.0";
33
+ VERSION = "0.11.2";
34
34
  AI_SECURITY_NUMERIC = {
35
35
  low: 100,
36
36
  medium: 75,
@@ -3482,7 +3482,7 @@ function discoverTestFiles(cwd) {
3482
3482
  }
3483
3483
  return found;
3484
3484
  }
3485
- function walk(dir, out, readdirSync6, statSync7) {
3485
+ function walk(dir, out, readdirSync6, statSync8) {
3486
3486
  let entries;
3487
3487
  try {
3488
3488
  entries = readdirSync6(dir);
@@ -3494,12 +3494,12 @@ function walk(dir, out, readdirSync6, statSync7) {
3494
3494
  const full = `${dir}/${entry}`;
3495
3495
  let stat;
3496
3496
  try {
3497
- stat = statSync7(full);
3497
+ stat = statSync8(full);
3498
3498
  } catch {
3499
3499
  continue;
3500
3500
  }
3501
3501
  if (stat.isDirectory()) {
3502
- walk(full, out, readdirSync6, statSync7);
3502
+ walk(full, out, readdirSync6, statSync8);
3503
3503
  } else if (stat.isFile()) {
3504
3504
  if (/\.(test|spec)\.[jt]sx?$/.test(entry) || /\.stories\.[jt]sx?$/.test(entry)) {
3505
3505
  out.push(full);
@@ -9408,8 +9408,8 @@ function hasExtendedSibling(filePath) {
9408
9408
  const base = basename(filePath);
9409
9409
  for (const ext of SOURCE_EXTENSIONS) {
9410
9410
  try {
9411
- const { statSync: statSync7 } = __require("fs");
9412
- statSync7(join7(dir, base + ext));
9411
+ const { statSync: statSync8 } = __require("fs");
9412
+ statSync8(join7(dir, base + ext));
9413
9413
  return true;
9414
9414
  } catch {
9415
9415
  }
@@ -9566,8 +9566,8 @@ var init_git = __esm({
9566
9566
  import { createHash as createHash2 } from "crypto";
9567
9567
  import { existsSync as existsSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync2, renameSync, mkdirSync, unlinkSync } from "fs";
9568
9568
  import { dirname as dirname4, isAbsolute, resolve as resolve7 } from "path";
9569
- function loadCache(cachePath6) {
9570
- const abs = isAbsolute(cachePath6) ? cachePath6 : resolve7(process.cwd(), cachePath6);
9569
+ function loadCache(cachePath4) {
9570
+ const abs = isAbsolute(cachePath4) ? cachePath4 : resolve7(process.cwd(), cachePath4);
9571
9571
  if (!existsSync8(abs)) return void 0;
9572
9572
  try {
9573
9573
  const raw = readFileSync9(abs, "utf-8");
@@ -9578,8 +9578,8 @@ function loadCache(cachePath6) {
9578
9578
  return void 0;
9579
9579
  }
9580
9580
  }
9581
- function saveCache(cachePath6, cache) {
9582
- const abs = isAbsolute(cachePath6) ? cachePath6 : resolve7(process.cwd(), cachePath6);
9581
+ function saveCache(cachePath4, cache) {
9582
+ const abs = isAbsolute(cachePath4) ? cachePath4 : resolve7(process.cwd(), cachePath4);
9583
9583
  mkdirSync(dirname4(abs), { recursive: true });
9584
9584
  const tmp = abs + ".tmp";
9585
9585
  if (existsSync8(tmp)) {
@@ -12876,38 +12876,46 @@ var init_heatmap = __esm({
12876
12876
  }
12877
12877
  });
12878
12878
 
12879
+ // ../core/dist/index.js
12880
+ import { writeFileSync as writeFileSync5, renameSync as renameSync2, existsSync as existsSync12, readFileSync as readFileSync15, statSync as statSync5, mkdirSync as mkdirSync3 } from "fs";
12881
+ import { join as join10, dirname as dirname6 } from "path";
12882
+ function inventoryPath(workspaceDir) {
12883
+ return join10(workspaceDir, ".slopbrick", INVENTORY_FILENAME);
12884
+ }
12885
+ function constitutionPath(workspaceDir) {
12886
+ return join10(workspaceDir, ".slopbrick", CONSTITUTION_FILENAME);
12887
+ }
12888
+ function ensureSlopbrickDir(workspaceDir) {
12889
+ mkdirSync3(join10(workspaceDir, ".slopbrick"), { recursive: true });
12890
+ }
12891
+ function writeJsonAtomic(filePath, payload) {
12892
+ ensureSlopbrickDir(dirname6(filePath));
12893
+ const tmp = `${filePath}.tmp`;
12894
+ writeFileSync5(tmp, JSON.stringify(payload, null, 2), "utf-8");
12895
+ renameSync2(tmp, filePath);
12896
+ }
12897
+ function saveInventory(workspaceDir, inventory) {
12898
+ writeJsonAtomic(inventoryPath(workspaceDir), inventory);
12899
+ }
12900
+ function saveConstitution(workspaceDir, constitution) {
12901
+ writeJsonAtomic(constitutionPath(workspaceDir), constitution);
12902
+ }
12903
+ var MEMORY_SCHEMA_VERSION, INVENTORY_FILENAME, CONSTITUTION_FILENAME;
12904
+ var init_dist = __esm({
12905
+ "../core/dist/index.js"() {
12906
+ "use strict";
12907
+ MEMORY_SCHEMA_VERSION = "2";
12908
+ INVENTORY_FILENAME = "inventory.json";
12909
+ CONSTITUTION_FILENAME = "constitution.json";
12910
+ }
12911
+ });
12912
+
12879
12913
  // src/engine/memory.ts
12880
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync15, writeFileSync as writeFileSync5 } from "fs";
12881
- import { dirname as dirname6, join as join10 } from "path";
12914
+ import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "fs";
12915
+ import { dirname as dirname7, join as join11 } from "path";
12882
12916
  import { createHash as createHash6 } from "crypto";
12883
- import {
12884
- MEMORY_SCHEMA_VERSION,
12885
- writeCacheFromInventory
12886
- } from "@usebrick/core";
12887
- import {
12888
- MEMORY_SCHEMA_VERSION as MEMORY_SCHEMA_VERSION2,
12889
- isMemoryPattern as isMemoryPattern2,
12890
- isComponentFingerprint as isComponentFingerprint2,
12891
- isInventoryFile as isInventoryFile2,
12892
- isConstitutionFile as isConstitutionFile2,
12893
- isFileMtimeEntry as isFileMtimeEntry2,
12894
- inventoryPath as inventoryPath2,
12895
- constitutionPath as constitutionPath2,
12896
- cachePath as cachePath3,
12897
- loadInventory as loadInventory2,
12898
- loadConstitution as loadConstitution2,
12899
- saveConstitution as saveConstitution2,
12900
- readCache as readCache3,
12901
- isInventoryFresh as isInventoryFresh2,
12902
- invalidateFile as invalidateFile2
12903
- } from "@usebrick/core";
12904
- async function saveInventory(workspaceDir, inventory, computeHash = computeFileHash) {
12905
- const { saveInventory: coreSave } = await import("@usebrick/core");
12906
- coreSave(workspaceDir, inventory);
12907
- writeCacheFromInventory(workspaceDir, inventory, computeHash);
12908
- }
12909
12917
  function telemetryPath(cwd) {
12910
- return join10(cwd, TELEMETRY_FILE);
12918
+ return join11(cwd, TELEMETRY_FILE);
12911
12919
  }
12912
12920
  function isSlopAuditRun(value) {
12913
12921
  if (typeof value !== "object" || value === null) return false;
@@ -12916,9 +12924,9 @@ function isSlopAuditRun(value) {
12916
12924
  }
12917
12925
  function readRuns(cwd) {
12918
12926
  const path = telemetryPath(cwd);
12919
- if (!existsSync12(path)) return [];
12927
+ if (!existsSync13(path)) return [];
12920
12928
  try {
12921
- const raw = readFileSync15(path, "utf-8");
12929
+ const raw = readFileSync16(path, "utf-8");
12922
12930
  const parsed = JSON.parse(raw);
12923
12931
  if (!Array.isArray(parsed)) return [];
12924
12932
  return parsed.filter(isSlopAuditRun);
@@ -12948,8 +12956,8 @@ function appendRun(cwd, report, thresholdExceeded2) {
12948
12956
  runs.splice(0, runs.length - MAX_RUNS);
12949
12957
  }
12950
12958
  const path = telemetryPath(cwd);
12951
- mkdirSync3(dirname6(path), { recursive: true });
12952
- writeFileSync5(path, JSON.stringify(runs, null, 2));
12959
+ mkdirSync4(dirname7(path), { recursive: true });
12960
+ writeFileSync6(path, JSON.stringify(runs, null, 2));
12953
12961
  return run2;
12954
12962
  }
12955
12963
  async function buildInventoryFromScan(scanResult, config, durationMs) {
@@ -13068,7 +13076,8 @@ var init_memory = __esm({
13068
13076
  init_types();
13069
13077
  init_patterns();
13070
13078
  init_cache_incremental();
13071
- TELEMETRY_FILE = join10(".slopbrick", "memory.json");
13079
+ init_dist();
13080
+ TELEMETRY_FILE = join11(".slopbrick", "memory.json");
13072
13081
  MAX_RUNS = 1e3;
13073
13082
  }
13074
13083
  });
@@ -13076,18 +13085,18 @@ var init_memory = __esm({
13076
13085
  // src/engine/telemetry.ts
13077
13086
  import {
13078
13087
  appendFileSync,
13079
- existsSync as existsSync13,
13080
- mkdirSync as mkdirSync4,
13081
- readFileSync as readFileSync16,
13088
+ existsSync as existsSync14,
13089
+ mkdirSync as mkdirSync5,
13090
+ readFileSync as readFileSync17,
13082
13091
  readdirSync as readdirSync5,
13083
- renameSync as renameSync2,
13092
+ renameSync as renameSync3,
13084
13093
  rmSync,
13085
- statSync as statSync5
13094
+ statSync as statSync6
13086
13095
  } from "fs";
13087
13096
  import { createHash as createHash7 } from "crypto";
13088
- import { dirname as dirname7, join as join11, relative as relative7 } from "path";
13097
+ import { dirname as dirname8, join as join12, relative as relative7 } from "path";
13089
13098
  function telemetryPath2(cwd) {
13090
- return join11(cwd, TELEMETRY_DIR, TELEMETRY_FILE2);
13099
+ return join12(cwd, TELEMETRY_DIR, TELEMETRY_FILE2);
13091
13100
  }
13092
13101
  function hashString(input) {
13093
13102
  return createHash7("sha256").update(input).digest("hex").slice(0, 16);
@@ -13140,33 +13149,33 @@ function isTelemetryFile(name) {
13140
13149
  }
13141
13150
  function rotateTelemetry(cwd) {
13142
13151
  const path = telemetryPath2(cwd);
13143
- if (!existsSync13(path)) {
13152
+ if (!existsSync14(path)) {
13144
13153
  return;
13145
13154
  }
13146
- const stats = statSync5(path);
13155
+ const stats = statSync6(path);
13147
13156
  if (stats.size < MAX_TELEMETRY_BYTES) {
13148
13157
  return;
13149
13158
  }
13150
- const dir = dirname7(path);
13159
+ const dir = dirname8(path);
13151
13160
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
13152
- renameSync2(path, join11(dir, `scans-${timestamp}.jsonl`));
13153
- const rotated = readdirSync5(dir).filter(isTelemetryFile).map((name) => ({ name, mtime: statSync5(join11(dir, name)).mtimeMs })).sort((a, b) => a.mtime - b.mtime);
13161
+ renameSync3(path, join12(dir, `scans-${timestamp}.jsonl`));
13162
+ const rotated = readdirSync5(dir).filter(isTelemetryFile).map((name) => ({ name, mtime: statSync6(join12(dir, name)).mtimeMs })).sort((a, b) => a.mtime - b.mtime);
13154
13163
  while (rotated.length > MAX_ROTATED_FILES) {
13155
13164
  const oldest = rotated.shift();
13156
13165
  if (oldest) {
13157
- rmSync(join11(dir, oldest.name), { force: true });
13166
+ rmSync(join12(dir, oldest.name), { force: true });
13158
13167
  }
13159
13168
  }
13160
13169
  }
13161
13170
  function readTelemetry(cwd) {
13162
- const dir = join11(cwd, TELEMETRY_DIR);
13163
- if (!existsSync13(dir)) {
13171
+ const dir = join12(cwd, TELEMETRY_DIR);
13172
+ if (!existsSync14(dir)) {
13164
13173
  return [];
13165
13174
  }
13166
- const files = readdirSync5(dir).filter(isTelemetryFile).sort().map((name) => join11(dir, name));
13175
+ const files = readdirSync5(dir).filter(isTelemetryFile).sort().map((name) => join12(dir, name));
13167
13176
  const payloads = [];
13168
13177
  for (const file of files) {
13169
- const raw = readFileSync16(file, "utf-8");
13178
+ const raw = readFileSync17(file, "utf-8");
13170
13179
  for (const line of raw.split("\n")) {
13171
13180
  const trimmed = line.trim();
13172
13181
  if (!trimmed) continue;
@@ -13198,9 +13207,9 @@ function recordTelemetry(cwd, report, results, config) {
13198
13207
  files: buildFileRecords(cwd, report, results)
13199
13208
  };
13200
13209
  const path = telemetryPath2(cwd);
13201
- const dir = dirname7(path);
13202
- if (!existsSync13(dir)) {
13203
- mkdirSync4(dir, { recursive: true });
13210
+ const dir = dirname8(path);
13211
+ if (!existsSync14(dir)) {
13212
+ mkdirSync5(dir, { recursive: true });
13204
13213
  }
13205
13214
  rotateTelemetry(cwd);
13206
13215
  appendFileSync(path, JSON.stringify(payload) + "\n", "utf-8");
@@ -13210,7 +13219,7 @@ var TELEMETRY_DIR, TELEMETRY_FILE2, MAX_TELEMETRY_BYTES, MAX_ROTATED_FILES;
13210
13219
  var init_telemetry = __esm({
13211
13220
  "src/engine/telemetry.ts"() {
13212
13221
  "use strict";
13213
- TELEMETRY_DIR = join11(".slopbrick", "flywheel");
13222
+ TELEMETRY_DIR = join12(".slopbrick", "flywheel");
13214
13223
  TELEMETRY_FILE2 = "scans.jsonl";
13215
13224
  MAX_TELEMETRY_BYTES = 10 * 1024 * 1024;
13216
13225
  MAX_ROTATED_FILES = 5;
@@ -13219,8 +13228,8 @@ var init_telemetry = __esm({
13219
13228
 
13220
13229
  // src/engine/flywheel.ts
13221
13230
  import { createHash as createHash8 } from "crypto";
13222
- import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync17, writeFileSync as writeFileSync6 } from "fs";
13223
- import { join as join12 } from "path";
13231
+ import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync18, writeFileSync as writeFileSync7 } from "fs";
13232
+ import { join as join13 } from "path";
13224
13233
  function severityBump(severity) {
13225
13234
  const order = ["low", "medium", "high"];
13226
13235
  const idx = order.indexOf(severity);
@@ -13321,30 +13330,30 @@ function migrateFlywheelState(state) {
13321
13330
  };
13322
13331
  }
13323
13332
  function loadFlywheelState(cwd) {
13324
- const path = join12(cwd, FLYWHEEL_DIR, STATE_FILE);
13325
- if (!existsSync14(path)) {
13333
+ const path = join13(cwd, FLYWHEEL_DIR, STATE_FILE);
13334
+ if (!existsSync15(path)) {
13326
13335
  return migrateFlywheelState({});
13327
13336
  }
13328
13337
  try {
13329
- const parsed = JSON.parse(readFileSync17(path, "utf-8"));
13338
+ const parsed = JSON.parse(readFileSync18(path, "utf-8"));
13330
13339
  return migrateFlywheelState(parsed);
13331
13340
  } catch {
13332
13341
  return migrateFlywheelState({});
13333
13342
  }
13334
13343
  }
13335
13344
  function loadResearchMetricsFromDisk(cwd) {
13336
- const flywheelDir = join12(cwd, FLYWHEEL_DIR);
13337
- const analysisPath = join12(flywheelDir, "analysis.json");
13338
- const candidatesPath = join12(flywheelDir, "rule-candidates.json");
13339
- const hasAnalysis = existsSync14(analysisPath);
13340
- const hasCandidates = existsSync14(candidatesPath);
13345
+ const flywheelDir = join13(cwd, FLYWHEEL_DIR);
13346
+ const analysisPath = join13(flywheelDir, "analysis.json");
13347
+ const candidatesPath = join13(flywheelDir, "rule-candidates.json");
13348
+ const hasAnalysis = existsSync15(analysisPath);
13349
+ const hasCandidates = existsSync15(candidatesPath);
13341
13350
  if (!hasAnalysis && !hasCandidates) return void 0;
13342
13351
  let generatedSampleCount = 0;
13343
13352
  let generatedRuleCoverage = 0;
13344
13353
  let candidateYield = 0;
13345
13354
  if (hasAnalysis) {
13346
13355
  try {
13347
- const raw = JSON.parse(readFileSync17(analysisPath, "utf8"));
13356
+ const raw = JSON.parse(readFileSync18(analysisPath, "utf8"));
13348
13357
  generatedSampleCount = raw.summary?.total ?? 0;
13349
13358
  generatedRuleCoverage = raw.summary?.coverage ?? 0;
13350
13359
  } catch {
@@ -13352,7 +13361,7 @@ function loadResearchMetricsFromDisk(cwd) {
13352
13361
  }
13353
13362
  if (hasCandidates) {
13354
13363
  try {
13355
- const raw = JSON.parse(readFileSync17(candidatesPath, "utf8"));
13364
+ const raw = JSON.parse(readFileSync18(candidatesPath, "utf8"));
13356
13365
  candidateYield = raw.candidates?.length ?? 0;
13357
13366
  } catch {
13358
13367
  }
@@ -13365,9 +13374,9 @@ function loadResearchMetricsFromDisk(cwd) {
13365
13374
  };
13366
13375
  }
13367
13376
  function saveFlywheelState(cwd, state) {
13368
- const dir = join12(cwd, FLYWHEEL_DIR);
13369
- if (!existsSync14(dir)) mkdirSync5(dir, { recursive: true });
13370
- writeFileSync6(join12(dir, STATE_FILE), JSON.stringify(state, null, 2));
13377
+ const dir = join13(cwd, FLYWHEEL_DIR);
13378
+ if (!existsSync15(dir)) mkdirSync6(dir, { recursive: true });
13379
+ writeFileSync7(join13(dir, STATE_FILE), JSON.stringify(state, null, 2));
13371
13380
  }
13372
13381
  function hashFile(filePath) {
13373
13382
  return createHash8("sha256").update(filePath).digest("hex").slice(0, 16);
@@ -13660,7 +13669,7 @@ var init_signal_strength2 = __esm({
13660
13669
  });
13661
13670
 
13662
13671
  // src/cli/tokens.ts
13663
- import { readFileSync as readFileSync18 } from "fs";
13672
+ import { readFileSync as readFileSync19 } from "fs";
13664
13673
  function parseDtcgTokens(raw) {
13665
13674
  let parsed;
13666
13675
  try {
@@ -13675,7 +13684,7 @@ function parseDtcgTokens(raw) {
13675
13684
  }
13676
13685
  function readDtcgTokensFile(path) {
13677
13686
  try {
13678
- const raw = readFileSync18(path, "utf-8");
13687
+ const raw = readFileSync19(path, "utf-8");
13679
13688
  return parseDtcgTokens(raw);
13680
13689
  } catch (error) {
13681
13690
  return { ok: false, error: `Cannot read ${path}: ${error.message}` };
@@ -14115,8 +14124,8 @@ __export(doc_freshness_exports, {
14115
14124
  extractInlineCodeSpans: () => extractInlineCodeSpans,
14116
14125
  extractMarkdownLinks: () => extractMarkdownLinks
14117
14126
  });
14118
- import { readFileSync as readFileSync19, existsSync as existsSync15 } from "fs";
14119
- import { join as join13, dirname as dirname9, relative as relative8 } from "path";
14127
+ import { readFileSync as readFileSync20, existsSync as existsSync16 } from "fs";
14128
+ import { join as join14, dirname as dirname10, relative as relative8 } from "path";
14120
14129
  import { globby as globby2 } from "globby";
14121
14130
  function extractInlineCodeSpans(source) {
14122
14131
  const hits = [];
@@ -14180,10 +14189,10 @@ function extractMarkdownLinks(source) {
14180
14189
  }
14181
14190
  function declaredPackages(cwd) {
14182
14191
  const out = /* @__PURE__ */ new Set();
14183
- const pkgPath = join13(cwd, "package.json");
14184
- if (!existsSync15(pkgPath)) return out;
14192
+ const pkgPath = join14(cwd, "package.json");
14193
+ if (!existsSync16(pkgPath)) return out;
14185
14194
  try {
14186
- const raw = readFileSync19(pkgPath, "utf-8");
14195
+ const raw = readFileSync20(pkgPath, "utf-8");
14187
14196
  const pkg = JSON.parse(raw);
14188
14197
  for (const k of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
14189
14198
  const v = pkg[k];
@@ -14219,7 +14228,7 @@ async function extractExports(cwd, config, maxFiles = 500) {
14219
14228
  for (const abs of limited) {
14220
14229
  let source;
14221
14230
  try {
14222
- source = readFileSync19(abs, "utf-8");
14231
+ source = readFileSync20(abs, "utf-8");
14223
14232
  } catch {
14224
14233
  continue;
14225
14234
  }
@@ -14318,10 +14327,10 @@ function detectExpiredCodeExamples(source, relPath, cwd, packages) {
14318
14327
  const imports = extractImports(block.body);
14319
14328
  for (const imp of imports) {
14320
14329
  if (imp.startsWith(".") || imp.startsWith("/")) {
14321
- const docDir = dirname9(join13(cwd, relPath));
14322
- const candidate = join13(docDir, imp);
14330
+ const docDir = dirname10(join14(cwd, relPath));
14331
+ const candidate = join14(docDir, imp);
14323
14332
  const exts = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
14324
- const found = exts.some((e) => existsSync15(candidate + e));
14333
+ const found = exts.some((e) => existsSync16(candidate + e));
14325
14334
  if (!found) {
14326
14335
  findings.push({
14327
14336
  ruleId: "docs/expired-code-example",
@@ -14360,7 +14369,7 @@ function detectExpiredCodeExamples(source, relPath, cwd, packages) {
14360
14369
  function detectBrokenLinks(source, relPath, cwd) {
14361
14370
  const findings = [];
14362
14371
  const links = extractMarkdownLinks(source);
14363
- const docDir = dirname9(join13(cwd, relPath));
14372
+ const docDir = dirname10(join14(cwd, relPath));
14364
14373
  for (const link of links) {
14365
14374
  const target = link.target;
14366
14375
  if (target.startsWith("http://") || target.startsWith("https://")) continue;
@@ -14368,8 +14377,8 @@ function detectBrokenLinks(source, relPath, cwd) {
14368
14377
  if (target.startsWith("#")) continue;
14369
14378
  if (target.startsWith("//")) continue;
14370
14379
  if (target.startsWith("/")) continue;
14371
- const resolved = join13(docDir, target);
14372
- if (!existsSync15(resolved)) {
14380
+ const resolved = join14(docDir, target);
14381
+ if (!existsSync16(resolved)) {
14373
14382
  findings.push({
14374
14383
  ruleId: "docs/broken-link",
14375
14384
  severity: "low",
@@ -14406,7 +14415,7 @@ async function buildDocFreshness(cwd, config, options = {}) {
14406
14415
  for (const abs of docLimited) {
14407
14416
  let source;
14408
14417
  try {
14409
- source = readFileSync19(abs, "utf-8");
14418
+ source = readFileSync20(abs, "utf-8");
14410
14419
  } catch {
14411
14420
  continue;
14412
14421
  }
@@ -14713,7 +14722,7 @@ __export(db_health_exports, {
14713
14722
  DB_RULE_WEIGHTS: () => DB_RULE_WEIGHTS,
14714
14723
  buildDbHealth: () => buildDbHealth
14715
14724
  });
14716
- import { readFileSync as readFileSync20 } from "fs";
14725
+ import { readFileSync as readFileSync21 } from "fs";
14717
14726
  import { relative as relative9 } from "path";
14718
14727
  import { globby as globby3 } from "globby";
14719
14728
  import { parse as parseSql, loadModule as loadSqlModule } from "pgsql-parser";
@@ -14724,7 +14733,7 @@ function ensureSqlModule() {
14724
14733
  async function parseSqlFile(filePath) {
14725
14734
  let raw;
14726
14735
  try {
14727
- raw = readFileSync20(filePath, "utf-8");
14736
+ raw = readFileSync21(filePath, "utf-8");
14728
14737
  } catch {
14729
14738
  return null;
14730
14739
  }
@@ -14964,7 +14973,7 @@ async function buildDbHealth(cwd, _config, options = {}) {
14964
14973
  for (const abs of tsFiles.slice(0, maxFiles)) {
14965
14974
  let source;
14966
14975
  try {
14967
- source = readFileSync20(abs, "utf-8");
14976
+ source = readFileSync21(abs, "utf-8");
14968
14977
  } catch {
14969
14978
  continue;
14970
14979
  }
@@ -15417,8 +15426,8 @@ __export(scan_exports, {
15417
15426
  scanProject: () => scanProject,
15418
15427
  watchProject: () => watchProject
15419
15428
  });
15420
- import { existsSync as existsSync16, writeFileSync as writeFileSync7, watch, statSync as statSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync21 } from "fs";
15421
- import { resolve as resolve9, join as join15, relative as relative10, extname as extname6, sep as sep2 } from "path";
15429
+ import { existsSync as existsSync17, writeFileSync as writeFileSync8, watch, statSync as statSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync22 } from "fs";
15430
+ import { resolve as resolve9, join as join16, relative as relative10, extname as extname6, sep as sep2 } from "path";
15422
15431
  function buildBaselineCache(report, configHash, gitHead, cwd) {
15423
15432
  const scores = {};
15424
15433
  for (const component of report.components) {
@@ -15441,10 +15450,10 @@ async function runScan(options, explicitPaths) {
15441
15450
  setLoggerQuiet(!!options.quiet);
15442
15451
  const startTime = Date.now();
15443
15452
  const cwd = resolve9(options.workspace ?? process.cwd());
15444
- if (!existsSync16(cwd)) {
15453
+ if (!existsSync17(cwd)) {
15445
15454
  throw new Error(`Workspace not found: ${cwd}`);
15446
15455
  }
15447
- const cwdStat = statSync6(cwd);
15456
+ const cwdStat = statSync7(cwd);
15448
15457
  if (!cwdStat.isDirectory()) {
15449
15458
  throw new Error(`Workspace is not a directory: ${cwd}`);
15450
15459
  }
@@ -15486,8 +15495,8 @@ async function runScan(options, explicitPaths) {
15486
15495
  const resolved = explicitPaths.map((p) => resolve9(cwd, p));
15487
15496
  const expanded = [];
15488
15497
  for (const p of resolved) {
15489
- if (existsSync16(p) && statSync6(p).isDirectory()) {
15490
- const found = await globby4(join15(p, "**/*"), { absolute: true, onlyFiles: true });
15498
+ if (existsSync17(p) && statSync7(p).isDirectory()) {
15499
+ const found = await globby4(join16(p, "**/*"), { absolute: true, onlyFiles: true });
15491
15500
  for (const f of found) {
15492
15501
  if (!ALL_SOURCE_EXTENSIONS.has(extname6(f).toLowerCase())) continue;
15493
15502
  const rel = relative10(cwd, f).split(sep2).join("/");
@@ -15525,8 +15534,8 @@ async function runScan(options, explicitPaths) {
15525
15534
  }
15526
15535
  let incrementalSummary;
15527
15536
  if (options.incremental) {
15528
- const cachePath6 = options.cachePath ?? ".slopbrick-cache.json";
15529
- const existing = loadCache(cachePath6);
15537
+ const cachePath4 = options.cachePath ?? ".slopbrick-cache.json";
15538
+ const existing = loadCache(cachePath4);
15530
15539
  const { toScan, unchanged } = partitionByCache(files, existing);
15531
15540
  files = toScan;
15532
15541
  incrementalSummary = { skipped: unchanged.length, rescanned: toScan.length };
@@ -15631,7 +15640,7 @@ async function runScan(options, explicitPaths) {
15631
15640
  const scannedPaths = new Set(results.map((result) => result.filePath));
15632
15641
  for (const [filePath, cached] of Object.entries(baseline.scores)) {
15633
15642
  if (scannedPaths.has(filePath)) continue;
15634
- if (!existsSync16(filePath)) continue;
15643
+ if (!existsSync17(filePath)) continue;
15635
15644
  scores.push({
15636
15645
  filePath,
15637
15646
  rawScore: 0,
@@ -15945,8 +15954,8 @@ async function runScan(options, explicitPaths) {
15945
15954
  appendRun(cwd, report, thresholdExceeded(report, config));
15946
15955
  }
15947
15956
  if (options.incremental) {
15948
- const cachePath6 = options.cachePath ?? ".slopbrick-cache.json";
15949
- const existing = loadCache(cachePath6) ?? emptyCache();
15957
+ const cachePath4 = options.cachePath ?? ".slopbrick-cache.json";
15958
+ const existing = loadCache(cachePath4) ?? emptyCache();
15950
15959
  const next = { ...existing, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
15951
15960
  for (const result of results) {
15952
15961
  try {
@@ -15960,7 +15969,7 @@ async function runScan(options, explicitPaths) {
15960
15969
  } catch {
15961
15970
  }
15962
15971
  }
15963
- saveCache(cachePath6, next);
15972
+ saveCache(cachePath4, next);
15964
15973
  if (incrementalSummary && !options.quiet) {
15965
15974
  logger.info(
15966
15975
  `Incremental: re-scanned ${incrementalSummary.rescanned}, skipped ${incrementalSummary.skipped} (unchanged).`
@@ -15992,10 +16001,10 @@ async function runScan(options, explicitPaths) {
15992
16001
  report.research = state.research;
15993
16002
  }
15994
16003
  if (flywheelOutput.suggestions.length > 0) {
15995
- const suggestionsDir = join15(cwd, ".slopbrick", "flywheel");
15996
- if (!existsSync16(suggestionsDir)) mkdirSync6(suggestionsDir, { recursive: true });
15997
- writeFileSync7(
15998
- join15(suggestionsDir, "rule-suggestions.json"),
16004
+ const suggestionsDir = join16(cwd, ".slopbrick", "flywheel");
16005
+ if (!existsSync17(suggestionsDir)) mkdirSync7(suggestionsDir, { recursive: true });
16006
+ writeFileSync8(
16007
+ join16(suggestionsDir, "rule-suggestions.json"),
15999
16008
  JSON.stringify(flywheelOutput.suggestions, null, 2)
16000
16009
  );
16001
16010
  }
@@ -16012,7 +16021,7 @@ async function runScan(options, explicitPaths) {
16012
16021
  );
16013
16022
  await saveInventory(cwd, inventory);
16014
16023
  const constitution = buildConstitutionFromConfig(config, cwd);
16015
- await saveConstitution2(cwd, constitution);
16024
+ await saveConstitution(cwd, constitution);
16016
16025
  if (!options.quiet && !machineReadableStdout) {
16017
16026
  logger.info(`Memory persisted to .slopbrick/ (${inventory.patterns.length} patterns, ${inventory.components.length} components).`);
16018
16027
  }
@@ -16043,7 +16052,7 @@ function collectBusinessLogicIssues(cwd, filePaths) {
16043
16052
  for (const absPath of filePaths) {
16044
16053
  let source;
16045
16054
  try {
16046
- source = readFileSync21(absPath, "utf-8");
16055
+ source = readFileSync22(absPath, "utf-8");
16047
16056
  } catch {
16048
16057
  continue;
16049
16058
  }
@@ -16112,7 +16121,7 @@ function renderOutput(report, options, cwd) {
16112
16121
  if (options.html) {
16113
16122
  const html = formatHtml(report);
16114
16123
  if (typeof options.html === "string") {
16115
- writeFileSync7(resolve9(options.html), html);
16124
+ writeFileSync8(resolve9(options.html), html);
16116
16125
  if (!options.quiet) {
16117
16126
  logger.info(`Wrote HTML report to ${options.html}`);
16118
16127
  }
@@ -16124,7 +16133,7 @@ function renderOutput(report, options, cwd) {
16124
16133
  if (options.json) {
16125
16134
  const json = formatJson(report);
16126
16135
  if (typeof options.json === "string") {
16127
- writeFileSync7(resolve9(options.json), json);
16136
+ writeFileSync8(resolve9(options.json), json);
16128
16137
  if (!options.quiet) {
16129
16138
  logger.info(`Wrote JSON report to ${options.json}`);
16130
16139
  }
@@ -16170,7 +16179,7 @@ async function watchProject(options, cwd, paths) {
16170
16179
  let currentBaseline;
16171
16180
  function getBaselineMtime() {
16172
16181
  try {
16173
- return statSync6(baselinePath(cwd)).mtimeMs;
16182
+ return statSync7(baselinePath(cwd)).mtimeMs;
16174
16183
  } catch {
16175
16184
  return void 0;
16176
16185
  }
@@ -16327,6 +16336,7 @@ var init_scan = __esm({
16327
16336
  init_unified_diff();
16328
16337
  init_heatmap();
16329
16338
  init_memory();
16339
+ init_dist();
16330
16340
  init_telemetry();
16331
16341
  init_flywheel();
16332
16342
  init_logger();
@@ -16340,17 +16350,17 @@ var init_scan = __esm({
16340
16350
  });
16341
16351
 
16342
16352
  // src/engine/memory-md.ts
16343
- import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync30, writeFileSync as writeFileSync13 } from "fs";
16344
- import { dirname as dirname14, join as join21 } from "path";
16353
+ import { existsSync as existsSync22, mkdirSync as mkdirSync13, readFileSync as readFileSync31, writeFileSync as writeFileSync14 } from "fs";
16354
+ import { dirname as dirname15, join as join22 } from "path";
16345
16355
  async function readMemoryMarkdown(workspaceDir) {
16346
16356
  return new Promise((resolve15) => {
16347
16357
  try {
16348
- const path = join21(workspaceDir, MEMORY_MD_FILE);
16349
- if (!existsSync21(path)) {
16358
+ const path = join22(workspaceDir, MEMORY_MD_FILE);
16359
+ if (!existsSync22(path)) {
16350
16360
  resolve15(null);
16351
16361
  return;
16352
16362
  }
16353
- const content = readFileSync30(path, "utf-8");
16363
+ const content = readFileSync31(path, "utf-8");
16354
16364
  resolve15(content);
16355
16365
  } catch {
16356
16366
  resolve15(null);
@@ -16361,7 +16371,7 @@ var MEMORY_MD_FILE;
16361
16371
  var init_memory_md = __esm({
16362
16372
  "src/engine/memory-md.ts"() {
16363
16373
  "use strict";
16364
- MEMORY_MD_FILE = join21(".slopbrick", "memory.md");
16374
+ MEMORY_MD_FILE = join22(".slopbrick", "memory.md");
16365
16375
  }
16366
16376
  });
16367
16377
 
@@ -16574,7 +16584,7 @@ __export(tools_exports, {
16574
16584
  TOOL_DEFINITIONS: () => TOOL_DEFINITIONS,
16575
16585
  handleToolCall: () => handleToolCall
16576
16586
  });
16577
- import { readFileSync as readFileSync31 } from "fs";
16587
+ import { readFileSync as readFileSync32 } from "fs";
16578
16588
  import { resolve as resolve13 } from "path";
16579
16589
  function toolError(message) {
16580
16590
  return {
@@ -16718,7 +16728,7 @@ function runCheckConstitution(args, ctx) {
16718
16728
  const absPath = resolve13(ctx.cwd, path);
16719
16729
  let source;
16720
16730
  try {
16721
- source = readFileSync31(absPath, "utf-8");
16731
+ source = readFileSync32(absPath, "utf-8");
16722
16732
  } catch (err) {
16723
16733
  return toolError(
16724
16734
  `Cannot read file ${absPath}: ${err instanceof Error ? err.message : String(err)}`
@@ -16780,14 +16790,14 @@ async function runBusinessLogicScore(args, ctx) {
16780
16790
  const maxFiles = typeof maxFilesRaw === "number" && Number.isFinite(maxFilesRaw) && maxFilesRaw > 0 ? Math.min(2e3, Math.floor(maxFilesRaw)) : 500;
16781
16791
  try {
16782
16792
  const { discoverFiles: discoverFiles2 } = await Promise.resolve().then(() => (init_discover(), discover_exports));
16783
- const { readFileSync: readFileSync37 } = await import("fs");
16793
+ const { readFileSync: readFileSync39 } = await import("fs");
16784
16794
  const allFiles = await discoverFiles2(ctx.cwd, ctx.config);
16785
16795
  const limited = allFiles.slice(0, maxFiles);
16786
16796
  const issues = [];
16787
16797
  for (const absPath of limited) {
16788
16798
  let source;
16789
16799
  try {
16790
- source = readFileSync37(absPath, "utf-8");
16800
+ source = readFileSync39(absPath, "utf-8");
16791
16801
  } catch {
16792
16802
  continue;
16793
16803
  }
@@ -17052,13 +17062,186 @@ var init_tools = __esm({
17052
17062
  }
17053
17063
  });
17054
17064
 
17065
+ // src/cli/migrate.ts
17066
+ var migrate_exports = {};
17067
+ __export(migrate_exports, {
17068
+ applyMigration: () => applyMigration,
17069
+ formatMigrate: () => formatMigrate,
17070
+ isAlreadyMigrated: () => isAlreadyMigrated,
17071
+ logger: () => logger,
17072
+ planMigration: () => planMigration,
17073
+ runMigrate: () => runMigrate
17074
+ });
17075
+ import { existsSync as existsSync25, readFileSync as readFileSync37, renameSync as renameSync4, writeFileSync as writeFileSync18 } from "fs";
17076
+ import { join as join26 } from "path";
17077
+ function planMigration(workspaceDir) {
17078
+ const moves = [];
17079
+ const rewrites = [];
17080
+ const gitignoreEdits = [];
17081
+ const oldDir = join26(workspaceDir, ".slop-audit");
17082
+ const newDir = join26(workspaceDir, ".slopbrick");
17083
+ if (existsSync25(oldDir)) {
17084
+ moves.push({ from: oldDir, to: newDir, kind: "dir" });
17085
+ rewrites.push({
17086
+ path: join26(newDir, "inventory.json"),
17087
+ field: "version",
17088
+ from: '"1"',
17089
+ to: '"2"'
17090
+ });
17091
+ rewrites.push({
17092
+ path: join26(newDir, "constitution.json"),
17093
+ field: "version",
17094
+ from: '"1"',
17095
+ to: '"2"'
17096
+ });
17097
+ }
17098
+ const oldCache = join26(workspaceDir, ".slop-audit-cache.json");
17099
+ const newCache = join26(workspaceDir, ".slopbrick-cache.json");
17100
+ if (existsSync25(oldCache)) {
17101
+ moves.push({ from: oldCache, to: newCache, kind: "file" });
17102
+ }
17103
+ for (const ext of ["mjs", "cjs", "js"]) {
17104
+ const oldCfg = join26(workspaceDir, `slop-audit.config.${ext}`);
17105
+ const newCfg = join26(workspaceDir, `slopbrick.config.${ext}`);
17106
+ if (existsSync25(oldCfg)) {
17107
+ moves.push({ from: oldCfg, to: newCfg, kind: "config" });
17108
+ }
17109
+ }
17110
+ const gi = join26(workspaceDir, ".gitignore");
17111
+ if (existsSync25(gi)) {
17112
+ const content = readFileSync37(gi, "utf-8");
17113
+ if (content.includes(".slop-audit/")) {
17114
+ gitignoreEdits.push({
17115
+ path: gi,
17116
+ from: ".slop-audit/",
17117
+ to: ".slopbrick/"
17118
+ });
17119
+ }
17120
+ if (content.includes(".slop-audit-cache.json")) {
17121
+ gitignoreEdits.push({
17122
+ path: gi,
17123
+ from: ".slop-audit-cache.json",
17124
+ to: ".slopbrick-cache.json"
17125
+ });
17126
+ }
17127
+ }
17128
+ return { moves, rewrites, gitignoreEdits };
17129
+ }
17130
+ function isAlreadyMigrated(workspaceDir) {
17131
+ return existsSync25(join26(workspaceDir, ".slopbrick")) && !existsSync25(join26(workspaceDir, ".slop-audit"));
17132
+ }
17133
+ function applyMigration(plan, options = {}) {
17134
+ if (options.dryRun) return;
17135
+ for (const m of plan.moves) {
17136
+ renameSync4(m.from, m.to);
17137
+ }
17138
+ for (const r of plan.rewrites) {
17139
+ if (!existsSync25(r.path)) continue;
17140
+ const content = readFileSync37(r.path, "utf-8");
17141
+ const next = content.replace(`"version": ${r.from}`, `"version": ${r.to}`);
17142
+ writeFileSync18(r.path, next);
17143
+ }
17144
+ for (const g of plan.gitignoreEdits) {
17145
+ const content = readFileSync37(g.path, "utf-8");
17146
+ const next = content.replaceAll(g.from, g.to);
17147
+ writeFileSync18(g.path, next);
17148
+ }
17149
+ }
17150
+ function runMigrate(options) {
17151
+ const { workspace, dryRun = false, force = false } = options;
17152
+ if (!existsSync25(workspace)) {
17153
+ return {
17154
+ ok: false,
17155
+ alreadyMigrated: false,
17156
+ planned: { moves: [], rewrites: [], gitignoreEdits: [] },
17157
+ applied: false,
17158
+ reason: `Workspace ${workspace} does not exist`
17159
+ };
17160
+ }
17161
+ const alreadyMigrated = isAlreadyMigrated(workspace);
17162
+ const newDir = join26(workspace, ".slopbrick");
17163
+ const oldDir = join26(workspace, ".slop-audit");
17164
+ if (existsSync25(newDir) && existsSync25(oldDir) && !force) {
17165
+ return {
17166
+ ok: false,
17167
+ alreadyMigrated: false,
17168
+ planned: { moves: [], rewrites: [], gitignoreEdits: [] },
17169
+ applied: false,
17170
+ reason: `Both .slopbrick/ and .slop-audit/ exist. Use --force to overwrite the .slopbrick/ directory, or manually resolve the conflict.`
17171
+ };
17172
+ }
17173
+ const planned = planMigration(workspace);
17174
+ const nothingToDo = planned.moves.length === 0 && planned.rewrites.length === 0 && planned.gitignoreEdits.length === 0;
17175
+ if (nothingToDo) {
17176
+ return {
17177
+ ok: true,
17178
+ alreadyMigrated,
17179
+ planned,
17180
+ applied: false
17181
+ };
17182
+ }
17183
+ applyMigration(planned, { dryRun });
17184
+ return {
17185
+ ok: true,
17186
+ alreadyMigrated: false,
17187
+ planned,
17188
+ applied: !dryRun
17189
+ };
17190
+ }
17191
+ function formatMigrate(result) {
17192
+ const lines = [];
17193
+ if (result.alreadyMigrated) {
17194
+ lines.push("Already migrated to v2 (no work needed).");
17195
+ return lines.join("\n");
17196
+ }
17197
+ if (!result.ok) {
17198
+ lines.push(`ERROR: ${result.reason ?? "unknown failure"}`);
17199
+ return lines.join("\n");
17200
+ }
17201
+ if (result.planned.moves.length === 0 && result.planned.gitignoreEdits.length === 0) {
17202
+ lines.push("Nothing to migrate \u2014 workspace is already on slopbrick v0.11.0+.");
17203
+ }
17204
+ if (result.planned.moves.length > 0) {
17205
+ lines.push("Moves:");
17206
+ for (const m of result.planned.moves) {
17207
+ lines.push(` ${m.from}`);
17208
+ lines.push(` \u2192 ${m.to} (${m.kind})`);
17209
+ }
17210
+ }
17211
+ if (result.planned.rewrites.length > 0) {
17212
+ lines.push("Schema version bumps:");
17213
+ for (const r of result.planned.rewrites) {
17214
+ lines.push(` ${r.path} ${r.field}: ${r.from} \u2192 ${r.to}`);
17215
+ }
17216
+ }
17217
+ if (result.planned.gitignoreEdits.length > 0) {
17218
+ lines.push(".gitignore edits:");
17219
+ for (const g of result.planned.gitignoreEdits) {
17220
+ lines.push(` ${g.path}: ${g.from} \u2192 ${g.to}`);
17221
+ }
17222
+ }
17223
+ lines.push("");
17224
+ if (result.applied) {
17225
+ lines.push("Migration applied. Run `slopbrick scan` to regenerate inventory at v2.");
17226
+ } else {
17227
+ lines.push("DRY RUN \u2014 no files were changed. Re-run without --dry-run to apply.");
17228
+ }
17229
+ return lines.join("\n");
17230
+ }
17231
+ var init_migrate = __esm({
17232
+ "src/cli/migrate.ts"() {
17233
+ "use strict";
17234
+ init_logger();
17235
+ }
17236
+ });
17237
+
17055
17238
  // src/index.ts
17056
17239
  init_types();
17057
17240
  init_config();
17058
17241
 
17059
17242
  // src/cli/program.ts
17060
- import { existsSync as existsSync24, writeFileSync as writeFileSync17, readFileSync as readFileSync36, mkdirSync as mkdirSync13 } from "fs";
17061
- import { resolve as resolve14, join as join25, dirname as dirname15, extname as extname8 } from "path";
17243
+ import { existsSync as existsSync26, writeFileSync as writeFileSync19, readFileSync as readFileSync38, mkdirSync as mkdirSync14 } from "fs";
17244
+ import { resolve as resolve14, join as join27, dirname as dirname16, extname as extname8 } from "path";
17062
17245
  import { performance } from "perf_hooks";
17063
17246
  import { Command } from "commander";
17064
17247
 
@@ -17103,7 +17286,7 @@ init_scan();
17103
17286
  // src/cli/drift.ts
17104
17287
  init_discover();
17105
17288
  init_patterns();
17106
- import { readFileSync as readFileSync22 } from "fs";
17289
+ import { readFileSync as readFileSync23 } from "fs";
17107
17290
  import { basename as basename4, relative as relative11 } from "path";
17108
17291
  async function runDrift(cwd, config, options = {}) {
17109
17292
  const maxFiles = options.maxFiles ?? 1e3;
@@ -17115,7 +17298,7 @@ async function runDrift(cwd, config, options = {}) {
17115
17298
  for (const absPath of limited) {
17116
17299
  let source;
17117
17300
  try {
17118
- source = readFileSync22(absPath, "utf-8");
17301
+ source = readFileSync23(absPath, "utf-8");
17119
17302
  } catch {
17120
17303
  continue;
17121
17304
  }
@@ -17310,7 +17493,7 @@ init_worker();
17310
17493
  init_metrics();
17311
17494
  init_patterns();
17312
17495
  init_discover();
17313
- import { readFileSync as readFileSync23 } from "fs";
17496
+ import { readFileSync as readFileSync24 } from "fs";
17314
17497
  import { extname as extname7, relative as relative12, resolve as resolve11 } from "path";
17315
17498
  import { execFile as execFileCb } from "child_process";
17316
17499
  import { promisify as promisify2 } from "util";
@@ -17383,7 +17566,7 @@ async function runPrScan(cwd, config, options = {}) {
17383
17566
  for (const absPath of candidates) {
17384
17567
  let source;
17385
17568
  try {
17386
- source = readFileSync23(absPath, "utf-8");
17569
+ source = readFileSync24(absPath, "utf-8");
17387
17570
  } catch {
17388
17571
  continue;
17389
17572
  }
@@ -17888,7 +18071,7 @@ function dbExitCode(result, options = {}) {
17888
18071
  // src/cli/business-logic.ts
17889
18072
  init_discover();
17890
18073
  init_business_logic();
17891
- import { readFileSync as readFileSync24 } from "fs";
18074
+ import { readFileSync as readFileSync25 } from "fs";
17892
18075
  import { relative as relative13 } from "path";
17893
18076
  async function runBusinessLogicScan(cwd, config, options = {}) {
17894
18077
  const maxFiles = options.maxFiles ?? 500;
@@ -17899,7 +18082,7 @@ async function runBusinessLogicScan(cwd, config, options = {}) {
17899
18082
  for (const absPath of limited) {
17900
18083
  let source;
17901
18084
  try {
17902
- source = readFileSync24(absPath, "utf-8");
18085
+ source = readFileSync25(absPath, "utf-8");
17903
18086
  } catch {
17904
18087
  continue;
17905
18088
  }
@@ -18004,7 +18187,7 @@ function capitalize(s) {
18004
18187
  // src/engine/patterns.ts
18005
18188
  init_patterns();
18006
18189
  init_discover();
18007
- import { readFileSync as readFileSync25 } from "fs";
18190
+ import { readFileSync as readFileSync26 } from "fs";
18008
18191
  import { basename as basename5, relative as relative14 } from "path";
18009
18192
  var PATTERN_CATEGORIES = [
18010
18193
  "modal",
@@ -18107,7 +18290,7 @@ function detectFormsFromFiles(files) {
18107
18290
  for (const f of files) {
18108
18291
  let source;
18109
18292
  try {
18110
- source = readFileSync25(f, "utf-8");
18293
+ source = readFileSync26(f, "utf-8");
18111
18294
  } catch {
18112
18295
  continue;
18113
18296
  }
@@ -18329,13 +18512,13 @@ init_discover();
18329
18512
  init_git();
18330
18513
  init_cache();
18331
18514
  init_logger();
18332
- import { writeFileSync as writeFileSync9, mkdirSync as mkdirSync8, rmSync as rmSync2 } from "fs";
18333
- import { join as join17, dirname as dirname11 } from "path";
18515
+ import { writeFileSync as writeFileSync10, mkdirSync as mkdirSync9, rmSync as rmSync2 } from "fs";
18516
+ import { join as join18, dirname as dirname12 } from "path";
18334
18517
  import { createInterface } from "readline";
18335
18518
 
18336
18519
  // src/rules/registry-loader.ts
18337
- import { existsSync as existsSync17, mkdirSync as mkdirSync7, readFileSync as readFileSync26, writeFileSync as writeFileSync8 } from "fs";
18338
- import { dirname as dirname10, join as join16 } from "path";
18520
+ import { existsSync as existsSync18, mkdirSync as mkdirSync8, readFileSync as readFileSync27, writeFileSync as writeFileSync9 } from "fs";
18521
+ import { dirname as dirname11, join as join17 } from "path";
18339
18522
 
18340
18523
  // src/data/shadcn-registry.json
18341
18524
  var shadcn_registry_default = {
@@ -18468,13 +18651,13 @@ var shadcn_registry_default = {
18468
18651
  // src/rules/registry-loader.ts
18469
18652
  var REGISTRY_URL = "https://ui.shadcn.com/registry.json";
18470
18653
  var BUNDLED_REGISTRY_VERSION = shadcn_registry_default.version;
18471
- function cachePath4(cwd) {
18472
- return join16(cwd, ".slopbrick", "cache", "registry-snapshot.json");
18654
+ function cachePath3(cwd) {
18655
+ return join17(cwd, ".slopbrick", "cache", "registry-snapshot.json");
18473
18656
  }
18474
18657
  function ensureCacheDir(cwd) {
18475
- const dir = dirname10(cachePath4(cwd));
18476
- if (!existsSync17(dir)) {
18477
- mkdirSync7(dir, { recursive: true });
18658
+ const dir = dirname11(cachePath3(cwd));
18659
+ if (!existsSync18(dir)) {
18660
+ mkdirSync8(dir, { recursive: true });
18478
18661
  }
18479
18662
  }
18480
18663
  function isValidSnapshot(value) {
@@ -18485,10 +18668,10 @@ function isValidSnapshot(value) {
18485
18668
  return true;
18486
18669
  }
18487
18670
  function isRegistryFresh(cwd) {
18488
- const cached = cachePath4(cwd);
18489
- if (!existsSync17(cached)) return false;
18671
+ const cached = cachePath3(cwd);
18672
+ if (!existsSync18(cached)) return false;
18490
18673
  try {
18491
- const parsed = JSON.parse(readFileSync26(cached, "utf8"));
18674
+ const parsed = JSON.parse(readFileSync27(cached, "utf8"));
18492
18675
  if (!isValidSnapshot(parsed)) return false;
18493
18676
  return parsed.version === BUNDLED_REGISTRY_VERSION;
18494
18677
  } catch {
@@ -18522,7 +18705,7 @@ async function refreshRegistrySnapshot(cwd, url = REGISTRY_URL, timeoutMs = 5e3)
18522
18705
  };
18523
18706
  }
18524
18707
  ensureCacheDir(cwd);
18525
- writeFileSync8(cachePath4(cwd), JSON.stringify(fetched, null, 2));
18708
+ writeFileSync9(cachePath3(cwd), JSON.stringify(fetched, null, 2));
18526
18709
  const fresh = fetched.version === BUNDLED_REGISTRY_VERSION;
18527
18710
  return {
18528
18711
  ok: true,
@@ -18532,7 +18715,7 @@ async function refreshRegistrySnapshot(cwd, url = REGISTRY_URL, timeoutMs = 5e3)
18532
18715
  }
18533
18716
  function copyBundledSnapshotToCache(cwd) {
18534
18717
  ensureCacheDir(cwd);
18535
- writeFileSync8(cachePath4(cwd), JSON.stringify(shadcn_registry_default, null, 2));
18718
+ writeFileSync9(cachePath3(cwd), JSON.stringify(shadcn_registry_default, null, 2));
18536
18719
  }
18537
18720
 
18538
18721
  // src/cli/init.ts
@@ -18685,9 +18868,9 @@ async function runDoctor(cwd) {
18685
18868
  }
18686
18869
  try {
18687
18870
  const { parseFile: tryParse } = await Promise.resolve().then(() => (init_parser(), parser_exports));
18688
- const testFile = join17(cwd, ".slopbrick", ".doctor-test.ts");
18689
- mkdirSync8(dirname11(testFile), { recursive: true });
18690
- writeFileSync9(testFile, "export const x = 1;\n");
18871
+ const testFile = join18(cwd, ".slopbrick", ".doctor-test.ts");
18872
+ mkdirSync9(dirname12(testFile), { recursive: true });
18873
+ writeFileSync10(testFile, "export const x = 1;\n");
18691
18874
  await tryParse(testFile);
18692
18875
  rmSync2(testFile, { force: true });
18693
18876
  ok("Parser is working.");
@@ -18746,12 +18929,12 @@ init_git();
18746
18929
  // src/cli/installer.ts
18747
18930
  import {
18748
18931
  chmodSync,
18749
- existsSync as existsSync19,
18750
- mkdirSync as mkdirSync9,
18751
- readFileSync as readFileSync28,
18752
- writeFileSync as writeFileSync10
18932
+ existsSync as existsSync20,
18933
+ mkdirSync as mkdirSync10,
18934
+ readFileSync as readFileSync29,
18935
+ writeFileSync as writeFileSync11
18753
18936
  } from "fs";
18754
- import { dirname as dirname12, join as join18 } from "path";
18937
+ import { dirname as dirname13, join as join19 } from "path";
18755
18938
  var BEGIN_SENTINEL = "# slopbrick-hook-begin";
18756
18939
  var END_SENTINEL = "# slopbrick-hook-end";
18757
18940
  var SENTINEL_BLOCK = `${BEGIN_SENTINEL}
@@ -18759,14 +18942,14 @@ npx slopbrick --staged
18759
18942
  ${END_SENTINEL}
18760
18943
  `;
18761
18944
  function hookPath(gitRoot) {
18762
- const huskyDir = join18(gitRoot, ".husky");
18763
- if (existsSync19(huskyDir)) {
18764
- return join18(huskyDir, "pre-commit");
18945
+ const huskyDir = join19(gitRoot, ".husky");
18946
+ if (existsSync20(huskyDir)) {
18947
+ return join19(huskyDir, "pre-commit");
18765
18948
  }
18766
- return join18(gitRoot, ".git", "hooks", "pre-commit");
18949
+ return join19(gitRoot, ".git", "hooks", "pre-commit");
18767
18950
  }
18768
18951
  function readHookContent(path) {
18769
- return readFileSync28(path, "utf8");
18952
+ return readFileSync29(path, "utf8");
18770
18953
  }
18771
18954
  function sentinelsPresent(content) {
18772
18955
  const lines = content.split(/\r?\n/);
@@ -18788,7 +18971,7 @@ function replaceSentinelBlock(content) {
18788
18971
  }
18789
18972
  function installHook(gitRoot) {
18790
18973
  const path = hookPath(gitRoot);
18791
- if (existsSync19(path)) {
18974
+ if (existsSync20(path)) {
18792
18975
  const content = readHookContent(path);
18793
18976
  const { begin, end } = sentinelsPresent(content);
18794
18977
  if (begin && end) {
@@ -18800,7 +18983,7 @@ function installHook(gitRoot) {
18800
18983
  exitCode: 0
18801
18984
  };
18802
18985
  }
18803
- writeFileSync10(path, replaced.endsWith("\n") ? replaced : `${replaced}
18986
+ writeFileSync11(path, replaced.endsWith("\n") ? replaced : `${replaced}
18804
18987
  `);
18805
18988
  chmodSync(path, 493);
18806
18989
  return {
@@ -18820,7 +19003,7 @@ function installHook(gitRoot) {
18820
19003
  }
18821
19004
  const normalized = content.length > 0 && !content.endsWith("\n") ? `${content}
18822
19005
  ` : content;
18823
- writeFileSync10(path, `${normalized}${SENTINEL_BLOCK}`);
19006
+ writeFileSync11(path, `${normalized}${SENTINEL_BLOCK}`);
18824
19007
  chmodSync(path, 493);
18825
19008
  return {
18826
19009
  ok: true,
@@ -18828,8 +19011,8 @@ function installHook(gitRoot) {
18828
19011
  exitCode: 0
18829
19012
  };
18830
19013
  }
18831
- mkdirSync9(dirname12(path), { recursive: true });
18832
- writeFileSync10(path, SENTINEL_BLOCK, { mode: 493 });
19014
+ mkdirSync10(dirname13(path), { recursive: true });
19015
+ writeFileSync11(path, SENTINEL_BLOCK, { mode: 493 });
18833
19016
  chmodSync(path, 493);
18834
19017
  return {
18835
19018
  ok: true,
@@ -18839,7 +19022,7 @@ function installHook(gitRoot) {
18839
19022
  }
18840
19023
  function uninstallHook(gitRoot) {
18841
19024
  const path = hookPath(gitRoot);
18842
- if (!existsSync19(path)) {
19025
+ if (!existsSync20(path)) {
18843
19026
  return {
18844
19027
  ok: true,
18845
19028
  message: "Hook not installed",
@@ -18875,7 +19058,7 @@ function uninstallHook(gitRoot) {
18875
19058
  }
18876
19059
  const remaining = [...lines.slice(0, start), ...lines.slice(end + 1)];
18877
19060
  const result = remaining.join("\n");
18878
- writeFileSync10(path, result.endsWith("\n") ? result : `${result}
19061
+ writeFileSync11(path, result.endsWith("\n") ? result : `${result}
18879
19062
  `);
18880
19063
  return {
18881
19064
  ok: true,
@@ -18941,8 +19124,8 @@ function createProvider(config) {
18941
19124
  }
18942
19125
 
18943
19126
  // src/research/generator.ts
18944
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync11 } from "fs";
18945
- import { join as join19 } from "path";
19127
+ import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "fs";
19128
+ import { join as join20 } from "path";
18946
19129
 
18947
19130
  // src/research/prompts.ts
18948
19131
  var DEFAULT_PROMPT_TEMPLATES = [
@@ -19005,14 +19188,14 @@ async function generateSamples(options) {
19005
19188
  }
19006
19189
  const samples = [];
19007
19190
  const ext = extForFramework(framework);
19008
- const dir = join19(outputDir, framework, componentType);
19009
- mkdirSync10(dir, { recursive: true });
19191
+ const dir = join20(outputDir, framework, componentType);
19192
+ mkdirSync11(dir, { recursive: true });
19010
19193
  for (let i = 1; i <= count; i += 1) {
19011
19194
  const raw = await provider.generateSample(renderPrompt(template), { temperature });
19012
19195
  const code = extractCodeFromMarkdown(raw);
19013
19196
  const fileName = `sample-${i}${ext}`;
19014
- const filePath = join19(dir, fileName);
19015
- writeFileSync11(filePath, code, "utf8");
19197
+ const filePath = join20(dir, fileName);
19198
+ writeFileSync12(filePath, code, "utf8");
19016
19199
  const sample = {
19017
19200
  filePath,
19018
19201
  framework,
@@ -19023,8 +19206,8 @@ async function generateSamples(options) {
19023
19206
  };
19024
19207
  samples.push(sample);
19025
19208
  }
19026
- const metadataPath = join19(dir, "metadata.json");
19027
- writeFileSync11(metadataPath, JSON.stringify(samples, null, 2), "utf8");
19209
+ const metadataPath = join20(dir, "metadata.json");
19210
+ writeFileSync12(metadataPath, JSON.stringify(samples, null, 2), "utf8");
19028
19211
  return samples;
19029
19212
  }
19030
19213
 
@@ -19230,37 +19413,37 @@ function slugify(value) {
19230
19413
 
19231
19414
  // src/research/calibrator.ts
19232
19415
  import { execFileSync as execFileSync2 } from "child_process";
19233
- import { existsSync as existsSync20, readFileSync as readFileSync29, writeFileSync as writeFileSync12, mkdirSync as mkdirSync11 } from "fs";
19234
- import { join as join20, resolve as resolve12 } from "path";
19416
+ import { existsSync as existsSync21, readFileSync as readFileSync30, writeFileSync as writeFileSync13, mkdirSync as mkdirSync12 } from "fs";
19417
+ import { join as join21, resolve as resolve12 } from "path";
19235
19418
  var DEFAULT_POSITIVE = "/Users/cheng/ai-slop-baseline/extracted/positive";
19236
19419
  var DEFAULT_NEGATIVE = "/Users/cheng/ai-slop-baseline/extracted/negative";
19237
19420
  function buildFileList(dir, extensions) {
19238
- const tmpList = join20("/tmp", `cal-build-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`);
19421
+ const tmpList = join21("/tmp", `cal-build-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`);
19239
19422
  const expr = extensions.map((e) => `-name '*.${e}'`).join(" -o ");
19240
19423
  execFileSync2("bash", ["-c", `find ${dir} -maxdepth 8 -type f \\( ${expr} \\) -print0 | xargs -0 realpath > ${tmpList}`]);
19241
- const out = readFileSync29(tmpList, "utf8");
19424
+ const out = readFileSync30(tmpList, "utf8");
19242
19425
  execFileSync2("rm", ["-f", tmpList]);
19243
19426
  return out.trim().split("\n").filter(Boolean);
19244
19427
  }
19245
19428
  function runScan2(fileListPath) {
19246
- const files = readFileSync29(fileListPath, "utf8").trim().split("\n").filter(Boolean);
19429
+ const files = readFileSync30(fileListPath, "utf8").trim().split("\n").filter(Boolean);
19247
19430
  const CHUNK = 600;
19248
19431
  const ruleFires = /* @__PURE__ */ new Map();
19249
19432
  const uniqueFilesPerRule = /* @__PURE__ */ new Map();
19250
19433
  let fileCount = 0;
19251
- const tmpOut = join20("/tmp", `calibrate-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
19434
+ const tmpOut = join21("/tmp", `calibrate-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
19252
19435
  for (let i = 0; i < files.length; i += CHUNK) {
19253
19436
  const chunk = files.slice(i, i + CHUNK);
19254
19437
  try {
19255
19438
  execFileSync2(
19256
19439
  "node",
19257
- [join20(process.cwd(), "bin", "slopbrick.js"), "scan", ...chunk, "--json", tmpOut, "--no-telemetry", "--quiet"],
19440
+ [join21(process.cwd(), "bin", "slopbrick.js"), "scan", ...chunk, "--json", tmpOut, "--no-telemetry", "--quiet"],
19258
19441
  { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
19259
19442
  );
19260
19443
  } catch {
19261
19444
  }
19262
- if (!existsSync20(tmpOut)) continue;
19263
- const report = JSON.parse(readFileSync29(tmpOut, "utf8"));
19445
+ if (!existsSync21(tmpOut)) continue;
19446
+ const report = JSON.parse(readFileSync30(tmpOut, "utf8"));
19264
19447
  fileCount += report.fileCount;
19265
19448
  for (const issue of report.issues) {
19266
19449
  ruleFires.set(issue.ruleId, (ruleFires.get(issue.ruleId) ?? 0) + 1);
@@ -19288,16 +19471,16 @@ function classify(posFiles, negFiles) {
19288
19471
  async function calibrate(cwd, options = {}) {
19289
19472
  const positiveDir = options.positiveDir ?? DEFAULT_POSITIVE;
19290
19473
  const negativeDir = options.negativeDir ?? DEFAULT_NEGATIVE;
19291
- if (!existsSync20(positiveDir)) throw new Error(`Positive corpus not found: ${positiveDir}`);
19292
- if (!existsSync20(negativeDir)) throw new Error(`Negative corpus not found: ${negativeDir}`);
19474
+ if (!existsSync21(positiveDir)) throw new Error(`Positive corpus not found: ${positiveDir}`);
19475
+ if (!existsSync21(negativeDir)) throw new Error(`Negative corpus not found: ${negativeDir}`);
19293
19476
  const positiveFiles = buildFileList(positiveDir, ["tsx", "ts", "jsx", "js"]);
19294
19477
  const negativeFiles = buildFileList(negativeDir, ["tsx", "ts"]);
19295
19478
  const posSample = options.positiveLimit ? positiveFiles.slice(0, options.positiveLimit) : positiveFiles;
19296
19479
  const negSample = options.negativeLimit ? negativeFiles.slice(0, options.negativeLimit) : negativeFiles;
19297
- const posListPath = join20("/tmp", `cal-pos-${Date.now()}.txt`);
19298
- const negListPath = join20("/tmp", `cal-neg-${Date.now()}.txt`);
19299
- writeFileSync12(posListPath, posSample.join("\n"));
19300
- writeFileSync12(negListPath, negSample.join("\n"));
19480
+ const posListPath = join21("/tmp", `cal-pos-${Date.now()}.txt`);
19481
+ const negListPath = join21("/tmp", `cal-neg-${Date.now()}.txt`);
19482
+ writeFileSync13(posListPath, posSample.join("\n"));
19483
+ writeFileSync13(negListPath, negSample.join("\n"));
19301
19484
  const builtins = await Promise.resolve().then(() => (init_builtins(), builtins_exports));
19302
19485
  const builtinRules2 = builtins.builtinRules ?? [];
19303
19486
  const metaById = /* @__PURE__ */ new Map();
@@ -19706,7 +19889,7 @@ var RULE_HINTS = {
19706
19889
  };
19707
19890
 
19708
19891
  // src/snippet/targets.ts
19709
- import { join as join23 } from "path";
19892
+ import { join as join24 } from "path";
19710
19893
 
19711
19894
  // src/snippet/render.ts
19712
19895
  function aiSpecificRules(rules) {
@@ -19937,7 +20120,7 @@ var SNIPPET_TARGETS = [
19937
20120
  }
19938
20121
  ];
19939
20122
  function resolveTargetPath(target) {
19940
- return target.isFolder ? join23(target.path, target.filename) : target.path;
20123
+ return target.isFolder ? join24(target.path, target.filename) : target.path;
19941
20124
  }
19942
20125
  function renderMatrix() {
19943
20126
  const lines = [];
@@ -20078,8 +20261,8 @@ init_unified_diff();
20078
20261
  init_heatmap();
20079
20262
 
20080
20263
  // src/report/flywheel.ts
20081
- import { existsSync as existsSync22, readFileSync as readFileSync32 } from "fs";
20082
- import { join as join24 } from "path";
20264
+ import { existsSync as existsSync23, readFileSync as readFileSync33 } from "fs";
20265
+ import { join as join25 } from "path";
20083
20266
  function average(values) {
20084
20267
  if (values.length === 0) return 0;
20085
20268
  return values.reduce((a, b) => a + b, 0) / values.length;
@@ -20170,8 +20353,8 @@ init_telemetry();
20170
20353
  init_memory();
20171
20354
 
20172
20355
  // src/fix/focus-ring.ts
20173
- import { existsSync as existsSync23 } from "fs";
20174
- import { readFileSync as readFileSync33, writeFileSync as writeFileSync14 } from "fs";
20356
+ import { existsSync as existsSync24 } from "fs";
20357
+ import { readFileSync as readFileSync34, writeFileSync as writeFileSync15 } from "fs";
20175
20358
  var ANCHOR_START = "/* @slopbrick:v1.0.0:fix:focus-ring */";
20176
20359
  var CSS_BLOCK = `/* @slopbrick:v1.0.0:fix:focus-ring */
20177
20360
  :focus-visible {
@@ -20180,15 +20363,15 @@ var CSS_BLOCK = `/* @slopbrick:v1.0.0:fix:focus-ring */
20180
20363
  }
20181
20364
  /* @slopbrick:v1.0.0:fix:focus-ring-end */`;
20182
20365
  function applyFocusRingFix(targetFile) {
20183
- if (!existsSync23(targetFile)) {
20366
+ if (!existsSync24(targetFile)) {
20184
20367
  return { applied: false, reason: "missing-global-css-target" };
20185
20368
  }
20186
- const content = readFileSync33(targetFile, "utf-8");
20369
+ const content = readFileSync34(targetFile, "utf-8");
20187
20370
  if (content.includes(ANCHOR_START)) {
20188
20371
  return { applied: false, reason: "already-present" };
20189
20372
  }
20190
20373
  const separator = content.endsWith("\n") ? "" : "\n";
20191
- writeFileSync14(targetFile, `${content}${separator}${CSS_BLOCK}
20374
+ writeFileSync15(targetFile, `${content}${separator}${CSS_BLOCK}
20192
20375
  `);
20193
20376
  return { applied: true };
20194
20377
  }
@@ -20197,21 +20380,21 @@ function applyFocusRingFix(targetFile) {
20197
20380
  init_layout_token();
20198
20381
 
20199
20382
  // src/fix/use-client.ts
20200
- import { readFileSync as readFileSync34, writeFileSync as writeFileSync15 } from "fs";
20383
+ import { readFileSync as readFileSync35, writeFileSync as writeFileSync16 } from "fs";
20201
20384
  function applyUseClientFix(filePath) {
20202
- const content = readFileSync34(filePath, "utf-8");
20385
+ const content = readFileSync35(filePath, "utf-8");
20203
20386
  const trimmed = content.trimStart();
20204
20387
  if (trimmed.startsWith("'use client'") || trimmed.startsWith('"use client"')) {
20205
20388
  return { applied: false, reason: "already-present" };
20206
20389
  }
20207
- writeFileSync15(filePath, `"use client";
20390
+ writeFileSync16(filePath, `"use client";
20208
20391
 
20209
20392
  ${content}`);
20210
20393
  return { applied: true };
20211
20394
  }
20212
20395
 
20213
20396
  // src/fix/visual-codemod.ts
20214
- import { readFileSync as readFileSync35, writeFileSync as writeFileSync16 } from "fs";
20397
+ import { readFileSync as readFileSync36, writeFileSync as writeFileSync17 } from "fs";
20215
20398
 
20216
20399
  // src/fix/visual-codemods/tailwind.ts
20217
20400
  var ARBITRARY_ESCAPE_RE = /\b(p|m|px|py|pt|pb|pl|pr|mx|my|mt|mb|ml|mr|gap|space-[xy]|w|h|min-w|max-w|min-h|max-h|text-\w+|leading-\w+|rounded|border)-?\[(-?\d+(?:\.\d+)?)(px|rem|em|%|vh|vw)?\]/g;
@@ -20437,7 +20620,7 @@ var ALL_CODEMODS = [
20437
20620
  { name: "aria-attr-typo", fn: applyAriaAttrTypoCodemod }
20438
20621
  ];
20439
20622
  function applyVisualCodemods(filePath) {
20440
- const original = readFileSync35(filePath, "utf-8");
20623
+ const original = readFileSync36(filePath, "utf-8");
20441
20624
  let content = original;
20442
20625
  const reasons = [];
20443
20626
  const seen = /* @__PURE__ */ new Set();
@@ -20453,7 +20636,7 @@ function applyVisualCodemods(filePath) {
20453
20636
  }
20454
20637
  }
20455
20638
  if (content !== original) {
20456
- writeFileSync16(filePath, content);
20639
+ writeFileSync17(filePath, content);
20457
20640
  }
20458
20641
  return {
20459
20642
  filePath,
@@ -20609,12 +20792,12 @@ async function runCli({ start }) {
20609
20792
  process.exit(0);
20610
20793
  }
20611
20794
  const cwd = resolve14(options.workspace ?? process.cwd());
20612
- const configPath = join25(cwd, "slopbrick.config.mjs");
20795
+ const configPath = join27(cwd, "slopbrick.config.mjs");
20613
20796
  const detected = detectStack(cwd);
20614
20797
  const fallbackConfig = { ...DEFAULT_CONFIG, ...detected };
20615
20798
  const proposed = serializeConfig(fallbackConfig);
20616
- if (existsSync24(configPath) && !cmdOptions.yes) {
20617
- const current = readFileSync36(configPath, "utf8");
20799
+ if (existsSync26(configPath) && !cmdOptions.yes) {
20800
+ const current = readFileSync38(configPath, "utf8");
20618
20801
  logger.error(`A config file already exists at ${configPath}.`);
20619
20802
  logger.error("To overwrite it with defaults, run `slopbrick init --yes`.");
20620
20803
  logger.error("");
@@ -20635,7 +20818,7 @@ async function runCli({ start }) {
20635
20818
  config = buildInitConfig(detected, answers);
20636
20819
  usedWizard = true;
20637
20820
  }
20638
- writeFileSync17(configPath, serializeConfig(config));
20821
+ writeFileSync19(configPath, serializeConfig(config));
20639
20822
  appendGitignore(cwd);
20640
20823
  const refresh = await refreshRegistrySnapshot(cwd);
20641
20824
  if (!refresh.ok) {
@@ -20665,21 +20848,21 @@ async function runCli({ start }) {
20665
20848
  return Boolean(opts[t.flag]);
20666
20849
  });
20667
20850
  for (const target of targetsToWrite) {
20668
- const snippetPath = join25(cwd, resolveTargetPath(target));
20669
- mkdirSync13(dirname15(snippetPath), { recursive: true });
20851
+ const snippetPath = join27(cwd, resolveTargetPath(target));
20852
+ mkdirSync14(dirname16(snippetPath), { recursive: true });
20670
20853
  const generated = target.generator(builtinRules);
20671
- if (!target.isFolder && existsSync24(snippetPath)) {
20672
- const existing = readFileSync36(snippetPath, "utf8");
20854
+ if (!target.isFolder && existsSync26(snippetPath)) {
20855
+ const existing = readFileSync38(snippetPath, "utf8");
20673
20856
  if (existing.includes("<!-- slopbrick:begin -->")) {
20674
20857
  const updated = existing.replace(
20675
20858
  /<!-- slopbrick:begin -->[\s\S]*?<!-- slopbrick:end -->/,
20676
20859
  "<!-- slopbrick:begin -->\n" + generated + "<!-- slopbrick:end -->"
20677
20860
  );
20678
- writeFileSync17(snippetPath, updated, "utf8");
20861
+ writeFileSync19(snippetPath, updated, "utf8");
20679
20862
  if (!options.quiet) logger.info(`Updated ${snippetPath}`);
20680
20863
  continue;
20681
20864
  }
20682
- writeFileSync17(
20865
+ writeFileSync19(
20683
20866
  snippetPath,
20684
20867
  existing + (existing.endsWith("\n") ? "\n" : "\n\n") + generated,
20685
20868
  "utf8"
@@ -20687,7 +20870,7 @@ async function runCli({ start }) {
20687
20870
  if (!options.quiet) logger.info(`Wrote ${snippetPath}`);
20688
20871
  continue;
20689
20872
  }
20690
- writeFileSync17(snippetPath, generated, "utf8");
20873
+ writeFileSync19(snippetPath, generated, "utf8");
20691
20874
  if (!options.quiet) logger.info(`Wrote ${snippetPath}`);
20692
20875
  }
20693
20876
  if (options.baseline) {
@@ -20756,8 +20939,8 @@ async function runCli({ start }) {
20756
20939
  const summary = summarizeTelemetry(payloads);
20757
20940
  if (cmdOptions.export) {
20758
20941
  const exportPath = resolve14(cmdOptions.export);
20759
- mkdirSync13(dirname15(exportPath), { recursive: true });
20760
- writeFileSync17(exportPath, JSON.stringify(summary, null, 2), "utf-8");
20942
+ mkdirSync14(dirname16(exportPath), { recursive: true });
20943
+ writeFileSync19(exportPath, JSON.stringify(summary, null, 2), "utf-8");
20761
20944
  logger.info(`Wrote flywheel summary to ${exportPath}`);
20762
20945
  process.exit(0);
20763
20946
  }
@@ -20915,16 +21098,16 @@ async function runCli({ start }) {
20915
21098
  research.command("analyze").description("analyze generated samples and report coverage").requiredOption("--input-dir <path>", "directory with generated samples containing metadata.json").option("--output <path>", "analysis output path", ".slopbrick/flywheel/analysis.json").option("--config <path>", "slopbrick config path").option("--framework <name>", "framework multiplier to apply", "react").action(async (cmdOptions) => {
20916
21099
  try {
20917
21100
  const metadataPath = resolve14(cmdOptions.inputDir, "metadata.json");
20918
- if (!existsSync24(metadataPath)) {
21101
+ if (!existsSync26(metadataPath)) {
20919
21102
  logger.error(`No metadata.json found in ${cmdOptions.inputDir}`);
20920
21103
  process.exit(2);
20921
21104
  }
20922
- const samples = JSON.parse(readFileSync36(metadataPath, "utf8"));
21105
+ const samples = JSON.parse(readFileSync38(metadataPath, "utf8"));
20923
21106
  const config = cmdOptions.config ? await loadConfig(cmdOptions.config) : { ...DEFAULT_CONFIG, framework: cmdOptions.framework };
20924
21107
  const analysis = await analyzeSamples(samples, config);
20925
21108
  const outputPath = resolve14(cmdOptions.output);
20926
- mkdirSync13(dirname15(outputPath), { recursive: true });
20927
- writeFileSync17(outputPath, JSON.stringify(analysis, null, 2), "utf8");
21109
+ mkdirSync14(dirname16(outputPath), { recursive: true });
21110
+ writeFileSync19(outputPath, JSON.stringify(analysis, null, 2), "utf8");
20928
21111
  logger.info(`Analyzed ${analysis.summary.total} samples; coverage: ${analysis.summary.coverage}%`);
20929
21112
  logger.info(`Wrote analysis to ${outputPath}`);
20930
21113
  } catch (error) {
@@ -20935,11 +21118,11 @@ async function runCli({ start }) {
20935
21118
  research.command("candidates").description("extract patterns from generated samples and emit candidate rules").requiredOption("--input-dir <path>", "directory with generated samples containing metadata.json").option("--output <path>", "output path", ".slopbrick/flywheel/rule-candidates.json").option("--config <path>", "slopbrick config path").option("--framework <name>", "framework multiplier to apply", "react").option("--min-frequency <n>", "minimum cluster frequency", parseCount, 2).option("--include-covered", "include samples already covered by AI-specific rules").action(async (cmdOptions) => {
20936
21119
  try {
20937
21120
  const metadataPath = resolve14(cmdOptions.inputDir, "metadata.json");
20938
- if (!existsSync24(metadataPath)) {
21121
+ if (!existsSync26(metadataPath)) {
20939
21122
  logger.error(`No metadata.json found in ${cmdOptions.inputDir}`);
20940
21123
  process.exit(2);
20941
21124
  }
20942
- const samples = JSON.parse(readFileSync36(metadataPath, "utf8"));
21125
+ const samples = JSON.parse(readFileSync38(metadataPath, "utf8"));
20943
21126
  const config = cmdOptions.config ? await loadConfig(cmdOptions.config) : { ...DEFAULT_CONFIG, framework: cmdOptions.framework };
20944
21127
  const analysis = await analyzeSamples(samples, config);
20945
21128
  const extraction = extractAndCluster(analysis.samples, {
@@ -20950,7 +21133,7 @@ async function runCli({ start }) {
20950
21133
  minFrequency: cmdOptions.minFrequency
20951
21134
  });
20952
21135
  const outputPath = resolve14(cmdOptions.output);
20953
- mkdirSync13(dirname15(outputPath), { recursive: true });
21136
+ mkdirSync14(dirname16(outputPath), { recursive: true });
20954
21137
  const payload = {
20955
21138
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
20956
21139
  sampleCount: analysis.summary.total,
@@ -20958,7 +21141,7 @@ async function runCli({ start }) {
20958
21141
  fingerprintCount: extraction.total,
20959
21142
  candidates
20960
21143
  };
20961
- writeFileSync17(outputPath, JSON.stringify(payload, null, 2), "utf8");
21144
+ writeFileSync19(outputPath, JSON.stringify(payload, null, 2), "utf8");
20962
21145
  logger.info(`Extracted ${extraction.total} fingerprints across ${analysis.summary.total} samples`);
20963
21146
  logger.info(`Wrote ${candidates.length} candidate rule(s) to ${outputPath}`);
20964
21147
  } catch (error) {
@@ -20976,8 +21159,8 @@ async function runCli({ start }) {
20976
21159
  negativeLimit: cmdOptions.negativeLimit
20977
21160
  });
20978
21161
  const outputPath = cmdOptions.output ? resolve14(cwd, cmdOptions.output) : resolve14(cwd, "corpus", "calibration-empirical.md");
20979
- mkdirSync13(dirname15(outputPath), { recursive: true });
20980
- writeFileSync17(outputPath, reportToMarkdown(report), "utf8");
21162
+ mkdirSync14(dirname16(outputPath), { recursive: true });
21163
+ writeFileSync19(outputPath, reportToMarkdown(report), "utf8");
20981
21164
  logger.info(
20982
21165
  "Calibrated " + report.rules.length + " rules across " + report.positiveFileCount + " positive + " + report.negativeFileCount + " negative files."
20983
21166
  );
@@ -21273,6 +21456,27 @@ async function runCli({ start }) {
21273
21456
  const exitCode = await runDoctor(process.cwd());
21274
21457
  if (exitCode !== 0) process.exit(exitCode);
21275
21458
  });
21459
+ program.command("migrate").description(
21460
+ "Migrate from slop-audit v0.10.x (.slop-audit/) to slopbrick v0.11.0+ (.slopbrick/). Renames artifact dir + cache + config file + bumps schema to v2 + updates .gitignore. Idempotent. Pass --dry-run to preview."
21461
+ ).option("--dry-run", "print the planned changes without touching the filesystem").option("--force", "overwrite .slopbrick/ if both old and new artifacts exist").option("--workspace <path>", "workspace directory", process.cwd()).option("--format <pretty|json>", "output format", "pretty").action(
21462
+ (cmdOptions, command) => {
21463
+ const globals = command.optsWithGlobals();
21464
+ const format = (cmdOptions.format ?? globals.format) === "json" ? "json" : "pretty";
21465
+ const cwd = resolve14(cmdOptions.workspace ?? process.cwd());
21466
+ const { runMigrate: runMigrate2, formatMigrate: formatMigrate2 } = (init_migrate(), __toCommonJS(migrate_exports));
21467
+ const result = runMigrate2({
21468
+ workspace: cwd,
21469
+ dryRun: cmdOptions.dryRun,
21470
+ force: cmdOptions.force
21471
+ });
21472
+ if (format === "json") {
21473
+ logger.info(JSON.stringify(result, null, 2));
21474
+ } else {
21475
+ logger.info(formatMigrate2(result));
21476
+ }
21477
+ process.exit(result.ok ? 0 : 1);
21478
+ }
21479
+ );
21276
21480
  program.command("rules").description("list all built-in rules with their categories, severities, and descriptions").option("--category <name>", "filter to a single category (visual, typo, layout, etc.)").option("--ai-only", "only show AI-specific rules").option("--json", "emit JSON instead of a pretty table").option("--show-signal-strength", "print per-rule precision/recall table").action((cmdOptions, command) => {
21277
21481
  const globals = command.optsWithGlobals();
21278
21482
  const wantJson = Boolean(cmdOptions.json || globals.json);
@@ -21360,7 +21564,7 @@ async function runCli({ start }) {
21360
21564
  });
21361
21565
  program.command("validate-config [path]").description("Statically validate a slopbrick.config.mjs without scanning").action(async (configPath) => {
21362
21566
  const path = configPath ? resolve14(configPath) : resolve14(process.cwd(), "slopbrick.config.mjs");
21363
- if (!existsSync24(path)) {
21567
+ if (!existsSync26(path)) {
21364
21568
  logger.error(`Error: config file not found: ${path}`);
21365
21569
  process.exit(2);
21366
21570
  }
@@ -21436,26 +21640,6 @@ ${formatMarkdown3(result.report)}`);
21436
21640
  init_threshold();
21437
21641
  init_render();
21438
21642
  init_find_similar();
21439
- import {
21440
- MEMORY_SCHEMA_VERSION as MEMORY_SCHEMA_VERSION3,
21441
- loadInventory as loadInventory3,
21442
- saveInventory as saveInventory2,
21443
- loadConstitution as loadConstitution3,
21444
- saveConstitution as saveConstitution3,
21445
- isInventoryFresh as isInventoryFresh3,
21446
- invalidateFile as invalidateFile3,
21447
- inventoryPath as inventoryPath3,
21448
- constitutionPath as constitutionPath3,
21449
- cachePath as cachePath5,
21450
- INVENTORY_FILENAME,
21451
- CONSTITUTION_FILENAME,
21452
- CACHE_FILENAME,
21453
- isMemoryPattern as isMemoryPattern3,
21454
- isComponentFingerprint as isComponentFingerprint3,
21455
- isInventoryFile as isInventoryFile3,
21456
- isConstitutionFile as isConstitutionFile3,
21457
- isFileMtimeEntry as isFileMtimeEntry3
21458
- } from "@usebrick/core";
21459
21643
  process.on("uncaughtException", (err) => {
21460
21644
  const { logger: logger6 } = (init_logger(), __toCommonJS(logger_exports));
21461
21645
  logger6.error(`Unexpected error: ${err.message}`);
@@ -21463,17 +21647,11 @@ process.on("uncaughtException", (err) => {
21463
21647
  });
21464
21648
  export {
21465
21649
  AI_SECURITY_NUMERIC,
21466
- CACHE_FILENAME,
21467
- CONSTITUTION_FILENAME,
21468
21650
  DEFAULT_CONFIG,
21469
- INVENTORY_FILENAME,
21470
- MEMORY_SCHEMA_VERSION3 as MEMORY_SCHEMA_VERSION,
21471
21651
  REPOSITORY_HEALTH_WEIGHTS,
21472
21652
  VERSION,
21473
21653
  baselineStatusMessage,
21474
- cachePath5 as cachePath,
21475
21654
  colorForSlop,
21476
- constitutionPath3 as constitutionPath,
21477
21655
  extractSignatures,
21478
21656
  failedThresholdCount,
21479
21657
  filterByDisabledDirectives,
@@ -21483,22 +21661,10 @@ export {
21483
21661
  formatBadge,
21484
21662
  formatReportFromFile,
21485
21663
  formatSparkline,
21486
- invalidateFile3 as invalidateFile,
21487
- inventoryPath3 as inventoryPath,
21488
- isComponentFingerprint3 as isComponentFingerprint,
21489
- isConstitutionFile3 as isConstitutionFile,
21490
- isFileMtimeEntry3 as isFileMtimeEntry,
21491
- isInventoryFile3 as isInventoryFile,
21492
- isInventoryFresh3 as isInventoryFresh,
21493
- isMemoryPattern3 as isMemoryPattern,
21494
21664
  loadConfig,
21495
- loadConstitution3 as loadConstitution,
21496
- loadInventory3 as loadInventory,
21497
21665
  readReportFile,
21498
21666
  runCli,
21499
21667
  runInitWizard,
21500
- saveConstitution3 as saveConstitution,
21501
- saveInventory2 as saveInventory,
21502
21668
  scanProject,
21503
21669
  serializeConfig,
21504
21670
  signatureSimilarity,