slopbrick 0.17.0 → 0.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/index.cjs +70 -18
- package/dist/index.js +70 -18
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ on every scan — your repository, encoded for the next agent.
|
|
|
46
46
|
lib, modal system, API client) once. The agent and the linter
|
|
47
47
|
enforce it together.
|
|
48
48
|
|
|
49
|
-
**Status:** v0.
|
|
49
|
+
**Status:** v0.17.0 (current). See the [CHANGELOG](./CHANGELOG.md) for
|
|
50
50
|
the full release notes.
|
|
51
51
|
|
|
52
52
|
---
|
|
@@ -76,9 +76,9 @@ For every other config question, see [`EXAMPLES.md`](./EXAMPLES.md).
|
|
|
76
76
|
|
|
77
77
|
---
|
|
78
78
|
|
|
79
|
-
## The headlines (4-score model, v0.
|
|
79
|
+
## The headlines (4-score model, v0.17.0+)
|
|
80
80
|
|
|
81
|
-
> **v0.15.0 introduced the 4-score model; v0.16.0
|
|
81
|
+
> **v0.15.0 introduced the 4-score model; v0.16.0 + v0.17.0 completed it.**
|
|
82
82
|
> The single `Slop Index` is replaced by **4 independent scores**
|
|
83
83
|
> (all 0-100, **higher = better**). The legacy `slopIndex` field
|
|
84
84
|
> is kept as optional on `ProjectReport` for backward compat with
|
|
@@ -153,7 +153,7 @@ Next step:
|
|
|
153
153
|
| Understand the Slop Index vs Coherence | [`docs/scoring-explained.md`](./docs/scoring-explained.md) |
|
|
154
154
|
| Connect Claude Code / Cursor / Copilot | [`docs/MCP.md`](./docs/MCP.md) |
|
|
155
155
|
| See the 4 `.slopbrick/` artifacts (structure, inventory, ...) | [`docs/repository-structure.md`](./docs/repository-structure.md) |
|
|
156
|
-
| See the
|
|
156
|
+
| See the 95 rules (per-rule descriptions + citations) | [`docs/rule-catalog.md`](./docs/rule-catalog.md) |
|
|
157
157
|
| See how the engine works (parser → facts → rules) | [`docs/architecture.md`](./docs/architecture.md) |
|
|
158
158
|
| See which frameworks are supported | [`docs/framework-parity-matrix.md`](./docs/framework-parity-matrix.md) |
|
|
159
159
|
| See what's changed in each release | [`CHANGELOG.md`](./CHANGELOG.md) |
|
package/dist/index.cjs
CHANGED
|
@@ -44844,6 +44844,14 @@ var init_config = __esm({
|
|
|
44844
44844
|
});
|
|
44845
44845
|
|
|
44846
44846
|
// src/cli/render.ts
|
|
44847
|
+
function redactSecrets(text) {
|
|
44848
|
+
if (!text) return text;
|
|
44849
|
+
let out = text;
|
|
44850
|
+
for (const pat of SECRET_PATTERNS) {
|
|
44851
|
+
out = out.replace(pat, REDACTED);
|
|
44852
|
+
}
|
|
44853
|
+
return out;
|
|
44854
|
+
}
|
|
44847
44855
|
function colorForSlop(slopIndex) {
|
|
44848
44856
|
if (slopIndex >= SLOP_BADGE_RED_THRESHOLD) return "red";
|
|
44849
44857
|
if (slopIndex >= SLOP_BADGE_ORANGE_THRESHOLD) return "orange";
|
|
@@ -44882,22 +44890,64 @@ function renderTrend(runs, count) {
|
|
|
44882
44890
|
const sparkline2 = formatSparkline(values);
|
|
44883
44891
|
return `Slop trend (last ${latest.length} runs): ${values.map((v) => Math.round(v)).join(" ")} ${sparkline2}`;
|
|
44884
44892
|
}
|
|
44885
|
-
function renderProgress(completed, total) {
|
|
44886
|
-
const
|
|
44887
|
-
|
|
44893
|
+
function renderProgress(completed, total, startMs = Date.now()) {
|
|
44894
|
+
const safeTotal = total > 0 ? total : 1;
|
|
44895
|
+
const ratio = Math.min(1, completed / safeTotal);
|
|
44896
|
+
const pct = (ratio * 100).toFixed(1);
|
|
44897
|
+
const barWidth = 20;
|
|
44898
|
+
const filled = Math.round(barWidth * ratio);
|
|
44899
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(barWidth - filled);
|
|
44900
|
+
const elapsedSec = ((Date.now() - startMs) / 1e3).toFixed(1);
|
|
44901
|
+
let eta = "";
|
|
44902
|
+
if (completed > 0 && ratio < 1) {
|
|
44903
|
+
const totalMs = Date.now() - startMs;
|
|
44904
|
+
const etaMs = totalMs / completed * (total - completed);
|
|
44905
|
+
eta = ` | ETA ${(etaMs / 1e3).toFixed(0)}s`;
|
|
44906
|
+
}
|
|
44907
|
+
const line = `\r[${bar}] ${completed}/${total} files (${pct}%) | ${elapsedSec}s${eta} `;
|
|
44908
|
+
if (process.stdout.isTTY) {
|
|
44909
|
+
process.stdout.write(line);
|
|
44910
|
+
} else {
|
|
44911
|
+
const lastPct = renderProgress._lastPct ?? -1;
|
|
44912
|
+
if (Math.abs(parseFloat(pct) - lastPct) >= 2) {
|
|
44913
|
+
process.stdout.write(`[${bar}] ${completed}/${total} files (${pct}%) | ${elapsedSec}s${eta}
|
|
44914
|
+
`);
|
|
44915
|
+
renderProgress._lastPct = parseFloat(pct);
|
|
44916
|
+
}
|
|
44917
|
+
}
|
|
44888
44918
|
}
|
|
44889
44919
|
function clearProgress() {
|
|
44890
|
-
process.stdout.
|
|
44920
|
+
if (process.stdout.isTTY) {
|
|
44921
|
+
process.stdout.write(`\r${" ".repeat(80)}\r`);
|
|
44922
|
+
}
|
|
44891
44923
|
}
|
|
44892
|
-
var SLOP_BADGE_RED_THRESHOLD, SLOP_BADGE_ORANGE_THRESHOLD, SLOP_BADGE_YELLOW_THRESHOLD, WATCH_DEBOUNCE_MS
|
|
44924
|
+
var SECRET_PATTERNS, REDACTED, SLOP_BADGE_RED_THRESHOLD, SLOP_BADGE_ORANGE_THRESHOLD, SLOP_BADGE_YELLOW_THRESHOLD, WATCH_DEBOUNCE_MS;
|
|
44893
44925
|
var init_render = __esm({
|
|
44894
44926
|
"src/cli/render.ts"() {
|
|
44895
44927
|
"use strict";
|
|
44928
|
+
SECRET_PATTERNS = [
|
|
44929
|
+
// AWS
|
|
44930
|
+
/\b(AKIA[0-9A-Z]{16})\b/g,
|
|
44931
|
+
// GitHub PAT
|
|
44932
|
+
/\b(ghp_[A-Za-z0-9]{36,})\b/g,
|
|
44933
|
+
/\b(github_pat_[A-Za-z0-9_]{82})\b/g,
|
|
44934
|
+
// Slack
|
|
44935
|
+
/\b(xox[abpr]-[A-Za-z0-9-]{10,})\b/g,
|
|
44936
|
+
// Stripe
|
|
44937
|
+
/\b(sk_live_[A-Za-z0-9]{24,})\b/g,
|
|
44938
|
+
/\b(pk_live_[A-Za-z0-9]{24,})\b/g,
|
|
44939
|
+
// Google API key
|
|
44940
|
+
/\b(AIza[0-9A-Za-z_-]{35})\b/g,
|
|
44941
|
+
// Generic JWT
|
|
44942
|
+
/\b(eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g,
|
|
44943
|
+
// PEM private key
|
|
44944
|
+
/-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/g
|
|
44945
|
+
];
|
|
44946
|
+
REDACTED = "[REDACTED]";
|
|
44896
44947
|
SLOP_BADGE_RED_THRESHOLD = 76;
|
|
44897
44948
|
SLOP_BADGE_ORANGE_THRESHOLD = 51;
|
|
44898
44949
|
SLOP_BADGE_YELLOW_THRESHOLD = 26;
|
|
44899
44950
|
WATCH_DEBOUNCE_MS = 100;
|
|
44900
|
-
SPINNER_FRAMES = ["|", "/", "-", "\\"];
|
|
44901
44951
|
}
|
|
44902
44952
|
});
|
|
44903
44953
|
|
|
@@ -45251,10 +45301,10 @@ function formatIssue(issue) {
|
|
|
45251
45301
|
const badge = severityBadge(issue.severity);
|
|
45252
45302
|
const location = issue.filePath ? `${issue.filePath}:${issue.line}:${issue.column}` : `${issue.line}:${issue.column}`;
|
|
45253
45303
|
const header = `[${badge}] ${issue.ruleId} \xB7 ${location}`;
|
|
45254
|
-
const body = ` ${import_chalk.default.dim(issue.message)}`;
|
|
45304
|
+
const body = ` ${import_chalk.default.dim(redactSecrets(issue.message))}`;
|
|
45255
45305
|
const lines = [header, body];
|
|
45256
45306
|
if (issue.advice) {
|
|
45257
|
-
lines.push(` ${import_chalk.default.cyan("\u2192")} ${issue.advice}`);
|
|
45307
|
+
lines.push(` ${import_chalk.default.cyan("\u2192")} ${redactSecrets(issue.advice)}`);
|
|
45258
45308
|
}
|
|
45259
45309
|
return lines.join("\n");
|
|
45260
45310
|
}
|
|
@@ -45316,26 +45366,26 @@ function formatBriefReport(report) {
|
|
|
45316
45366
|
lines.push(formatVerdict(report));
|
|
45317
45367
|
lines.push("");
|
|
45318
45368
|
const scoreLines = [
|
|
45319
|
-
{
|
|
45320
|
-
{
|
|
45321
|
-
{
|
|
45322
|
-
{
|
|
45369
|
+
{ label: "AI Quality", field: "aiQuality", value: report.aiQuality },
|
|
45370
|
+
{ label: "Engineering Hygiene", field: "engineeringHygiene", value: report.engineeringHygiene },
|
|
45371
|
+
{ label: "Security", field: "security", value: report.security },
|
|
45372
|
+
{ label: "Repository Health", field: "repositoryHealth", value: report.repositoryHealth }
|
|
45323
45373
|
];
|
|
45324
45374
|
const deltaSuffix = formatDeltaSuffix(report);
|
|
45325
|
-
scoreLines.forEach(({
|
|
45375
|
+
scoreLines.forEach(({ label, field, value }, idx) => {
|
|
45326
45376
|
const band = scoreBand(value);
|
|
45327
|
-
const
|
|
45377
|
+
const paddedLabel = label.padEnd(20, " ");
|
|
45328
45378
|
const valueStr = value.toFixed(0).padStart(3, " ");
|
|
45329
45379
|
const delta = idx === 0 ? deltaSuffix : "";
|
|
45330
45380
|
lines.push(
|
|
45331
|
-
` ${
|
|
45381
|
+
` ${paddedLabel} ${band.color(valueStr)} ${import_chalk.default.dim(band.label)} ${import_chalk.default.dim.italic(`(${field})`)}${delta}`
|
|
45332
45382
|
);
|
|
45333
45383
|
});
|
|
45334
45384
|
const passed = report.aiQuality >= 70;
|
|
45335
45385
|
lines.push("");
|
|
45336
45386
|
lines.push(
|
|
45337
45387
|
import_chalk.default.dim(
|
|
45338
|
-
` CI gate:
|
|
45388
|
+
` CI gate: AI Quality >= 70 -> ${passed ? import_chalk.default.green("pass") : import_chalk.default.red("fail")}`
|
|
45339
45389
|
)
|
|
45340
45390
|
);
|
|
45341
45391
|
const suppressed = report.defaultOffSuppressedCount ?? 0;
|
|
@@ -45360,6 +45410,7 @@ var init_pretty = __esm({
|
|
|
45360
45410
|
"src/report/pretty.ts"() {
|
|
45361
45411
|
"use strict";
|
|
45362
45412
|
import_chalk = __toESM(require("chalk"), 1);
|
|
45413
|
+
init_render();
|
|
45363
45414
|
CATEGORY_GLOSSARY = {
|
|
45364
45415
|
visual: { short: "visual style", long: "colors, spacing, font sizes, layout" },
|
|
45365
45416
|
typo: { short: "typography", long: "font weights, line heights, font choices" },
|
|
@@ -52404,7 +52455,7 @@ async function runScan(options, explicitPaths) {
|
|
|
52404
52455
|
}
|
|
52405
52456
|
if (defaultOffApplied > 0 && !options.quiet && !machineReadableStdout) {
|
|
52406
52457
|
console.error(
|
|
52407
|
-
`[
|
|
52458
|
+
`[v${VERSION}] auto-suppressed ${defaultOffApplied} INVERTED/NOISY issue(s) from ${defaultOff.size} default-off rule(s). See the main output for the trust-signal summary. Re-enable per-rule via \`rules: { 'rule/id': 'medium' }\` in slopbrick.config.mjs.`
|
|
52408
52459
|
);
|
|
52409
52460
|
}
|
|
52410
52461
|
allIssues.sort((a, b) => SEVERITY_WEIGHTS[b.severity] - SEVERITY_WEIGHTS[a.severity]);
|
|
@@ -52463,6 +52514,7 @@ var init_scan = __esm({
|
|
|
52463
52514
|
init_signal_strength2();
|
|
52464
52515
|
init_tokens();
|
|
52465
52516
|
init_finalizeReport();
|
|
52517
|
+
init_types();
|
|
52466
52518
|
init_baseline_cache();
|
|
52467
52519
|
init_printFixSummary();
|
|
52468
52520
|
init_renderOutput();
|
|
@@ -57010,7 +57062,7 @@ process.on("uncaughtException", (err) => {
|
|
|
57010
57062
|
});
|
|
57011
57063
|
async function runCli({ start }) {
|
|
57012
57064
|
try {
|
|
57013
|
-
const program = new import_commander2.Command().name("slopbrick").description("Repository Coherence Scanner \u2014 surface AI-induced pattern drift, secret leaks, and design-token violations").version(VERSION).option("--framework <name>", "framework multiplier to apply").option("--include <glob>", "include pattern (repeatable)", collectGlob, []).option("--exclude <glob>", "exclude pattern (repeatable)", collectGlob, []).option("--ai-only", "only report AI-specific issues").option("--human-only", "only report human-facing issues").option("--ignore-wcag22", "ignore WCAG 2.2 related issues").option("--format <pretty|json|sarif|html>", "output format", "pretty").option("--threads <n>", "number of worker threads", parseThreads).option("--since <ref>", "only scan files changed since git ref").option("--diff <ref>", "alias for --since <ref>; also adds PR Slop Score to the report").option("--workspace <path>", "workspace/project path", process.cwd()).option("--tighten", "tighten baseline allowances").option("--fix", "apply auto-fixes").option("--dry-run", "with --fix: print what would change without writing").option("--show-fixes-diff", "print unified diff of proposed auto-fixes").option("--doctor", "run diagnostics").option("--watch", "watch files and re-run").option("--suggest", "print remediation advice").option("--why-failing", "print the top 5 rules dragging the score down").option("--brief", "terse output (verdict + headline + threshold + delta only)").option("--heatmap", "print migration ROI heatmap").option("--quiet", "suppress non-error output").option("--verbose", "enable debug logging (file paths, timings, rule-fire counts)").option("--strict", "exit 2 if any high-severity issue remains").option("--no-increase", "exit 2 if slop index increased since last run").option("--baseline", "save a baseline after this scan").option("--trend [n]", "print a sparkline of the last n runs", parseTrend).option("--json [path]", "write JSON report to path or stdout").option("--html [path]", "write HTML report to path or stdout").option("--staged", "scan only changed files (staged and unstaged)").option("--changed", "scan working-tree changes (staged + unstaged + untracked)").option("--incremental", "skip unchanged files using the persisted hash cache").option("--cache-path <path>", "path to the incremental-scan cache (default: .slopbrick-cache.json)").option("--tokens <path>", "merge tokens.json layout values into the arbitrary-value allowlist").option("--cache", "cache parsed AST results locally");
|
|
57065
|
+
const program = new import_commander2.Command().name("slopbrick").description("Repository Coherence Scanner \u2014 surface AI-induced pattern drift, secret leaks, and design-token violations").version(VERSION).option("--framework <name>", "framework multiplier to apply").option("--include <glob>", "include pattern (repeatable)", collectGlob, []).option("--exclude <glob>", "exclude pattern (repeatable)", collectGlob, []).option("--ai-only", "only report AI-specific issues").option("--human-only", "only report human-facing issues").option("--ignore-wcag22", "ignore WCAG 2.2 related issues").option("--format <pretty|json|sarif|html>", "output format", "pretty").option("--threads <n>", "number of worker threads", parseThreads).option("--since <ref>", "only scan files changed since git ref").option("--diff <ref>", "alias for --since <ref>; also adds PR Slop Score to the report").option("--workspace <path>", "workspace/project path", process.cwd()).option("--tighten", "tighten baseline allowances").option("--fix", "apply auto-fixes").option("--dry-run", "with --fix: print what would change without writing").option("--show-fixes-diff", "print unified diff of proposed auto-fixes").option("--doctor", "run diagnostics").option("--watch", "watch files and re-run").option("--suggest", "print remediation advice").option("--why-failing", "print the top 5 rules dragging the score down").option("--brief", "terse output (verdict + headline + threshold + delta only)").option("--heatmap", "print migration ROI heatmap").option("--quiet", "suppress non-error output").option("--verbose", "enable debug logging (file paths, timings, rule-fire counts)").option("--strict", "exit 2 if any high-severity issue remains").option("--no-increase", "exit 2 if slop index increased since last run").option("--baseline", "save a baseline after this scan").option("--trend [n]", "print a sparkline of the last n runs", parseTrend).option("--json [path]", "write JSON report to path or stdout").option("--html [path]", "write HTML report to path or stdout").option("--staged", "scan only changed files (staged and unstaged)").option("--changed", "scan working-tree changes (staged + unstaged + untracked)").option("--incremental", "skip unchanged files using the persisted hash cache").option("--cache-path <path>", "path to the incremental-scan cache (default: .slopbrick-cache.json)").option("--tokens <path>", "merge tokens.json layout values into the arbitrary-value allowlist").option("--cache", "cache parsed AST results locally").option("--no-color", "suppress ANSI color codes in output").option("--security-only", "run only the security/* rules").option("--full", "show the complete report (all issues, all categories)");
|
|
57014
57066
|
program.command("init").description("create a slopbrick config file").option("--yes", "overwrite existing config").option("--all", "write snippets for every supported agent").option("--matrix", "print the agent x file matrix and exit").option("--cursor", "also generate .cursor/rules/slopbrick.mdc for Cursor AI").option("--cursorrules", "legacy Cursor .cursorrules format").option("--agents-md", "also generate AGENTS.md for Codex / opencode / Pi / Cline").option("--claude-md", "also generate CLAUDE.md for Claude Code (takes precedence over AGENTS.md)").option("--aider", "also generate CONVENTIONS.md for Aider").option("--windsurf", "also generate .windsurfrules for Windsurf").option("--cline", "also generate .clinerules/AGENTS.md for Cline").option("--gemini", "also generate .gemini/GEMINI.md for Gemini CLI").option("--copilot", "also generate .github/copilot-instructions.md for GitHub Copilot").action(async (cmdOptions, command) => {
|
|
57015
57067
|
const options = command.optsWithGlobals();
|
|
57016
57068
|
if (cmdOptions.matrix) {
|
package/dist/index.js
CHANGED
|
@@ -44836,6 +44836,14 @@ var init_config = __esm({
|
|
|
44836
44836
|
});
|
|
44837
44837
|
|
|
44838
44838
|
// src/cli/render.ts
|
|
44839
|
+
function redactSecrets(text) {
|
|
44840
|
+
if (!text) return text;
|
|
44841
|
+
let out = text;
|
|
44842
|
+
for (const pat of SECRET_PATTERNS) {
|
|
44843
|
+
out = out.replace(pat, REDACTED);
|
|
44844
|
+
}
|
|
44845
|
+
return out;
|
|
44846
|
+
}
|
|
44839
44847
|
function colorForSlop(slopIndex) {
|
|
44840
44848
|
if (slopIndex >= SLOP_BADGE_RED_THRESHOLD) return "red";
|
|
44841
44849
|
if (slopIndex >= SLOP_BADGE_ORANGE_THRESHOLD) return "orange";
|
|
@@ -44874,22 +44882,64 @@ function renderTrend(runs, count) {
|
|
|
44874
44882
|
const sparkline2 = formatSparkline(values);
|
|
44875
44883
|
return `Slop trend (last ${latest.length} runs): ${values.map((v) => Math.round(v)).join(" ")} ${sparkline2}`;
|
|
44876
44884
|
}
|
|
44877
|
-
function renderProgress(completed, total) {
|
|
44878
|
-
const
|
|
44879
|
-
|
|
44885
|
+
function renderProgress(completed, total, startMs = Date.now()) {
|
|
44886
|
+
const safeTotal = total > 0 ? total : 1;
|
|
44887
|
+
const ratio = Math.min(1, completed / safeTotal);
|
|
44888
|
+
const pct = (ratio * 100).toFixed(1);
|
|
44889
|
+
const barWidth = 20;
|
|
44890
|
+
const filled = Math.round(barWidth * ratio);
|
|
44891
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(barWidth - filled);
|
|
44892
|
+
const elapsedSec = ((Date.now() - startMs) / 1e3).toFixed(1);
|
|
44893
|
+
let eta = "";
|
|
44894
|
+
if (completed > 0 && ratio < 1) {
|
|
44895
|
+
const totalMs = Date.now() - startMs;
|
|
44896
|
+
const etaMs = totalMs / completed * (total - completed);
|
|
44897
|
+
eta = ` | ETA ${(etaMs / 1e3).toFixed(0)}s`;
|
|
44898
|
+
}
|
|
44899
|
+
const line = `\r[${bar}] ${completed}/${total} files (${pct}%) | ${elapsedSec}s${eta} `;
|
|
44900
|
+
if (process.stdout.isTTY) {
|
|
44901
|
+
process.stdout.write(line);
|
|
44902
|
+
} else {
|
|
44903
|
+
const lastPct = renderProgress._lastPct ?? -1;
|
|
44904
|
+
if (Math.abs(parseFloat(pct) - lastPct) >= 2) {
|
|
44905
|
+
process.stdout.write(`[${bar}] ${completed}/${total} files (${pct}%) | ${elapsedSec}s${eta}
|
|
44906
|
+
`);
|
|
44907
|
+
renderProgress._lastPct = parseFloat(pct);
|
|
44908
|
+
}
|
|
44909
|
+
}
|
|
44880
44910
|
}
|
|
44881
44911
|
function clearProgress() {
|
|
44882
|
-
process.stdout.
|
|
44912
|
+
if (process.stdout.isTTY) {
|
|
44913
|
+
process.stdout.write(`\r${" ".repeat(80)}\r`);
|
|
44914
|
+
}
|
|
44883
44915
|
}
|
|
44884
|
-
var SLOP_BADGE_RED_THRESHOLD, SLOP_BADGE_ORANGE_THRESHOLD, SLOP_BADGE_YELLOW_THRESHOLD, WATCH_DEBOUNCE_MS
|
|
44916
|
+
var SECRET_PATTERNS, REDACTED, SLOP_BADGE_RED_THRESHOLD, SLOP_BADGE_ORANGE_THRESHOLD, SLOP_BADGE_YELLOW_THRESHOLD, WATCH_DEBOUNCE_MS;
|
|
44885
44917
|
var init_render = __esm({
|
|
44886
44918
|
"src/cli/render.ts"() {
|
|
44887
44919
|
"use strict";
|
|
44920
|
+
SECRET_PATTERNS = [
|
|
44921
|
+
// AWS
|
|
44922
|
+
/\b(AKIA[0-9A-Z]{16})\b/g,
|
|
44923
|
+
// GitHub PAT
|
|
44924
|
+
/\b(ghp_[A-Za-z0-9]{36,})\b/g,
|
|
44925
|
+
/\b(github_pat_[A-Za-z0-9_]{82})\b/g,
|
|
44926
|
+
// Slack
|
|
44927
|
+
/\b(xox[abpr]-[A-Za-z0-9-]{10,})\b/g,
|
|
44928
|
+
// Stripe
|
|
44929
|
+
/\b(sk_live_[A-Za-z0-9]{24,})\b/g,
|
|
44930
|
+
/\b(pk_live_[A-Za-z0-9]{24,})\b/g,
|
|
44931
|
+
// Google API key
|
|
44932
|
+
/\b(AIza[0-9A-Za-z_-]{35})\b/g,
|
|
44933
|
+
// Generic JWT
|
|
44934
|
+
/\b(eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g,
|
|
44935
|
+
// PEM private key
|
|
44936
|
+
/-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/g
|
|
44937
|
+
];
|
|
44938
|
+
REDACTED = "[REDACTED]";
|
|
44888
44939
|
SLOP_BADGE_RED_THRESHOLD = 76;
|
|
44889
44940
|
SLOP_BADGE_ORANGE_THRESHOLD = 51;
|
|
44890
44941
|
SLOP_BADGE_YELLOW_THRESHOLD = 26;
|
|
44891
44942
|
WATCH_DEBOUNCE_MS = 100;
|
|
44892
|
-
SPINNER_FRAMES = ["|", "/", "-", "\\"];
|
|
44893
44943
|
}
|
|
44894
44944
|
});
|
|
44895
44945
|
|
|
@@ -45244,10 +45294,10 @@ function formatIssue(issue) {
|
|
|
45244
45294
|
const badge = severityBadge(issue.severity);
|
|
45245
45295
|
const location = issue.filePath ? `${issue.filePath}:${issue.line}:${issue.column}` : `${issue.line}:${issue.column}`;
|
|
45246
45296
|
const header = `[${badge}] ${issue.ruleId} \xB7 ${location}`;
|
|
45247
|
-
const body = ` ${chalk.dim(issue.message)}`;
|
|
45297
|
+
const body = ` ${chalk.dim(redactSecrets(issue.message))}`;
|
|
45248
45298
|
const lines = [header, body];
|
|
45249
45299
|
if (issue.advice) {
|
|
45250
|
-
lines.push(` ${chalk.cyan("\u2192")} ${issue.advice}`);
|
|
45300
|
+
lines.push(` ${chalk.cyan("\u2192")} ${redactSecrets(issue.advice)}`);
|
|
45251
45301
|
}
|
|
45252
45302
|
return lines.join("\n");
|
|
45253
45303
|
}
|
|
@@ -45309,26 +45359,26 @@ function formatBriefReport(report) {
|
|
|
45309
45359
|
lines.push(formatVerdict(report));
|
|
45310
45360
|
lines.push("");
|
|
45311
45361
|
const scoreLines = [
|
|
45312
|
-
{
|
|
45313
|
-
{
|
|
45314
|
-
{
|
|
45315
|
-
{
|
|
45362
|
+
{ label: "AI Quality", field: "aiQuality", value: report.aiQuality },
|
|
45363
|
+
{ label: "Engineering Hygiene", field: "engineeringHygiene", value: report.engineeringHygiene },
|
|
45364
|
+
{ label: "Security", field: "security", value: report.security },
|
|
45365
|
+
{ label: "Repository Health", field: "repositoryHealth", value: report.repositoryHealth }
|
|
45316
45366
|
];
|
|
45317
45367
|
const deltaSuffix = formatDeltaSuffix(report);
|
|
45318
|
-
scoreLines.forEach(({
|
|
45368
|
+
scoreLines.forEach(({ label, field, value }, idx) => {
|
|
45319
45369
|
const band = scoreBand(value);
|
|
45320
|
-
const
|
|
45370
|
+
const paddedLabel = label.padEnd(20, " ");
|
|
45321
45371
|
const valueStr = value.toFixed(0).padStart(3, " ");
|
|
45322
45372
|
const delta = idx === 0 ? deltaSuffix : "";
|
|
45323
45373
|
lines.push(
|
|
45324
|
-
` ${
|
|
45374
|
+
` ${paddedLabel} ${band.color(valueStr)} ${chalk.dim(band.label)} ${chalk.dim.italic(`(${field})`)}${delta}`
|
|
45325
45375
|
);
|
|
45326
45376
|
});
|
|
45327
45377
|
const passed = report.aiQuality >= 70;
|
|
45328
45378
|
lines.push("");
|
|
45329
45379
|
lines.push(
|
|
45330
45380
|
chalk.dim(
|
|
45331
|
-
` CI gate:
|
|
45381
|
+
` CI gate: AI Quality >= 70 -> ${passed ? chalk.green("pass") : chalk.red("fail")}`
|
|
45332
45382
|
)
|
|
45333
45383
|
);
|
|
45334
45384
|
const suppressed = report.defaultOffSuppressedCount ?? 0;
|
|
@@ -45352,6 +45402,7 @@ var CATEGORY_GLOSSARY;
|
|
|
45352
45402
|
var init_pretty = __esm({
|
|
45353
45403
|
"src/report/pretty.ts"() {
|
|
45354
45404
|
"use strict";
|
|
45405
|
+
init_render();
|
|
45355
45406
|
CATEGORY_GLOSSARY = {
|
|
45356
45407
|
visual: { short: "visual style", long: "colors, spacing, font sizes, layout" },
|
|
45357
45408
|
typo: { short: "typography", long: "font weights, line heights, font choices" },
|
|
@@ -52401,7 +52452,7 @@ async function runScan(options, explicitPaths) {
|
|
|
52401
52452
|
}
|
|
52402
52453
|
if (defaultOffApplied > 0 && !options.quiet && !machineReadableStdout) {
|
|
52403
52454
|
console.error(
|
|
52404
|
-
`[
|
|
52455
|
+
`[v${VERSION}] auto-suppressed ${defaultOffApplied} INVERTED/NOISY issue(s) from ${defaultOff.size} default-off rule(s). See the main output for the trust-signal summary. Re-enable per-rule via \`rules: { 'rule/id': 'medium' }\` in slopbrick.config.mjs.`
|
|
52405
52456
|
);
|
|
52406
52457
|
}
|
|
52407
52458
|
allIssues.sort((a, b) => SEVERITY_WEIGHTS[b.severity] - SEVERITY_WEIGHTS[a.severity]);
|
|
@@ -52457,6 +52508,7 @@ var init_scan = __esm({
|
|
|
52457
52508
|
init_signal_strength2();
|
|
52458
52509
|
init_tokens();
|
|
52459
52510
|
init_finalizeReport();
|
|
52511
|
+
init_types();
|
|
52460
52512
|
init_baseline_cache();
|
|
52461
52513
|
init_printFixSummary();
|
|
52462
52514
|
init_renderOutput();
|
|
@@ -56929,7 +56981,7 @@ process.on("uncaughtException", (err) => {
|
|
|
56929
56981
|
});
|
|
56930
56982
|
async function runCli({ start }) {
|
|
56931
56983
|
try {
|
|
56932
|
-
const program = new Command().name("slopbrick").description("Repository Coherence Scanner \u2014 surface AI-induced pattern drift, secret leaks, and design-token violations").version(VERSION).option("--framework <name>", "framework multiplier to apply").option("--include <glob>", "include pattern (repeatable)", collectGlob, []).option("--exclude <glob>", "exclude pattern (repeatable)", collectGlob, []).option("--ai-only", "only report AI-specific issues").option("--human-only", "only report human-facing issues").option("--ignore-wcag22", "ignore WCAG 2.2 related issues").option("--format <pretty|json|sarif|html>", "output format", "pretty").option("--threads <n>", "number of worker threads", parseThreads).option("--since <ref>", "only scan files changed since git ref").option("--diff <ref>", "alias for --since <ref>; also adds PR Slop Score to the report").option("--workspace <path>", "workspace/project path", process.cwd()).option("--tighten", "tighten baseline allowances").option("--fix", "apply auto-fixes").option("--dry-run", "with --fix: print what would change without writing").option("--show-fixes-diff", "print unified diff of proposed auto-fixes").option("--doctor", "run diagnostics").option("--watch", "watch files and re-run").option("--suggest", "print remediation advice").option("--why-failing", "print the top 5 rules dragging the score down").option("--brief", "terse output (verdict + headline + threshold + delta only)").option("--heatmap", "print migration ROI heatmap").option("--quiet", "suppress non-error output").option("--verbose", "enable debug logging (file paths, timings, rule-fire counts)").option("--strict", "exit 2 if any high-severity issue remains").option("--no-increase", "exit 2 if slop index increased since last run").option("--baseline", "save a baseline after this scan").option("--trend [n]", "print a sparkline of the last n runs", parseTrend).option("--json [path]", "write JSON report to path or stdout").option("--html [path]", "write HTML report to path or stdout").option("--staged", "scan only changed files (staged and unstaged)").option("--changed", "scan working-tree changes (staged + unstaged + untracked)").option("--incremental", "skip unchanged files using the persisted hash cache").option("--cache-path <path>", "path to the incremental-scan cache (default: .slopbrick-cache.json)").option("--tokens <path>", "merge tokens.json layout values into the arbitrary-value allowlist").option("--cache", "cache parsed AST results locally");
|
|
56984
|
+
const program = new Command().name("slopbrick").description("Repository Coherence Scanner \u2014 surface AI-induced pattern drift, secret leaks, and design-token violations").version(VERSION).option("--framework <name>", "framework multiplier to apply").option("--include <glob>", "include pattern (repeatable)", collectGlob, []).option("--exclude <glob>", "exclude pattern (repeatable)", collectGlob, []).option("--ai-only", "only report AI-specific issues").option("--human-only", "only report human-facing issues").option("--ignore-wcag22", "ignore WCAG 2.2 related issues").option("--format <pretty|json|sarif|html>", "output format", "pretty").option("--threads <n>", "number of worker threads", parseThreads).option("--since <ref>", "only scan files changed since git ref").option("--diff <ref>", "alias for --since <ref>; also adds PR Slop Score to the report").option("--workspace <path>", "workspace/project path", process.cwd()).option("--tighten", "tighten baseline allowances").option("--fix", "apply auto-fixes").option("--dry-run", "with --fix: print what would change without writing").option("--show-fixes-diff", "print unified diff of proposed auto-fixes").option("--doctor", "run diagnostics").option("--watch", "watch files and re-run").option("--suggest", "print remediation advice").option("--why-failing", "print the top 5 rules dragging the score down").option("--brief", "terse output (verdict + headline + threshold + delta only)").option("--heatmap", "print migration ROI heatmap").option("--quiet", "suppress non-error output").option("--verbose", "enable debug logging (file paths, timings, rule-fire counts)").option("--strict", "exit 2 if any high-severity issue remains").option("--no-increase", "exit 2 if slop index increased since last run").option("--baseline", "save a baseline after this scan").option("--trend [n]", "print a sparkline of the last n runs", parseTrend).option("--json [path]", "write JSON report to path or stdout").option("--html [path]", "write HTML report to path or stdout").option("--staged", "scan only changed files (staged and unstaged)").option("--changed", "scan working-tree changes (staged + unstaged + untracked)").option("--incremental", "skip unchanged files using the persisted hash cache").option("--cache-path <path>", "path to the incremental-scan cache (default: .slopbrick-cache.json)").option("--tokens <path>", "merge tokens.json layout values into the arbitrary-value allowlist").option("--cache", "cache parsed AST results locally").option("--no-color", "suppress ANSI color codes in output").option("--security-only", "run only the security/* rules").option("--full", "show the complete report (all issues, all categories)");
|
|
56933
56985
|
program.command("init").description("create a slopbrick config file").option("--yes", "overwrite existing config").option("--all", "write snippets for every supported agent").option("--matrix", "print the agent x file matrix and exit").option("--cursor", "also generate .cursor/rules/slopbrick.mdc for Cursor AI").option("--cursorrules", "legacy Cursor .cursorrules format").option("--agents-md", "also generate AGENTS.md for Codex / opencode / Pi / Cline").option("--claude-md", "also generate CLAUDE.md for Claude Code (takes precedence over AGENTS.md)").option("--aider", "also generate CONVENTIONS.md for Aider").option("--windsurf", "also generate .windsurfrules for Windsurf").option("--cline", "also generate .clinerules/AGENTS.md for Cline").option("--gemini", "also generate .gemini/GEMINI.md for Gemini CLI").option("--copilot", "also generate .github/copilot-instructions.md for GitHub Copilot").action(async (cmdOptions, command) => {
|
|
56934
56986
|
const options = command.optsWithGlobals();
|
|
56935
56987
|
if (cmdOptions.matrix) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slopbrick",
|
|
3
|
-
"version": "0.17.
|
|
4
|
-
"description": "Discovered, modeled, and governed repository structure. SlopBrick scans source code, classifies it against
|
|
3
|
+
"version": "0.17.2",
|
|
4
|
+
"description": "Discovered, modeled, and governed repository structure. SlopBrick scans source code, classifies it against 95 rules in 15 categories, computes 4 scores (aiQuality, engineeringHygiene, security, repositoryHealth), and persists the structure for AI agents and CI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"slopbrick": "bin/slopbrick.js"
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"minimatch": "^9.0.5",
|
|
55
55
|
"pgsql-parser": "^17.9.15"
|
|
56
56
|
},
|
|
57
|
-
"//_usebrick_note": "@usebrick/core and @usebrick/engine are private workspace packages. They are listed in devDependencies to keep pnpm-lock.yaml in sync; tsup.config.ts
|
|
57
|
+
"//_usebrick_note": "@usebrick/core and @usebrick/engine are private workspace packages. They are listed in devDependencies to keep pnpm-lock.yaml in sync; tsup.config.ts \u2192 noExternal bundles them into dist/; the prepack-guard.mjs allowlist includes both. Neither ends up in the published tarball's runtime deps. See https://github.com/usebrick/platform/blob/main/docs/repository-structure.md",
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/node": "^20.14.0",
|
|
60
60
|
"@usebrick/core": "workspace:*",
|
|
@@ -111,4 +111,4 @@
|
|
|
111
111
|
"url": "https://github.com/usebrick/platform/issues"
|
|
112
112
|
},
|
|
113
113
|
"homepage": "https://github.com/usebrick/platform#readme"
|
|
114
|
-
}
|
|
114
|
+
}
|