slopbrick 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -36,7 +36,7 @@ var VERSION, AI_SECURITY_NUMERIC, REPOSITORY_HEALTH_WEIGHTS;
36
36
  var init_types = __esm({
37
37
  "src/types.ts"() {
38
38
  "use strict";
39
- VERSION = "0.10.0";
39
+ VERSION = "0.11.1";
40
40
  AI_SECURITY_NUMERIC = {
41
41
  low: 100,
42
42
  medium: 75,
@@ -16764,14 +16764,14 @@ async function runBusinessLogicScore(args, ctx) {
16764
16764
  const maxFiles = typeof maxFilesRaw === "number" && Number.isFinite(maxFilesRaw) && maxFilesRaw > 0 ? Math.min(2e3, Math.floor(maxFilesRaw)) : 500;
16765
16765
  try {
16766
16766
  const { discoverFiles: discoverFiles2 } = await Promise.resolve().then(() => (init_discover(), discover_exports));
16767
- const { readFileSync: readFileSync37 } = await import("fs");
16767
+ const { readFileSync: readFileSync38 } = await import("fs");
16768
16768
  const allFiles = await discoverFiles2(ctx.cwd, ctx.config);
16769
16769
  const limited = allFiles.slice(0, maxFiles);
16770
16770
  const issues = [];
16771
16771
  for (const absPath of limited) {
16772
16772
  let source;
16773
16773
  try {
16774
- source = readFileSync37(absPath, "utf-8");
16774
+ source = readFileSync38(absPath, "utf-8");
16775
16775
  } catch {
16776
16776
  continue;
16777
16777
  }
@@ -17038,6 +17038,180 @@ var init_tools = __esm({
17038
17038
  }
17039
17039
  });
17040
17040
 
17041
+ // src/cli/migrate.ts
17042
+ var migrate_exports = {};
17043
+ __export(migrate_exports, {
17044
+ applyMigration: () => applyMigration,
17045
+ formatMigrate: () => formatMigrate,
17046
+ isAlreadyMigrated: () => isAlreadyMigrated,
17047
+ logger: () => logger,
17048
+ planMigration: () => planMigration,
17049
+ runMigrate: () => runMigrate
17050
+ });
17051
+ function planMigration(workspaceDir) {
17052
+ const moves = [];
17053
+ const rewrites = [];
17054
+ const gitignoreEdits = [];
17055
+ const oldDir = (0, import_node_path38.join)(workspaceDir, ".slop-audit");
17056
+ const newDir = (0, import_node_path38.join)(workspaceDir, ".slopbrick");
17057
+ if ((0, import_node_fs39.existsSync)(oldDir)) {
17058
+ moves.push({ from: oldDir, to: newDir, kind: "dir" });
17059
+ rewrites.push({
17060
+ path: (0, import_node_path38.join)(newDir, "inventory.json"),
17061
+ field: "version",
17062
+ from: '"1"',
17063
+ to: '"2"'
17064
+ });
17065
+ rewrites.push({
17066
+ path: (0, import_node_path38.join)(newDir, "constitution.json"),
17067
+ field: "version",
17068
+ from: '"1"',
17069
+ to: '"2"'
17070
+ });
17071
+ }
17072
+ const oldCache = (0, import_node_path38.join)(workspaceDir, ".slop-audit-cache.json");
17073
+ const newCache = (0, import_node_path38.join)(workspaceDir, ".slopbrick-cache.json");
17074
+ if ((0, import_node_fs39.existsSync)(oldCache)) {
17075
+ moves.push({ from: oldCache, to: newCache, kind: "file" });
17076
+ }
17077
+ for (const ext of ["mjs", "cjs", "js"]) {
17078
+ const oldCfg = (0, import_node_path38.join)(workspaceDir, `slop-audit.config.${ext}`);
17079
+ const newCfg = (0, import_node_path38.join)(workspaceDir, `slopbrick.config.${ext}`);
17080
+ if ((0, import_node_fs39.existsSync)(oldCfg)) {
17081
+ moves.push({ from: oldCfg, to: newCfg, kind: "config" });
17082
+ }
17083
+ }
17084
+ const gi = (0, import_node_path38.join)(workspaceDir, ".gitignore");
17085
+ if ((0, import_node_fs39.existsSync)(gi)) {
17086
+ const content = (0, import_node_fs39.readFileSync)(gi, "utf-8");
17087
+ if (content.includes(".slop-audit/")) {
17088
+ gitignoreEdits.push({
17089
+ path: gi,
17090
+ from: ".slop-audit/",
17091
+ to: ".slopbrick/"
17092
+ });
17093
+ }
17094
+ if (content.includes(".slop-audit-cache.json")) {
17095
+ gitignoreEdits.push({
17096
+ path: gi,
17097
+ from: ".slop-audit-cache.json",
17098
+ to: ".slopbrick-cache.json"
17099
+ });
17100
+ }
17101
+ }
17102
+ return { moves, rewrites, gitignoreEdits };
17103
+ }
17104
+ function isAlreadyMigrated(workspaceDir) {
17105
+ return (0, import_node_fs39.existsSync)((0, import_node_path38.join)(workspaceDir, ".slopbrick")) && !(0, import_node_fs39.existsSync)((0, import_node_path38.join)(workspaceDir, ".slop-audit"));
17106
+ }
17107
+ function applyMigration(plan, options = {}) {
17108
+ if (options.dryRun) return;
17109
+ for (const m of plan.moves) {
17110
+ (0, import_node_fs39.renameSync)(m.from, m.to);
17111
+ }
17112
+ for (const r of plan.rewrites) {
17113
+ if (!(0, import_node_fs39.existsSync)(r.path)) continue;
17114
+ const content = (0, import_node_fs39.readFileSync)(r.path, "utf-8");
17115
+ const next = content.replace(`"version": ${r.from}`, `"version": ${r.to}`);
17116
+ (0, import_node_fs39.writeFileSync)(r.path, next);
17117
+ }
17118
+ for (const g of plan.gitignoreEdits) {
17119
+ const content = (0, import_node_fs39.readFileSync)(g.path, "utf-8");
17120
+ const next = content.replaceAll(g.from, g.to);
17121
+ (0, import_node_fs39.writeFileSync)(g.path, next);
17122
+ }
17123
+ }
17124
+ function runMigrate(options) {
17125
+ const { workspace, dryRun = false, force = false } = options;
17126
+ if (!(0, import_node_fs39.existsSync)(workspace)) {
17127
+ return {
17128
+ ok: false,
17129
+ alreadyMigrated: false,
17130
+ planned: { moves: [], rewrites: [], gitignoreEdits: [] },
17131
+ applied: false,
17132
+ reason: `Workspace ${workspace} does not exist`
17133
+ };
17134
+ }
17135
+ const alreadyMigrated = isAlreadyMigrated(workspace);
17136
+ const newDir = (0, import_node_path38.join)(workspace, ".slopbrick");
17137
+ const oldDir = (0, import_node_path38.join)(workspace, ".slop-audit");
17138
+ if ((0, import_node_fs39.existsSync)(newDir) && (0, import_node_fs39.existsSync)(oldDir) && !force) {
17139
+ return {
17140
+ ok: false,
17141
+ alreadyMigrated: false,
17142
+ planned: { moves: [], rewrites: [], gitignoreEdits: [] },
17143
+ applied: false,
17144
+ reason: `Both .slopbrick/ and .slop-audit/ exist. Use --force to overwrite the .slopbrick/ directory, or manually resolve the conflict.`
17145
+ };
17146
+ }
17147
+ const planned = planMigration(workspace);
17148
+ const nothingToDo = planned.moves.length === 0 && planned.rewrites.length === 0 && planned.gitignoreEdits.length === 0;
17149
+ if (nothingToDo) {
17150
+ return {
17151
+ ok: true,
17152
+ alreadyMigrated,
17153
+ planned,
17154
+ applied: false
17155
+ };
17156
+ }
17157
+ applyMigration(planned, { dryRun });
17158
+ return {
17159
+ ok: true,
17160
+ alreadyMigrated: false,
17161
+ planned,
17162
+ applied: !dryRun
17163
+ };
17164
+ }
17165
+ function formatMigrate(result) {
17166
+ const lines = [];
17167
+ if (result.alreadyMigrated) {
17168
+ lines.push("Already migrated to v2 (no work needed).");
17169
+ return lines.join("\n");
17170
+ }
17171
+ if (!result.ok) {
17172
+ lines.push(`ERROR: ${result.reason ?? "unknown failure"}`);
17173
+ return lines.join("\n");
17174
+ }
17175
+ if (result.planned.moves.length === 0 && result.planned.gitignoreEdits.length === 0) {
17176
+ lines.push("Nothing to migrate \u2014 workspace is already on slopbrick v0.11.0+.");
17177
+ }
17178
+ if (result.planned.moves.length > 0) {
17179
+ lines.push("Moves:");
17180
+ for (const m of result.planned.moves) {
17181
+ lines.push(` ${m.from}`);
17182
+ lines.push(` \u2192 ${m.to} (${m.kind})`);
17183
+ }
17184
+ }
17185
+ if (result.planned.rewrites.length > 0) {
17186
+ lines.push("Schema version bumps:");
17187
+ for (const r of result.planned.rewrites) {
17188
+ lines.push(` ${r.path} ${r.field}: ${r.from} \u2192 ${r.to}`);
17189
+ }
17190
+ }
17191
+ if (result.planned.gitignoreEdits.length > 0) {
17192
+ lines.push(".gitignore edits:");
17193
+ for (const g of result.planned.gitignoreEdits) {
17194
+ lines.push(` ${g.path}: ${g.from} \u2192 ${g.to}`);
17195
+ }
17196
+ }
17197
+ lines.push("");
17198
+ if (result.applied) {
17199
+ lines.push("Migration applied. Run `slopbrick scan` to regenerate inventory at v2.");
17200
+ } else {
17201
+ lines.push("DRY RUN \u2014 no files were changed. Re-run without --dry-run to apply.");
17202
+ }
17203
+ return lines.join("\n");
17204
+ }
17205
+ var import_node_fs39, import_node_path38;
17206
+ var init_migrate = __esm({
17207
+ "src/cli/migrate.ts"() {
17208
+ "use strict";
17209
+ import_node_fs39 = require("fs");
17210
+ import_node_path38 = require("path");
17211
+ init_logger();
17212
+ }
17213
+ });
17214
+
17041
17215
  // src/index.ts
17042
17216
  var src_exports = {};
17043
17217
  __export(src_exports, {
@@ -17089,8 +17263,8 @@ init_types();
17089
17263
  init_config();
17090
17264
 
17091
17265
  // src/cli/program.ts
17092
- var import_node_fs39 = require("fs");
17093
- var import_node_path38 = require("path");
17266
+ var import_node_fs40 = require("fs");
17267
+ var import_node_path39 = require("path");
17094
17268
  var import_node_perf_hooks = require("perf_hooks");
17095
17269
  var import_commander2 = require("commander");
17096
17270
 
@@ -20634,13 +20808,13 @@ async function runCli({ start }) {
20634
20808
  logger.info(renderMatrix());
20635
20809
  process.exit(0);
20636
20810
  }
20637
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
20638
- const configPath = (0, import_node_path38.join)(cwd, "slopbrick.config.mjs");
20811
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
20812
+ const configPath = (0, import_node_path39.join)(cwd, "slopbrick.config.mjs");
20639
20813
  const detected = detectStack(cwd);
20640
20814
  const fallbackConfig = { ...DEFAULT_CONFIG, ...detected };
20641
20815
  const proposed = serializeConfig(fallbackConfig);
20642
- if ((0, import_node_fs39.existsSync)(configPath) && !cmdOptions.yes) {
20643
- const current = (0, import_node_fs39.readFileSync)(configPath, "utf8");
20816
+ if ((0, import_node_fs40.existsSync)(configPath) && !cmdOptions.yes) {
20817
+ const current = (0, import_node_fs40.readFileSync)(configPath, "utf8");
20644
20818
  logger.error(`A config file already exists at ${configPath}.`);
20645
20819
  logger.error("To overwrite it with defaults, run `slopbrick init --yes`.");
20646
20820
  logger.error("");
@@ -20661,7 +20835,7 @@ async function runCli({ start }) {
20661
20835
  config = buildInitConfig(detected, answers);
20662
20836
  usedWizard = true;
20663
20837
  }
20664
- (0, import_node_fs39.writeFileSync)(configPath, serializeConfig(config));
20838
+ (0, import_node_fs40.writeFileSync)(configPath, serializeConfig(config));
20665
20839
  appendGitignore(cwd);
20666
20840
  const refresh = await refreshRegistrySnapshot(cwd);
20667
20841
  if (!refresh.ok) {
@@ -20691,21 +20865,21 @@ async function runCli({ start }) {
20691
20865
  return Boolean(opts[t.flag]);
20692
20866
  });
20693
20867
  for (const target of targetsToWrite) {
20694
- const snippetPath = (0, import_node_path38.join)(cwd, resolveTargetPath(target));
20695
- (0, import_node_fs39.mkdirSync)((0, import_node_path38.dirname)(snippetPath), { recursive: true });
20868
+ const snippetPath = (0, import_node_path39.join)(cwd, resolveTargetPath(target));
20869
+ (0, import_node_fs40.mkdirSync)((0, import_node_path39.dirname)(snippetPath), { recursive: true });
20696
20870
  const generated = target.generator(builtinRules);
20697
- if (!target.isFolder && (0, import_node_fs39.existsSync)(snippetPath)) {
20698
- const existing = (0, import_node_fs39.readFileSync)(snippetPath, "utf8");
20871
+ if (!target.isFolder && (0, import_node_fs40.existsSync)(snippetPath)) {
20872
+ const existing = (0, import_node_fs40.readFileSync)(snippetPath, "utf8");
20699
20873
  if (existing.includes("<!-- slopbrick:begin -->")) {
20700
20874
  const updated = existing.replace(
20701
20875
  /<!-- slopbrick:begin -->[\s\S]*?<!-- slopbrick:end -->/,
20702
20876
  "<!-- slopbrick:begin -->\n" + generated + "<!-- slopbrick:end -->"
20703
20877
  );
20704
- (0, import_node_fs39.writeFileSync)(snippetPath, updated, "utf8");
20878
+ (0, import_node_fs40.writeFileSync)(snippetPath, updated, "utf8");
20705
20879
  if (!options.quiet) logger.info(`Updated ${snippetPath}`);
20706
20880
  continue;
20707
20881
  }
20708
- (0, import_node_fs39.writeFileSync)(
20882
+ (0, import_node_fs40.writeFileSync)(
20709
20883
  snippetPath,
20710
20884
  existing + (existing.endsWith("\n") ? "\n" : "\n\n") + generated,
20711
20885
  "utf8"
@@ -20713,7 +20887,7 @@ async function runCli({ start }) {
20713
20887
  if (!options.quiet) logger.info(`Wrote ${snippetPath}`);
20714
20888
  continue;
20715
20889
  }
20716
- (0, import_node_fs39.writeFileSync)(snippetPath, generated, "utf8");
20890
+ (0, import_node_fs40.writeFileSync)(snippetPath, generated, "utf8");
20717
20891
  if (!options.quiet) logger.info(`Wrote ${snippetPath}`);
20718
20892
  }
20719
20893
  if (options.baseline) {
@@ -20730,7 +20904,7 @@ async function runCli({ start }) {
20730
20904
  });
20731
20905
  program.command("install").description("install the git pre-commit hook").action(async (_cmdOptions, command) => {
20732
20906
  const options = command.optsWithGlobals();
20733
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
20907
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
20734
20908
  const root = getGitRoot(cwd);
20735
20909
  if (!root) {
20736
20910
  logger.error("Not a Git repository. Run `git init` first, or remove --staged from your command.");
@@ -20744,7 +20918,7 @@ async function runCli({ start }) {
20744
20918
  });
20745
20919
  program.command("uninstall").description("uninstall the git pre-commit hook").action(async (_cmdOptions, command) => {
20746
20920
  const options = command.optsWithGlobals();
20747
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
20921
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
20748
20922
  const root = getGitRoot(cwd);
20749
20923
  if (!root) {
20750
20924
  logger.error("Not a Git repository. Run `git init` first, or remove --staged from your command.");
@@ -20765,7 +20939,7 @@ async function runCli({ start }) {
20765
20939
  program.command("suggest").description("print remediation advice").action(async (_cmdOptions, command) => {
20766
20940
  const options = command.optsWithGlobals();
20767
20941
  const { report } = await runScan(options);
20768
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
20942
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
20769
20943
  logger.info(formatAdvice(report));
20770
20944
  const diff = formatUnifiedDiff(report, cwd);
20771
20945
  if (diff) logger.info(diff);
@@ -20773,7 +20947,7 @@ async function runCli({ start }) {
20773
20947
  });
20774
20948
  program.command("flywheel").description("summarize aggregated scan telemetry").option("--format <pretty|json>", "output format", "pretty").option("--export <path>", "write summary as JSON to <path>").action(async (cmdOptions, command) => {
20775
20949
  const options = command.optsWithGlobals();
20776
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
20950
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
20777
20951
  const payloads = readTelemetry(cwd);
20778
20952
  if (payloads.length === 0) {
20779
20953
  logger.info("No flywheel telemetry found. Run a scan first.");
@@ -20781,9 +20955,9 @@ async function runCli({ start }) {
20781
20955
  }
20782
20956
  const summary = summarizeTelemetry(payloads);
20783
20957
  if (cmdOptions.export) {
20784
- const exportPath = (0, import_node_path38.resolve)(cmdOptions.export);
20785
- (0, import_node_fs39.mkdirSync)((0, import_node_path38.dirname)(exportPath), { recursive: true });
20786
- (0, import_node_fs39.writeFileSync)(exportPath, JSON.stringify(summary, null, 2), "utf-8");
20958
+ const exportPath = (0, import_node_path39.resolve)(cmdOptions.export);
20959
+ (0, import_node_fs40.mkdirSync)((0, import_node_path39.dirname)(exportPath), { recursive: true });
20960
+ (0, import_node_fs40.writeFileSync)(exportPath, JSON.stringify(summary, null, 2), "utf-8");
20787
20961
  logger.info(`Wrote flywheel summary to ${exportPath}`);
20788
20962
  process.exit(0);
20789
20963
  }
@@ -20806,7 +20980,7 @@ async function runCli({ start }) {
20806
20980
  logger.error("--heatmap and --suggest can't be used together. Pick one: a heatmap of severity, or text advice.");
20807
20981
  process.exit(2);
20808
20982
  }
20809
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
20983
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
20810
20984
  if (options.trend !== void 0) {
20811
20985
  const runs = readRuns(cwd);
20812
20986
  if (runs.length === 0) {
@@ -20839,7 +21013,7 @@ async function runCli({ start }) {
20839
21013
  const scanElapsed = Math.round(import_node_perf_hooks.performance.now() - scanStart);
20840
21014
  const totalElapsed = Math.round(import_node_perf_hooks.performance.now() - start);
20841
21015
  if (options.baseline) {
20842
- const cwd2 = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
21016
+ const cwd2 = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
20843
21017
  const configHash = hashConfig(config);
20844
21018
  const gitHead = await getGitHead(cwd2) ?? "unknown";
20845
21019
  const cache = buildBaselineCache(report, configHash, gitHead, cwd2);
@@ -20933,24 +21107,24 @@ async function runCli({ start }) {
20933
21107
  framework: cmdOptions.framework,
20934
21108
  componentType: cmdOptions.componentType,
20935
21109
  provider,
20936
- outputDir: (0, import_node_path38.resolve)(cmdOptions.outputDir),
21110
+ outputDir: (0, import_node_path39.resolve)(cmdOptions.outputDir),
20937
21111
  temperature: cmdOptions.temperature
20938
21112
  });
20939
21113
  logger.info(`Generated ${samples.length} samples in ${cmdOptions.outputDir}`);
20940
21114
  });
20941
21115
  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) => {
20942
21116
  try {
20943
- const metadataPath = (0, import_node_path38.resolve)(cmdOptions.inputDir, "metadata.json");
20944
- if (!(0, import_node_fs39.existsSync)(metadataPath)) {
21117
+ const metadataPath = (0, import_node_path39.resolve)(cmdOptions.inputDir, "metadata.json");
21118
+ if (!(0, import_node_fs40.existsSync)(metadataPath)) {
20945
21119
  logger.error(`No metadata.json found in ${cmdOptions.inputDir}`);
20946
21120
  process.exit(2);
20947
21121
  }
20948
- const samples = JSON.parse((0, import_node_fs39.readFileSync)(metadataPath, "utf8"));
21122
+ const samples = JSON.parse((0, import_node_fs40.readFileSync)(metadataPath, "utf8"));
20949
21123
  const config = cmdOptions.config ? await loadConfig(cmdOptions.config) : { ...DEFAULT_CONFIG, framework: cmdOptions.framework };
20950
21124
  const analysis = await analyzeSamples(samples, config);
20951
- const outputPath = (0, import_node_path38.resolve)(cmdOptions.output);
20952
- (0, import_node_fs39.mkdirSync)((0, import_node_path38.dirname)(outputPath), { recursive: true });
20953
- (0, import_node_fs39.writeFileSync)(outputPath, JSON.stringify(analysis, null, 2), "utf8");
21125
+ const outputPath = (0, import_node_path39.resolve)(cmdOptions.output);
21126
+ (0, import_node_fs40.mkdirSync)((0, import_node_path39.dirname)(outputPath), { recursive: true });
21127
+ (0, import_node_fs40.writeFileSync)(outputPath, JSON.stringify(analysis, null, 2), "utf8");
20954
21128
  logger.info(`Analyzed ${analysis.summary.total} samples; coverage: ${analysis.summary.coverage}%`);
20955
21129
  logger.info(`Wrote analysis to ${outputPath}`);
20956
21130
  } catch (error) {
@@ -20960,12 +21134,12 @@ async function runCli({ start }) {
20960
21134
  });
20961
21135
  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) => {
20962
21136
  try {
20963
- const metadataPath = (0, import_node_path38.resolve)(cmdOptions.inputDir, "metadata.json");
20964
- if (!(0, import_node_fs39.existsSync)(metadataPath)) {
21137
+ const metadataPath = (0, import_node_path39.resolve)(cmdOptions.inputDir, "metadata.json");
21138
+ if (!(0, import_node_fs40.existsSync)(metadataPath)) {
20965
21139
  logger.error(`No metadata.json found in ${cmdOptions.inputDir}`);
20966
21140
  process.exit(2);
20967
21141
  }
20968
- const samples = JSON.parse((0, import_node_fs39.readFileSync)(metadataPath, "utf8"));
21142
+ const samples = JSON.parse((0, import_node_fs40.readFileSync)(metadataPath, "utf8"));
20969
21143
  const config = cmdOptions.config ? await loadConfig(cmdOptions.config) : { ...DEFAULT_CONFIG, framework: cmdOptions.framework };
20970
21144
  const analysis = await analyzeSamples(samples, config);
20971
21145
  const extraction = extractAndCluster(analysis.samples, {
@@ -20975,8 +21149,8 @@ async function runCli({ start }) {
20975
21149
  const candidates = clustersToCandidates(extraction.clusters, {
20976
21150
  minFrequency: cmdOptions.minFrequency
20977
21151
  });
20978
- const outputPath = (0, import_node_path38.resolve)(cmdOptions.output);
20979
- (0, import_node_fs39.mkdirSync)((0, import_node_path38.dirname)(outputPath), { recursive: true });
21152
+ const outputPath = (0, import_node_path39.resolve)(cmdOptions.output);
21153
+ (0, import_node_fs40.mkdirSync)((0, import_node_path39.dirname)(outputPath), { recursive: true });
20980
21154
  const payload = {
20981
21155
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
20982
21156
  sampleCount: analysis.summary.total,
@@ -20984,7 +21158,7 @@ async function runCli({ start }) {
20984
21158
  fingerprintCount: extraction.total,
20985
21159
  candidates
20986
21160
  };
20987
- (0, import_node_fs39.writeFileSync)(outputPath, JSON.stringify(payload, null, 2), "utf8");
21161
+ (0, import_node_fs40.writeFileSync)(outputPath, JSON.stringify(payload, null, 2), "utf8");
20988
21162
  logger.info(`Extracted ${extraction.total} fingerprints across ${analysis.summary.total} samples`);
20989
21163
  logger.info(`Wrote ${candidates.length} candidate rule(s) to ${outputPath}`);
20990
21164
  } catch (error) {
@@ -21001,9 +21175,9 @@ async function runCli({ start }) {
21001
21175
  positiveLimit: cmdOptions.positiveLimit,
21002
21176
  negativeLimit: cmdOptions.negativeLimit
21003
21177
  });
21004
- const outputPath = cmdOptions.output ? (0, import_node_path38.resolve)(cwd, cmdOptions.output) : (0, import_node_path38.resolve)(cwd, "corpus", "calibration-empirical.md");
21005
- (0, import_node_fs39.mkdirSync)((0, import_node_path38.dirname)(outputPath), { recursive: true });
21006
- (0, import_node_fs39.writeFileSync)(outputPath, reportToMarkdown(report), "utf8");
21178
+ const outputPath = cmdOptions.output ? (0, import_node_path39.resolve)(cwd, cmdOptions.output) : (0, import_node_path39.resolve)(cwd, "corpus", "calibration-empirical.md");
21179
+ (0, import_node_fs40.mkdirSync)((0, import_node_path39.dirname)(outputPath), { recursive: true });
21180
+ (0, import_node_fs40.writeFileSync)(outputPath, reportToMarkdown(report), "utf8");
21007
21181
  logger.info(
21008
21182
  "Calibrated " + report.rules.length + " rules across " + report.positiveFileCount + " positive + " + report.negativeFileCount + " negative files."
21009
21183
  );
@@ -21041,7 +21215,7 @@ async function runCli({ start }) {
21041
21215
  const options = command.optsWithGlobals();
21042
21216
  const rawFormat = options.format ?? cmdOptions.format ?? "pretty";
21043
21217
  const format = rawFormat === "json" || rawFormat === "pretty" ? rawFormat : "pretty";
21044
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
21218
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
21045
21219
  const { config } = await runScan({ ...options, workspace: cwd });
21046
21220
  const result = await runDrift(cwd, config, { maxFiles: cmdOptions.maxFiles });
21047
21221
  logger.info(formatDrift(result, { json: format === "json" }));
@@ -21058,7 +21232,7 @@ async function runCli({ start }) {
21058
21232
  async (cmdOptions, command) => {
21059
21233
  try {
21060
21234
  const options = command.optsWithGlobals();
21061
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
21235
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
21062
21236
  const rawFormat = options.format ?? cmdOptions.format ?? "text";
21063
21237
  const format = rawFormat === "json" || rawFormat === "markdown" || rawFormat === "text" ? rawFormat : "text";
21064
21238
  const { config } = await runScan({ ...options, workspace: cwd });
@@ -21085,7 +21259,7 @@ async function runCli({ start }) {
21085
21259
  const options = command.optsWithGlobals();
21086
21260
  const rawFormat = options.format ?? cmdOptions.format ?? "pretty";
21087
21261
  const format = rawFormat === "json" || rawFormat === "pretty" ? rawFormat : "pretty";
21088
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
21262
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
21089
21263
  const { report } = await runScan({ ...options, workspace: cwd });
21090
21264
  const securityIssues = report.issues.filter((i) => i.category === "security");
21091
21265
  const { risk, findings } = computeAiSecurityRisk(securityIssues);
@@ -21136,7 +21310,7 @@ async function runCli({ start }) {
21136
21310
  const options = command.optsWithGlobals();
21137
21311
  const rawFormat = options.format ?? cmdOptions.format ?? "pretty";
21138
21312
  const format = rawFormat === "json" || rawFormat === "pretty" ? rawFormat : "pretty";
21139
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
21313
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
21140
21314
  const { config } = await runScan({ ...options, workspace: cwd });
21141
21315
  const { result } = await runTestScan(cwd, config, { strict: options.strict });
21142
21316
  logger.info(formatTestReport(result, { json: format === "json" }));
@@ -21155,7 +21329,7 @@ async function runCli({ start }) {
21155
21329
  const options = command.optsWithGlobals();
21156
21330
  const rawFormat = options.format ?? cmdOptions.format ?? "pretty";
21157
21331
  const format = rawFormat === "json" || rawFormat === "pretty" ? rawFormat : "pretty";
21158
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
21332
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
21159
21333
  const { config } = await runScan({ ...options, workspace: cwd });
21160
21334
  const score = await buildArchitectureScore(cwd, config, cmdOptions.maxFiles);
21161
21335
  const out = format === "json" ? JSON.stringify(score, null, 2) : formatArchitectureScore(score);
@@ -21175,7 +21349,7 @@ async function runCli({ start }) {
21175
21349
  const options = command.optsWithGlobals();
21176
21350
  const rawFormat = options.format ?? cmdOptions.format ?? "text";
21177
21351
  const format = rawFormat === "json" || rawFormat === "markdown" || rawFormat === "text" ? rawFormat : "text";
21178
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
21352
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
21179
21353
  const { config } = await runScan({ ...options, workspace: cwd });
21180
21354
  const result = await runBusinessLogicScan(cwd, config, {
21181
21355
  maxFiles: cmdOptions.maxFiles
@@ -21197,7 +21371,7 @@ async function runCli({ start }) {
21197
21371
  const rawFormat = options.format ?? cmdOptions.format ?? "text";
21198
21372
  const format = rawFormat === "json" || rawFormat === "text" ? rawFormat : "text";
21199
21373
  const strict = options.strict ?? cmdOptions.strict ?? false;
21200
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
21374
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
21201
21375
  const { config } = await runScan({ ...options, workspace: cwd });
21202
21376
  const result = await runMaintenanceCostScan(cwd, config, {
21203
21377
  maxFiles: cmdOptions.maxFiles,
@@ -21220,7 +21394,7 @@ async function runCli({ start }) {
21220
21394
  const rawFormat = options.format ?? cmdOptions.format ?? "text";
21221
21395
  const format = rawFormat === "json" || rawFormat === "markdown" || rawFormat === "text" ? rawFormat : "text";
21222
21396
  const strict = options.strict ?? cmdOptions.strict ?? false;
21223
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
21397
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
21224
21398
  const { config } = await runScan({ ...options, workspace: cwd });
21225
21399
  const result = await runDocsScan(cwd, config, {
21226
21400
  maxDocFiles: cmdOptions.maxFiles,
@@ -21248,7 +21422,7 @@ async function runCli({ start }) {
21248
21422
  const rawFormat = options.format ?? cmdOptions.format ?? "text";
21249
21423
  const format = rawFormat === "json" || rawFormat === "markdown" || rawFormat === "text" ? rawFormat : "text";
21250
21424
  const strict = options.strict ?? cmdOptions.strict ?? false;
21251
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
21425
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
21252
21426
  const { config } = await runScan({ ...options, workspace: cwd });
21253
21427
  const result = await runDbScan(cwd, config, {
21254
21428
  maxFiles: cmdOptions.maxFiles,
@@ -21275,7 +21449,7 @@ async function runCli({ start }) {
21275
21449
  const options = command.optsWithGlobals();
21276
21450
  const rawFormat = options.format ?? cmdOptions.format ?? "text";
21277
21451
  const format = rawFormat === "json" || rawFormat === "markdown" || rawFormat === "text" ? rawFormat : "text";
21278
- const cwd = (0, import_node_path38.resolve)(options.workspace ?? process.cwd());
21452
+ const cwd = (0, import_node_path39.resolve)(options.workspace ?? process.cwd());
21279
21453
  const { config } = await runScan({ ...options, workspace: cwd });
21280
21454
  const result = await runPatternsScan(cwd, config, {
21281
21455
  maxFiles: cmdOptions.maxFiles,
@@ -21299,6 +21473,27 @@ async function runCli({ start }) {
21299
21473
  const exitCode = await runDoctor(process.cwd());
21300
21474
  if (exitCode !== 0) process.exit(exitCode);
21301
21475
  });
21476
+ program.command("migrate").description(
21477
+ "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."
21478
+ ).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(
21479
+ (cmdOptions, command) => {
21480
+ const globals = command.optsWithGlobals();
21481
+ const format = (cmdOptions.format ?? globals.format) === "json" ? "json" : "pretty";
21482
+ const cwd = (0, import_node_path39.resolve)(cmdOptions.workspace ?? process.cwd());
21483
+ const { runMigrate: runMigrate2, formatMigrate: formatMigrate2 } = (init_migrate(), __toCommonJS(migrate_exports));
21484
+ const result = runMigrate2({
21485
+ workspace: cwd,
21486
+ dryRun: cmdOptions.dryRun,
21487
+ force: cmdOptions.force
21488
+ });
21489
+ if (format === "json") {
21490
+ logger.info(JSON.stringify(result, null, 2));
21491
+ } else {
21492
+ logger.info(formatMigrate2(result));
21493
+ }
21494
+ process.exit(result.ok ? 0 : 1);
21495
+ }
21496
+ );
21302
21497
  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) => {
21303
21498
  const globals = command.optsWithGlobals();
21304
21499
  const wantJson = Boolean(cmdOptions.json || globals.json);
@@ -21385,13 +21580,13 @@ async function runCli({ start }) {
21385
21580
  if ("error" in result) process.exit(2);
21386
21581
  });
21387
21582
  program.command("validate-config [path]").description("Statically validate a slopbrick.config.mjs without scanning").action(async (configPath) => {
21388
- const path = configPath ? (0, import_node_path38.resolve)(configPath) : (0, import_node_path38.resolve)(process.cwd(), "slopbrick.config.mjs");
21389
- if (!(0, import_node_fs39.existsSync)(path)) {
21583
+ const path = configPath ? (0, import_node_path39.resolve)(configPath) : (0, import_node_path39.resolve)(process.cwd(), "slopbrick.config.mjs");
21584
+ if (!(0, import_node_fs40.existsSync)(path)) {
21390
21585
  logger.error(`Error: config file not found: ${path}`);
21391
21586
  process.exit(2);
21392
21587
  }
21393
21588
  try {
21394
- const mod = (0, import_node_path38.extname)(path) === ".cjs" ? require(path) : await import(path);
21589
+ const mod = (0, import_node_path39.extname)(path) === ".cjs" ? require(path) : await import(path);
21395
21590
  const userConfig = mod.default ?? mod;
21396
21591
  const result = validateConfig(userConfig);
21397
21592
  if (result.errors.length === 0) {
package/dist/index.d.cts CHANGED
@@ -272,7 +272,7 @@ interface SignalStrength {
272
272
  defaultOff?: boolean;
273
273
  }
274
274
 
275
- declare const VERSION = "0.10.0";
275
+ declare const VERSION = "0.11.1";
276
276
  /**
277
277
  * Categorical bucket for the AI Maintenance Cost score.
278
278
  *
package/dist/index.d.ts CHANGED
@@ -272,7 +272,7 @@ interface SignalStrength {
272
272
  defaultOff?: boolean;
273
273
  }
274
274
 
275
- declare const VERSION = "0.10.0";
275
+ declare const VERSION = "0.11.1";
276
276
  /**
277
277
  * Categorical bucket for the AI Maintenance Cost score.
278
278
  *
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.1";
34
34
  AI_SECURITY_NUMERIC = {
35
35
  low: 100,
36
36
  medium: 75,
@@ -16780,14 +16780,14 @@ async function runBusinessLogicScore(args, ctx) {
16780
16780
  const maxFiles = typeof maxFilesRaw === "number" && Number.isFinite(maxFilesRaw) && maxFilesRaw > 0 ? Math.min(2e3, Math.floor(maxFilesRaw)) : 500;
16781
16781
  try {
16782
16782
  const { discoverFiles: discoverFiles2 } = await Promise.resolve().then(() => (init_discover(), discover_exports));
16783
- const { readFileSync: readFileSync37 } = await import("fs");
16783
+ const { readFileSync: readFileSync38 } = await import("fs");
16784
16784
  const allFiles = await discoverFiles2(ctx.cwd, ctx.config);
16785
16785
  const limited = allFiles.slice(0, maxFiles);
16786
16786
  const issues = [];
16787
16787
  for (const absPath of limited) {
16788
16788
  let source;
16789
16789
  try {
16790
- source = readFileSync37(absPath, "utf-8");
16790
+ source = readFileSync38(absPath, "utf-8");
16791
16791
  } catch {
16792
16792
  continue;
16793
16793
  }
@@ -17052,13 +17052,186 @@ var init_tools = __esm({
17052
17052
  }
17053
17053
  });
17054
17054
 
17055
+ // src/cli/migrate.ts
17056
+ var migrate_exports = {};
17057
+ __export(migrate_exports, {
17058
+ applyMigration: () => applyMigration,
17059
+ formatMigrate: () => formatMigrate,
17060
+ isAlreadyMigrated: () => isAlreadyMigrated,
17061
+ logger: () => logger,
17062
+ planMigration: () => planMigration,
17063
+ runMigrate: () => runMigrate
17064
+ });
17065
+ import { existsSync as existsSync24, readFileSync as readFileSync36, renameSync as renameSync3, writeFileSync as writeFileSync17 } from "fs";
17066
+ import { join as join25 } from "path";
17067
+ function planMigration(workspaceDir) {
17068
+ const moves = [];
17069
+ const rewrites = [];
17070
+ const gitignoreEdits = [];
17071
+ const oldDir = join25(workspaceDir, ".slop-audit");
17072
+ const newDir = join25(workspaceDir, ".slopbrick");
17073
+ if (existsSync24(oldDir)) {
17074
+ moves.push({ from: oldDir, to: newDir, kind: "dir" });
17075
+ rewrites.push({
17076
+ path: join25(newDir, "inventory.json"),
17077
+ field: "version",
17078
+ from: '"1"',
17079
+ to: '"2"'
17080
+ });
17081
+ rewrites.push({
17082
+ path: join25(newDir, "constitution.json"),
17083
+ field: "version",
17084
+ from: '"1"',
17085
+ to: '"2"'
17086
+ });
17087
+ }
17088
+ const oldCache = join25(workspaceDir, ".slop-audit-cache.json");
17089
+ const newCache = join25(workspaceDir, ".slopbrick-cache.json");
17090
+ if (existsSync24(oldCache)) {
17091
+ moves.push({ from: oldCache, to: newCache, kind: "file" });
17092
+ }
17093
+ for (const ext of ["mjs", "cjs", "js"]) {
17094
+ const oldCfg = join25(workspaceDir, `slop-audit.config.${ext}`);
17095
+ const newCfg = join25(workspaceDir, `slopbrick.config.${ext}`);
17096
+ if (existsSync24(oldCfg)) {
17097
+ moves.push({ from: oldCfg, to: newCfg, kind: "config" });
17098
+ }
17099
+ }
17100
+ const gi = join25(workspaceDir, ".gitignore");
17101
+ if (existsSync24(gi)) {
17102
+ const content = readFileSync36(gi, "utf-8");
17103
+ if (content.includes(".slop-audit/")) {
17104
+ gitignoreEdits.push({
17105
+ path: gi,
17106
+ from: ".slop-audit/",
17107
+ to: ".slopbrick/"
17108
+ });
17109
+ }
17110
+ if (content.includes(".slop-audit-cache.json")) {
17111
+ gitignoreEdits.push({
17112
+ path: gi,
17113
+ from: ".slop-audit-cache.json",
17114
+ to: ".slopbrick-cache.json"
17115
+ });
17116
+ }
17117
+ }
17118
+ return { moves, rewrites, gitignoreEdits };
17119
+ }
17120
+ function isAlreadyMigrated(workspaceDir) {
17121
+ return existsSync24(join25(workspaceDir, ".slopbrick")) && !existsSync24(join25(workspaceDir, ".slop-audit"));
17122
+ }
17123
+ function applyMigration(plan, options = {}) {
17124
+ if (options.dryRun) return;
17125
+ for (const m of plan.moves) {
17126
+ renameSync3(m.from, m.to);
17127
+ }
17128
+ for (const r of plan.rewrites) {
17129
+ if (!existsSync24(r.path)) continue;
17130
+ const content = readFileSync36(r.path, "utf-8");
17131
+ const next = content.replace(`"version": ${r.from}`, `"version": ${r.to}`);
17132
+ writeFileSync17(r.path, next);
17133
+ }
17134
+ for (const g of plan.gitignoreEdits) {
17135
+ const content = readFileSync36(g.path, "utf-8");
17136
+ const next = content.replaceAll(g.from, g.to);
17137
+ writeFileSync17(g.path, next);
17138
+ }
17139
+ }
17140
+ function runMigrate(options) {
17141
+ const { workspace, dryRun = false, force = false } = options;
17142
+ if (!existsSync24(workspace)) {
17143
+ return {
17144
+ ok: false,
17145
+ alreadyMigrated: false,
17146
+ planned: { moves: [], rewrites: [], gitignoreEdits: [] },
17147
+ applied: false,
17148
+ reason: `Workspace ${workspace} does not exist`
17149
+ };
17150
+ }
17151
+ const alreadyMigrated = isAlreadyMigrated(workspace);
17152
+ const newDir = join25(workspace, ".slopbrick");
17153
+ const oldDir = join25(workspace, ".slop-audit");
17154
+ if (existsSync24(newDir) && existsSync24(oldDir) && !force) {
17155
+ return {
17156
+ ok: false,
17157
+ alreadyMigrated: false,
17158
+ planned: { moves: [], rewrites: [], gitignoreEdits: [] },
17159
+ applied: false,
17160
+ reason: `Both .slopbrick/ and .slop-audit/ exist. Use --force to overwrite the .slopbrick/ directory, or manually resolve the conflict.`
17161
+ };
17162
+ }
17163
+ const planned = planMigration(workspace);
17164
+ const nothingToDo = planned.moves.length === 0 && planned.rewrites.length === 0 && planned.gitignoreEdits.length === 0;
17165
+ if (nothingToDo) {
17166
+ return {
17167
+ ok: true,
17168
+ alreadyMigrated,
17169
+ planned,
17170
+ applied: false
17171
+ };
17172
+ }
17173
+ applyMigration(planned, { dryRun });
17174
+ return {
17175
+ ok: true,
17176
+ alreadyMigrated: false,
17177
+ planned,
17178
+ applied: !dryRun
17179
+ };
17180
+ }
17181
+ function formatMigrate(result) {
17182
+ const lines = [];
17183
+ if (result.alreadyMigrated) {
17184
+ lines.push("Already migrated to v2 (no work needed).");
17185
+ return lines.join("\n");
17186
+ }
17187
+ if (!result.ok) {
17188
+ lines.push(`ERROR: ${result.reason ?? "unknown failure"}`);
17189
+ return lines.join("\n");
17190
+ }
17191
+ if (result.planned.moves.length === 0 && result.planned.gitignoreEdits.length === 0) {
17192
+ lines.push("Nothing to migrate \u2014 workspace is already on slopbrick v0.11.0+.");
17193
+ }
17194
+ if (result.planned.moves.length > 0) {
17195
+ lines.push("Moves:");
17196
+ for (const m of result.planned.moves) {
17197
+ lines.push(` ${m.from}`);
17198
+ lines.push(` \u2192 ${m.to} (${m.kind})`);
17199
+ }
17200
+ }
17201
+ if (result.planned.rewrites.length > 0) {
17202
+ lines.push("Schema version bumps:");
17203
+ for (const r of result.planned.rewrites) {
17204
+ lines.push(` ${r.path} ${r.field}: ${r.from} \u2192 ${r.to}`);
17205
+ }
17206
+ }
17207
+ if (result.planned.gitignoreEdits.length > 0) {
17208
+ lines.push(".gitignore edits:");
17209
+ for (const g of result.planned.gitignoreEdits) {
17210
+ lines.push(` ${g.path}: ${g.from} \u2192 ${g.to}`);
17211
+ }
17212
+ }
17213
+ lines.push("");
17214
+ if (result.applied) {
17215
+ lines.push("Migration applied. Run `slopbrick scan` to regenerate inventory at v2.");
17216
+ } else {
17217
+ lines.push("DRY RUN \u2014 no files were changed. Re-run without --dry-run to apply.");
17218
+ }
17219
+ return lines.join("\n");
17220
+ }
17221
+ var init_migrate = __esm({
17222
+ "src/cli/migrate.ts"() {
17223
+ "use strict";
17224
+ init_logger();
17225
+ }
17226
+ });
17227
+
17055
17228
  // src/index.ts
17056
17229
  init_types();
17057
17230
  init_config();
17058
17231
 
17059
17232
  // 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";
17233
+ import { existsSync as existsSync25, writeFileSync as writeFileSync18, readFileSync as readFileSync37, mkdirSync as mkdirSync13 } from "fs";
17234
+ import { resolve as resolve14, join as join26, dirname as dirname15, extname as extname8 } from "path";
17062
17235
  import { performance } from "perf_hooks";
17063
17236
  import { Command } from "commander";
17064
17237
 
@@ -20609,12 +20782,12 @@ async function runCli({ start }) {
20609
20782
  process.exit(0);
20610
20783
  }
20611
20784
  const cwd = resolve14(options.workspace ?? process.cwd());
20612
- const configPath = join25(cwd, "slopbrick.config.mjs");
20785
+ const configPath = join26(cwd, "slopbrick.config.mjs");
20613
20786
  const detected = detectStack(cwd);
20614
20787
  const fallbackConfig = { ...DEFAULT_CONFIG, ...detected };
20615
20788
  const proposed = serializeConfig(fallbackConfig);
20616
- if (existsSync24(configPath) && !cmdOptions.yes) {
20617
- const current = readFileSync36(configPath, "utf8");
20789
+ if (existsSync25(configPath) && !cmdOptions.yes) {
20790
+ const current = readFileSync37(configPath, "utf8");
20618
20791
  logger.error(`A config file already exists at ${configPath}.`);
20619
20792
  logger.error("To overwrite it with defaults, run `slopbrick init --yes`.");
20620
20793
  logger.error("");
@@ -20635,7 +20808,7 @@ async function runCli({ start }) {
20635
20808
  config = buildInitConfig(detected, answers);
20636
20809
  usedWizard = true;
20637
20810
  }
20638
- writeFileSync17(configPath, serializeConfig(config));
20811
+ writeFileSync18(configPath, serializeConfig(config));
20639
20812
  appendGitignore(cwd);
20640
20813
  const refresh = await refreshRegistrySnapshot(cwd);
20641
20814
  if (!refresh.ok) {
@@ -20665,21 +20838,21 @@ async function runCli({ start }) {
20665
20838
  return Boolean(opts[t.flag]);
20666
20839
  });
20667
20840
  for (const target of targetsToWrite) {
20668
- const snippetPath = join25(cwd, resolveTargetPath(target));
20841
+ const snippetPath = join26(cwd, resolveTargetPath(target));
20669
20842
  mkdirSync13(dirname15(snippetPath), { recursive: true });
20670
20843
  const generated = target.generator(builtinRules);
20671
- if (!target.isFolder && existsSync24(snippetPath)) {
20672
- const existing = readFileSync36(snippetPath, "utf8");
20844
+ if (!target.isFolder && existsSync25(snippetPath)) {
20845
+ const existing = readFileSync37(snippetPath, "utf8");
20673
20846
  if (existing.includes("<!-- slopbrick:begin -->")) {
20674
20847
  const updated = existing.replace(
20675
20848
  /<!-- slopbrick:begin -->[\s\S]*?<!-- slopbrick:end -->/,
20676
20849
  "<!-- slopbrick:begin -->\n" + generated + "<!-- slopbrick:end -->"
20677
20850
  );
20678
- writeFileSync17(snippetPath, updated, "utf8");
20851
+ writeFileSync18(snippetPath, updated, "utf8");
20679
20852
  if (!options.quiet) logger.info(`Updated ${snippetPath}`);
20680
20853
  continue;
20681
20854
  }
20682
- writeFileSync17(
20855
+ writeFileSync18(
20683
20856
  snippetPath,
20684
20857
  existing + (existing.endsWith("\n") ? "\n" : "\n\n") + generated,
20685
20858
  "utf8"
@@ -20687,7 +20860,7 @@ async function runCli({ start }) {
20687
20860
  if (!options.quiet) logger.info(`Wrote ${snippetPath}`);
20688
20861
  continue;
20689
20862
  }
20690
- writeFileSync17(snippetPath, generated, "utf8");
20863
+ writeFileSync18(snippetPath, generated, "utf8");
20691
20864
  if (!options.quiet) logger.info(`Wrote ${snippetPath}`);
20692
20865
  }
20693
20866
  if (options.baseline) {
@@ -20757,7 +20930,7 @@ async function runCli({ start }) {
20757
20930
  if (cmdOptions.export) {
20758
20931
  const exportPath = resolve14(cmdOptions.export);
20759
20932
  mkdirSync13(dirname15(exportPath), { recursive: true });
20760
- writeFileSync17(exportPath, JSON.stringify(summary, null, 2), "utf-8");
20933
+ writeFileSync18(exportPath, JSON.stringify(summary, null, 2), "utf-8");
20761
20934
  logger.info(`Wrote flywheel summary to ${exportPath}`);
20762
20935
  process.exit(0);
20763
20936
  }
@@ -20915,16 +21088,16 @@ async function runCli({ start }) {
20915
21088
  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
21089
  try {
20917
21090
  const metadataPath = resolve14(cmdOptions.inputDir, "metadata.json");
20918
- if (!existsSync24(metadataPath)) {
21091
+ if (!existsSync25(metadataPath)) {
20919
21092
  logger.error(`No metadata.json found in ${cmdOptions.inputDir}`);
20920
21093
  process.exit(2);
20921
21094
  }
20922
- const samples = JSON.parse(readFileSync36(metadataPath, "utf8"));
21095
+ const samples = JSON.parse(readFileSync37(metadataPath, "utf8"));
20923
21096
  const config = cmdOptions.config ? await loadConfig(cmdOptions.config) : { ...DEFAULT_CONFIG, framework: cmdOptions.framework };
20924
21097
  const analysis = await analyzeSamples(samples, config);
20925
21098
  const outputPath = resolve14(cmdOptions.output);
20926
21099
  mkdirSync13(dirname15(outputPath), { recursive: true });
20927
- writeFileSync17(outputPath, JSON.stringify(analysis, null, 2), "utf8");
21100
+ writeFileSync18(outputPath, JSON.stringify(analysis, null, 2), "utf8");
20928
21101
  logger.info(`Analyzed ${analysis.summary.total} samples; coverage: ${analysis.summary.coverage}%`);
20929
21102
  logger.info(`Wrote analysis to ${outputPath}`);
20930
21103
  } catch (error) {
@@ -20935,11 +21108,11 @@ async function runCli({ start }) {
20935
21108
  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
21109
  try {
20937
21110
  const metadataPath = resolve14(cmdOptions.inputDir, "metadata.json");
20938
- if (!existsSync24(metadataPath)) {
21111
+ if (!existsSync25(metadataPath)) {
20939
21112
  logger.error(`No metadata.json found in ${cmdOptions.inputDir}`);
20940
21113
  process.exit(2);
20941
21114
  }
20942
- const samples = JSON.parse(readFileSync36(metadataPath, "utf8"));
21115
+ const samples = JSON.parse(readFileSync37(metadataPath, "utf8"));
20943
21116
  const config = cmdOptions.config ? await loadConfig(cmdOptions.config) : { ...DEFAULT_CONFIG, framework: cmdOptions.framework };
20944
21117
  const analysis = await analyzeSamples(samples, config);
20945
21118
  const extraction = extractAndCluster(analysis.samples, {
@@ -20958,7 +21131,7 @@ async function runCli({ start }) {
20958
21131
  fingerprintCount: extraction.total,
20959
21132
  candidates
20960
21133
  };
20961
- writeFileSync17(outputPath, JSON.stringify(payload, null, 2), "utf8");
21134
+ writeFileSync18(outputPath, JSON.stringify(payload, null, 2), "utf8");
20962
21135
  logger.info(`Extracted ${extraction.total} fingerprints across ${analysis.summary.total} samples`);
20963
21136
  logger.info(`Wrote ${candidates.length} candidate rule(s) to ${outputPath}`);
20964
21137
  } catch (error) {
@@ -20977,7 +21150,7 @@ async function runCli({ start }) {
20977
21150
  });
20978
21151
  const outputPath = cmdOptions.output ? resolve14(cwd, cmdOptions.output) : resolve14(cwd, "corpus", "calibration-empirical.md");
20979
21152
  mkdirSync13(dirname15(outputPath), { recursive: true });
20980
- writeFileSync17(outputPath, reportToMarkdown(report), "utf8");
21153
+ writeFileSync18(outputPath, reportToMarkdown(report), "utf8");
20981
21154
  logger.info(
20982
21155
  "Calibrated " + report.rules.length + " rules across " + report.positiveFileCount + " positive + " + report.negativeFileCount + " negative files."
20983
21156
  );
@@ -21273,6 +21446,27 @@ async function runCli({ start }) {
21273
21446
  const exitCode = await runDoctor(process.cwd());
21274
21447
  if (exitCode !== 0) process.exit(exitCode);
21275
21448
  });
21449
+ program.command("migrate").description(
21450
+ "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."
21451
+ ).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(
21452
+ (cmdOptions, command) => {
21453
+ const globals = command.optsWithGlobals();
21454
+ const format = (cmdOptions.format ?? globals.format) === "json" ? "json" : "pretty";
21455
+ const cwd = resolve14(cmdOptions.workspace ?? process.cwd());
21456
+ const { runMigrate: runMigrate2, formatMigrate: formatMigrate2 } = (init_migrate(), __toCommonJS(migrate_exports));
21457
+ const result = runMigrate2({
21458
+ workspace: cwd,
21459
+ dryRun: cmdOptions.dryRun,
21460
+ force: cmdOptions.force
21461
+ });
21462
+ if (format === "json") {
21463
+ logger.info(JSON.stringify(result, null, 2));
21464
+ } else {
21465
+ logger.info(formatMigrate2(result));
21466
+ }
21467
+ process.exit(result.ok ? 0 : 1);
21468
+ }
21469
+ );
21276
21470
  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
21471
  const globals = command.optsWithGlobals();
21278
21472
  const wantJson = Boolean(cmdOptions.json || globals.json);
@@ -21360,7 +21554,7 @@ async function runCli({ start }) {
21360
21554
  });
21361
21555
  program.command("validate-config [path]").description("Statically validate a slopbrick.config.mjs without scanning").action(async (configPath) => {
21362
21556
  const path = configPath ? resolve14(configPath) : resolve14(process.cwd(), "slopbrick.config.mjs");
21363
- if (!existsSync24(path)) {
21557
+ if (!existsSync25(path)) {
21364
21558
  logger.error(`Error: config file not found: ${path}`);
21365
21559
  process.exit(2);
21366
21560
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slopbrick",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Repository Coherence Scanner for AI-coded codebases — detect cross-file pattern drift, AI-induced design-token violations, and security failures that AI agents introduce disproportionately. Part of the usebrick.dev platform. Ships 8 MCP tools so Claude Code / Cursor / Copilot follow your patterns instead of reinventing them.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,6 +17,16 @@
17
17
  },
18
18
  "./package.json": "./package.json"
19
19
  },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "bin",
26
+ "README.md",
27
+ "LICENSE",
28
+ "!dist/**/*.map"
29
+ ],
20
30
  "scripts": {
21
31
  "dev": "tsx src/index.ts",
22
32
  "generate:rules": "tsx scripts/generate-rule-registry.ts",
@@ -34,7 +44,7 @@
34
44
  },
35
45
  "dependencies": {
36
46
  "@swc/core": "^1.10.0",
37
- "@usebrick/core": "file:../core",
47
+ "@usebrick/core": "workspace:*",
38
48
  "chalk": "^4.1.2",
39
49
  "commander": "^12.1.0",
40
50
  "globby": "^14.0.0",
@@ -53,13 +63,6 @@
53
63
  "engines": {
54
64
  "node": ">=18"
55
65
  },
56
- "files": [
57
- "dist",
58
- "bin",
59
- "README.md",
60
- "LICENSE",
61
- "!dist/**/*.map"
62
- ],
63
66
  "keywords": [
64
67
  "ai-coding-agents",
65
68
  "claude-code",
@@ -88,10 +91,10 @@
88
91
  "license": "MIT",
89
92
  "repository": {
90
93
  "type": "git",
91
- "url": "git+https://github.com/usebrick/slopbrick.git"
94
+ "url": "git+https://github.com/usebrick/platform.git"
92
95
  },
93
96
  "bugs": {
94
- "url": "https://github.com/usebrick/slopbrick/issues"
97
+ "url": "https://github.com/usebrick/platform/issues"
95
98
  },
96
- "homepage": "https://github.com/usebrick/slopbrick#readme"
99
+ "homepage": "https://github.com/usebrick/platform#readme"
97
100
  }