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 +250 -55
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +218 -24
- package/package.json +15 -12
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.
|
|
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:
|
|
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 =
|
|
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
|
|
17093
|
-
var
|
|
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,
|
|
20638
|
-
const configPath = (0,
|
|
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,
|
|
20643
|
-
const current = (0,
|
|
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,
|
|
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,
|
|
20695
|
-
(0,
|
|
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,
|
|
20698
|
-
const existing = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
20785
|
-
(0,
|
|
20786
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
20944
|
-
if (!(0,
|
|
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,
|
|
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,
|
|
20952
|
-
(0,
|
|
20953
|
-
(0,
|
|
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,
|
|
20964
|
-
if (!(0,
|
|
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,
|
|
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,
|
|
20979
|
-
(0,
|
|
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,
|
|
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,
|
|
21005
|
-
(0,
|
|
21006
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
21389
|
-
if (!(0,
|
|
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,
|
|
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
package/dist/index.d.ts
CHANGED
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.
|
|
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:
|
|
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 =
|
|
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
|
|
17061
|
-
import { resolve as resolve14, join as
|
|
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 =
|
|
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 (
|
|
20617
|
-
const current =
|
|
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
|
-
|
|
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 =
|
|
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 &&
|
|
20672
|
-
const existing =
|
|
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
|
-
|
|
20851
|
+
writeFileSync18(snippetPath, updated, "utf8");
|
|
20679
20852
|
if (!options.quiet) logger.info(`Updated ${snippetPath}`);
|
|
20680
20853
|
continue;
|
|
20681
20854
|
}
|
|
20682
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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(
|
|
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
|
-
|
|
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 (!
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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.
|
|
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": "
|
|
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/
|
|
94
|
+
"url": "git+https://github.com/usebrick/platform.git"
|
|
92
95
|
},
|
|
93
96
|
"bugs": {
|
|
94
|
-
"url": "https://github.com/usebrick/
|
|
97
|
+
"url": "https://github.com/usebrick/platform/issues"
|
|
95
98
|
},
|
|
96
|
-
"homepage": "https://github.com/usebrick/
|
|
99
|
+
"homepage": "https://github.com/usebrick/platform#readme"
|
|
97
100
|
}
|