slopbrick 0.20.0 → 0.21.0
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 +5 -5
- package/dist/engine/worker.cjs +76 -17
- package/dist/engine/worker.js +76 -17
- package/dist/index.cjs +289 -94
- package/dist/index.d.cts +29 -11
- package/dist/index.d.ts +29 -11
- package/dist/index.js +289 -94
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ var VERSION;
|
|
|
19
19
|
var init_header = __esm({
|
|
20
20
|
"src/types/_header.ts"() {
|
|
21
21
|
"use strict";
|
|
22
|
-
VERSION = "0.
|
|
22
|
+
VERSION = "0.21.0";
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -25921,8 +25921,8 @@ var init_comment_ratio = __esm({
|
|
|
25921
25921
|
init_rule();
|
|
25922
25922
|
init_corpus_baselines2();
|
|
25923
25923
|
MIN_FILE_LINES = 20;
|
|
25924
|
-
FALLBACK_LOW = 0.
|
|
25925
|
-
FALLBACK_HIGH = 0.
|
|
25924
|
+
FALLBACK_LOW = 0.02;
|
|
25925
|
+
FALLBACK_HIGH = 0.55;
|
|
25926
25926
|
LINE_COMMENT_RE = /^\s*(?:\/\/|#|--|;|%)/;
|
|
25927
25927
|
BLOCK_COMMENT_OPEN_RE = /\/\*/;
|
|
25928
25928
|
aiCommentRatioRule = createRule({
|
|
@@ -26064,9 +26064,9 @@ var init_compression_profile = __esm({
|
|
|
26064
26064
|
init_rule();
|
|
26065
26065
|
init_ncd_compression();
|
|
26066
26066
|
MIN_BYTES = 2e3;
|
|
26067
|
-
GZIP_RATIO_AI_THRESHOLD = 0.
|
|
26068
|
-
MEAN_LINE_NCD_HUMAN_MAX = 0.
|
|
26069
|
-
NCD_CV_AI_MAX = 0.
|
|
26067
|
+
GZIP_RATIO_AI_THRESHOLD = 0.6;
|
|
26068
|
+
MEAN_LINE_NCD_HUMAN_MAX = 0.25;
|
|
26069
|
+
NCD_CV_AI_MAX = 0.4;
|
|
26070
26070
|
aiCompressionProfileRule = createRule({
|
|
26071
26071
|
id: "ai/compression-profile",
|
|
26072
26072
|
category: "ai",
|
|
@@ -26235,6 +26235,13 @@ var init_default_react_stack = __esm({
|
|
|
26235
26235
|
category: "ai",
|
|
26236
26236
|
severity: "low",
|
|
26237
26237
|
aiSpecific: true,
|
|
26238
|
+
// v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.001
|
|
26239
|
+
// (fires 1 time per 1,000 files). The "≥3 of default stack"
|
|
26240
|
+
// threshold catches almost nothing on real codebases — the
|
|
26241
|
+
// default stack is now ubiquitous, so the signal is near-zero.
|
|
26242
|
+
// Disable until the threshold is lowered or the rule is split
|
|
26243
|
+
// into per-library detectors.
|
|
26244
|
+
defaultOff: true,
|
|
26238
26245
|
description: "File uses \u22653 of the default React stack (Next.js + TanStack Query + Zustand + shadcn/ui) \u2014 Sascha 2025 + Nam et al. MSR 2026",
|
|
26239
26246
|
create(context) {
|
|
26240
26247
|
return context;
|
|
@@ -26508,6 +26515,10 @@ var init_library_reinvention = __esm({
|
|
|
26508
26515
|
category: "ai",
|
|
26509
26516
|
severity: "medium",
|
|
26510
26517
|
aiSpecific: true,
|
|
26518
|
+
// v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.000
|
|
26519
|
+
// (fires never on the corpus). The "≥2 reinvented patterns"
|
|
26520
|
+
// threshold is too strict. Disable until rewritten.
|
|
26521
|
+
defaultOff: true,
|
|
26511
26522
|
description: "File reinvents \u22652 common patterns (date picker, form validation, chart, modal, etc.) without importing the canonical library \u2014 GitClear 2025 + Cui et al. 2025",
|
|
26512
26523
|
create(context) {
|
|
26513
26524
|
return context;
|
|
@@ -26714,6 +26725,12 @@ var init_markdown_leakage = __esm({
|
|
|
26714
26725
|
category: "ai",
|
|
26715
26726
|
severity: "high",
|
|
26716
26727
|
aiSpecific: true,
|
|
26728
|
+
// v0.20.0 calibration: v8.5 verdict = INVERTED (lift 65,504× but
|
|
26729
|
+
// recall 0.000 — fires never on the corpus, and when it does fire
|
|
26730
|
+
// it's wrong). The rule was on by default, contributing false
|
|
26731
|
+
// positives to the self-scan. Disable until the rule is rewritten
|
|
26732
|
+
// or the v9 corpus shows it works.
|
|
26733
|
+
defaultOff: true,
|
|
26717
26734
|
description: "Stray Markdown fence or bare language name at top of file \u2014 AI tools leak the fenced-block format into code",
|
|
26718
26735
|
create(context) {
|
|
26719
26736
|
return context;
|
|
@@ -27188,6 +27205,11 @@ var init_text_like_ratio = __esm({
|
|
|
27188
27205
|
category: "ai",
|
|
27189
27206
|
severity: "low",
|
|
27190
27207
|
aiSpecific: true,
|
|
27208
|
+
// v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.000
|
|
27209
|
+
// (fires never on the corpus). The rule is too specific to ever
|
|
27210
|
+
// fire on real code — the prose-detection heuristic is too
|
|
27211
|
+
// conservative. Disable until rewritten.
|
|
27212
|
+
defaultOff: true,
|
|
27191
27213
|
description: "High ratio of prose-like lines embedded in code \u2014 AI tools often leave natural-language explanations in source files (Yotkova 2026)",
|
|
27192
27214
|
create(context) {
|
|
27193
27215
|
return context;
|
|
@@ -28417,7 +28439,7 @@ function collectExports(cwd) {
|
|
|
28417
28439
|
/\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g,
|
|
28418
28440
|
// v0.18.6: also collect field names from `export interface` and
|
|
28419
28441
|
// `export type` declarations. Without this, fields like
|
|
28420
|
-
// `crossFileDrift`, `
|
|
28442
|
+
// `crossFileDrift`, `aiSlopScore`, `engineeringHygiene` are
|
|
28421
28443
|
// flagged as stale even though they're valid type fields.
|
|
28422
28444
|
/^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\s*[?:]/gm
|
|
28423
28445
|
]) {
|
|
@@ -30082,6 +30104,9 @@ var init_gap_monopoly = __esm({
|
|
|
30082
30104
|
GAP_RE = /\bgap(?:-x|-y)?-(\d+)\b/g;
|
|
30083
30105
|
gapMonopolyRule = createRule({
|
|
30084
30106
|
id: "layout/gap-monopoly",
|
|
30107
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
30108
|
+
// until rewritten.
|
|
30109
|
+
defaultOff: true,
|
|
30085
30110
|
category: "layout",
|
|
30086
30111
|
severity: "medium",
|
|
30087
30112
|
aiSpecific: false,
|
|
@@ -30194,6 +30219,9 @@ var init_math_grid_uniformity = __esm({
|
|
|
30194
30219
|
GRID_COLS_RE = /\bgrid-cols-(\d+)\b/g;
|
|
30195
30220
|
mathGridUniformityRule = createRule({
|
|
30196
30221
|
id: "layout/math-grid-uniformity",
|
|
30222
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
30223
|
+
// until rewritten.
|
|
30224
|
+
defaultOff: true,
|
|
30197
30225
|
category: "layout",
|
|
30198
30226
|
severity: "high",
|
|
30199
30227
|
aiSpecific: true,
|
|
@@ -30306,6 +30334,9 @@ var init_spacing_grid = __esm({
|
|
|
30306
30334
|
]);
|
|
30307
30335
|
spacingGridRule = createRule({
|
|
30308
30336
|
id: "layout/spacing-grid",
|
|
30337
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
30338
|
+
// until rewritten.
|
|
30339
|
+
defaultOff: true,
|
|
30309
30340
|
category: "layout",
|
|
30310
30341
|
severity: "medium",
|
|
30311
30342
|
aiSpecific: false,
|
|
@@ -30429,15 +30460,9 @@ var init_boundary_violation = __esm({
|
|
|
30429
30460
|
if (context.supportsRsc === false) return [];
|
|
30430
30461
|
if (!facts.v2) return [];
|
|
30431
30462
|
const issues = [];
|
|
30432
|
-
const hookLines = /* @__PURE__ */ new Map();
|
|
30433
|
-
for (const h of facts.v2.logic.hooks) {
|
|
30434
|
-
const arr = hookLines.get(h.name) ?? [];
|
|
30435
|
-
arr.push({ line: h.line, column: h.column });
|
|
30436
|
-
hookLines.set(h.name, arr);
|
|
30437
|
-
}
|
|
30438
30463
|
for (const component of facts.v2.components) {
|
|
30439
30464
|
if (!component.isServerComponent) continue;
|
|
30440
|
-
for (const hook of
|
|
30465
|
+
for (const hook of component.hookCalls) {
|
|
30441
30466
|
if (!context.clientHooks.has(hook.name)) continue;
|
|
30442
30467
|
issues.push({
|
|
30443
30468
|
ruleId: "logic/boundary-violation",
|
|
@@ -30445,8 +30470,8 @@ var init_boundary_violation = __esm({
|
|
|
30445
30470
|
severity: "high",
|
|
30446
30471
|
aiSpecific: true,
|
|
30447
30472
|
message: `'${hook.name}' only works in interactive client components. This component runs on the server.`,
|
|
30448
|
-
line:
|
|
30449
|
-
column:
|
|
30473
|
+
line: hook.line,
|
|
30474
|
+
column: hook.column,
|
|
30450
30475
|
advice: "Add the 'use client' directive at the top of this file, or move the stateful part to a separate client component.",
|
|
30451
30476
|
fix: {
|
|
30452
30477
|
kind: "insert",
|
|
@@ -34703,7 +34728,7 @@ function isHealthFile(value) {
|
|
|
34703
34728
|
if (!isVersion3(value.version)) return false;
|
|
34704
34729
|
if (typeof value.generatedAt !== "string") return false;
|
|
34705
34730
|
if (typeof value.workspace !== "string") return false;
|
|
34706
|
-
if (!isNumber(value.
|
|
34731
|
+
if (!isNumber(value.aiSlopScore)) return false;
|
|
34707
34732
|
if (!isNumber(value.engineeringHygiene)) return false;
|
|
34708
34733
|
if (!isNumber(value.security)) return false;
|
|
34709
34734
|
if (!isNumber(value.repositoryHealth)) return false;
|
|
@@ -34864,7 +34889,7 @@ var init_dist = __esm({
|
|
|
34864
34889
|
"../core/dist/index.js"() {
|
|
34865
34890
|
"use strict";
|
|
34866
34891
|
init_zod();
|
|
34867
|
-
STRUCTURE_SCHEMA_VERSION = "
|
|
34892
|
+
STRUCTURE_SCHEMA_VERSION = "5";
|
|
34868
34893
|
INVENTORY_FILENAME = "inventory.json";
|
|
34869
34894
|
CONSTITUTION_FILENAME = "constitution.json";
|
|
34870
34895
|
CACHE_FILENAME = "cache.json";
|
|
@@ -35331,11 +35356,11 @@ async function appendRun(cwd, report, version, io, thresholdExceeded2) {
|
|
|
35331
35356
|
const run2 = {
|
|
35332
35357
|
timestamp: report.generatedAt,
|
|
35333
35358
|
version,
|
|
35334
|
-
slopIndex: report.
|
|
35359
|
+
slopIndex: report.aiSlopScore,
|
|
35335
35360
|
// MemoryAuditRun keeps slopIndex as a historical legacy field
|
|
35336
35361
|
categoryScores: { ...report.categoryScores },
|
|
35337
35362
|
topOffenseIds: topOffenseIds(report),
|
|
35338
|
-
thresholdExceeded: thresholdExceeded2 ?? report.
|
|
35363
|
+
thresholdExceeded: thresholdExceeded2 ?? report.aiSlopScore > 0
|
|
35339
35364
|
};
|
|
35340
35365
|
runs.push(run2);
|
|
35341
35366
|
if (runs.length > MAX_RUNS) {
|
|
@@ -35467,7 +35492,7 @@ function buildHealthFromReport(report, workspace, options = {}) {
|
|
|
35467
35492
|
version: STRUCTURE_SCHEMA_VERSION,
|
|
35468
35493
|
generatedAt: report.generatedAt,
|
|
35469
35494
|
workspace,
|
|
35470
|
-
|
|
35495
|
+
aiSlopScore: Math.round(report.aiSlopScore),
|
|
35471
35496
|
engineeringHygiene: Math.round(report.engineeringHygiene),
|
|
35472
35497
|
security: Math.round(report.security),
|
|
35473
35498
|
repositoryHealth: Math.round(report.repositoryHealth),
|
|
@@ -40355,7 +40380,12 @@ var init_weak_assertion = __esm({
|
|
|
40355
40380
|
"toBeUndefined",
|
|
40356
40381
|
"toBeTruthy",
|
|
40357
40382
|
"toBeFalsy",
|
|
40358
|
-
"toBeNull"
|
|
40383
|
+
"toBeNull",
|
|
40384
|
+
"toBeGreaterThan",
|
|
40385
|
+
"toBeLessThan",
|
|
40386
|
+
"toContain",
|
|
40387
|
+
"toHaveLength",
|
|
40388
|
+
"toBeInstanceOf"
|
|
40359
40389
|
]);
|
|
40360
40390
|
}
|
|
40361
40391
|
});
|
|
@@ -41322,6 +41352,10 @@ var init_math_color_cluster = __esm({
|
|
|
41322
41352
|
HEX_COLOR_RE = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b/g;
|
|
41323
41353
|
mathColorClusterRule = createRule({
|
|
41324
41354
|
id: "visual/math-color-cluster",
|
|
41355
|
+
// v0.20.0 calibration: recall 0.000, fires never. The
|
|
41356
|
+
// "math-color cluster" heuristic is too specific to ever fire
|
|
41357
|
+
// on real code. Disable until rewritten.
|
|
41358
|
+
defaultOff: true,
|
|
41325
41359
|
category: "visual",
|
|
41326
41360
|
severity: "high",
|
|
41327
41361
|
aiSpecific: true,
|
|
@@ -41383,6 +41417,9 @@ var init_math_default_font = __esm({
|
|
|
41383
41417
|
NEXT_FONT_IMPORT_RE = /import\s+.*from\s+['"]next\/font\/(?:google|local)['"]/;
|
|
41384
41418
|
mathDefaultFontRule = createRule({
|
|
41385
41419
|
id: "visual/math-default-font",
|
|
41420
|
+
// v0.20.0 calibration: recall 0.001, fires 1 per 1,000 files.
|
|
41421
|
+
// Too specific to be useful. Disable until rewritten.
|
|
41422
|
+
defaultOff: true,
|
|
41386
41423
|
category: "visual",
|
|
41387
41424
|
severity: "high",
|
|
41388
41425
|
aiSpecific: true,
|
|
@@ -41458,6 +41495,9 @@ var init_math_font_entropy = __esm({
|
|
|
41458
41495
|
TEXT_SIZE_RE = /\btext-(?:xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl)\b/g;
|
|
41459
41496
|
mathFontEntropyRule = createRule({
|
|
41460
41497
|
id: "visual/math-font-entropy",
|
|
41498
|
+
// v0.20.0 calibration: recall 0.004, fires 0 times on self-scan
|
|
41499
|
+
// (verified). Disable until rewritten.
|
|
41500
|
+
defaultOff: true,
|
|
41461
41501
|
category: "visual",
|
|
41462
41502
|
severity: "high",
|
|
41463
41503
|
aiSpecific: true,
|
|
@@ -41598,6 +41638,10 @@ var init_math_rounded_entropy = __esm({
|
|
|
41598
41638
|
ROUNDED_RE = /\brounded(?:-none|-sm|-md|-lg|-xl|-2xl|-3xl|-full)?\b/g;
|
|
41599
41639
|
mathRoundedEntropyRule = createRule({
|
|
41600
41640
|
id: "visual/math-rounded-entropy",
|
|
41641
|
+
// v0.20.0 calibration: recall 0.004, fires 0 times on self-scan
|
|
41642
|
+
// (verified). The entropy heuristic is too specific to ever fire
|
|
41643
|
+
// on real code. Disable until rewritten.
|
|
41644
|
+
defaultOff: true,
|
|
41601
41645
|
category: "visual",
|
|
41602
41646
|
severity: "high",
|
|
41603
41647
|
aiSpecific: true,
|
|
@@ -41647,6 +41691,9 @@ var init_math_spacing_entropy = __esm({
|
|
|
41647
41691
|
SPACING_PREFIX_RE2 = /\b(p|px|py|pt|pb|pl|pr|m|mx|my|mt|mb|ml|mr|gap|gap-x|gap-y)-(\d+)\b/g;
|
|
41648
41692
|
mathSpacingEntropyRule = createRule({
|
|
41649
41693
|
id: "visual/math-spacing-entropy",
|
|
41694
|
+
// v0.20.0 calibration: recall 0.002, fires 0 times on self-scan
|
|
41695
|
+
// (verified). Disable until rewritten.
|
|
41696
|
+
defaultOff: true,
|
|
41650
41697
|
category: "visual",
|
|
41651
41698
|
severity: "medium",
|
|
41652
41699
|
aiSpecific: true,
|
|
@@ -44492,7 +44539,7 @@ var init_naturalness_anomaly = __esm({
|
|
|
44492
44539
|
init_dist2();
|
|
44493
44540
|
init_visitor();
|
|
44494
44541
|
MIN_LENGTH = 50;
|
|
44495
|
-
DISTINCT_RATIO_FLOOR = 0.
|
|
44542
|
+
DISTINCT_RATIO_FLOOR = 0.2;
|
|
44496
44543
|
naturalnessAnomalyRule = createRule({
|
|
44497
44544
|
id: "visual/naturalness-anomaly",
|
|
44498
44545
|
category: "visual",
|
|
@@ -44568,6 +44615,9 @@ var init_radius_scale_violation = __esm({
|
|
|
44568
44615
|
SCALE_TOLERANCE = 1e-3;
|
|
44569
44616
|
radiusScaleViolationRule = createRule({
|
|
44570
44617
|
id: "visual/radius-scale-violation",
|
|
44618
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
44619
|
+
// until rewritten.
|
|
44620
|
+
defaultOff: true,
|
|
44571
44621
|
category: "visual",
|
|
44572
44622
|
severity: "medium",
|
|
44573
44623
|
aiSpecific: false,
|
|
@@ -44744,6 +44794,10 @@ var init_dragging_movements = __esm({
|
|
|
44744
44794
|
ALT_ROLE_RE = /^(?:button|application|tab|menuitem)$/;
|
|
44745
44795
|
draggingMovementsRule = createRule({
|
|
44746
44796
|
id: "wcag/dragging-movements",
|
|
44797
|
+
// v0.20.0 calibration: v8.5 = DORMANT but defaultOff: false
|
|
44798
|
+
// was a bug — DORMANT rules should be off by default. The
|
|
44799
|
+
// rule was contributing 0 useful fires. Disable.
|
|
44800
|
+
defaultOff: true,
|
|
44747
44801
|
category: "wcag",
|
|
44748
44802
|
severity: "medium",
|
|
44749
44803
|
aiSpecific: false,
|
|
@@ -44913,6 +44967,11 @@ var init_missing_alt = __esm({
|
|
|
44913
44967
|
HAS_PRESENTATION_ROLE_RE = /\brole\s*=\s*["'](?:presentation|none)["']/i;
|
|
44914
44968
|
missingAltRule = createRule({
|
|
44915
44969
|
id: "wcag/missing-alt",
|
|
44970
|
+
// v0.20.0 calibration: lift 1.0, recall 0.000. Lift of 1.0
|
|
44971
|
+
// means the rule has zero discriminative power — it fires
|
|
44972
|
+
// equally on positive and negative examples. Dead rule.
|
|
44973
|
+
// Disable until rewritten.
|
|
44974
|
+
defaultOff: true,
|
|
44916
44975
|
category: "wcag",
|
|
44917
44976
|
severity: "medium",
|
|
44918
44977
|
aiSpecific: false,
|
|
@@ -46241,17 +46300,11 @@ function colorForSlop(slopIndex) {
|
|
|
46241
46300
|
if (slopIndex >= SLOP_BADGE_YELLOW_THRESHOLD) return "yellow";
|
|
46242
46301
|
return "green";
|
|
46243
46302
|
}
|
|
46244
|
-
function colorForQuality(score) {
|
|
46245
|
-
if (score >= SLOP_BADGE_RED_THRESHOLD) return "green";
|
|
46246
|
-
if (score >= SLOP_BADGE_ORANGE_THRESHOLD) return "yellow";
|
|
46247
|
-
if (score >= SLOP_BADGE_YELLOW_THRESHOLD) return "orange";
|
|
46248
|
-
return "red";
|
|
46249
|
-
}
|
|
46250
46303
|
function formatBadge(report) {
|
|
46251
|
-
const score = report.
|
|
46304
|
+
const score = report.aiSlopScore ?? 0;
|
|
46252
46305
|
const rounded = Math.round(score);
|
|
46253
|
-
const color =
|
|
46254
|
-
return `[](https://github.com/usebrick/platform)`;
|
|
46255
46308
|
}
|
|
46256
46309
|
function formatSparkline(values) {
|
|
46257
46310
|
if (values.length === 0) return "";
|
|
@@ -46381,18 +46434,25 @@ function scoreBand(score) {
|
|
|
46381
46434
|
if (score >= 40) return { label: "needs work", color: chalk.yellow };
|
|
46382
46435
|
return { label: "concerning", color: chalk.red };
|
|
46383
46436
|
}
|
|
46437
|
+
function slopScoreBand(score) {
|
|
46438
|
+
if (score >= 70) return { label: "saturated", color: chalk.red };
|
|
46439
|
+
if (score >= 50) return { label: "high", color: chalk.red };
|
|
46440
|
+
if (score >= 30) return { label: "medium", color: chalk.yellow };
|
|
46441
|
+
if (score >= 10) return { label: "low", color: chalk.green };
|
|
46442
|
+
return { label: "no slop", color: chalk.green };
|
|
46443
|
+
}
|
|
46384
46444
|
function formatDeltaSuffix(report) {
|
|
46385
46445
|
if (typeof report.previousSlopIndex !== "number") return "";
|
|
46386
|
-
const delta = report.
|
|
46446
|
+
const delta = report.aiSlopScore - report.previousSlopIndex;
|
|
46387
46447
|
if (Math.abs(delta) < 0.5) return "";
|
|
46388
46448
|
const arrow = delta < 0 ? "\u2193" : "\u2191";
|
|
46389
46449
|
const absDelta = Math.abs(delta).toFixed(0);
|
|
46390
|
-
const word = delta
|
|
46391
|
-
const color = delta
|
|
46450
|
+
const word = delta < 0 ? "cleaner" : "worse";
|
|
46451
|
+
const color = delta < 0 ? chalk.green : chalk.red;
|
|
46392
46452
|
return color(` ${arrow}${absDelta} (${word})`);
|
|
46393
46453
|
}
|
|
46394
46454
|
function formatVerdict(report) {
|
|
46395
|
-
const score = report.
|
|
46455
|
+
const score = report.aiSlopScore;
|
|
46396
46456
|
const band = scoreBand(score);
|
|
46397
46457
|
const ranked = rankByImpact(report.issues);
|
|
46398
46458
|
const topRule = ranked[0];
|
|
@@ -46476,7 +46536,7 @@ function formatNextStep(report) {
|
|
|
46476
46536
|
` \u2192 \`slopbrick scan --baseline\` to accept today's scores as the new floor`
|
|
46477
46537
|
)
|
|
46478
46538
|
);
|
|
46479
|
-
if (report.
|
|
46539
|
+
if (report.aiSlopScore > 30) {
|
|
46480
46540
|
lines.push(
|
|
46481
46541
|
chalk.dim(
|
|
46482
46542
|
` \u2192 \`slopbrick scan --why-failing\` for the top 5 issues dragging the score down`
|
|
@@ -46500,11 +46560,11 @@ function formatWhyFailing(report) {
|
|
|
46500
46560
|
if (ranked.length === 0) {
|
|
46501
46561
|
return chalk.green("Nothing is failing the threshold. Score is clean.");
|
|
46502
46562
|
}
|
|
46503
|
-
const headline = report.
|
|
46504
|
-
const status = headline
|
|
46505
|
-
const colorize = headline
|
|
46563
|
+
const headline = report.aiSlopScore;
|
|
46564
|
+
const status = headline <= 30 ? "PASS" : "FAIL";
|
|
46565
|
+
const colorize = headline <= 30 ? chalk.green : chalk.red;
|
|
46506
46566
|
const lines = [];
|
|
46507
|
-
lines.push(colorize.bold(`Headline score: ${headline.toFixed(0)}/100 (${status} \u2014
|
|
46567
|
+
lines.push(colorize.bold(`Headline score: ${headline.toFixed(0)}/100 (${status} \u2014 above 30)`));
|
|
46508
46568
|
lines.push("");
|
|
46509
46569
|
lines.push(chalk.bold("Top 5 rules dragging the score down:"));
|
|
46510
46570
|
for (let i = 0; i < ranked.length; i++) {
|
|
@@ -46528,30 +46588,42 @@ function severityBadge(severity) {
|
|
|
46528
46588
|
const label = severity.toUpperCase().padEnd(8, " ");
|
|
46529
46589
|
return colorize(label);
|
|
46530
46590
|
}
|
|
46591
|
+
function scoreToMessage(score, type) {
|
|
46592
|
+
const tiers = SCORE_MESSAGES[type] ?? [];
|
|
46593
|
+
for (const [threshold, message] of tiers) {
|
|
46594
|
+
if (score >= threshold) return message;
|
|
46595
|
+
}
|
|
46596
|
+
const last = tiers[tiers.length - 1];
|
|
46597
|
+
return last ? last[1] : "Score unavailable";
|
|
46598
|
+
}
|
|
46531
46599
|
function formatCompositeScore(report) {
|
|
46532
|
-
const slop = report.
|
|
46600
|
+
const slop = report.aiSlopScore;
|
|
46533
46601
|
const slopValue = slop.toFixed(0).padStart(3, " ");
|
|
46534
|
-
const band =
|
|
46602
|
+
const band = slopScoreBand(slop);
|
|
46535
46603
|
const status = chalk.bold(`[${band.label.toUpperCase()}]`);
|
|
46536
46604
|
const lines = [];
|
|
46537
46605
|
const deltaSuffix = formatDeltaSuffix(report);
|
|
46538
46606
|
lines.push(
|
|
46539
|
-
chalk.bold(`AI
|
|
46607
|
+
chalk.bold(`AI Slop Score: ${slopValue} / 100 ${band.color(status)}${deltaSuffix}`)
|
|
46540
46608
|
);
|
|
46609
|
+
lines.push(` ${chalk.dim(scoreToMessage(slop, "slop"))}`);
|
|
46541
46610
|
lines.push(
|
|
46542
46611
|
chalk.dim(
|
|
46543
|
-
"
|
|
46612
|
+
"lower = cleaner \xB7 measures AI-slop signatures (0 = no AI slop detected, 100 = max AI slop). The rules are calibrated to detect AI patterns, so this measures how much AI-style fingerprint the codebase has. Same number in .slopbrick/health.json."
|
|
46544
46613
|
)
|
|
46545
46614
|
);
|
|
46546
46615
|
lines.push(
|
|
46547
46616
|
` \u251C\u2500 boundary: ${report.boundaryScore.toFixed(0).padStart(3, " ")} (40%) ${chalk.dim("\u2014 structural integrity")}`
|
|
46548
46617
|
);
|
|
46618
|
+
lines.push(` \u2502 ${chalk.dim(scoreToMessage(report.boundaryScore, "boundary"))}`);
|
|
46549
46619
|
lines.push(
|
|
46550
46620
|
` \u251C\u2500 context: ${report.contextScore.toFixed(0).padStart(3, " ")} (35%) ${chalk.dim("\u2014 props / state / imports")}`
|
|
46551
46621
|
);
|
|
46622
|
+
lines.push(` \u2502 ${chalk.dim(scoreToMessage(report.contextScore, "context"))}`);
|
|
46552
46623
|
lines.push(
|
|
46553
46624
|
` \u2514\u2500 visual: ${report.visualScore.toFixed(0).padStart(3, " ")} (25%) ${chalk.dim("\u2014 CSS / a11y / layout")}`
|
|
46554
46625
|
);
|
|
46626
|
+
lines.push(` ${chalk.dim(scoreToMessage(report.visualScore, "visual"))}`);
|
|
46555
46627
|
const coherence = report.coherence;
|
|
46556
46628
|
if (typeof coherence === "number") {
|
|
46557
46629
|
const coherenceValue = coherence.toFixed(0).padStart(3, " ");
|
|
@@ -46568,25 +46640,52 @@ function formatCompositeScore(report) {
|
|
|
46568
46640
|
function formatCoherenceScores(report) {
|
|
46569
46641
|
const lines = [];
|
|
46570
46642
|
const domains = [
|
|
46571
|
-
{
|
|
46572
|
-
|
|
46573
|
-
|
|
46643
|
+
{
|
|
46644
|
+
key: "codeHygiene",
|
|
46645
|
+
label: "Code Hygiene",
|
|
46646
|
+
measure: "issues per category across arch/logic/layout/component/test",
|
|
46647
|
+
direction: "higher"
|
|
46648
|
+
},
|
|
46649
|
+
{
|
|
46650
|
+
key: "accessibility",
|
|
46651
|
+
label: "Accessibility",
|
|
46652
|
+
measure: "WCAG violations, ARIA misuse, focus management",
|
|
46653
|
+
direction: "higher"
|
|
46654
|
+
},
|
|
46655
|
+
{
|
|
46656
|
+
key: "performance",
|
|
46657
|
+
label: "Performance",
|
|
46658
|
+
measure: "render-blocking, N+1 queries, bundle bloat",
|
|
46659
|
+
direction: "higher"
|
|
46660
|
+
}
|
|
46574
46661
|
];
|
|
46575
|
-
for (const { key, label } of domains) {
|
|
46662
|
+
for (const { key, label, measure, direction } of domains) {
|
|
46576
46663
|
const value = report[key];
|
|
46577
46664
|
if (typeof value === "number") {
|
|
46578
46665
|
const padded = value.toString().padStart(3, " ");
|
|
46579
|
-
|
|
46666
|
+
const arrow = direction === "higher" ? "\u2191" : "\u2193";
|
|
46667
|
+
lines.push(
|
|
46668
|
+
`${label.padEnd(20)} ${padded}/100 (${arrow} ${direction} = better \xB7 ${measure})`
|
|
46669
|
+
);
|
|
46670
|
+
const messageType = key === "codeHygiene" ? "hygiene" : key === "accessibility" ? "accessibility" : key === "performance" ? "performance" : "hygiene";
|
|
46671
|
+
lines.push(`${"".padEnd(20)} ${chalk.dim(scoreToMessage(value, messageType))}`);
|
|
46580
46672
|
}
|
|
46581
46673
|
}
|
|
46582
46674
|
const bl = report.businessLogicCoherence;
|
|
46583
46675
|
if (typeof bl === "number") {
|
|
46584
46676
|
const blValue = bl.toString().padStart(3, " ");
|
|
46585
|
-
lines.push(
|
|
46677
|
+
lines.push(
|
|
46678
|
+
`${"Business Logic".padEnd(20)} ${blValue}/100 (\u2191 higher = better \xB7 tangled dependencies, cyclic imports, god modules)`
|
|
46679
|
+
);
|
|
46586
46680
|
}
|
|
46587
46681
|
if (report.aiSecurityRisk) {
|
|
46588
|
-
const
|
|
46589
|
-
lines.push(
|
|
46682
|
+
const secLabel = report.aiSecurityRisk.toUpperCase();
|
|
46683
|
+
lines.push(
|
|
46684
|
+
`${"Security Risk".padEnd(20)} ${secLabel.padEnd(9)} (\u2191 higher = better \xB7 inverted from risk level \xB7 CRITICAL = worst)`
|
|
46685
|
+
);
|
|
46686
|
+
const secToScore = { low: 100, medium: 67, high: 33, critical: 0 };
|
|
46687
|
+
const secNum = secToScore[report.aiSecurityRisk] ?? 0;
|
|
46688
|
+
lines.push(`${"".padEnd(20)} ${chalk.dim(scoreToMessage(secNum, "security"))}`);
|
|
46590
46689
|
}
|
|
46591
46690
|
return lines;
|
|
46592
46691
|
}
|
|
@@ -46654,11 +46753,11 @@ function formatDriftSection(report) {
|
|
|
46654
46753
|
}
|
|
46655
46754
|
function formatThresholds(report) {
|
|
46656
46755
|
const limit = report.thresholds.meanSlop;
|
|
46657
|
-
const headline = report.
|
|
46756
|
+
const headline = report.aiSlopScore;
|
|
46658
46757
|
const passed = headline >= 70;
|
|
46659
46758
|
const valueText = `${headline.toFixed(1)} \u2265 70`.padStart(12, " ");
|
|
46660
46759
|
const status = passed ? chalk.green("pass") : chalk.red("fail");
|
|
46661
|
-
const result = ["Threshold (CI gate)", ` AI
|
|
46760
|
+
const result = ["Threshold (CI gate)", ` AI Slop Score ${valueText} ${status}`];
|
|
46662
46761
|
void limit;
|
|
46663
46762
|
return result;
|
|
46664
46763
|
}
|
|
@@ -46739,7 +46838,7 @@ function formatPretty(report) {
|
|
|
46739
46838
|
}
|
|
46740
46839
|
function formatScoringExplainer(_report) {
|
|
46741
46840
|
return chalk.dim(
|
|
46742
|
-
"Four orthogonal scores (all 0-100, higher = better): AI
|
|
46841
|
+
"Four orthogonal scores (all 0-100, higher = better): AI Slop Score (AI-slop signatures; the CI gate, AI Slop Score >= 70), Engineering Hygiene (issues per category across arch/logic/layout/component/test), Security (AI-flagged security risks, inverted from risk level), Repository Health (composite: 0.4*AI Slop Score + 0.3*Engineering Hygiene + 0.2*Security + 0.1*Test Quality). Only AI Slop Score gates CI; the others are informational. Default-off rules (INVERTED/NOISY/DORMANT) are suppressed from the scores automatically."
|
|
46743
46842
|
);
|
|
46744
46843
|
}
|
|
46745
46844
|
function formatWhyFailingReport(report) {
|
|
@@ -46750,7 +46849,7 @@ function formatBriefReport(report) {
|
|
|
46750
46849
|
lines.push(formatVerdict(report));
|
|
46751
46850
|
lines.push("");
|
|
46752
46851
|
const scoreLines = [
|
|
46753
|
-
{ label: "AI
|
|
46852
|
+
{ label: "AI Slop Score", field: "aiSlopScore", value: report.aiSlopScore },
|
|
46754
46853
|
{ label: "Engineering Hygiene", field: "engineeringHygiene", value: report.engineeringHygiene },
|
|
46755
46854
|
{ label: "Security", field: "security", value: report.security },
|
|
46756
46855
|
{ label: "Repository Health", field: "repositoryHealth", value: report.repositoryHealth }
|
|
@@ -46765,11 +46864,11 @@ function formatBriefReport(report) {
|
|
|
46765
46864
|
` ${paddedLabel} ${band.color(valueStr)} ${chalk.dim(band.label)} ${chalk.dim.italic(`(${field})`)}${delta}`
|
|
46766
46865
|
);
|
|
46767
46866
|
});
|
|
46768
|
-
const passed = report.
|
|
46867
|
+
const passed = report.aiSlopScore <= 30;
|
|
46769
46868
|
lines.push("");
|
|
46770
46869
|
lines.push(
|
|
46771
46870
|
chalk.dim(
|
|
46772
|
-
` CI gate: AI
|
|
46871
|
+
` CI gate: AI Slop Score <= 30 -> ${passed ? chalk.green("pass") : chalk.red("fail")}`
|
|
46773
46872
|
)
|
|
46774
46873
|
);
|
|
46775
46874
|
const suppressed = report.defaultOffSuppressedCount ?? 0;
|
|
@@ -46789,7 +46888,7 @@ function formatBriefReport(report) {
|
|
|
46789
46888
|
);
|
|
46790
46889
|
return lines.join("\n");
|
|
46791
46890
|
}
|
|
46792
|
-
var CATEGORY_GLOSSARY;
|
|
46891
|
+
var CATEGORY_GLOSSARY, SCORE_MESSAGES;
|
|
46793
46892
|
var init_pretty = __esm({
|
|
46794
46893
|
"src/report/pretty.ts"() {
|
|
46795
46894
|
"use strict";
|
|
@@ -46812,6 +46911,96 @@ var init_pretty = __esm({
|
|
|
46812
46911
|
product: { short: "product", long: "feature flags, dead code, churn" },
|
|
46813
46912
|
i18n: { short: "i18n", long: "translations, locale handling" }
|
|
46814
46913
|
};
|
|
46914
|
+
SCORE_MESSAGES = {
|
|
46915
|
+
// AI Slop Score (v0.21.0: lower = cleaner; field stores the RAW
|
|
46916
|
+
// amount of slop. 0 = no AI slop, 100 = max AI slop. The tier
|
|
46917
|
+
// thresholds are flipped vs the v0.20.1 inverted reading.)
|
|
46918
|
+
slop: [
|
|
46919
|
+
[90, "Repo is saturated with AI slop"],
|
|
46920
|
+
[70, "Repo has a high amount of AI slop"],
|
|
46921
|
+
[50, "Repo has a medium amount of AI slop"],
|
|
46922
|
+
[30, "Repo has a low amount of AI slop"],
|
|
46923
|
+
[0, "Repo has no detectable AI slop"]
|
|
46924
|
+
],
|
|
46925
|
+
// Engineering Hygiene (higher = better = fewer arch/logic/layout issues)
|
|
46926
|
+
hygiene: [
|
|
46927
|
+
[90, "Repo is clean"],
|
|
46928
|
+
[70, "Repo has minor code quality issues"],
|
|
46929
|
+
[50, "Repo has moderate code quality issues"],
|
|
46930
|
+
[30, "Repo has significant code quality issues"],
|
|
46931
|
+
[0, "Repo has serious code quality issues"]
|
|
46932
|
+
],
|
|
46933
|
+
// Security (higher = better = inverted from risk level)
|
|
46934
|
+
security: [
|
|
46935
|
+
[90, "Repo has no security risks"],
|
|
46936
|
+
[70, "Repo has low security risk"],
|
|
46937
|
+
[50, "Repo has medium security risk"],
|
|
46938
|
+
[30, "Repo has high security risk"],
|
|
46939
|
+
[0, "Repo has critical security risk"]
|
|
46940
|
+
],
|
|
46941
|
+
// Repository Health (higher = better = composite of 4 scores)
|
|
46942
|
+
health: [
|
|
46943
|
+
[90, "Repo is healthy"],
|
|
46944
|
+
[70, "Repo has minor concerns"],
|
|
46945
|
+
[50, "Repo has moderate concerns"],
|
|
46946
|
+
[30, "Repo has significant concerns"],
|
|
46947
|
+
[0, "Repo has serious concerns"]
|
|
46948
|
+
],
|
|
46949
|
+
// Test Quality (higher = better = more test coverage, better assertions)
|
|
46950
|
+
test: [
|
|
46951
|
+
[90, "Tests are comprehensive"],
|
|
46952
|
+
[70, "Good test coverage"],
|
|
46953
|
+
[50, "Moderate test coverage"],
|
|
46954
|
+
[30, "Weak test coverage"],
|
|
46955
|
+
[0, "Tests are inadequate"]
|
|
46956
|
+
],
|
|
46957
|
+
// Business Logic Coherence (higher = better = fewer cyclic imports, etc.)
|
|
46958
|
+
logic: [
|
|
46959
|
+
[90, "Logic is clean"],
|
|
46960
|
+
[70, "Minor logic tangles"],
|
|
46961
|
+
[50, "Moderate logic tangles"],
|
|
46962
|
+
[30, "Significant logic tangles"],
|
|
46963
|
+
[0, "Serious logic tangles"]
|
|
46964
|
+
],
|
|
46965
|
+
// Accessibility (higher = better = fewer WCAG violations)
|
|
46966
|
+
accessibility: [
|
|
46967
|
+
[90, "No accessibility issues"],
|
|
46968
|
+
[70, "Minor accessibility issues"],
|
|
46969
|
+
[50, "Moderate accessibility issues"],
|
|
46970
|
+
[30, "Significant accessibility issues"],
|
|
46971
|
+
[0, "Critical accessibility issues"]
|
|
46972
|
+
],
|
|
46973
|
+
// Performance (higher = better = fewer render-blocking, N+1, bloat)
|
|
46974
|
+
performance: [
|
|
46975
|
+
[90, "No performance issues"],
|
|
46976
|
+
[70, "Minor performance issues"],
|
|
46977
|
+
[50, "Moderate performance issues"],
|
|
46978
|
+
[30, "Significant performance issues"],
|
|
46979
|
+
[0, "Critical performance issues"]
|
|
46980
|
+
],
|
|
46981
|
+
// Sub-score buckets (boundary/context/visual within AI Slop Score)
|
|
46982
|
+
boundary: [
|
|
46983
|
+
[90, "Boundary is clean"],
|
|
46984
|
+
[70, "Minor boundary issues"],
|
|
46985
|
+
[50, "Moderate boundary issues"],
|
|
46986
|
+
[30, "Significant boundary issues"],
|
|
46987
|
+
[0, "Boundary is broken"]
|
|
46988
|
+
],
|
|
46989
|
+
context: [
|
|
46990
|
+
[90, "Context is clean"],
|
|
46991
|
+
[70, "Minor context issues"],
|
|
46992
|
+
[50, "Moderate context issues"],
|
|
46993
|
+
[30, "Significant context issues"],
|
|
46994
|
+
[0, "Context is broken"]
|
|
46995
|
+
],
|
|
46996
|
+
visual: [
|
|
46997
|
+
[90, "Visual is clean"],
|
|
46998
|
+
[70, "Minor visual issues"],
|
|
46999
|
+
[50, "Moderate visual issues"],
|
|
47000
|
+
[30, "Significant visual issues"],
|
|
47001
|
+
[0, "Visual is broken"]
|
|
47002
|
+
]
|
|
47003
|
+
};
|
|
46815
47004
|
}
|
|
46816
47005
|
});
|
|
46817
47006
|
|
|
@@ -46819,14 +47008,14 @@ var init_pretty = __esm({
|
|
|
46819
47008
|
import { readFileSync as readFileSync10, existsSync as existsSync11, writeFileSync as writeFileSync2 } from "fs";
|
|
46820
47009
|
import { join as join11, resolve as resolve6, relative as relative3 } from "path";
|
|
46821
47010
|
function thresholdExceeded(report, config) {
|
|
46822
|
-
if ((report.
|
|
47011
|
+
if ((report.aiSlopScore ?? 0) > config.thresholds.meanSlop) {
|
|
46823
47012
|
return true;
|
|
46824
47013
|
}
|
|
46825
47014
|
return categoryThresholdBreached(report, config.thresholds.categoryThresholds);
|
|
46826
47015
|
}
|
|
46827
47016
|
function failedThresholdCount(report, config) {
|
|
46828
47017
|
let count = 0;
|
|
46829
|
-
if (report.
|
|
47018
|
+
if (report.aiSlopScore > config.thresholds.meanSlop) count += 1;
|
|
46830
47019
|
if (report.p90Score > config.thresholds.p90Slop) count += 1;
|
|
46831
47020
|
if (report.peakScore > config.thresholds.individualSlopThreshold) count += 1;
|
|
46832
47021
|
const cat = config.thresholds.categoryThresholds;
|
|
@@ -50141,14 +50330,21 @@ function aggregateReport(scores, issueGroups, config, compositeScores) {
|
|
|
50141
50330
|
}
|
|
50142
50331
|
}
|
|
50143
50332
|
const denominator = componentCount || 1;
|
|
50333
|
+
const slopAmount = {
|
|
50334
|
+
boundary: Math.min(100, Math.log10(1 + bucketPoints.boundary / denominator) / Math.log10(11) * 100),
|
|
50335
|
+
context: Math.min(100, Math.log10(1 + bucketPoints.context / denominator) / Math.log10(11) * 100),
|
|
50336
|
+
visual: Math.min(100, Math.log10(1 + bucketPoints.visual / denominator) / Math.log10(11) * 100)
|
|
50337
|
+
};
|
|
50144
50338
|
const subscore = {
|
|
50145
|
-
boundary:
|
|
50146
|
-
context:
|
|
50147
|
-
visual:
|
|
50339
|
+
boundary: 100 - slopAmount.boundary,
|
|
50340
|
+
context: 100 - slopAmount.context,
|
|
50341
|
+
visual: 100 - slopAmount.visual
|
|
50148
50342
|
};
|
|
50149
50343
|
const compositeWeights = config.compositeWeights ?? COMPOSITE_WEIGHTS;
|
|
50150
|
-
const
|
|
50151
|
-
const
|
|
50344
|
+
const aiSlopScoreRaw = compositeWeights.boundary * slopAmount.boundary + compositeWeights.context * slopAmount.context + compositeWeights.visual * slopAmount.visual;
|
|
50345
|
+
const aiSlopScore = Math.max(0, Math.min(100, aiSlopScoreRaw));
|
|
50346
|
+
const slopIndex = aiSlopScore;
|
|
50347
|
+
const assemblyHealth = Math.max(0, 100 - aiSlopScore);
|
|
50152
50348
|
const categoryPoints = {
|
|
50153
50349
|
visual: 0,
|
|
50154
50350
|
typo: 0,
|
|
@@ -50178,7 +50374,6 @@ function aggregateReport(scores, issueGroups, config, compositeScores) {
|
|
|
50178
50374
|
categoryScores[category] = categoryScores[category] / componentCount * 100;
|
|
50179
50375
|
}
|
|
50180
50376
|
}
|
|
50181
|
-
const aiQuality = Math.max(0, Math.min(100, 100 - slopIndex));
|
|
50182
50377
|
const flatIssues = [];
|
|
50183
50378
|
for (const group of issueGroups) {
|
|
50184
50379
|
for (const issue of group.issues) {
|
|
@@ -50214,7 +50409,7 @@ function aggregateReport(scores, issueGroups, config, compositeScores) {
|
|
|
50214
50409
|
const engineeringHygiene = Math.max(0, Math.min(100, 100 - engineeringRaw));
|
|
50215
50410
|
const testQualityResult = buildTestQualityScore(flatIssues, componentCount);
|
|
50216
50411
|
const testQuality = Math.max(0, Math.min(100, testQualityResult.score));
|
|
50217
|
-
const repositoryHealthRaw = 0.4 *
|
|
50412
|
+
const repositoryHealthRaw = 0.4 * (100 - aiSlopScore) + 0.3 * engineeringHygiene + 0.2 * security + 0.1 * testQuality;
|
|
50218
50413
|
const repositoryHealth = Math.max(0, Math.min(100, repositoryHealthRaw));
|
|
50219
50414
|
let compositeAggregate;
|
|
50220
50415
|
if (compositeScores && compositeScores.length > 0) {
|
|
@@ -50235,7 +50430,7 @@ function aggregateReport(scores, issueGroups, config, compositeScores) {
|
|
|
50235
50430
|
}
|
|
50236
50431
|
}
|
|
50237
50432
|
return {
|
|
50238
|
-
|
|
50433
|
+
aiSlopScore,
|
|
50239
50434
|
engineeringHygiene,
|
|
50240
50435
|
security,
|
|
50241
50436
|
repositoryHealth,
|
|
@@ -52395,8 +52590,8 @@ function computeAiMaintenanceCostFromReport(report, options = {}) {
|
|
|
52395
52590
|
}
|
|
52396
52591
|
const approxLoc = options.linesOfCode ?? (report.fileCount ?? 0) * 50;
|
|
52397
52592
|
const axes = {
|
|
52398
|
-
slopIndex: report.
|
|
52399
|
-
// The axis is named slopIndex for historical naming; the value is now
|
|
52593
|
+
slopIndex: report.aiSlopScore,
|
|
52594
|
+
// The axis is named slopIndex for historical naming; the value is now aiSlopScore (0-100, higher is better, but axisSlopIndex inverts it)
|
|
52400
52595
|
architectureConsistency: report.architectureConsistency,
|
|
52401
52596
|
aiSecurityRisk: report.aiSecurityRisk,
|
|
52402
52597
|
constitutionViolations: options.constitutionViolations,
|
|
@@ -52668,10 +52863,10 @@ function aiDebtFromScore(score) {
|
|
|
52668
52863
|
}
|
|
52669
52864
|
function perAxis(inputs) {
|
|
52670
52865
|
const out = [];
|
|
52671
|
-
if (inputs.
|
|
52866
|
+
if (inputs.aiSlopScore !== void 0 && !Number.isNaN(inputs.aiSlopScore)) {
|
|
52672
52867
|
out.push({
|
|
52673
52868
|
axis: "slopIndex",
|
|
52674
|
-
health: clamp1002(inputs.
|
|
52869
|
+
health: clamp1002(inputs.aiSlopScore),
|
|
52675
52870
|
weight: REPOSITORY_HEALTH_WEIGHTS.slopIndex
|
|
52676
52871
|
});
|
|
52677
52872
|
} else if (inputs.slopIndex !== void 0 && !Number.isNaN(inputs.slopIndex)) {
|
|
@@ -52783,10 +52978,10 @@ function buildRepositoryHealthFromReport(report, options = {}) {
|
|
|
52783
52978
|
const totalDesignTokens = (options.spacingViolations ?? 0) + (options.radiusViolations ?? 0);
|
|
52784
52979
|
const inputs = {
|
|
52785
52980
|
// v0.15.0 U.4+: the v3 headline score. Passed as the
|
|
52786
|
-
// `
|
|
52981
|
+
// `aiSlopScore` input (higher = better). The perAxis handler
|
|
52787
52982
|
// maps it to the "slopIndex" axis in the breakdown so
|
|
52788
52983
|
// dashboards don't have to migrate.
|
|
52789
|
-
|
|
52984
|
+
aiSlopScore: report.aiSlopScore,
|
|
52790
52985
|
architectureConsistency: report.architectureConsistency,
|
|
52791
52986
|
aiSecurityRisk: report.aiSecurityRisk,
|
|
52792
52987
|
designTokenViolations: totalDesignTokens > 0 ? {
|
|
@@ -53076,7 +53271,7 @@ async function enrichReport(input) {
|
|
|
53076
53271
|
const aiSignalCount = sortedIssues.filter((i) => i.aiSpecific === true).length;
|
|
53077
53272
|
aiMaintenanceCost = computeAiMaintenanceCostFromReport2(
|
|
53078
53273
|
{
|
|
53079
|
-
|
|
53274
|
+
aiSlopScore: aggregated.aiSlopScore ?? 0,
|
|
53080
53275
|
architectureConsistency,
|
|
53081
53276
|
aiSecurityRisk,
|
|
53082
53277
|
highSeverityIssueCount: sortedIssues.filter((i) => i.severity === "high").length,
|
|
@@ -53135,7 +53330,7 @@ async function enrichReport(input) {
|
|
|
53135
53330
|
)?.count ?? 0;
|
|
53136
53331
|
const composite = buildRepositoryHealthFromReport2(
|
|
53137
53332
|
{
|
|
53138
|
-
|
|
53333
|
+
aiSlopScore: aggregated.aiSlopScore ?? 0,
|
|
53139
53334
|
engineeringHygiene: aggregated.engineeringHygiene ?? 0,
|
|
53140
53335
|
security: aggregated.security ?? 0,
|
|
53141
53336
|
repositoryHealth: aggregated.repositoryHealth ?? 0,
|
|
@@ -53279,7 +53474,7 @@ function assembleScanReport(input) {
|
|
|
53279
53474
|
version: VERSION,
|
|
53280
53475
|
generatedAt,
|
|
53281
53476
|
configPath,
|
|
53282
|
-
|
|
53477
|
+
aiSlopScore: aggregated.aiSlopScore,
|
|
53283
53478
|
engineeringHygiene: aggregated.engineeringHygiene,
|
|
53284
53479
|
security: aggregated.security,
|
|
53285
53480
|
// repositoryHealth is set later from `enrichment.repositoryHealth`
|
|
@@ -53318,7 +53513,7 @@ function assembleScanReport(input) {
|
|
|
53318
53513
|
// v0.15.0 U.4+: the previous-run value is stored as
|
|
53319
53514
|
// `previousSlopIndex` on ProjectReport for backward compat with
|
|
53320
53515
|
// historical telemetry. The value itself is the previous run's
|
|
53321
|
-
//
|
|
53516
|
+
// aiSlopScore (higher = better).
|
|
53322
53517
|
previousSlopIndex: previousRun?.slopIndex,
|
|
53323
53518
|
previousRunTimestamp: previousRun?.timestamp,
|
|
53324
53519
|
p90Score: aggregated.p90Score,
|
|
@@ -53467,9 +53662,9 @@ function recordTelemetry(cwd, report, results, config) {
|
|
|
53467
53662
|
componentCount: report.componentCount,
|
|
53468
53663
|
// v0.15.0 U.4+: slopIndex is deprecated; the telemetry field is
|
|
53469
53664
|
// still named slopIndex for backward compat with historical
|
|
53470
|
-
// payloads, but the value is now
|
|
53665
|
+
// payloads, but the value is now aiSlopScore (0-100, higher is
|
|
53471
53666
|
// better).
|
|
53472
|
-
slopIndex: report.
|
|
53667
|
+
slopIndex: report.aiSlopScore,
|
|
53473
53668
|
assemblyHealth: report.assemblyHealth,
|
|
53474
53669
|
categoryScores: { ...report.categoryScores },
|
|
53475
53670
|
p90Score: report.p90Score,
|
|
@@ -53736,11 +53931,11 @@ async function persistRun(input) {
|
|
|
53736
53931
|
} = input;
|
|
53737
53932
|
const memoryReport = {
|
|
53738
53933
|
generatedAt: report.generatedAt,
|
|
53739
|
-
|
|
53934
|
+
aiSlopScore: report.aiSlopScore ?? 0,
|
|
53740
53935
|
engineeringHygiene: report.engineeringHygiene ?? 0,
|
|
53741
53936
|
security: report.security ?? 0,
|
|
53742
53937
|
repositoryHealth: report.repositoryHealth ?? 0,
|
|
53743
|
-
slopIndex: report.slopIndex ?? report.
|
|
53938
|
+
slopIndex: report.slopIndex ?? report.aiSlopScore ?? 0,
|
|
53744
53939
|
categoryScores: report.categoryScores,
|
|
53745
53940
|
issues: report.issues ?? []
|
|
53746
53941
|
};
|
|
@@ -53839,7 +54034,7 @@ async function persistRun(input) {
|
|
|
53839
54034
|
if (!options.quiet && !machineReadableStdout) {
|
|
53840
54035
|
const compositeSuffix = health.compositeScore ? ` composite=${health.compositeScore.tier}@${health.compositeScore.mean.toFixed(2)}` : "";
|
|
53841
54036
|
logger.info(
|
|
53842
|
-
`Memory persisted to .slopbrick/ (${inventory.patterns.length} patterns, ${inventory.components.length} components, ${md.length} bytes of structure.md, health.json: repo=${health.repositoryHealth} aiQ=${health.
|
|
54037
|
+
`Memory persisted to .slopbrick/ (${inventory.patterns.length} patterns, ${inventory.components.length} components, ${md.length} bytes of structure.md, health.json: repo=${health.repositoryHealth} aiQ=${health.aiSlopScore} eng=${health.engineeringHygiene} sec=${health.security}${compositeSuffix}).`
|
|
53843
54038
|
);
|
|
53844
54039
|
}
|
|
53845
54040
|
} catch (err) {
|
|
@@ -53915,7 +54110,7 @@ async function finalizeReport(input) {
|
|
|
53915
54110
|
config,
|
|
53916
54111
|
results,
|
|
53917
54112
|
aggregated: {
|
|
53918
|
-
|
|
54113
|
+
aiSlopScore: aggregated.aiSlopScore,
|
|
53919
54114
|
engineeringHygiene: aggregated.engineeringHygiene,
|
|
53920
54115
|
security: aggregated.security,
|
|
53921
54116
|
repositoryHealth: aggregated.repositoryHealth,
|
|
@@ -53947,11 +54142,11 @@ async function finalizeReport(input) {
|
|
|
53947
54142
|
const previous = (await readRuns(cwd, fsMemoryIO)).at(-1);
|
|
53948
54143
|
if (previous) {
|
|
53949
54144
|
const previousBaseline = previous.slopIndex;
|
|
53950
|
-
if ((report.
|
|
54145
|
+
if ((report.aiSlopScore ?? 0) > previousBaseline) {
|
|
53951
54146
|
noIncreaseFailure = true;
|
|
53952
54147
|
if (!options.quiet) {
|
|
53953
54148
|
logger.error(
|
|
53954
|
-
`AI
|
|
54149
|
+
`AI Slop Score went UP from ${previousBaseline.toFixed(1)} to ${(report.aiSlopScore ?? 0).toFixed(1)} \u2014 your code got sloppier. (Both values are 0-100, lower = cleaner; the comparison is against the previous run's raw slop amount.) See which files changed and fix the new issues.`
|
|
53955
54150
|
);
|
|
53956
54151
|
}
|
|
53957
54152
|
}
|
|
@@ -54383,8 +54578,8 @@ function renderHeader(report) {
|
|
|
54383
54578
|
<span class="score-label">Repository Health (composite)</span>
|
|
54384
54579
|
</div>
|
|
54385
54580
|
<div class="score-card ai-quality">
|
|
54386
|
-
<span class="score-value">${namedScores.
|
|
54387
|
-
<span class="score-label">AI
|
|
54581
|
+
<span class="score-value">${namedScores.aiSlopScore}</span>
|
|
54582
|
+
<span class="score-label">AI Slop Score</span>
|
|
54388
54583
|
</div>
|
|
54389
54584
|
<div class="score-card engineering-hygiene">
|
|
54390
54585
|
<span class="score-value">${namedScores.engineeringHygiene}</span>
|
|
@@ -54414,7 +54609,7 @@ function extractNamedScores(report) {
|
|
|
54414
54609
|
const score = (v) => typeof v === "number" && !Number.isNaN(v) ? Math.round(v).toString() : "\u2013";
|
|
54415
54610
|
const r = report;
|
|
54416
54611
|
return {
|
|
54417
|
-
|
|
54612
|
+
aiSlopScore: score(r.aiSlopScore),
|
|
54418
54613
|
engineeringHygiene: score(r.engineeringHygiene),
|
|
54419
54614
|
security: score(r.security),
|
|
54420
54615
|
repositoryHealth: score(r.repositoryHealth ?? report.repositoryHealth)
|
|
@@ -55767,7 +55962,7 @@ async function watchProject(options, cwd, paths) {
|
|
|
55767
55962
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
55768
55963
|
configPath,
|
|
55769
55964
|
// v0.15.0 U.4+: the 4-score model replaces the single slopIndex.
|
|
55770
|
-
|
|
55965
|
+
aiSlopScore: aggregated.aiSlopScore,
|
|
55771
55966
|
engineeringHygiene: aggregated.engineeringHygiene,
|
|
55772
55967
|
security: aggregated.security,
|
|
55773
55968
|
repositoryHealth: aggregated.repositoryHealth,
|
|
@@ -58377,7 +58572,7 @@ function formatMarkdown(report) {
|
|
|
58377
58572
|
const ext = report;
|
|
58378
58573
|
lines.push(`## Repository Health`);
|
|
58379
58574
|
lines.push("");
|
|
58380
|
-
lines.push(`- **AI
|
|
58575
|
+
lines.push(`- **AI Slop Score** ${Math.round(ext.aiSlopScore ?? 0)}/100`);
|
|
58381
58576
|
lines.push(`- **Engineering Hygiene** ${Math.round(ext.engineeringHygiene ?? 0)}/100`);
|
|
58382
58577
|
lines.push(`- **Security** ${Math.round(ext.security ?? 0)}/100`);
|
|
58383
58578
|
lines.push(`- **Repository Health** ${Math.round(report.repositoryHealth ?? 0)}/100`);
|
|
@@ -59996,7 +60191,7 @@ async function runMaintenanceCostScan(cwd, config, options = {}) {
|
|
|
59996
60191
|
const hasAiSignals = detectAiSignals(scan.report, config);
|
|
59997
60192
|
const result = computeAiMaintenanceCostFromReport(
|
|
59998
60193
|
{
|
|
59999
|
-
|
|
60194
|
+
aiSlopScore: scan.report.aiSlopScore,
|
|
60000
60195
|
engineeringHygiene: scan.report.engineeringHygiene,
|
|
60001
60196
|
security: scan.report.security,
|
|
60002
60197
|
repositoryHealth: scan.report.repositoryHealth,
|