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.cjs
CHANGED
|
@@ -36,7 +36,7 @@ var VERSION;
|
|
|
36
36
|
var init_header = __esm({
|
|
37
37
|
"src/types/_header.ts"() {
|
|
38
38
|
"use strict";
|
|
39
|
-
VERSION = "0.
|
|
39
|
+
VERSION = "0.21.0";
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
|
|
@@ -25938,8 +25938,8 @@ var init_comment_ratio = __esm({
|
|
|
25938
25938
|
init_rule();
|
|
25939
25939
|
init_corpus_baselines2();
|
|
25940
25940
|
MIN_FILE_LINES = 20;
|
|
25941
|
-
FALLBACK_LOW = 0.
|
|
25942
|
-
FALLBACK_HIGH = 0.
|
|
25941
|
+
FALLBACK_LOW = 0.02;
|
|
25942
|
+
FALLBACK_HIGH = 0.55;
|
|
25943
25943
|
LINE_COMMENT_RE = /^\s*(?:\/\/|#|--|;|%)/;
|
|
25944
25944
|
BLOCK_COMMENT_OPEN_RE = /\/\*/;
|
|
25945
25945
|
aiCommentRatioRule = createRule({
|
|
@@ -26082,9 +26082,9 @@ var init_compression_profile = __esm({
|
|
|
26082
26082
|
init_rule();
|
|
26083
26083
|
init_ncd_compression();
|
|
26084
26084
|
MIN_BYTES = 2e3;
|
|
26085
|
-
GZIP_RATIO_AI_THRESHOLD = 0.
|
|
26086
|
-
MEAN_LINE_NCD_HUMAN_MAX = 0.
|
|
26087
|
-
NCD_CV_AI_MAX = 0.
|
|
26085
|
+
GZIP_RATIO_AI_THRESHOLD = 0.6;
|
|
26086
|
+
MEAN_LINE_NCD_HUMAN_MAX = 0.25;
|
|
26087
|
+
NCD_CV_AI_MAX = 0.4;
|
|
26088
26088
|
aiCompressionProfileRule = createRule({
|
|
26089
26089
|
id: "ai/compression-profile",
|
|
26090
26090
|
category: "ai",
|
|
@@ -26253,6 +26253,13 @@ var init_default_react_stack = __esm({
|
|
|
26253
26253
|
category: "ai",
|
|
26254
26254
|
severity: "low",
|
|
26255
26255
|
aiSpecific: true,
|
|
26256
|
+
// v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.001
|
|
26257
|
+
// (fires 1 time per 1,000 files). The "≥3 of default stack"
|
|
26258
|
+
// threshold catches almost nothing on real codebases — the
|
|
26259
|
+
// default stack is now ubiquitous, so the signal is near-zero.
|
|
26260
|
+
// Disable until the threshold is lowered or the rule is split
|
|
26261
|
+
// into per-library detectors.
|
|
26262
|
+
defaultOff: true,
|
|
26256
26263
|
description: "File uses \u22653 of the default React stack (Next.js + TanStack Query + Zustand + shadcn/ui) \u2014 Sascha 2025 + Nam et al. MSR 2026",
|
|
26257
26264
|
create(context) {
|
|
26258
26265
|
return context;
|
|
@@ -26526,6 +26533,10 @@ var init_library_reinvention = __esm({
|
|
|
26526
26533
|
category: "ai",
|
|
26527
26534
|
severity: "medium",
|
|
26528
26535
|
aiSpecific: true,
|
|
26536
|
+
// v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.000
|
|
26537
|
+
// (fires never on the corpus). The "≥2 reinvented patterns"
|
|
26538
|
+
// threshold is too strict. Disable until rewritten.
|
|
26539
|
+
defaultOff: true,
|
|
26529
26540
|
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",
|
|
26530
26541
|
create(context) {
|
|
26531
26542
|
return context;
|
|
@@ -26732,6 +26743,12 @@ var init_markdown_leakage = __esm({
|
|
|
26732
26743
|
category: "ai",
|
|
26733
26744
|
severity: "high",
|
|
26734
26745
|
aiSpecific: true,
|
|
26746
|
+
// v0.20.0 calibration: v8.5 verdict = INVERTED (lift 65,504× but
|
|
26747
|
+
// recall 0.000 — fires never on the corpus, and when it does fire
|
|
26748
|
+
// it's wrong). The rule was on by default, contributing false
|
|
26749
|
+
// positives to the self-scan. Disable until the rule is rewritten
|
|
26750
|
+
// or the v9 corpus shows it works.
|
|
26751
|
+
defaultOff: true,
|
|
26735
26752
|
description: "Stray Markdown fence or bare language name at top of file \u2014 AI tools leak the fenced-block format into code",
|
|
26736
26753
|
create(context) {
|
|
26737
26754
|
return context;
|
|
@@ -27206,6 +27223,11 @@ var init_text_like_ratio = __esm({
|
|
|
27206
27223
|
category: "ai",
|
|
27207
27224
|
severity: "low",
|
|
27208
27225
|
aiSpecific: true,
|
|
27226
|
+
// v0.20.0 calibration: v8.5 verdict = USEFUL but recall 0.000
|
|
27227
|
+
// (fires never on the corpus). The rule is too specific to ever
|
|
27228
|
+
// fire on real code — the prose-detection heuristic is too
|
|
27229
|
+
// conservative. Disable until rewritten.
|
|
27230
|
+
defaultOff: true,
|
|
27209
27231
|
description: "High ratio of prose-like lines embedded in code \u2014 AI tools often leave natural-language explanations in source files (Yotkova 2026)",
|
|
27210
27232
|
create(context) {
|
|
27211
27233
|
return context;
|
|
@@ -28433,7 +28455,7 @@ function collectExports(cwd) {
|
|
|
28433
28455
|
/\bexport\s+default\s+(?:function\s+|class\s+)?([A-Za-z_$][\w$]*)/g,
|
|
28434
28456
|
// v0.18.6: also collect field names from `export interface` and
|
|
28435
28457
|
// `export type` declarations. Without this, fields like
|
|
28436
|
-
// `crossFileDrift`, `
|
|
28458
|
+
// `crossFileDrift`, `aiSlopScore`, `engineeringHygiene` are
|
|
28437
28459
|
// flagged as stale even though they're valid type fields.
|
|
28438
28460
|
/^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\s*[?:]/gm
|
|
28439
28461
|
]) {
|
|
@@ -30100,6 +30122,9 @@ var init_gap_monopoly = __esm({
|
|
|
30100
30122
|
GAP_RE = /\bgap(?:-x|-y)?-(\d+)\b/g;
|
|
30101
30123
|
gapMonopolyRule = createRule({
|
|
30102
30124
|
id: "layout/gap-monopoly",
|
|
30125
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
30126
|
+
// until rewritten.
|
|
30127
|
+
defaultOff: true,
|
|
30103
30128
|
category: "layout",
|
|
30104
30129
|
severity: "medium",
|
|
30105
30130
|
aiSpecific: false,
|
|
@@ -30212,6 +30237,9 @@ var init_math_grid_uniformity = __esm({
|
|
|
30212
30237
|
GRID_COLS_RE = /\bgrid-cols-(\d+)\b/g;
|
|
30213
30238
|
mathGridUniformityRule = createRule({
|
|
30214
30239
|
id: "layout/math-grid-uniformity",
|
|
30240
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
30241
|
+
// until rewritten.
|
|
30242
|
+
defaultOff: true,
|
|
30215
30243
|
category: "layout",
|
|
30216
30244
|
severity: "high",
|
|
30217
30245
|
aiSpecific: true,
|
|
@@ -30324,6 +30352,9 @@ var init_spacing_grid = __esm({
|
|
|
30324
30352
|
]);
|
|
30325
30353
|
spacingGridRule = createRule({
|
|
30326
30354
|
id: "layout/spacing-grid",
|
|
30355
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
30356
|
+
// until rewritten.
|
|
30357
|
+
defaultOff: true,
|
|
30327
30358
|
category: "layout",
|
|
30328
30359
|
severity: "medium",
|
|
30329
30360
|
aiSpecific: false,
|
|
@@ -30447,15 +30478,9 @@ var init_boundary_violation = __esm({
|
|
|
30447
30478
|
if (context.supportsRsc === false) return [];
|
|
30448
30479
|
if (!facts.v2) return [];
|
|
30449
30480
|
const issues = [];
|
|
30450
|
-
const hookLines = /* @__PURE__ */ new Map();
|
|
30451
|
-
for (const h of facts.v2.logic.hooks) {
|
|
30452
|
-
const arr = hookLines.get(h.name) ?? [];
|
|
30453
|
-
arr.push({ line: h.line, column: h.column });
|
|
30454
|
-
hookLines.set(h.name, arr);
|
|
30455
|
-
}
|
|
30456
30481
|
for (const component of facts.v2.components) {
|
|
30457
30482
|
if (!component.isServerComponent) continue;
|
|
30458
|
-
for (const hook of
|
|
30483
|
+
for (const hook of component.hookCalls) {
|
|
30459
30484
|
if (!context.clientHooks.has(hook.name)) continue;
|
|
30460
30485
|
issues.push({
|
|
30461
30486
|
ruleId: "logic/boundary-violation",
|
|
@@ -30463,8 +30488,8 @@ var init_boundary_violation = __esm({
|
|
|
30463
30488
|
severity: "high",
|
|
30464
30489
|
aiSpecific: true,
|
|
30465
30490
|
message: `'${hook.name}' only works in interactive client components. This component runs on the server.`,
|
|
30466
|
-
line:
|
|
30467
|
-
column:
|
|
30491
|
+
line: hook.line,
|
|
30492
|
+
column: hook.column,
|
|
30468
30493
|
advice: "Add the 'use client' directive at the top of this file, or move the stateful part to a separate client component.",
|
|
30469
30494
|
fix: {
|
|
30470
30495
|
kind: "insert",
|
|
@@ -34719,7 +34744,7 @@ function isHealthFile(value) {
|
|
|
34719
34744
|
if (!isVersion3(value.version)) return false;
|
|
34720
34745
|
if (typeof value.generatedAt !== "string") return false;
|
|
34721
34746
|
if (typeof value.workspace !== "string") return false;
|
|
34722
|
-
if (!isNumber(value.
|
|
34747
|
+
if (!isNumber(value.aiSlopScore)) return false;
|
|
34723
34748
|
if (!isNumber(value.engineeringHygiene)) return false;
|
|
34724
34749
|
if (!isNumber(value.security)) return false;
|
|
34725
34750
|
if (!isNumber(value.repositoryHealth)) return false;
|
|
@@ -34882,7 +34907,7 @@ var init_dist = __esm({
|
|
|
34882
34907
|
import_fs = require("fs");
|
|
34883
34908
|
import_path = require("path");
|
|
34884
34909
|
init_zod();
|
|
34885
|
-
STRUCTURE_SCHEMA_VERSION = "
|
|
34910
|
+
STRUCTURE_SCHEMA_VERSION = "5";
|
|
34886
34911
|
INVENTORY_FILENAME = "inventory.json";
|
|
34887
34912
|
CONSTITUTION_FILENAME = "constitution.json";
|
|
34888
34913
|
CACHE_FILENAME = "cache.json";
|
|
@@ -35340,11 +35365,11 @@ async function appendRun(cwd, report, version, io, thresholdExceeded2) {
|
|
|
35340
35365
|
const run2 = {
|
|
35341
35366
|
timestamp: report.generatedAt,
|
|
35342
35367
|
version,
|
|
35343
|
-
slopIndex: report.
|
|
35368
|
+
slopIndex: report.aiSlopScore,
|
|
35344
35369
|
// MemoryAuditRun keeps slopIndex as a historical legacy field
|
|
35345
35370
|
categoryScores: { ...report.categoryScores },
|
|
35346
35371
|
topOffenseIds: topOffenseIds(report),
|
|
35347
|
-
thresholdExceeded: thresholdExceeded2 ?? report.
|
|
35372
|
+
thresholdExceeded: thresholdExceeded2 ?? report.aiSlopScore > 0
|
|
35348
35373
|
};
|
|
35349
35374
|
runs.push(run2);
|
|
35350
35375
|
if (runs.length > MAX_RUNS) {
|
|
@@ -35476,7 +35501,7 @@ function buildHealthFromReport(report, workspace, options = {}) {
|
|
|
35476
35501
|
version: STRUCTURE_SCHEMA_VERSION,
|
|
35477
35502
|
generatedAt: report.generatedAt,
|
|
35478
35503
|
workspace,
|
|
35479
|
-
|
|
35504
|
+
aiSlopScore: Math.round(report.aiSlopScore),
|
|
35480
35505
|
engineeringHygiene: Math.round(report.engineeringHygiene),
|
|
35481
35506
|
security: Math.round(report.security),
|
|
35482
35507
|
repositoryHealth: Math.round(report.repositoryHealth),
|
|
@@ -40373,7 +40398,12 @@ var init_weak_assertion = __esm({
|
|
|
40373
40398
|
"toBeUndefined",
|
|
40374
40399
|
"toBeTruthy",
|
|
40375
40400
|
"toBeFalsy",
|
|
40376
|
-
"toBeNull"
|
|
40401
|
+
"toBeNull",
|
|
40402
|
+
"toBeGreaterThan",
|
|
40403
|
+
"toBeLessThan",
|
|
40404
|
+
"toContain",
|
|
40405
|
+
"toHaveLength",
|
|
40406
|
+
"toBeInstanceOf"
|
|
40377
40407
|
]);
|
|
40378
40408
|
}
|
|
40379
40409
|
});
|
|
@@ -41340,6 +41370,10 @@ var init_math_color_cluster = __esm({
|
|
|
41340
41370
|
HEX_COLOR_RE = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b/g;
|
|
41341
41371
|
mathColorClusterRule = createRule({
|
|
41342
41372
|
id: "visual/math-color-cluster",
|
|
41373
|
+
// v0.20.0 calibration: recall 0.000, fires never. The
|
|
41374
|
+
// "math-color cluster" heuristic is too specific to ever fire
|
|
41375
|
+
// on real code. Disable until rewritten.
|
|
41376
|
+
defaultOff: true,
|
|
41343
41377
|
category: "visual",
|
|
41344
41378
|
severity: "high",
|
|
41345
41379
|
aiSpecific: true,
|
|
@@ -41401,6 +41435,9 @@ var init_math_default_font = __esm({
|
|
|
41401
41435
|
NEXT_FONT_IMPORT_RE = /import\s+.*from\s+['"]next\/font\/(?:google|local)['"]/;
|
|
41402
41436
|
mathDefaultFontRule = createRule({
|
|
41403
41437
|
id: "visual/math-default-font",
|
|
41438
|
+
// v0.20.0 calibration: recall 0.001, fires 1 per 1,000 files.
|
|
41439
|
+
// Too specific to be useful. Disable until rewritten.
|
|
41440
|
+
defaultOff: true,
|
|
41404
41441
|
category: "visual",
|
|
41405
41442
|
severity: "high",
|
|
41406
41443
|
aiSpecific: true,
|
|
@@ -41476,6 +41513,9 @@ var init_math_font_entropy = __esm({
|
|
|
41476
41513
|
TEXT_SIZE_RE = /\btext-(?:xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl)\b/g;
|
|
41477
41514
|
mathFontEntropyRule = createRule({
|
|
41478
41515
|
id: "visual/math-font-entropy",
|
|
41516
|
+
// v0.20.0 calibration: recall 0.004, fires 0 times on self-scan
|
|
41517
|
+
// (verified). Disable until rewritten.
|
|
41518
|
+
defaultOff: true,
|
|
41479
41519
|
category: "visual",
|
|
41480
41520
|
severity: "high",
|
|
41481
41521
|
aiSpecific: true,
|
|
@@ -41616,6 +41656,10 @@ var init_math_rounded_entropy = __esm({
|
|
|
41616
41656
|
ROUNDED_RE = /\brounded(?:-none|-sm|-md|-lg|-xl|-2xl|-3xl|-full)?\b/g;
|
|
41617
41657
|
mathRoundedEntropyRule = createRule({
|
|
41618
41658
|
id: "visual/math-rounded-entropy",
|
|
41659
|
+
// v0.20.0 calibration: recall 0.004, fires 0 times on self-scan
|
|
41660
|
+
// (verified). The entropy heuristic is too specific to ever fire
|
|
41661
|
+
// on real code. Disable until rewritten.
|
|
41662
|
+
defaultOff: true,
|
|
41619
41663
|
category: "visual",
|
|
41620
41664
|
severity: "high",
|
|
41621
41665
|
aiSpecific: true,
|
|
@@ -41665,6 +41709,9 @@ var init_math_spacing_entropy = __esm({
|
|
|
41665
41709
|
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;
|
|
41666
41710
|
mathSpacingEntropyRule = createRule({
|
|
41667
41711
|
id: "visual/math-spacing-entropy",
|
|
41712
|
+
// v0.20.0 calibration: recall 0.002, fires 0 times on self-scan
|
|
41713
|
+
// (verified). Disable until rewritten.
|
|
41714
|
+
defaultOff: true,
|
|
41668
41715
|
category: "visual",
|
|
41669
41716
|
severity: "medium",
|
|
41670
41717
|
aiSpecific: true,
|
|
@@ -44510,7 +44557,7 @@ var init_naturalness_anomaly = __esm({
|
|
|
44510
44557
|
init_dist2();
|
|
44511
44558
|
init_visitor();
|
|
44512
44559
|
MIN_LENGTH = 50;
|
|
44513
|
-
DISTINCT_RATIO_FLOOR = 0.
|
|
44560
|
+
DISTINCT_RATIO_FLOOR = 0.2;
|
|
44514
44561
|
naturalnessAnomalyRule = createRule({
|
|
44515
44562
|
id: "visual/naturalness-anomaly",
|
|
44516
44563
|
category: "visual",
|
|
@@ -44586,6 +44633,9 @@ var init_radius_scale_violation = __esm({
|
|
|
44586
44633
|
SCALE_TOLERANCE = 1e-3;
|
|
44587
44634
|
radiusScaleViolationRule = createRule({
|
|
44588
44635
|
id: "visual/radius-scale-violation",
|
|
44636
|
+
// v0.20.0 calibration: recall 0.000, fires never. Disable
|
|
44637
|
+
// until rewritten.
|
|
44638
|
+
defaultOff: true,
|
|
44589
44639
|
category: "visual",
|
|
44590
44640
|
severity: "medium",
|
|
44591
44641
|
aiSpecific: false,
|
|
@@ -44762,6 +44812,10 @@ var init_dragging_movements = __esm({
|
|
|
44762
44812
|
ALT_ROLE_RE = /^(?:button|application|tab|menuitem)$/;
|
|
44763
44813
|
draggingMovementsRule = createRule({
|
|
44764
44814
|
id: "wcag/dragging-movements",
|
|
44815
|
+
// v0.20.0 calibration: v8.5 = DORMANT but defaultOff: false
|
|
44816
|
+
// was a bug — DORMANT rules should be off by default. The
|
|
44817
|
+
// rule was contributing 0 useful fires. Disable.
|
|
44818
|
+
defaultOff: true,
|
|
44765
44819
|
category: "wcag",
|
|
44766
44820
|
severity: "medium",
|
|
44767
44821
|
aiSpecific: false,
|
|
@@ -44931,6 +44985,11 @@ var init_missing_alt = __esm({
|
|
|
44931
44985
|
HAS_PRESENTATION_ROLE_RE = /\brole\s*=\s*["'](?:presentation|none)["']/i;
|
|
44932
44986
|
missingAltRule = createRule({
|
|
44933
44987
|
id: "wcag/missing-alt",
|
|
44988
|
+
// v0.20.0 calibration: lift 1.0, recall 0.000. Lift of 1.0
|
|
44989
|
+
// means the rule has zero discriminative power — it fires
|
|
44990
|
+
// equally on positive and negative examples. Dead rule.
|
|
44991
|
+
// Disable until rewritten.
|
|
44992
|
+
defaultOff: true,
|
|
44934
44993
|
category: "wcag",
|
|
44935
44994
|
severity: "medium",
|
|
44936
44995
|
aiSpecific: false,
|
|
@@ -46260,17 +46319,11 @@ function colorForSlop(slopIndex) {
|
|
|
46260
46319
|
if (slopIndex >= SLOP_BADGE_YELLOW_THRESHOLD) return "yellow";
|
|
46261
46320
|
return "green";
|
|
46262
46321
|
}
|
|
46263
|
-
function colorForQuality(score) {
|
|
46264
|
-
if (score >= SLOP_BADGE_RED_THRESHOLD) return "green";
|
|
46265
|
-
if (score >= SLOP_BADGE_ORANGE_THRESHOLD) return "yellow";
|
|
46266
|
-
if (score >= SLOP_BADGE_YELLOW_THRESHOLD) return "orange";
|
|
46267
|
-
return "red";
|
|
46268
|
-
}
|
|
46269
46322
|
function formatBadge(report) {
|
|
46270
|
-
const score = report.
|
|
46323
|
+
const score = report.aiSlopScore ?? 0;
|
|
46271
46324
|
const rounded = Math.round(score);
|
|
46272
|
-
const color =
|
|
46273
|
-
return `[](https://github.com/usebrick/platform)`;
|
|
46274
46327
|
}
|
|
46275
46328
|
function formatSparkline(values) {
|
|
46276
46329
|
if (values.length === 0) return "";
|
|
@@ -46399,18 +46452,25 @@ function scoreBand(score) {
|
|
|
46399
46452
|
if (score >= 40) return { label: "needs work", color: import_chalk.default.yellow };
|
|
46400
46453
|
return { label: "concerning", color: import_chalk.default.red };
|
|
46401
46454
|
}
|
|
46455
|
+
function slopScoreBand(score) {
|
|
46456
|
+
if (score >= 70) return { label: "saturated", color: import_chalk.default.red };
|
|
46457
|
+
if (score >= 50) return { label: "high", color: import_chalk.default.red };
|
|
46458
|
+
if (score >= 30) return { label: "medium", color: import_chalk.default.yellow };
|
|
46459
|
+
if (score >= 10) return { label: "low", color: import_chalk.default.green };
|
|
46460
|
+
return { label: "no slop", color: import_chalk.default.green };
|
|
46461
|
+
}
|
|
46402
46462
|
function formatDeltaSuffix(report) {
|
|
46403
46463
|
if (typeof report.previousSlopIndex !== "number") return "";
|
|
46404
|
-
const delta = report.
|
|
46464
|
+
const delta = report.aiSlopScore - report.previousSlopIndex;
|
|
46405
46465
|
if (Math.abs(delta) < 0.5) return "";
|
|
46406
46466
|
const arrow = delta < 0 ? "\u2193" : "\u2191";
|
|
46407
46467
|
const absDelta = Math.abs(delta).toFixed(0);
|
|
46408
|
-
const word = delta
|
|
46409
|
-
const color = delta
|
|
46468
|
+
const word = delta < 0 ? "cleaner" : "worse";
|
|
46469
|
+
const color = delta < 0 ? import_chalk.default.green : import_chalk.default.red;
|
|
46410
46470
|
return color(` ${arrow}${absDelta} (${word})`);
|
|
46411
46471
|
}
|
|
46412
46472
|
function formatVerdict(report) {
|
|
46413
|
-
const score = report.
|
|
46473
|
+
const score = report.aiSlopScore;
|
|
46414
46474
|
const band = scoreBand(score);
|
|
46415
46475
|
const ranked = rankByImpact(report.issues);
|
|
46416
46476
|
const topRule = ranked[0];
|
|
@@ -46494,7 +46554,7 @@ function formatNextStep(report) {
|
|
|
46494
46554
|
` \u2192 \`slopbrick scan --baseline\` to accept today's scores as the new floor`
|
|
46495
46555
|
)
|
|
46496
46556
|
);
|
|
46497
|
-
if (report.
|
|
46557
|
+
if (report.aiSlopScore > 30) {
|
|
46498
46558
|
lines.push(
|
|
46499
46559
|
import_chalk.default.dim(
|
|
46500
46560
|
` \u2192 \`slopbrick scan --why-failing\` for the top 5 issues dragging the score down`
|
|
@@ -46518,11 +46578,11 @@ function formatWhyFailing(report) {
|
|
|
46518
46578
|
if (ranked.length === 0) {
|
|
46519
46579
|
return import_chalk.default.green("Nothing is failing the threshold. Score is clean.");
|
|
46520
46580
|
}
|
|
46521
|
-
const headline = report.
|
|
46522
|
-
const status = headline
|
|
46523
|
-
const colorize = headline
|
|
46581
|
+
const headline = report.aiSlopScore;
|
|
46582
|
+
const status = headline <= 30 ? "PASS" : "FAIL";
|
|
46583
|
+
const colorize = headline <= 30 ? import_chalk.default.green : import_chalk.default.red;
|
|
46524
46584
|
const lines = [];
|
|
46525
|
-
lines.push(colorize.bold(`Headline score: ${headline.toFixed(0)}/100 (${status} \u2014
|
|
46585
|
+
lines.push(colorize.bold(`Headline score: ${headline.toFixed(0)}/100 (${status} \u2014 above 30)`));
|
|
46526
46586
|
lines.push("");
|
|
46527
46587
|
lines.push(import_chalk.default.bold("Top 5 rules dragging the score down:"));
|
|
46528
46588
|
for (let i = 0; i < ranked.length; i++) {
|
|
@@ -46546,30 +46606,42 @@ function severityBadge(severity) {
|
|
|
46546
46606
|
const label = severity.toUpperCase().padEnd(8, " ");
|
|
46547
46607
|
return colorize(label);
|
|
46548
46608
|
}
|
|
46609
|
+
function scoreToMessage(score, type) {
|
|
46610
|
+
const tiers = SCORE_MESSAGES[type] ?? [];
|
|
46611
|
+
for (const [threshold, message] of tiers) {
|
|
46612
|
+
if (score >= threshold) return message;
|
|
46613
|
+
}
|
|
46614
|
+
const last = tiers[tiers.length - 1];
|
|
46615
|
+
return last ? last[1] : "Score unavailable";
|
|
46616
|
+
}
|
|
46549
46617
|
function formatCompositeScore(report) {
|
|
46550
|
-
const slop = report.
|
|
46618
|
+
const slop = report.aiSlopScore;
|
|
46551
46619
|
const slopValue = slop.toFixed(0).padStart(3, " ");
|
|
46552
|
-
const band =
|
|
46620
|
+
const band = slopScoreBand(slop);
|
|
46553
46621
|
const status = import_chalk.default.bold(`[${band.label.toUpperCase()}]`);
|
|
46554
46622
|
const lines = [];
|
|
46555
46623
|
const deltaSuffix = formatDeltaSuffix(report);
|
|
46556
46624
|
lines.push(
|
|
46557
|
-
import_chalk.default.bold(`AI
|
|
46625
|
+
import_chalk.default.bold(`AI Slop Score: ${slopValue} / 100 ${band.color(status)}${deltaSuffix}`)
|
|
46558
46626
|
);
|
|
46627
|
+
lines.push(` ${import_chalk.default.dim(scoreToMessage(slop, "slop"))}`);
|
|
46559
46628
|
lines.push(
|
|
46560
46629
|
import_chalk.default.dim(
|
|
46561
|
-
"
|
|
46630
|
+
"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."
|
|
46562
46631
|
)
|
|
46563
46632
|
);
|
|
46564
46633
|
lines.push(
|
|
46565
46634
|
` \u251C\u2500 boundary: ${report.boundaryScore.toFixed(0).padStart(3, " ")} (40%) ${import_chalk.default.dim("\u2014 structural integrity")}`
|
|
46566
46635
|
);
|
|
46636
|
+
lines.push(` \u2502 ${import_chalk.default.dim(scoreToMessage(report.boundaryScore, "boundary"))}`);
|
|
46567
46637
|
lines.push(
|
|
46568
46638
|
` \u251C\u2500 context: ${report.contextScore.toFixed(0).padStart(3, " ")} (35%) ${import_chalk.default.dim("\u2014 props / state / imports")}`
|
|
46569
46639
|
);
|
|
46640
|
+
lines.push(` \u2502 ${import_chalk.default.dim(scoreToMessage(report.contextScore, "context"))}`);
|
|
46570
46641
|
lines.push(
|
|
46571
46642
|
` \u2514\u2500 visual: ${report.visualScore.toFixed(0).padStart(3, " ")} (25%) ${import_chalk.default.dim("\u2014 CSS / a11y / layout")}`
|
|
46572
46643
|
);
|
|
46644
|
+
lines.push(` ${import_chalk.default.dim(scoreToMessage(report.visualScore, "visual"))}`);
|
|
46573
46645
|
const coherence = report.coherence;
|
|
46574
46646
|
if (typeof coherence === "number") {
|
|
46575
46647
|
const coherenceValue = coherence.toFixed(0).padStart(3, " ");
|
|
@@ -46586,25 +46658,52 @@ function formatCompositeScore(report) {
|
|
|
46586
46658
|
function formatCoherenceScores(report) {
|
|
46587
46659
|
const lines = [];
|
|
46588
46660
|
const domains = [
|
|
46589
|
-
{
|
|
46590
|
-
|
|
46591
|
-
|
|
46661
|
+
{
|
|
46662
|
+
key: "codeHygiene",
|
|
46663
|
+
label: "Code Hygiene",
|
|
46664
|
+
measure: "issues per category across arch/logic/layout/component/test",
|
|
46665
|
+
direction: "higher"
|
|
46666
|
+
},
|
|
46667
|
+
{
|
|
46668
|
+
key: "accessibility",
|
|
46669
|
+
label: "Accessibility",
|
|
46670
|
+
measure: "WCAG violations, ARIA misuse, focus management",
|
|
46671
|
+
direction: "higher"
|
|
46672
|
+
},
|
|
46673
|
+
{
|
|
46674
|
+
key: "performance",
|
|
46675
|
+
label: "Performance",
|
|
46676
|
+
measure: "render-blocking, N+1 queries, bundle bloat",
|
|
46677
|
+
direction: "higher"
|
|
46678
|
+
}
|
|
46592
46679
|
];
|
|
46593
|
-
for (const { key, label } of domains) {
|
|
46680
|
+
for (const { key, label, measure, direction } of domains) {
|
|
46594
46681
|
const value = report[key];
|
|
46595
46682
|
if (typeof value === "number") {
|
|
46596
46683
|
const padded = value.toString().padStart(3, " ");
|
|
46597
|
-
|
|
46684
|
+
const arrow = direction === "higher" ? "\u2191" : "\u2193";
|
|
46685
|
+
lines.push(
|
|
46686
|
+
`${label.padEnd(20)} ${padded}/100 (${arrow} ${direction} = better \xB7 ${measure})`
|
|
46687
|
+
);
|
|
46688
|
+
const messageType = key === "codeHygiene" ? "hygiene" : key === "accessibility" ? "accessibility" : key === "performance" ? "performance" : "hygiene";
|
|
46689
|
+
lines.push(`${"".padEnd(20)} ${import_chalk.default.dim(scoreToMessage(value, messageType))}`);
|
|
46598
46690
|
}
|
|
46599
46691
|
}
|
|
46600
46692
|
const bl = report.businessLogicCoherence;
|
|
46601
46693
|
if (typeof bl === "number") {
|
|
46602
46694
|
const blValue = bl.toString().padStart(3, " ");
|
|
46603
|
-
lines.push(
|
|
46695
|
+
lines.push(
|
|
46696
|
+
`${"Business Logic".padEnd(20)} ${blValue}/100 (\u2191 higher = better \xB7 tangled dependencies, cyclic imports, god modules)`
|
|
46697
|
+
);
|
|
46604
46698
|
}
|
|
46605
46699
|
if (report.aiSecurityRisk) {
|
|
46606
|
-
const
|
|
46607
|
-
lines.push(
|
|
46700
|
+
const secLabel = report.aiSecurityRisk.toUpperCase();
|
|
46701
|
+
lines.push(
|
|
46702
|
+
`${"Security Risk".padEnd(20)} ${secLabel.padEnd(9)} (\u2191 higher = better \xB7 inverted from risk level \xB7 CRITICAL = worst)`
|
|
46703
|
+
);
|
|
46704
|
+
const secToScore = { low: 100, medium: 67, high: 33, critical: 0 };
|
|
46705
|
+
const secNum = secToScore[report.aiSecurityRisk] ?? 0;
|
|
46706
|
+
lines.push(`${"".padEnd(20)} ${import_chalk.default.dim(scoreToMessage(secNum, "security"))}`);
|
|
46608
46707
|
}
|
|
46609
46708
|
return lines;
|
|
46610
46709
|
}
|
|
@@ -46672,11 +46771,11 @@ function formatDriftSection(report) {
|
|
|
46672
46771
|
}
|
|
46673
46772
|
function formatThresholds(report) {
|
|
46674
46773
|
const limit = report.thresholds.meanSlop;
|
|
46675
|
-
const headline = report.
|
|
46774
|
+
const headline = report.aiSlopScore;
|
|
46676
46775
|
const passed = headline >= 70;
|
|
46677
46776
|
const valueText = `${headline.toFixed(1)} \u2265 70`.padStart(12, " ");
|
|
46678
46777
|
const status = passed ? import_chalk.default.green("pass") : import_chalk.default.red("fail");
|
|
46679
|
-
const result = ["Threshold (CI gate)", ` AI
|
|
46778
|
+
const result = ["Threshold (CI gate)", ` AI Slop Score ${valueText} ${status}`];
|
|
46680
46779
|
void limit;
|
|
46681
46780
|
return result;
|
|
46682
46781
|
}
|
|
@@ -46757,7 +46856,7 @@ function formatPretty(report) {
|
|
|
46757
46856
|
}
|
|
46758
46857
|
function formatScoringExplainer(_report) {
|
|
46759
46858
|
return import_chalk.default.dim(
|
|
46760
|
-
"Four orthogonal scores (all 0-100, higher = better): AI
|
|
46859
|
+
"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."
|
|
46761
46860
|
);
|
|
46762
46861
|
}
|
|
46763
46862
|
function formatWhyFailingReport(report) {
|
|
@@ -46768,7 +46867,7 @@ function formatBriefReport(report) {
|
|
|
46768
46867
|
lines.push(formatVerdict(report));
|
|
46769
46868
|
lines.push("");
|
|
46770
46869
|
const scoreLines = [
|
|
46771
|
-
{ label: "AI
|
|
46870
|
+
{ label: "AI Slop Score", field: "aiSlopScore", value: report.aiSlopScore },
|
|
46772
46871
|
{ label: "Engineering Hygiene", field: "engineeringHygiene", value: report.engineeringHygiene },
|
|
46773
46872
|
{ label: "Security", field: "security", value: report.security },
|
|
46774
46873
|
{ label: "Repository Health", field: "repositoryHealth", value: report.repositoryHealth }
|
|
@@ -46783,11 +46882,11 @@ function formatBriefReport(report) {
|
|
|
46783
46882
|
` ${paddedLabel} ${band.color(valueStr)} ${import_chalk.default.dim(band.label)} ${import_chalk.default.dim.italic(`(${field})`)}${delta}`
|
|
46784
46883
|
);
|
|
46785
46884
|
});
|
|
46786
|
-
const passed = report.
|
|
46885
|
+
const passed = report.aiSlopScore <= 30;
|
|
46787
46886
|
lines.push("");
|
|
46788
46887
|
lines.push(
|
|
46789
46888
|
import_chalk.default.dim(
|
|
46790
|
-
` CI gate: AI
|
|
46889
|
+
` CI gate: AI Slop Score <= 30 -> ${passed ? import_chalk.default.green("pass") : import_chalk.default.red("fail")}`
|
|
46791
46890
|
)
|
|
46792
46891
|
);
|
|
46793
46892
|
const suppressed = report.defaultOffSuppressedCount ?? 0;
|
|
@@ -46807,7 +46906,7 @@ function formatBriefReport(report) {
|
|
|
46807
46906
|
);
|
|
46808
46907
|
return lines.join("\n");
|
|
46809
46908
|
}
|
|
46810
|
-
var import_chalk, CATEGORY_GLOSSARY;
|
|
46909
|
+
var import_chalk, CATEGORY_GLOSSARY, SCORE_MESSAGES;
|
|
46811
46910
|
var init_pretty = __esm({
|
|
46812
46911
|
"src/report/pretty.ts"() {
|
|
46813
46912
|
"use strict";
|
|
@@ -46831,19 +46930,109 @@ var init_pretty = __esm({
|
|
|
46831
46930
|
product: { short: "product", long: "feature flags, dead code, churn" },
|
|
46832
46931
|
i18n: { short: "i18n", long: "translations, locale handling" }
|
|
46833
46932
|
};
|
|
46933
|
+
SCORE_MESSAGES = {
|
|
46934
|
+
// AI Slop Score (v0.21.0: lower = cleaner; field stores the RAW
|
|
46935
|
+
// amount of slop. 0 = no AI slop, 100 = max AI slop. The tier
|
|
46936
|
+
// thresholds are flipped vs the v0.20.1 inverted reading.)
|
|
46937
|
+
slop: [
|
|
46938
|
+
[90, "Repo is saturated with AI slop"],
|
|
46939
|
+
[70, "Repo has a high amount of AI slop"],
|
|
46940
|
+
[50, "Repo has a medium amount of AI slop"],
|
|
46941
|
+
[30, "Repo has a low amount of AI slop"],
|
|
46942
|
+
[0, "Repo has no detectable AI slop"]
|
|
46943
|
+
],
|
|
46944
|
+
// Engineering Hygiene (higher = better = fewer arch/logic/layout issues)
|
|
46945
|
+
hygiene: [
|
|
46946
|
+
[90, "Repo is clean"],
|
|
46947
|
+
[70, "Repo has minor code quality issues"],
|
|
46948
|
+
[50, "Repo has moderate code quality issues"],
|
|
46949
|
+
[30, "Repo has significant code quality issues"],
|
|
46950
|
+
[0, "Repo has serious code quality issues"]
|
|
46951
|
+
],
|
|
46952
|
+
// Security (higher = better = inverted from risk level)
|
|
46953
|
+
security: [
|
|
46954
|
+
[90, "Repo has no security risks"],
|
|
46955
|
+
[70, "Repo has low security risk"],
|
|
46956
|
+
[50, "Repo has medium security risk"],
|
|
46957
|
+
[30, "Repo has high security risk"],
|
|
46958
|
+
[0, "Repo has critical security risk"]
|
|
46959
|
+
],
|
|
46960
|
+
// Repository Health (higher = better = composite of 4 scores)
|
|
46961
|
+
health: [
|
|
46962
|
+
[90, "Repo is healthy"],
|
|
46963
|
+
[70, "Repo has minor concerns"],
|
|
46964
|
+
[50, "Repo has moderate concerns"],
|
|
46965
|
+
[30, "Repo has significant concerns"],
|
|
46966
|
+
[0, "Repo has serious concerns"]
|
|
46967
|
+
],
|
|
46968
|
+
// Test Quality (higher = better = more test coverage, better assertions)
|
|
46969
|
+
test: [
|
|
46970
|
+
[90, "Tests are comprehensive"],
|
|
46971
|
+
[70, "Good test coverage"],
|
|
46972
|
+
[50, "Moderate test coverage"],
|
|
46973
|
+
[30, "Weak test coverage"],
|
|
46974
|
+
[0, "Tests are inadequate"]
|
|
46975
|
+
],
|
|
46976
|
+
// Business Logic Coherence (higher = better = fewer cyclic imports, etc.)
|
|
46977
|
+
logic: [
|
|
46978
|
+
[90, "Logic is clean"],
|
|
46979
|
+
[70, "Minor logic tangles"],
|
|
46980
|
+
[50, "Moderate logic tangles"],
|
|
46981
|
+
[30, "Significant logic tangles"],
|
|
46982
|
+
[0, "Serious logic tangles"]
|
|
46983
|
+
],
|
|
46984
|
+
// Accessibility (higher = better = fewer WCAG violations)
|
|
46985
|
+
accessibility: [
|
|
46986
|
+
[90, "No accessibility issues"],
|
|
46987
|
+
[70, "Minor accessibility issues"],
|
|
46988
|
+
[50, "Moderate accessibility issues"],
|
|
46989
|
+
[30, "Significant accessibility issues"],
|
|
46990
|
+
[0, "Critical accessibility issues"]
|
|
46991
|
+
],
|
|
46992
|
+
// Performance (higher = better = fewer render-blocking, N+1, bloat)
|
|
46993
|
+
performance: [
|
|
46994
|
+
[90, "No performance issues"],
|
|
46995
|
+
[70, "Minor performance issues"],
|
|
46996
|
+
[50, "Moderate performance issues"],
|
|
46997
|
+
[30, "Significant performance issues"],
|
|
46998
|
+
[0, "Critical performance issues"]
|
|
46999
|
+
],
|
|
47000
|
+
// Sub-score buckets (boundary/context/visual within AI Slop Score)
|
|
47001
|
+
boundary: [
|
|
47002
|
+
[90, "Boundary is clean"],
|
|
47003
|
+
[70, "Minor boundary issues"],
|
|
47004
|
+
[50, "Moderate boundary issues"],
|
|
47005
|
+
[30, "Significant boundary issues"],
|
|
47006
|
+
[0, "Boundary is broken"]
|
|
47007
|
+
],
|
|
47008
|
+
context: [
|
|
47009
|
+
[90, "Context is clean"],
|
|
47010
|
+
[70, "Minor context issues"],
|
|
47011
|
+
[50, "Moderate context issues"],
|
|
47012
|
+
[30, "Significant context issues"],
|
|
47013
|
+
[0, "Context is broken"]
|
|
47014
|
+
],
|
|
47015
|
+
visual: [
|
|
47016
|
+
[90, "Visual is clean"],
|
|
47017
|
+
[70, "Minor visual issues"],
|
|
47018
|
+
[50, "Moderate visual issues"],
|
|
47019
|
+
[30, "Significant visual issues"],
|
|
47020
|
+
[0, "Visual is broken"]
|
|
47021
|
+
]
|
|
47022
|
+
};
|
|
46834
47023
|
}
|
|
46835
47024
|
});
|
|
46836
47025
|
|
|
46837
47026
|
// src/cli/threshold.ts
|
|
46838
47027
|
function thresholdExceeded(report, config) {
|
|
46839
|
-
if ((report.
|
|
47028
|
+
if ((report.aiSlopScore ?? 0) > config.thresholds.meanSlop) {
|
|
46840
47029
|
return true;
|
|
46841
47030
|
}
|
|
46842
47031
|
return categoryThresholdBreached(report, config.thresholds.categoryThresholds);
|
|
46843
47032
|
}
|
|
46844
47033
|
function failedThresholdCount(report, config) {
|
|
46845
47034
|
let count = 0;
|
|
46846
|
-
if (report.
|
|
47035
|
+
if (report.aiSlopScore > config.thresholds.meanSlop) count += 1;
|
|
46847
47036
|
if (report.p90Score > config.thresholds.p90Slop) count += 1;
|
|
46848
47037
|
if (report.peakScore > config.thresholds.individualSlopThreshold) count += 1;
|
|
46849
47038
|
const cat = config.thresholds.categoryThresholds;
|
|
@@ -50162,14 +50351,21 @@ function aggregateReport(scores, issueGroups, config, compositeScores) {
|
|
|
50162
50351
|
}
|
|
50163
50352
|
}
|
|
50164
50353
|
const denominator = componentCount || 1;
|
|
50354
|
+
const slopAmount = {
|
|
50355
|
+
boundary: Math.min(100, Math.log10(1 + bucketPoints.boundary / denominator) / Math.log10(11) * 100),
|
|
50356
|
+
context: Math.min(100, Math.log10(1 + bucketPoints.context / denominator) / Math.log10(11) * 100),
|
|
50357
|
+
visual: Math.min(100, Math.log10(1 + bucketPoints.visual / denominator) / Math.log10(11) * 100)
|
|
50358
|
+
};
|
|
50165
50359
|
const subscore = {
|
|
50166
|
-
boundary:
|
|
50167
|
-
context:
|
|
50168
|
-
visual:
|
|
50360
|
+
boundary: 100 - slopAmount.boundary,
|
|
50361
|
+
context: 100 - slopAmount.context,
|
|
50362
|
+
visual: 100 - slopAmount.visual
|
|
50169
50363
|
};
|
|
50170
50364
|
const compositeWeights = config.compositeWeights ?? COMPOSITE_WEIGHTS;
|
|
50171
|
-
const
|
|
50172
|
-
const
|
|
50365
|
+
const aiSlopScoreRaw = compositeWeights.boundary * slopAmount.boundary + compositeWeights.context * slopAmount.context + compositeWeights.visual * slopAmount.visual;
|
|
50366
|
+
const aiSlopScore = Math.max(0, Math.min(100, aiSlopScoreRaw));
|
|
50367
|
+
const slopIndex = aiSlopScore;
|
|
50368
|
+
const assemblyHealth = Math.max(0, 100 - aiSlopScore);
|
|
50173
50369
|
const categoryPoints = {
|
|
50174
50370
|
visual: 0,
|
|
50175
50371
|
typo: 0,
|
|
@@ -50199,7 +50395,6 @@ function aggregateReport(scores, issueGroups, config, compositeScores) {
|
|
|
50199
50395
|
categoryScores[category] = categoryScores[category] / componentCount * 100;
|
|
50200
50396
|
}
|
|
50201
50397
|
}
|
|
50202
|
-
const aiQuality = Math.max(0, Math.min(100, 100 - slopIndex));
|
|
50203
50398
|
const flatIssues = [];
|
|
50204
50399
|
for (const group of issueGroups) {
|
|
50205
50400
|
for (const issue of group.issues) {
|
|
@@ -50235,7 +50430,7 @@ function aggregateReport(scores, issueGroups, config, compositeScores) {
|
|
|
50235
50430
|
const engineeringHygiene = Math.max(0, Math.min(100, 100 - engineeringRaw));
|
|
50236
50431
|
const testQualityResult = buildTestQualityScore(flatIssues, componentCount);
|
|
50237
50432
|
const testQuality = Math.max(0, Math.min(100, testQualityResult.score));
|
|
50238
|
-
const repositoryHealthRaw = 0.4 *
|
|
50433
|
+
const repositoryHealthRaw = 0.4 * (100 - aiSlopScore) + 0.3 * engineeringHygiene + 0.2 * security + 0.1 * testQuality;
|
|
50239
50434
|
const repositoryHealth = Math.max(0, Math.min(100, repositoryHealthRaw));
|
|
50240
50435
|
let compositeAggregate;
|
|
50241
50436
|
if (compositeScores && compositeScores.length > 0) {
|
|
@@ -50256,7 +50451,7 @@ function aggregateReport(scores, issueGroups, config, compositeScores) {
|
|
|
50256
50451
|
}
|
|
50257
50452
|
}
|
|
50258
50453
|
return {
|
|
50259
|
-
|
|
50454
|
+
aiSlopScore,
|
|
50260
50455
|
engineeringHygiene,
|
|
50261
50456
|
security,
|
|
50262
50457
|
repositoryHealth,
|
|
@@ -52417,8 +52612,8 @@ function computeAiMaintenanceCostFromReport(report, options = {}) {
|
|
|
52417
52612
|
}
|
|
52418
52613
|
const approxLoc = options.linesOfCode ?? (report.fileCount ?? 0) * 50;
|
|
52419
52614
|
const axes = {
|
|
52420
|
-
slopIndex: report.
|
|
52421
|
-
// The axis is named slopIndex for historical naming; the value is now
|
|
52615
|
+
slopIndex: report.aiSlopScore,
|
|
52616
|
+
// The axis is named slopIndex for historical naming; the value is now aiSlopScore (0-100, higher is better, but axisSlopIndex inverts it)
|
|
52422
52617
|
architectureConsistency: report.architectureConsistency,
|
|
52423
52618
|
aiSecurityRisk: report.aiSecurityRisk,
|
|
52424
52619
|
constitutionViolations: options.constitutionViolations,
|
|
@@ -52690,10 +52885,10 @@ function aiDebtFromScore(score) {
|
|
|
52690
52885
|
}
|
|
52691
52886
|
function perAxis(inputs) {
|
|
52692
52887
|
const out = [];
|
|
52693
|
-
if (inputs.
|
|
52888
|
+
if (inputs.aiSlopScore !== void 0 && !Number.isNaN(inputs.aiSlopScore)) {
|
|
52694
52889
|
out.push({
|
|
52695
52890
|
axis: "slopIndex",
|
|
52696
|
-
health: clamp1002(inputs.
|
|
52891
|
+
health: clamp1002(inputs.aiSlopScore),
|
|
52697
52892
|
weight: REPOSITORY_HEALTH_WEIGHTS.slopIndex
|
|
52698
52893
|
});
|
|
52699
52894
|
} else if (inputs.slopIndex !== void 0 && !Number.isNaN(inputs.slopIndex)) {
|
|
@@ -52805,10 +53000,10 @@ function buildRepositoryHealthFromReport(report, options = {}) {
|
|
|
52805
53000
|
const totalDesignTokens = (options.spacingViolations ?? 0) + (options.radiusViolations ?? 0);
|
|
52806
53001
|
const inputs = {
|
|
52807
53002
|
// v0.15.0 U.4+: the v3 headline score. Passed as the
|
|
52808
|
-
// `
|
|
53003
|
+
// `aiSlopScore` input (higher = better). The perAxis handler
|
|
52809
53004
|
// maps it to the "slopIndex" axis in the breakdown so
|
|
52810
53005
|
// dashboards don't have to migrate.
|
|
52811
|
-
|
|
53006
|
+
aiSlopScore: report.aiSlopScore,
|
|
52812
53007
|
architectureConsistency: report.architectureConsistency,
|
|
52813
53008
|
aiSecurityRisk: report.aiSecurityRisk,
|
|
52814
53009
|
designTokenViolations: totalDesignTokens > 0 ? {
|
|
@@ -53096,7 +53291,7 @@ async function enrichReport(input) {
|
|
|
53096
53291
|
const aiSignalCount = sortedIssues.filter((i) => i.aiSpecific === true).length;
|
|
53097
53292
|
aiMaintenanceCost = computeAiMaintenanceCostFromReport2(
|
|
53098
53293
|
{
|
|
53099
|
-
|
|
53294
|
+
aiSlopScore: aggregated.aiSlopScore ?? 0,
|
|
53100
53295
|
architectureConsistency,
|
|
53101
53296
|
aiSecurityRisk,
|
|
53102
53297
|
highSeverityIssueCount: sortedIssues.filter((i) => i.severity === "high").length,
|
|
@@ -53155,7 +53350,7 @@ async function enrichReport(input) {
|
|
|
53155
53350
|
)?.count ?? 0;
|
|
53156
53351
|
const composite = buildRepositoryHealthFromReport2(
|
|
53157
53352
|
{
|
|
53158
|
-
|
|
53353
|
+
aiSlopScore: aggregated.aiSlopScore ?? 0,
|
|
53159
53354
|
engineeringHygiene: aggregated.engineeringHygiene ?? 0,
|
|
53160
53355
|
security: aggregated.security ?? 0,
|
|
53161
53356
|
repositoryHealth: aggregated.repositoryHealth ?? 0,
|
|
@@ -53302,7 +53497,7 @@ function assembleScanReport(input) {
|
|
|
53302
53497
|
version: VERSION,
|
|
53303
53498
|
generatedAt,
|
|
53304
53499
|
configPath,
|
|
53305
|
-
|
|
53500
|
+
aiSlopScore: aggregated.aiSlopScore,
|
|
53306
53501
|
engineeringHygiene: aggregated.engineeringHygiene,
|
|
53307
53502
|
security: aggregated.security,
|
|
53308
53503
|
// repositoryHealth is set later from `enrichment.repositoryHealth`
|
|
@@ -53341,7 +53536,7 @@ function assembleScanReport(input) {
|
|
|
53341
53536
|
// v0.15.0 U.4+: the previous-run value is stored as
|
|
53342
53537
|
// `previousSlopIndex` on ProjectReport for backward compat with
|
|
53343
53538
|
// historical telemetry. The value itself is the previous run's
|
|
53344
|
-
//
|
|
53539
|
+
// aiSlopScore (higher = better).
|
|
53345
53540
|
previousSlopIndex: previousRun?.slopIndex,
|
|
53346
53541
|
previousRunTimestamp: previousRun?.timestamp,
|
|
53347
53542
|
p90Score: aggregated.p90Score,
|
|
@@ -53478,9 +53673,9 @@ function recordTelemetry(cwd, report, results, config) {
|
|
|
53478
53673
|
componentCount: report.componentCount,
|
|
53479
53674
|
// v0.15.0 U.4+: slopIndex is deprecated; the telemetry field is
|
|
53480
53675
|
// still named slopIndex for backward compat with historical
|
|
53481
|
-
// payloads, but the value is now
|
|
53676
|
+
// payloads, but the value is now aiSlopScore (0-100, higher is
|
|
53482
53677
|
// better).
|
|
53483
|
-
slopIndex: report.
|
|
53678
|
+
slopIndex: report.aiSlopScore,
|
|
53484
53679
|
assemblyHealth: report.assemblyHealth,
|
|
53485
53680
|
categoryScores: { ...report.categoryScores },
|
|
53486
53681
|
p90Score: report.p90Score,
|
|
@@ -53748,11 +53943,11 @@ async function persistRun(input) {
|
|
|
53748
53943
|
} = input;
|
|
53749
53944
|
const memoryReport = {
|
|
53750
53945
|
generatedAt: report.generatedAt,
|
|
53751
|
-
|
|
53946
|
+
aiSlopScore: report.aiSlopScore ?? 0,
|
|
53752
53947
|
engineeringHygiene: report.engineeringHygiene ?? 0,
|
|
53753
53948
|
security: report.security ?? 0,
|
|
53754
53949
|
repositoryHealth: report.repositoryHealth ?? 0,
|
|
53755
|
-
slopIndex: report.slopIndex ?? report.
|
|
53950
|
+
slopIndex: report.slopIndex ?? report.aiSlopScore ?? 0,
|
|
53756
53951
|
categoryScores: report.categoryScores,
|
|
53757
53952
|
issues: report.issues ?? []
|
|
53758
53953
|
};
|
|
@@ -53851,7 +54046,7 @@ async function persistRun(input) {
|
|
|
53851
54046
|
if (!options.quiet && !machineReadableStdout) {
|
|
53852
54047
|
const compositeSuffix = health.compositeScore ? ` composite=${health.compositeScore.tier}@${health.compositeScore.mean.toFixed(2)}` : "";
|
|
53853
54048
|
logger.info(
|
|
53854
|
-
`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.
|
|
54049
|
+
`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}).`
|
|
53855
54050
|
);
|
|
53856
54051
|
}
|
|
53857
54052
|
} catch (err) {
|
|
@@ -53930,7 +54125,7 @@ async function finalizeReport(input) {
|
|
|
53930
54125
|
config,
|
|
53931
54126
|
results,
|
|
53932
54127
|
aggregated: {
|
|
53933
|
-
|
|
54128
|
+
aiSlopScore: aggregated.aiSlopScore,
|
|
53934
54129
|
engineeringHygiene: aggregated.engineeringHygiene,
|
|
53935
54130
|
security: aggregated.security,
|
|
53936
54131
|
repositoryHealth: aggregated.repositoryHealth,
|
|
@@ -53962,11 +54157,11 @@ async function finalizeReport(input) {
|
|
|
53962
54157
|
const previous = (await readRuns(cwd, fsMemoryIO)).at(-1);
|
|
53963
54158
|
if (previous) {
|
|
53964
54159
|
const previousBaseline = previous.slopIndex;
|
|
53965
|
-
if ((report.
|
|
54160
|
+
if ((report.aiSlopScore ?? 0) > previousBaseline) {
|
|
53966
54161
|
noIncreaseFailure = true;
|
|
53967
54162
|
if (!options.quiet) {
|
|
53968
54163
|
logger.error(
|
|
53969
|
-
`AI
|
|
54164
|
+
`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.`
|
|
53970
54165
|
);
|
|
53971
54166
|
}
|
|
53972
54167
|
}
|
|
@@ -54399,8 +54594,8 @@ function renderHeader(report) {
|
|
|
54399
54594
|
<span class="score-label">Repository Health (composite)</span>
|
|
54400
54595
|
</div>
|
|
54401
54596
|
<div class="score-card ai-quality">
|
|
54402
|
-
<span class="score-value">${namedScores.
|
|
54403
|
-
<span class="score-label">AI
|
|
54597
|
+
<span class="score-value">${namedScores.aiSlopScore}</span>
|
|
54598
|
+
<span class="score-label">AI Slop Score</span>
|
|
54404
54599
|
</div>
|
|
54405
54600
|
<div class="score-card engineering-hygiene">
|
|
54406
54601
|
<span class="score-value">${namedScores.engineeringHygiene}</span>
|
|
@@ -54430,7 +54625,7 @@ function extractNamedScores(report) {
|
|
|
54430
54625
|
const score = (v) => typeof v === "number" && !Number.isNaN(v) ? Math.round(v).toString() : "\u2013";
|
|
54431
54626
|
const r = report;
|
|
54432
54627
|
return {
|
|
54433
|
-
|
|
54628
|
+
aiSlopScore: score(r.aiSlopScore),
|
|
54434
54629
|
engineeringHygiene: score(r.engineeringHygiene),
|
|
54435
54630
|
security: score(r.security),
|
|
54436
54631
|
repositoryHealth: score(r.repositoryHealth ?? report.repositoryHealth)
|
|
@@ -55780,7 +55975,7 @@ async function watchProject(options, cwd, paths) {
|
|
|
55780
55975
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
55781
55976
|
configPath,
|
|
55782
55977
|
// v0.15.0 U.4+: the 4-score model replaces the single slopIndex.
|
|
55783
|
-
|
|
55978
|
+
aiSlopScore: aggregated.aiSlopScore,
|
|
55784
55979
|
engineeringHygiene: aggregated.engineeringHygiene,
|
|
55785
55980
|
security: aggregated.security,
|
|
55786
55981
|
repositoryHealth: aggregated.repositoryHealth,
|
|
@@ -58468,7 +58663,7 @@ function formatMarkdown(report) {
|
|
|
58468
58663
|
const ext = report;
|
|
58469
58664
|
lines.push(`## Repository Health`);
|
|
58470
58665
|
lines.push("");
|
|
58471
|
-
lines.push(`- **AI
|
|
58666
|
+
lines.push(`- **AI Slop Score** ${Math.round(ext.aiSlopScore ?? 0)}/100`);
|
|
58472
58667
|
lines.push(`- **Engineering Hygiene** ${Math.round(ext.engineeringHygiene ?? 0)}/100`);
|
|
58473
58668
|
lines.push(`- **Security** ${Math.round(ext.security ?? 0)}/100`);
|
|
58474
58669
|
lines.push(`- **Repository Health** ${Math.round(report.repositoryHealth ?? 0)}/100`);
|
|
@@ -60087,7 +60282,7 @@ async function runMaintenanceCostScan(cwd, config, options = {}) {
|
|
|
60087
60282
|
const hasAiSignals = detectAiSignals(scan.report, config);
|
|
60088
60283
|
const result = computeAiMaintenanceCostFromReport(
|
|
60089
60284
|
{
|
|
60090
|
-
|
|
60285
|
+
aiSlopScore: scan.report.aiSlopScore,
|
|
60091
60286
|
engineeringHygiene: scan.report.engineeringHygiene,
|
|
60092
60287
|
security: scan.report.security,
|
|
60093
60288
|
repositoryHealth: scan.report.repositoryHealth,
|