slopbrick 0.18.3 → 0.18.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/worker.cjs +426 -5
- package/dist/engine/worker.js +426 -5
- package/dist/index.cjs +735 -76
- package/dist/index.d.cts +611 -511
- package/dist/index.d.ts +611 -511
- package/dist/index.js +734 -75
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -4,67 +4,35 @@ import * as _usebrick_core from '@usebrick/core';
|
|
|
4
4
|
import { SignalStrengthEntry } from '@usebrick/core';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* reviewers can match against import strings without needing the
|
|
10
|
-
* original package name. For example, `@reduxjs/toolkit` and `redux`
|
|
11
|
-
* both surface as `'redux'` under `stateManagement`.
|
|
7
|
+
* v0.18.4 (Phase B R-M2): types split.
|
|
8
|
+
* Module: _header
|
|
12
9
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* VERSION constant — read from package.json at runtime so a release
|
|
11
|
+
* version bump propagates without a separate code change here. The
|
|
12
|
+
* build's `define` rule inlines this at compile time, so the
|
|
13
|
+
* value is baked in.
|
|
14
|
+
*
|
|
15
|
+
* The literal `process.env.SLOPBRICK_VERSION` is replaced by
|
|
16
|
+
* tsup's esbuild `define` at build time, using the version field
|
|
17
|
+
* from this package's package.json. No runtime file I/O, so the
|
|
18
|
+
* dist/ bundle stays self-contained and the path-relative
|
|
19
|
+
* `require('../package.json')` trick (which breaks after bundling)
|
|
20
|
+
* is no longer needed.
|
|
17
21
|
*/
|
|
18
|
-
|
|
19
|
-
/** State-management libraries, e.g. 'zustand', 'redux', 'jotai'. */
|
|
20
|
-
stateManagement?: string[];
|
|
21
|
-
/** Data-fetching / server-state libraries, e.g. 'react-query', 'swr'. */
|
|
22
|
-
dataFetching?: string[];
|
|
23
|
-
/** UI component libraries, e.g. 'shadcn', 'radix', 'mui'. */
|
|
24
|
-
uiLibrary?: string[];
|
|
25
|
-
/** Form + schema-validation libraries, e.g. 'react-hook-form', 'zod'. */
|
|
26
|
-
forms?: string[];
|
|
27
|
-
/** Styling solutions, e.g. 'tailwind', 'styled-components', 'emotion'. */
|
|
28
|
-
styling?: string[];
|
|
29
|
-
/** Routing libraries, e.g. 'next', 'react-router', 'tanstack-router'. */
|
|
30
|
-
routing?: string[];
|
|
31
|
-
/** Free-form user-declared categories, keyed by category name. */
|
|
32
|
-
custom?: Record<string, string[]>;
|
|
33
|
-
/**
|
|
34
|
-
* Explicit deny-list of bare package specifiers and `@scope/`
|
|
35
|
-
* prefixes. Anything in this list is forbidden — even if it would
|
|
36
|
-
* otherwise match a canonical category. Matching rules (see
|
|
37
|
-
* `matchForbidden`):
|
|
38
|
-
* - `forbidden: ['moment']` matches `moment` exactly.
|
|
39
|
-
* - `forbidden: ['@types/']` matches any `@types/...` import.
|
|
40
|
-
* - `forbidden: ['lodash']` matches `lodash` but not `lodash-es`.
|
|
41
|
-
*/
|
|
42
|
-
forbidden?: string[];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
type BusinessLogicCategory = 'pricing' | 'validation' | 'formatting';
|
|
46
|
-
interface BusinessLogicIssue {
|
|
47
|
-
category: BusinessLogicCategory;
|
|
48
|
-
filePath: string;
|
|
49
|
-
line: number;
|
|
50
|
-
column: number;
|
|
51
|
-
ruleId: string;
|
|
52
|
-
message: string;
|
|
53
|
-
advice?: string;
|
|
54
|
-
}
|
|
22
|
+
declare const VERSION: string;
|
|
55
23
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
24
|
+
/**
|
|
25
|
+
* v0.18.4 (Phase B R-M2): types split.
|
|
26
|
+
* Module: primitives
|
|
27
|
+
*/
|
|
28
|
+
type Severity = 'low' | 'medium' | 'high';
|
|
29
|
+
type RuleSeverity = Severity | 'auto';
|
|
30
|
+
type Category = 'visual' | 'typo' | 'wcag' | 'layout' | 'component' | 'logic' | 'arch' | 'perf' | 'security' | 'test' | 'docs' | 'db' | 'ai' | 'context' | 'product' | 'i18n';
|
|
31
|
+
/**
|
|
32
|
+
* `react` covers `.tsx`, `.jsx`, `.ts`, `.js`. Other values are detected
|
|
33
|
+
* by file extension. Unknown extensions fall back to `'react'`.
|
|
34
|
+
*/
|
|
35
|
+
type Framework$1 = 'react' | 'vue' | 'svelte' | 'astro' | 'html';
|
|
68
36
|
|
|
69
37
|
interface FileMeta {
|
|
70
38
|
/** Absolute path to the file (or relative path from cwd, whichever the
|
|
@@ -232,6 +200,11 @@ interface ScanFactsV2 {
|
|
|
232
200
|
optimisticUpdates: OptimisticUpdateFact[];
|
|
233
201
|
};
|
|
234
202
|
designTokens: DesignTokens;
|
|
203
|
+
/**
|
|
204
|
+
* dead-code detector rules. Populated by the visitor's identifier
|
|
205
|
+
* walk + import/declaration/branch handlers. See `DeadCodeFacts`
|
|
206
|
+
* above for the shape. */
|
|
207
|
+
deadCode: DeadCodeFacts;
|
|
235
208
|
/**
|
|
236
209
|
* templates. Replaces the synthetic `<template>` elements that the
|
|
237
210
|
* migration injected into `jsx.elements`. */
|
|
@@ -248,294 +221,78 @@ interface ScanFactsV2 {
|
|
|
248
221
|
* and `--suggest` output). Not all rules need this. */
|
|
249
222
|
_source?: string;
|
|
250
223
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
* (6 values, used for LR math and calibration) to UI buckets (3 values,
|
|
255
|
-
* used in the report and JSON output).
|
|
256
|
-
*
|
|
257
|
-
* Per the user's design intent ("INVERTED is basically HYGIENE"), INVERTED
|
|
258
|
-
* rules are in the same UI bucket as HYGIENE rules. The engine still
|
|
259
|
-
* distinguishes them (INVERTED has LR < 1, HYGIENE has LR ≈ 1).
|
|
260
|
-
*/
|
|
261
|
-
type Bucket = 'ai' | 'hygiene' | 'suppressed';
|
|
262
|
-
|
|
263
|
-
declare const VERSION: string;
|
|
264
|
-
/**
|
|
265
|
-
* Runtime shape of `.slopbrick/health.json`. The on-disk JSON
|
|
266
|
-
* matches `packages/core/schemas/v1/health.schema.json` (v3); this
|
|
267
|
-
* interface is the slopbrick-side runtime companion. Schema is the
|
|
268
|
-
* source of truth — keep this in sync with the codegen output in
|
|
269
|
-
* `packages/core/src/generated/health.ts` (RepositoryMemoryHealth).
|
|
270
|
-
*
|
|
271
|
-
* v3 dropped the single `slopIndex` headline in favor of four named
|
|
272
|
-
* scores:
|
|
273
|
-
* - `aiQuality` — AI-specific findings (USEFUL + OK verdicts)
|
|
274
|
-
* - `engineeringHygiene` — HYGIENE + INVERTED rules
|
|
275
|
-
* - `security` — security/* rules
|
|
276
|
-
* - `repositoryHealth` — composite (0.5*aiQ + 0.3*eng + 0.2*sec)
|
|
224
|
+
/** What kind of declaration produced a binding. */
|
|
225
|
+
type BindingKind = 'import-specifier' | 'import-default' | 'import-namespace' | 'var' | 'let' | 'const' | 'function' | 'class' | 'type' | 'interface' | 'enum' | 'parameter' | 'catch-clause';
|
|
226
|
+
/** A single binding declaration the visitor found in the file.
|
|
277
227
|
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
228
|
+
* `isReferenced` is set to true if ANY identifier reference to this
|
|
229
|
+
* name appeared in the file. The visitor uses a single global
|
|
230
|
+
* referenced-name set per file (intra-scope shadowing is rare enough
|
|
231
|
+
* in practice that file-level resolution catches ~95% of dead code
|
|
232
|
+
* with negligible false positives — the remaining 5% is mostly
|
|
233
|
+
* intentional re-exports which are handled separately). */
|
|
234
|
+
interface BindingRecord {
|
|
235
|
+
/** The local name as written (`Button` for `import { Button } from ...`,
|
|
236
|
+
* `setFoo` for a parameter, etc.). */
|
|
237
|
+
name: string;
|
|
238
|
+
kind: BindingKind;
|
|
239
|
+
/** Source line (1-indexed). */
|
|
240
|
+
line: number;
|
|
241
|
+
/** Source column (0-indexed). */
|
|
242
|
+
column: number;
|
|
243
|
+
/** The owning source — used to differentiate top-level imports from
|
|
244
|
+
* inline `import()` expressions, which are dynamic and out of
|
|
245
|
+
* scope for the dead-import rule. */
|
|
246
|
+
source?: string;
|
|
247
|
+
/** Set to true when the visitor saw at least one reference to this
|
|
248
|
+
* name later in the file. */
|
|
249
|
+
isReferenced: boolean;
|
|
250
|
+
}
|
|
251
|
+
/** A literal boolean condition (`if (true)`, `while (false)`,
|
|
252
|
+
* ternary `cond ? a : false`) that makes the branch statically
|
|
253
|
+
* decidable. */
|
|
254
|
+
interface ConstantConditionRecord {
|
|
255
|
+
/** The kind of construct. */
|
|
256
|
+
kind: 'if-true' | 'if-false' | 'while-true' | 'while-false' | 'ternary';
|
|
257
|
+
/** The condition's source text. */
|
|
258
|
+
condition: string;
|
|
259
|
+
line: number;
|
|
260
|
+
column: number;
|
|
300
261
|
}
|
|
301
|
-
/**
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
*
|
|
313
|
-
* The numeric `health` (0-100, higher = better) and `monthlyUSD`
|
|
314
|
-
* (estimated monthly cost to fix the underlying issues) are exposed
|
|
315
|
-
* alongside the label for agents and trend pipelines that need them.
|
|
316
|
-
*/
|
|
317
|
-
type AiMaintenanceCost = 'low' | 'medium' | 'high' | 'critical';
|
|
318
|
-
/** Per-axis contribution to the AI Maintenance Cost score. */
|
|
319
|
-
interface MaintenanceAxisHealth {
|
|
320
|
-
/** Stable axis name (`slopIndex`, `architectureConsistency`, `aiSecurityRisk`, ...). */
|
|
321
|
-
axis: string;
|
|
322
|
-
/** Display label printed by the pretty reporter. */
|
|
323
|
-
label: string;
|
|
324
|
-
/** 0-100, higher = better. Derived from the underlying signal. */
|
|
325
|
-
health: number;
|
|
326
|
-
/** How this was derived, e.g. `100 - slopIndex (inverted)`. */
|
|
327
|
-
source: string;
|
|
262
|
+
/** A statement that is unreachable because an earlier statement in
|
|
263
|
+
* the same function body unconditionally exited (`return`, `throw`,
|
|
264
|
+
* `break`, `continue`). */
|
|
265
|
+
interface UnreachableStatementRecord {
|
|
266
|
+
/** Why the earlier statement exited. */
|
|
267
|
+
terminator: 'return' | 'throw' | 'break' | 'continue';
|
|
268
|
+
/** First line of the unreachable statement. */
|
|
269
|
+
line: number;
|
|
270
|
+
column: number;
|
|
271
|
+
/** A short snippet (first 60 chars) for the rule's message. */
|
|
272
|
+
snippet: string;
|
|
328
273
|
}
|
|
329
|
-
/**
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
/** v0.15.0 U.4+: 0-100, higher = better. */
|
|
336
|
-
engineeringHygiene?: number;
|
|
337
|
-
/** v0.15.0 U.4+: 0-100, higher = better. */
|
|
338
|
-
security?: number;
|
|
339
|
-
/** v0.15.0 U.4+: 0-100, higher = better. */
|
|
340
|
-
repositoryHealth?: number;
|
|
341
|
-
/** 0-100, lower = better. @deprecated v0.15.0: use aiQuality. Kept
|
|
342
|
-
* for backward compat with existing test fixtures and historical
|
|
343
|
-
* telemetry. The axis inverts it internally. */
|
|
344
|
-
slopIndex?: number;
|
|
345
|
-
/** 0-100, higher = better. From `buildArchitectureScore`. */
|
|
346
|
-
architectureConsistency?: number;
|
|
347
|
-
/** Categorical — mapped to numeric via `MAINTENANCE_SECURITY_NUMERIC`. */
|
|
348
|
-
aiSecurityRisk?: 'low' | 'medium' | 'high' | 'critical';
|
|
349
|
-
/** Total constitution violations (sum across categories). */
|
|
350
|
-
constitutionViolations?: number;
|
|
351
|
-
/** Spacing + radius scale violation counts. */
|
|
352
|
-
designTokenDrift?: {
|
|
353
|
-
spacing: number;
|
|
354
|
-
radius: number;
|
|
355
|
-
};
|
|
356
|
-
/** Total high-severity issues across the scan. Extra penalty. */
|
|
357
|
-
highSeverityIssueCount?: number;
|
|
358
|
-
/** Approximate LoC; defaults to 0 if unknown (small-project path). */
|
|
359
|
-
linesOfCode?: number;
|
|
360
|
-
/** False when the user has not declared a constitution / design tokens
|
|
361
|
-
* (no drift run). When true, applies the 1.5–2.5× AI multiplier. */
|
|
362
|
-
hasAiSignals?: boolean;
|
|
274
|
+
/** The dead-code domain. Empty when the file has nothing the visitor
|
|
275
|
+
* classified as suspicious. */
|
|
276
|
+
interface DeadCodeFacts {
|
|
277
|
+
bindings: BindingRecord[];
|
|
278
|
+
constantConditions: ConstantConditionRecord[];
|
|
279
|
+
unreachableStatements: UnreachableStatementRecord[];
|
|
363
280
|
}
|
|
364
|
-
|
|
365
|
-
interface
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
* with the CodeClimate grade→minutes mapping for per-issue cost. */
|
|
373
|
-
monthlyUSD: number;
|
|
374
|
-
/** Per-axis breakdown, sorted by health ascending (worst first). */
|
|
375
|
-
axes: MaintenanceAxisHealth[];
|
|
376
|
-
/** Per-bucket advice for managers. */
|
|
377
|
-
advice: string;
|
|
281
|
+
|
|
282
|
+
interface FixSuggestion {
|
|
283
|
+
kind: 'insert' | 'replace' | 'css-anchor';
|
|
284
|
+
description: string;
|
|
285
|
+
targetFile?: string;
|
|
286
|
+
anchor?: string;
|
|
287
|
+
oldValue?: string;
|
|
288
|
+
newValue?: string;
|
|
378
289
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
*
|
|
386
|
-
* Drives the `--strict` exit code for `slopbrick docs` (high/critical = 1).
|
|
387
|
-
*/
|
|
388
|
-
type DocDriftLevel = 'low' | 'medium' | 'high' | 'critical';
|
|
389
|
-
/** A single doc-drift finding — one rule firing on one line. */
|
|
390
|
-
interface DocFinding {
|
|
391
|
-
ruleId: 'docs/stale-package-reference' | 'docs/stale-function-reference' | 'docs/expired-code-example' | 'docs/broken-link';
|
|
392
|
-
severity: 'low' | 'medium' | 'high';
|
|
393
|
-
docFile: string;
|
|
394
|
-
line: number;
|
|
395
|
-
column: number;
|
|
396
|
-
message: string;
|
|
397
|
-
advice: string;
|
|
398
|
-
/** For `stale-package-reference`: the package the doc references. */
|
|
399
|
-
package?: string;
|
|
400
|
-
/** For `stale-function-reference`: the identifier. */
|
|
401
|
-
identifier?: string;
|
|
402
|
-
/** For `broken-link`: the unresolved link target. */
|
|
403
|
-
link?: string;
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* Categorical drift band on the db-health score.
|
|
407
|
-
* low — 80-100
|
|
408
|
-
* medium — 60-79
|
|
409
|
-
* high — 40-59
|
|
410
|
-
* critical — 0-39
|
|
411
|
-
*/
|
|
412
|
-
type DbDriftLevel = 'low' | 'medium' | 'high' | 'critical';
|
|
413
|
-
/** A single db-health finding — one rule firing on one table/file. */
|
|
414
|
-
interface DbFinding {
|
|
415
|
-
ruleId: 'db/missing-fk-index' | 'db/duplicate-index' | 'db/missing-not-null' | 'db/enum-sprawl' | 'db/naming-inconsistency' | 'db/sql-concat';
|
|
416
|
-
severity: 'low' | 'medium' | 'high';
|
|
417
|
-
dbFile: string;
|
|
418
|
-
line: number;
|
|
419
|
-
column: number;
|
|
420
|
-
message: string;
|
|
421
|
-
advice: string;
|
|
422
|
-
/** For rules that target a specific table. */
|
|
423
|
-
table?: string;
|
|
424
|
-
/** For column-level rules. */
|
|
425
|
-
columnName?: string;
|
|
426
|
-
}
|
|
427
|
-
/**
|
|
428
|
-
* Letter-grade band on the composite Repository Health score.
|
|
429
|
-
* Drives the `aiDebt` field — same categorical shape as `aiSecurityRisk`.
|
|
430
|
-
*
|
|
431
|
-
* low — 80-100 (clean; safe to ship)
|
|
432
|
-
* medium — 60-79 (manageable; revisit quarterly)
|
|
433
|
-
* high — 40-59 (block new feature work in affected subsystems)
|
|
434
|
-
* critical — 0-39 (dedicated refactor sprint required)
|
|
435
|
-
*/
|
|
436
|
-
type AiDebt = 'low' | 'medium' | 'high' | 'critical';
|
|
437
|
-
/** Numeric mapping for categorical `aiSecurityRisk` used in the composite. */
|
|
438
|
-
declare const AI_SECURITY_NUMERIC: Record<'low' | 'medium' | 'high' | 'critical', number>;
|
|
439
|
-
/** Inputs to the pure `buildRepositoryHealth` function. Every input is optional. */
|
|
440
|
-
interface RepositoryHealthInputs {
|
|
441
|
-
/** v0.15.0 U.4+: 0-100, higher = better. The new headline score
|
|
442
|
-
* that replaces slopIndex. Tests and callers should pass this
|
|
443
|
-
* going forward. */
|
|
444
|
-
aiQuality?: number;
|
|
445
|
-
/** v0.15.0 U.4+: 0-100, higher = better. */
|
|
446
|
-
engineeringHygiene?: number;
|
|
447
|
-
/** v0.15.0 U.4+: 0-100, higher = better. */
|
|
448
|
-
security?: number;
|
|
449
|
-
/** v0.15.0 U.4+: 0-100, higher = better. */
|
|
450
|
-
repositoryHealth?: number;
|
|
451
|
-
/** 0-100, lower = better. Inverted to 100 - x for the composite.
|
|
452
|
-
* @deprecated v0.15.0: use aiQuality. Kept for backward compat. */
|
|
453
|
-
slopIndex?: number;
|
|
454
|
-
/** 0-100, higher = better. */
|
|
455
|
-
architectureConsistency?: number;
|
|
456
|
-
/** Categorical — mapped via AI_SECURITY_NUMERIC. */
|
|
457
|
-
aiSecurityRisk?: 'low' | 'medium' | 'high' | 'critical';
|
|
458
|
-
/** Spacing + radius scale violation counts. */
|
|
459
|
-
designTokenViolations?: {
|
|
460
|
-
spacing: number;
|
|
461
|
-
radius: number;
|
|
462
|
-
};
|
|
463
|
-
/** Total high-severity issue count (extra penalty). */
|
|
464
|
-
highSeverityIssueCount?: number;
|
|
465
|
-
/** 0-100, higher = better. From `slopbrick test` (Phase 5). */
|
|
466
|
-
testQuality?: number;
|
|
467
|
-
/** 0-100, higher = better. From `slopbrick business-logic` (Phase 7). */
|
|
468
|
-
businessLogicCoherence?: number;
|
|
469
|
-
/** 0-100, higher = better. From `slopbrick docs` (Phase 6). */
|
|
470
|
-
docFreshness?: number;
|
|
471
|
-
/** 0-100, higher = better. From `slopbrick db` (Phase 8). */
|
|
472
|
-
dbHealth?: number;
|
|
473
|
-
/** v0.10 — MDL log-likelihood ratio from `computeMDLikelihood`.
|
|
474
|
-
* Positive = rule-firing evidence favors m_ai; negative favors
|
|
475
|
-
* m_human. Surfaced on the result for reporting; NOT folded into
|
|
476
|
-
* the weighted-average composite (which is heuristic and remains
|
|
477
|
-
* unchanged in this phase). Caller computes the ratio from the
|
|
478
|
-
* distinct `ruleId` set across the report's issues. */
|
|
479
|
-
mdlLogRatio?: number;
|
|
480
|
-
}
|
|
481
|
-
/** Result returned by `buildRepositoryHealth` + `buildRepositoryHealthFromReport`. */
|
|
482
|
-
interface RepositoryHealth {
|
|
483
|
-
/** Composite 0-100 score (higher = better). */
|
|
484
|
-
score: number;
|
|
485
|
-
/** Letter-grade band derived from `score`. */
|
|
486
|
-
aiDebt: AiDebt;
|
|
487
|
-
/** Per-axis contribution breakdown (axis → 0-100, higher = better). */
|
|
488
|
-
breakdown: Record<string, number>;
|
|
489
|
-
/** Per-axis weight actually applied (post-renormalization). */
|
|
490
|
-
appliedWeights: Record<string, number>;
|
|
491
|
-
/** Warnings for managers (e.g. "Critical security risk overrides everything"). */
|
|
492
|
-
warnings: string[];
|
|
493
|
-
/** One-line summary. */
|
|
494
|
-
headline: string;
|
|
495
|
-
/** v0.10 — MDL log-likelihood ratio (positive = m_ai evidence).
|
|
496
|
-
* Surfaced when the caller passed `mdlLogRatio` to
|
|
497
|
-
* `buildRepositoryHealth`. Not folded into the weighted-average
|
|
498
|
-
* composite in this phase (kept separate so the heuristic score
|
|
499
|
-
* can be audited independently of the MDL axis). */
|
|
500
|
-
mdlLogRatio?: number;
|
|
501
|
-
}
|
|
502
|
-
/** Default weights — sum to 1.0. Optional axes are skipped, and the
|
|
503
|
-
* remaining weights renormalize to 1.0. Tuned to give `aiSecurityRisk`
|
|
504
|
-
* the heaviest weight (it is the catastrophic single-event dimension)
|
|
505
|
-
* while still letting slopIndex + architecture drive most cases. */
|
|
506
|
-
declare const REPOSITORY_HEALTH_WEIGHTS: {
|
|
507
|
-
readonly slopIndex: 0.2;
|
|
508
|
-
readonly architectureConsistency: 0.2;
|
|
509
|
-
readonly aiSecurityRisk: 0.2;
|
|
510
|
-
readonly designTokenViolations: 0.1;
|
|
511
|
-
readonly testQuality: 0.1;
|
|
512
|
-
readonly businessLogicCoherence: 0.1;
|
|
513
|
-
readonly docFreshness: 0.05;
|
|
514
|
-
readonly dbHealth: 0.05;
|
|
515
|
-
};
|
|
516
|
-
|
|
517
|
-
type Severity = 'low' | 'medium' | 'high';
|
|
518
|
-
type RuleSeverity = Severity | 'auto';
|
|
519
|
-
type Category = 'visual' | 'typo' | 'wcag' | 'layout' | 'component' | 'logic' | 'arch' | 'perf' | 'security' | 'test' | 'docs' | 'db' | 'ai' | 'context' | 'product' | 'i18n';
|
|
520
|
-
/**
|
|
521
|
-
* `react` covers `.tsx`, `.jsx`, `.ts`, `.js`. Other values are detected
|
|
522
|
-
* by file extension. Unknown extensions fall back to `'react'`.
|
|
523
|
-
*/
|
|
524
|
-
type Framework$1 = 'react' | 'vue' | 'svelte' | 'astro' | 'html';
|
|
525
|
-
interface FixSuggestion {
|
|
526
|
-
kind: 'insert' | 'replace' | 'css-anchor';
|
|
527
|
-
description: string;
|
|
528
|
-
targetFile?: string;
|
|
529
|
-
anchor?: string;
|
|
530
|
-
oldValue?: string;
|
|
531
|
-
newValue?: string;
|
|
532
|
-
}
|
|
533
|
-
interface Issue {
|
|
534
|
-
ruleId: string;
|
|
535
|
-
category: Category;
|
|
536
|
-
severity: Severity;
|
|
537
|
-
aiSpecific: boolean;
|
|
538
|
-
filePath?: string;
|
|
290
|
+
interface Issue {
|
|
291
|
+
ruleId: string;
|
|
292
|
+
category: Category;
|
|
293
|
+
severity: Severity;
|
|
294
|
+
aiSpecific: boolean;
|
|
295
|
+
filePath?: string;
|
|
539
296
|
message: string;
|
|
540
297
|
line: number;
|
|
541
298
|
column: number;
|
|
@@ -816,19 +573,527 @@ interface FileScanResult {
|
|
|
816
573
|
*/
|
|
817
574
|
compositeScore?: _usebrick_engine.CompositeScore;
|
|
818
575
|
}
|
|
819
|
-
interface ComponentScore {
|
|
576
|
+
interface ComponentScore {
|
|
577
|
+
filePath: string;
|
|
578
|
+
rawScore: number;
|
|
579
|
+
componentScore: number;
|
|
580
|
+
adjustedScore: number;
|
|
581
|
+
componentCount: number;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Constitution declared by the user (or auto-detected from package.json).
|
|
586
|
+
* Each allow-list field is a list of canonical names — agents and
|
|
587
|
+
* reviewers can match against import strings without needing the
|
|
588
|
+
* original package name. For example, `@reduxjs/toolkit` and `redux`
|
|
589
|
+
* both surface as `'redux'` under `stateManagement`.
|
|
590
|
+
*
|
|
591
|
+
* `forbidden` is the deny-list: bare package specifiers and `@scope/`
|
|
592
|
+
* prefixes that the project has decided are off-limits. Matched against
|
|
593
|
+
* imports during `slop_check_constitution` and the `slopbrick drift`
|
|
594
|
+
* command.
|
|
595
|
+
*/
|
|
596
|
+
interface Constitution {
|
|
597
|
+
/** State-management libraries, e.g. 'zustand', 'redux', 'jotai'. */
|
|
598
|
+
stateManagement?: string[];
|
|
599
|
+
/** Data-fetching / server-state libraries, e.g. 'react-query', 'swr'. */
|
|
600
|
+
dataFetching?: string[];
|
|
601
|
+
/** UI component libraries, e.g. 'shadcn', 'radix', 'mui'. */
|
|
602
|
+
uiLibrary?: string[];
|
|
603
|
+
/** Form + schema-validation libraries, e.g. 'react-hook-form', 'zod'. */
|
|
604
|
+
forms?: string[];
|
|
605
|
+
/** Styling solutions, e.g. 'tailwind', 'styled-components', 'emotion'. */
|
|
606
|
+
styling?: string[];
|
|
607
|
+
/** Routing libraries, e.g. 'next', 'react-router', 'tanstack-router'. */
|
|
608
|
+
routing?: string[];
|
|
609
|
+
/** Free-form user-declared categories, keyed by category name. */
|
|
610
|
+
custom?: Record<string, string[]>;
|
|
611
|
+
/**
|
|
612
|
+
* Explicit deny-list of bare package specifiers and `@scope/`
|
|
613
|
+
* prefixes. Anything in this list is forbidden — even if it would
|
|
614
|
+
* otherwise match a canonical category. Matching rules (see
|
|
615
|
+
* `matchForbidden`):
|
|
616
|
+
* - `forbidden: ['moment']` matches `moment` exactly.
|
|
617
|
+
* - `forbidden: ['@types/']` matches any `@types/...` import.
|
|
618
|
+
* - `forbidden: ['lodash']` matches `lodash` but not `lodash-es`.
|
|
619
|
+
*/
|
|
620
|
+
forbidden?: string[];
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
interface MagicNumberSpacingConfig {
|
|
624
|
+
ignoreProperties?: string[];
|
|
625
|
+
ignoreZero?: boolean;
|
|
626
|
+
}
|
|
627
|
+
interface RuleContext {
|
|
628
|
+
config: ResolvedConfig;
|
|
629
|
+
filePath: string;
|
|
630
|
+
cwd: string;
|
|
631
|
+
framework?: string;
|
|
632
|
+
uiLibraries?: string[];
|
|
633
|
+
hasTailwind?: boolean;
|
|
634
|
+
supportsRsc?: boolean;
|
|
635
|
+
hotspotIssues?: Issue[];
|
|
636
|
+
}
|
|
637
|
+
interface Rule<Context = unknown> {
|
|
638
|
+
id: string;
|
|
639
|
+
category: Category;
|
|
640
|
+
severity: Severity;
|
|
641
|
+
aiSpecific: boolean;
|
|
642
|
+
/** Round 25: short description shown by `slopbrick rules` and used in docs. */
|
|
643
|
+
description?: string;
|
|
644
|
+
create(context: RuleContext): Context;
|
|
645
|
+
analyze(context: Context, facts: ScanFacts): Issue[];
|
|
646
|
+
}
|
|
647
|
+
interface ResolvedConfig {
|
|
648
|
+
framework?: string;
|
|
649
|
+
hasTailwind?: boolean;
|
|
650
|
+
supportsRsc?: boolean;
|
|
651
|
+
uiLibraries?: string[];
|
|
652
|
+
include: string[];
|
|
653
|
+
exclude: string[];
|
|
654
|
+
rules: Record<string, RuleSeverity | 'off'>;
|
|
655
|
+
categoryWeights?: Record<Category, number>;
|
|
656
|
+
/**
|
|
657
|
+
* Defaults to { boundary: 0.40, context: 0.35, visual: 0.25 } when unset. */
|
|
658
|
+
compositeWeights?: {
|
|
659
|
+
boundary: number;
|
|
660
|
+
context: number;
|
|
661
|
+
visual: number;
|
|
662
|
+
};
|
|
663
|
+
frameworkMultipliers: Record<string, number>;
|
|
664
|
+
ruleConfig: Record<string, unknown>;
|
|
665
|
+
gapTokens?: string[];
|
|
666
|
+
globalCssTarget?: string;
|
|
667
|
+
projectMemory?: boolean;
|
|
668
|
+
telemetry?: boolean;
|
|
669
|
+
thresholds: {
|
|
670
|
+
meanSlop: number;
|
|
671
|
+
p90Slop: number;
|
|
672
|
+
individualSlopThreshold: number;
|
|
673
|
+
/** Round 20: per-category thresholds (security fails, typo warns). Optional. */
|
|
674
|
+
categoryThresholds?: Partial<Record<Category, number>>;
|
|
675
|
+
};
|
|
676
|
+
spacingScale?: number[];
|
|
677
|
+
/**
|
|
678
|
+
* Declared border-radius scale in rem (Tailwind default) plus the
|
|
679
|
+
* string 'full' for the 9999px utility. Numeric values that don't
|
|
680
|
+
* fall on the scale (after px→rem conversion) trigger the
|
|
681
|
+
* `visual/radius-scale-violation` rule.
|
|
682
|
+
*/
|
|
683
|
+
radiusScale?: (number | 'full')[];
|
|
684
|
+
typographyScale?: string[];
|
|
685
|
+
arbitraryValueAllowlist: (string | RegExp)[];
|
|
686
|
+
clampAllowlist?: (string | RegExp)[];
|
|
687
|
+
/** Phase 2 §10: allowed import paths from brick.config.json. Imports
|
|
688
|
+
* from `@/components/*` not matching these patterns are flagged by
|
|
689
|
+
* `context/import-path-mismatch`. */
|
|
690
|
+
allowedImports?: string[];
|
|
691
|
+
wcag: {
|
|
692
|
+
targetSizeExemptSelectors: string[];
|
|
693
|
+
targetSizeRequireTailwind?: boolean;
|
|
694
|
+
};
|
|
695
|
+
/**
|
|
696
|
+
* Declared repository constitution: "this repo uses Zustand for
|
|
697
|
+
* state, shadcn for UI, react-query for data fetching" plus an
|
|
698
|
+
* explicit deny-list of forbidden packages. Drives the `slop_suggest`
|
|
699
|
+
* and `slop_check_constitution` MCP tools and the
|
|
700
|
+
* `slopbrick drift` reporter. Auto-detected from package.json when
|
|
701
|
+
* unset; user declarations always win (including explicit empty
|
|
702
|
+
* arrays, which mean "we deliberately don't use this category").
|
|
703
|
+
*/
|
|
704
|
+
constitution?: Constitution;
|
|
705
|
+
/**
|
|
706
|
+
* PR slop score threshold (Phase 11). `slopbrick pr` exits 1 when
|
|
707
|
+
* the PR introduces more than this many weighted slop points across
|
|
708
|
+
* the changed files. Default: 20. Lower it to fail PRs that add any
|
|
709
|
+
* meaningful slop; raise it to be more permissive. Overridden on
|
|
710
|
+
* the CLI by `--threshold <n>`.
|
|
711
|
+
*/
|
|
712
|
+
prScoreThreshold?: number;
|
|
713
|
+
/**
|
|
714
|
+
* Phase 5 — Test Intelligence opt-in toggles. The four `test/*`
|
|
715
|
+
* rules are safe-by-default (each rule short-circuits on non-test
|
|
716
|
+
* files via `isTestFile()`), but `test/missing-edge-case` walks
|
|
717
|
+
* production code to find untested branches. Off by default —
|
|
718
|
+
* opt-in per project.
|
|
719
|
+
*/
|
|
720
|
+
testIntelligence?: {
|
|
721
|
+
/** Walk production AST to find branches without test coverage. */
|
|
722
|
+
missingEdgeCase?: boolean;
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* v0.15.0+: The user-facing 3-bucket taxonomy. Maps engine verdicts
|
|
728
|
+
* (6 values, used for LR math and calibration) to UI buckets (3 values,
|
|
729
|
+
* used in the report and JSON output).
|
|
730
|
+
*
|
|
731
|
+
* Per the user's design intent ("INVERTED is basically HYGIENE"), INVERTED
|
|
732
|
+
* rules are in the same UI bucket as HYGIENE rules. The engine still
|
|
733
|
+
* distinguishes them (INVERTED has LR < 1, HYGIENE has LR ≈ 1).
|
|
734
|
+
*/
|
|
735
|
+
type Bucket = 'ai' | 'hygiene' | 'suppressed';
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* v0.18.4 (Phase B R-M2): types split.
|
|
739
|
+
* Module: report
|
|
740
|
+
*/
|
|
741
|
+
/**
|
|
742
|
+
* Runtime shape of `.slopbrick/health.json`. The on-disk JSON
|
|
743
|
+
* matches `packages/core/schemas/v1/health.schema.json` (v3); this
|
|
744
|
+
* interface is the slopbrick-side runtime companion. Schema is the
|
|
745
|
+
* source of truth — keep this in sync with the codegen output in
|
|
746
|
+
* `packages/core/src/generated/health.ts` (RepositoryMemoryHealth).
|
|
747
|
+
*
|
|
748
|
+
* v3 dropped the single `slopIndex` headline in favor of four named
|
|
749
|
+
* scores:
|
|
750
|
+
* - `aiQuality` — AI-specific findings (USEFUL + OK verdicts)
|
|
751
|
+
* - `engineeringHygiene` — HYGIENE + INVERTED rules
|
|
752
|
+
* - `security` — security/* rules
|
|
753
|
+
* - `repositoryHealth` — composite (0.5*aiQ + 0.3*eng + 0.2*sec)
|
|
754
|
+
*
|
|
755
|
+
* Plus optional verdict + bucket distributions for agent-readable
|
|
756
|
+
* insight.
|
|
757
|
+
*/
|
|
758
|
+
interface HealthFile {
|
|
759
|
+
version: typeof _usebrick_core.STRUCTURE_SCHEMA_VERSION;
|
|
760
|
+
generatedAt: string;
|
|
761
|
+
workspace: string;
|
|
762
|
+
aiQuality: number;
|
|
763
|
+
engineeringHygiene: number;
|
|
764
|
+
security: number;
|
|
765
|
+
repositoryHealth: number;
|
|
766
|
+
verdictDistribution?: Record<_usebrick_core.Verdict, number>;
|
|
767
|
+
bucketDistribution?: Record<Bucket, number>;
|
|
768
|
+
categoryScores: Record<string, number>;
|
|
769
|
+
issueCounts: {
|
|
770
|
+
high: number;
|
|
771
|
+
medium: number;
|
|
772
|
+
low: number;
|
|
773
|
+
};
|
|
774
|
+
constitutionDrift?: number;
|
|
775
|
+
topOffenseIds?: string[];
|
|
776
|
+
scanDurationMs?: number;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Categorical bucket for the AI Maintenance Cost score.
|
|
780
|
+
*
|
|
781
|
+
* low — ship as-is, revisit quarterly
|
|
782
|
+
* medium — schedule cleanup work
|
|
783
|
+
* high — block new feature work in the affected subsystem
|
|
784
|
+
* critical — dedicated refactor sprint required
|
|
785
|
+
*
|
|
786
|
+
* Deliberately a categorical label (not numeric). Same reasoning as
|
|
787
|
+
* `aiSecurityRisk`: a single bucket is harder to game than a number,
|
|
788
|
+
* and a manager can read "AI Maintenance Cost: HIGH" in two seconds.
|
|
789
|
+
*
|
|
790
|
+
* The numeric `health` (0-100, higher = better) and `monthlyUSD`
|
|
791
|
+
* (estimated monthly cost to fix the underlying issues) are exposed
|
|
792
|
+
* alongside the label for agents and trend pipelines that need them.
|
|
793
|
+
*/
|
|
794
|
+
type AiMaintenanceCost = 'low' | 'medium' | 'high' | 'critical';
|
|
795
|
+
/** Per-axis contribution to the AI Maintenance Cost score. */
|
|
796
|
+
interface MaintenanceAxisHealth {
|
|
797
|
+
/** Stable axis name (`slopIndex`, `architectureConsistency`, `aiSecurityRisk`, ...). */
|
|
798
|
+
axis: string;
|
|
799
|
+
/** Display label printed by the pretty reporter. */
|
|
800
|
+
label: string;
|
|
801
|
+
/** 0-100, higher = better. Derived from the underlying signal. */
|
|
802
|
+
health: number;
|
|
803
|
+
/** How this was derived, e.g. `100 - slopIndex (inverted)`. */
|
|
804
|
+
source: string;
|
|
805
|
+
}
|
|
806
|
+
/** Inputs to the pure `computeAiMaintenanceCost` function. Every axis is optional. */
|
|
807
|
+
interface MaintenanceAxes {
|
|
808
|
+
/** v0.15.0 U.4+: 0-100, higher = better. The new headline score
|
|
809
|
+
* that replaces slopIndex. Tests and callers should pass this
|
|
810
|
+
* going forward. */
|
|
811
|
+
aiQuality?: number;
|
|
812
|
+
/** v0.15.0 U.4+: 0-100, higher = better. */
|
|
813
|
+
engineeringHygiene?: number;
|
|
814
|
+
/** v0.15.0 U.4+: 0-100, higher = better. */
|
|
815
|
+
security?: number;
|
|
816
|
+
/** v0.15.0 U.4+: 0-100, higher = better. */
|
|
817
|
+
repositoryHealth?: number;
|
|
818
|
+
/** 0-100, lower = better. @deprecated v0.15.0: use aiQuality. Kept
|
|
819
|
+
* for backward compat with existing test fixtures and historical
|
|
820
|
+
* telemetry. The axis inverts it internally. */
|
|
821
|
+
slopIndex?: number;
|
|
822
|
+
/** 0-100, higher = better. From `buildArchitectureScore`. */
|
|
823
|
+
architectureConsistency?: number;
|
|
824
|
+
/** Categorical — mapped to numeric via `MAINTENANCE_SECURITY_NUMERIC`. */
|
|
825
|
+
aiSecurityRisk?: 'low' | 'medium' | 'high' | 'critical';
|
|
826
|
+
/** Total constitution violations (sum across categories). */
|
|
827
|
+
constitutionViolations?: number;
|
|
828
|
+
/** Spacing + radius scale violation counts. */
|
|
829
|
+
designTokenDrift?: {
|
|
830
|
+
spacing: number;
|
|
831
|
+
radius: number;
|
|
832
|
+
};
|
|
833
|
+
/** Total high-severity issues across the scan. Extra penalty. */
|
|
834
|
+
highSeverityIssueCount?: number;
|
|
835
|
+
/** Approximate LoC; defaults to 0 if unknown (small-project path). */
|
|
836
|
+
linesOfCode?: number;
|
|
837
|
+
/** False when the user has not declared a constitution / design tokens
|
|
838
|
+
* (no drift run). When true, applies the 1.5–2.5× AI multiplier. */
|
|
839
|
+
hasAiSignals?: boolean;
|
|
840
|
+
}
|
|
841
|
+
/** Result returned by `computeAiMaintenanceCost` + `computeAiMaintenanceCostFromReport`. */
|
|
842
|
+
interface AiMaintenanceCostResult {
|
|
843
|
+
/** Categorical headline. */
|
|
844
|
+
cost: AiMaintenanceCost;
|
|
845
|
+
/** Weighted health in 0-100 (higher = better). Bucket is derived from this. */
|
|
846
|
+
health: number;
|
|
847
|
+
/** Estimated monthly USD cost to fix the underlying issues.
|
|
848
|
+
* Anchored to Sonar's $306K/yr/MLoC baseline ($25.50/1k LoC/month)
|
|
849
|
+
* with the CodeClimate grade→minutes mapping for per-issue cost. */
|
|
850
|
+
monthlyUSD: number;
|
|
851
|
+
/** Per-axis breakdown, sorted by health ascending (worst first). */
|
|
852
|
+
axes: MaintenanceAxisHealth[];
|
|
853
|
+
/** Per-bucket advice for managers. */
|
|
854
|
+
advice: string;
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Categorical drift band on the doc-freshness score.
|
|
858
|
+
* low — 80-100
|
|
859
|
+
* medium — 60-79
|
|
860
|
+
* high — 40-59
|
|
861
|
+
* critical — 0-39
|
|
862
|
+
*
|
|
863
|
+
* Drives the `--strict` exit code for `slopbrick docs` (high/critical = 1).
|
|
864
|
+
*/
|
|
865
|
+
type DocDriftLevel = 'low' | 'medium' | 'high' | 'critical';
|
|
866
|
+
/** A single doc-drift finding — one rule firing on one line. */
|
|
867
|
+
interface DocFinding {
|
|
868
|
+
ruleId: 'docs/stale-package-reference' | 'docs/stale-function-reference' | 'docs/expired-code-example' | 'docs/broken-link';
|
|
869
|
+
severity: 'low' | 'medium' | 'high';
|
|
870
|
+
docFile: string;
|
|
871
|
+
line: number;
|
|
872
|
+
column: number;
|
|
873
|
+
message: string;
|
|
874
|
+
advice: string;
|
|
875
|
+
/** For `stale-package-reference`: the package the doc references. */
|
|
876
|
+
package?: string;
|
|
877
|
+
/** For `stale-function-reference`: the identifier. */
|
|
878
|
+
identifier?: string;
|
|
879
|
+
/** For `broken-link`: the unresolved link target. */
|
|
880
|
+
link?: string;
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Categorical drift band on the db-health score.
|
|
884
|
+
* low — 80-100
|
|
885
|
+
* medium — 60-79
|
|
886
|
+
* high — 40-59
|
|
887
|
+
* critical — 0-39
|
|
888
|
+
*/
|
|
889
|
+
type DbDriftLevel = 'low' | 'medium' | 'high' | 'critical';
|
|
890
|
+
/** A single db-health finding — one rule firing on one table/file. */
|
|
891
|
+
interface DbFinding {
|
|
892
|
+
ruleId: 'db/missing-fk-index' | 'db/duplicate-index' | 'db/missing-not-null' | 'db/enum-sprawl' | 'db/naming-inconsistency' | 'db/sql-concat';
|
|
893
|
+
severity: 'low' | 'medium' | 'high';
|
|
894
|
+
dbFile: string;
|
|
895
|
+
line: number;
|
|
896
|
+
column: number;
|
|
897
|
+
message: string;
|
|
898
|
+
advice: string;
|
|
899
|
+
/** For rules that target a specific table. */
|
|
900
|
+
table?: string;
|
|
901
|
+
/** For column-level rules. */
|
|
902
|
+
columnName?: string;
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Letter-grade band on the composite Repository Health score.
|
|
906
|
+
* Drives the `aiDebt` field — same categorical shape as `aiSecurityRisk`.
|
|
907
|
+
*
|
|
908
|
+
* low — 80-100 (clean; safe to ship)
|
|
909
|
+
* medium — 60-79 (manageable; revisit quarterly)
|
|
910
|
+
* high — 40-59 (block new feature work in affected subsystems)
|
|
911
|
+
* critical — 0-39 (dedicated refactor sprint required)
|
|
912
|
+
*/
|
|
913
|
+
type AiDebt = 'low' | 'medium' | 'high' | 'critical';
|
|
914
|
+
/** Numeric mapping for categorical `aiSecurityRisk` used in the composite. */
|
|
915
|
+
declare const AI_SECURITY_NUMERIC: Record<'low' | 'medium' | 'high' | 'critical', number>;
|
|
916
|
+
/** Inputs to the pure `buildRepositoryHealth` function. Every input is optional. */
|
|
917
|
+
interface RepositoryHealthInputs {
|
|
918
|
+
/** v0.15.0 U.4+: 0-100, higher = better. The new headline score
|
|
919
|
+
* that replaces slopIndex. Tests and callers should pass this
|
|
920
|
+
* going forward. */
|
|
921
|
+
aiQuality?: number;
|
|
922
|
+
/** v0.15.0 U.4+: 0-100, higher = better. */
|
|
923
|
+
engineeringHygiene?: number;
|
|
924
|
+
/** v0.15.0 U.4+: 0-100, higher = better. */
|
|
925
|
+
security?: number;
|
|
926
|
+
/** v0.15.0 U.4+: 0-100, higher = better. */
|
|
927
|
+
repositoryHealth?: number;
|
|
928
|
+
/** 0-100, lower = better. Inverted to 100 - x for the composite.
|
|
929
|
+
* @deprecated v0.15.0: use aiQuality. Kept for backward compat. */
|
|
930
|
+
slopIndex?: number;
|
|
931
|
+
/** 0-100, higher = better. */
|
|
932
|
+
architectureConsistency?: number;
|
|
933
|
+
/** Categorical — mapped via AI_SECURITY_NUMERIC. */
|
|
934
|
+
aiSecurityRisk?: 'low' | 'medium' | 'high' | 'critical';
|
|
935
|
+
/** Spacing + radius scale violation counts. */
|
|
936
|
+
designTokenViolations?: {
|
|
937
|
+
spacing: number;
|
|
938
|
+
radius: number;
|
|
939
|
+
};
|
|
940
|
+
/** Total high-severity issue count (extra penalty). */
|
|
941
|
+
highSeverityIssueCount?: number;
|
|
942
|
+
/** 0-100, higher = better. From `slopbrick test` (Phase 5). */
|
|
943
|
+
testQuality?: number;
|
|
944
|
+
/** 0-100, higher = better. From `slopbrick business-logic` (Phase 7). */
|
|
945
|
+
businessLogicCoherence?: number;
|
|
946
|
+
/** 0-100, higher = better. From `slopbrick docs` (Phase 6). */
|
|
947
|
+
docFreshness?: number;
|
|
948
|
+
/** 0-100, higher = better. From `slopbrick db` (Phase 8). */
|
|
949
|
+
dbHealth?: number;
|
|
950
|
+
/** v0.10 — MDL log-likelihood ratio from `computeMDLikelihood`.
|
|
951
|
+
* Positive = rule-firing evidence favors m_ai; negative favors
|
|
952
|
+
* m_human. Surfaced on the result for reporting; NOT folded into
|
|
953
|
+
* the weighted-average composite (which is heuristic and remains
|
|
954
|
+
* unchanged in this phase). Caller computes the ratio from the
|
|
955
|
+
* distinct `ruleId` set across the report's issues. */
|
|
956
|
+
mdlLogRatio?: number;
|
|
957
|
+
}
|
|
958
|
+
/** Result returned by `buildRepositoryHealth` + `buildRepositoryHealthFromReport`. */
|
|
959
|
+
interface RepositoryHealth {
|
|
960
|
+
/** Composite 0-100 score (higher = better). */
|
|
961
|
+
score: number;
|
|
962
|
+
/** Letter-grade band derived from `score`. */
|
|
963
|
+
aiDebt: AiDebt;
|
|
964
|
+
/** Per-axis contribution breakdown (axis → 0-100, higher = better). */
|
|
965
|
+
breakdown: Record<string, number>;
|
|
966
|
+
/** Per-axis weight actually applied (post-renormalization). */
|
|
967
|
+
appliedWeights: Record<string, number>;
|
|
968
|
+
/** Warnings for managers (e.g. "Critical security risk overrides everything"). */
|
|
969
|
+
warnings: string[];
|
|
970
|
+
/** One-line summary. */
|
|
971
|
+
headline: string;
|
|
972
|
+
/** v0.10 — MDL log-likelihood ratio (positive = m_ai evidence).
|
|
973
|
+
* Surfaced when the caller passed `mdlLogRatio` to
|
|
974
|
+
* `buildRepositoryHealth`. Not folded into the weighted-average
|
|
975
|
+
* composite in this phase (kept separate so the heuristic score
|
|
976
|
+
* can be audited independently of the MDL axis). */
|
|
977
|
+
mdlLogRatio?: number;
|
|
978
|
+
}
|
|
979
|
+
/** Default weights — sum to 1.0. Optional axes are skipped, and the
|
|
980
|
+
* remaining weights renormalize to 1.0. Tuned to give `aiSecurityRisk`
|
|
981
|
+
* the heaviest weight (it is the catastrophic single-event dimension)
|
|
982
|
+
* while still letting slopIndex + architecture drive most cases. */
|
|
983
|
+
declare const REPOSITORY_HEALTH_WEIGHTS: {
|
|
984
|
+
readonly slopIndex: 0.2;
|
|
985
|
+
readonly architectureConsistency: 0.2;
|
|
986
|
+
readonly aiSecurityRisk: 0.2;
|
|
987
|
+
readonly designTokenViolations: 0.1;
|
|
988
|
+
readonly testQuality: 0.1;
|
|
989
|
+
readonly businessLogicCoherence: 0.1;
|
|
990
|
+
readonly docFreshness: 0.05;
|
|
991
|
+
readonly dbHealth: 0.05;
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
interface TopOffender {
|
|
995
|
+
filePath: string;
|
|
996
|
+
/** Count of issues attributed to this file (from per-file results). */
|
|
997
|
+
issueCount: number;
|
|
998
|
+
/** Adjusted score for the file (post-baseline, post-framework-multiplier). */
|
|
999
|
+
adjustedScore: number;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
type BusinessLogicCategory = 'pricing' | 'validation' | 'formatting';
|
|
1003
|
+
interface BusinessLogicIssue {
|
|
1004
|
+
category: BusinessLogicCategory;
|
|
820
1005
|
filePath: string;
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1006
|
+
line: number;
|
|
1007
|
+
column: number;
|
|
1008
|
+
ruleId: string;
|
|
1009
|
+
message: string;
|
|
1010
|
+
advice?: string;
|
|
825
1011
|
}
|
|
1012
|
+
|
|
1013
|
+
interface CategoryDeduction {
|
|
1014
|
+
category: string;
|
|
1015
|
+
count: number;
|
|
1016
|
+
/** Weight per unit (per-extra or per-N-findings). */
|
|
1017
|
+
weight: number;
|
|
1018
|
+
/** Total deduction applied to the score. */
|
|
1019
|
+
deduction: number;
|
|
1020
|
+
/** Human-readable summary, e.g. "3 modal systems, 2 beyond baseline". */
|
|
1021
|
+
summary: string;
|
|
1022
|
+
/** Concrete findings (filenames, category labels, etc.). */
|
|
1023
|
+
findings: string[];
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* v0.18.4 (Phase B R-M2): types split.
|
|
1028
|
+
* Module: baseline
|
|
1029
|
+
*
|
|
1030
|
+
* Cross-module deps: `Category` + `Severity` from `./primitives`,
|
|
1031
|
+
* `Issue` from `./scan`.
|
|
1032
|
+
*/
|
|
1033
|
+
|
|
826
1034
|
interface BaselineMeta {
|
|
827
1035
|
active: boolean;
|
|
828
1036
|
version: string;
|
|
829
1037
|
baselineRevision: number;
|
|
830
1038
|
createdAt: string;
|
|
831
1039
|
}
|
|
1040
|
+
interface BaselineCache {
|
|
1041
|
+
version: string;
|
|
1042
|
+
config_hash: string;
|
|
1043
|
+
git_head: string;
|
|
1044
|
+
baseline_created: string;
|
|
1045
|
+
baseline_revision: number;
|
|
1046
|
+
totalComponentCount: number;
|
|
1047
|
+
scores: Record<string, {
|
|
1048
|
+
baselineScore: number;
|
|
1049
|
+
componentCount: number;
|
|
1050
|
+
}>;
|
|
1051
|
+
}
|
|
1052
|
+
interface SlopAuditRun {
|
|
1053
|
+
timestamp: string;
|
|
1054
|
+
version: string;
|
|
1055
|
+
slopIndex: number;
|
|
1056
|
+
categoryScores: Record<Category, number>;
|
|
1057
|
+
topOffenseIds: string[];
|
|
1058
|
+
thresholdExceeded: boolean;
|
|
1059
|
+
}
|
|
1060
|
+
interface AutoTunedRule {
|
|
1061
|
+
ruleId: string;
|
|
1062
|
+
severity: Severity;
|
|
1063
|
+
reason: string;
|
|
1064
|
+
}
|
|
1065
|
+
interface RuleSuggestion {
|
|
1066
|
+
pattern: string;
|
|
1067
|
+
example: string;
|
|
1068
|
+
count: number;
|
|
1069
|
+
suggestedRuleId: string;
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Optional research/flywheel metrics. Surfaced in the JSON report and the
|
|
1073
|
+
* flywheel summary when present. Generated by the
|
|
1074
|
+
* `research generate | analyze | candidates` pipeline.
|
|
1075
|
+
*/
|
|
1076
|
+
interface ResearchMetrics {
|
|
1077
|
+
generatedSampleCount: number;
|
|
1078
|
+
generatedRuleCoverage: number;
|
|
1079
|
+
/** Number of fingerprint clusters that turned into candidate rules. */
|
|
1080
|
+
candidateYield: number;
|
|
1081
|
+
/** When the research artifacts were last refreshed. */
|
|
1082
|
+
updatedAt: string;
|
|
1083
|
+
}
|
|
1084
|
+
interface FlywheelState {
|
|
1085
|
+
version: string;
|
|
1086
|
+
updatedAt: string;
|
|
1087
|
+
autoTuned: AutoTunedRule[];
|
|
1088
|
+
research?: ResearchMetrics;
|
|
1089
|
+
}
|
|
1090
|
+
interface FlywheelOutput {
|
|
1091
|
+
autoTuned: AutoTunedRule[];
|
|
1092
|
+
hotspotIssues: Issue[];
|
|
1093
|
+
suggestions: RuleSuggestion[];
|
|
1094
|
+
research?: ResearchMetrics;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
832
1097
|
interface ProjectReport {
|
|
833
1098
|
version: string;
|
|
834
1099
|
generatedAt: string;
|
|
@@ -1085,171 +1350,6 @@ interface ProjectReport {
|
|
|
1085
1350
|
* previousSlopIndex so the delta line can say "vs 2026-06-27". */
|
|
1086
1351
|
previousRunTimestamp?: string;
|
|
1087
1352
|
}
|
|
1088
|
-
interface TopOffender {
|
|
1089
|
-
filePath: string;
|
|
1090
|
-
/** Count of issues attributed to this file (from per-file results). */
|
|
1091
|
-
issueCount: number;
|
|
1092
|
-
/** Adjusted score for the file (post-baseline, post-framework-multiplier). */
|
|
1093
|
-
adjustedScore: number;
|
|
1094
|
-
}
|
|
1095
|
-
interface BaselineCache {
|
|
1096
|
-
version: string;
|
|
1097
|
-
config_hash: string;
|
|
1098
|
-
git_head: string;
|
|
1099
|
-
baseline_created: string;
|
|
1100
|
-
baseline_revision: number;
|
|
1101
|
-
totalComponentCount: number;
|
|
1102
|
-
scores: Record<string, {
|
|
1103
|
-
baselineScore: number;
|
|
1104
|
-
componentCount: number;
|
|
1105
|
-
}>;
|
|
1106
|
-
}
|
|
1107
|
-
interface SlopAuditRun {
|
|
1108
|
-
timestamp: string;
|
|
1109
|
-
version: string;
|
|
1110
|
-
slopIndex: number;
|
|
1111
|
-
categoryScores: Record<Category, number>;
|
|
1112
|
-
topOffenseIds: string[];
|
|
1113
|
-
thresholdExceeded: boolean;
|
|
1114
|
-
}
|
|
1115
|
-
interface AutoTunedRule {
|
|
1116
|
-
ruleId: string;
|
|
1117
|
-
severity: Severity;
|
|
1118
|
-
reason: string;
|
|
1119
|
-
}
|
|
1120
|
-
interface RuleSuggestion {
|
|
1121
|
-
pattern: string;
|
|
1122
|
-
example: string;
|
|
1123
|
-
count: number;
|
|
1124
|
-
suggestedRuleId: string;
|
|
1125
|
-
}
|
|
1126
|
-
/**
|
|
1127
|
-
* Optional research/flywheel metrics. Surfaced in the JSON report and the
|
|
1128
|
-
* flywheel summary when present. Generated by the
|
|
1129
|
-
* `research generate | analyze | candidates` pipeline.
|
|
1130
|
-
*/
|
|
1131
|
-
interface ResearchMetrics {
|
|
1132
|
-
generatedSampleCount: number;
|
|
1133
|
-
generatedRuleCoverage: number;
|
|
1134
|
-
/** Number of fingerprint clusters that turned into candidate rules. */
|
|
1135
|
-
candidateYield: number;
|
|
1136
|
-
/** When the research artifacts were last refreshed. */
|
|
1137
|
-
updatedAt: string;
|
|
1138
|
-
}
|
|
1139
|
-
interface FlywheelState {
|
|
1140
|
-
version: string;
|
|
1141
|
-
updatedAt: string;
|
|
1142
|
-
autoTuned: AutoTunedRule[];
|
|
1143
|
-
research?: ResearchMetrics;
|
|
1144
|
-
}
|
|
1145
|
-
interface FlywheelOutput {
|
|
1146
|
-
autoTuned: AutoTunedRule[];
|
|
1147
|
-
hotspotIssues: Issue[];
|
|
1148
|
-
suggestions: RuleSuggestion[];
|
|
1149
|
-
research?: ResearchMetrics;
|
|
1150
|
-
}
|
|
1151
|
-
interface MagicNumberSpacingConfig {
|
|
1152
|
-
ignoreProperties?: string[];
|
|
1153
|
-
ignoreZero?: boolean;
|
|
1154
|
-
}
|
|
1155
|
-
interface RuleContext {
|
|
1156
|
-
config: ResolvedConfig;
|
|
1157
|
-
filePath: string;
|
|
1158
|
-
cwd: string;
|
|
1159
|
-
framework?: string;
|
|
1160
|
-
uiLibraries?: string[];
|
|
1161
|
-
hasTailwind?: boolean;
|
|
1162
|
-
supportsRsc?: boolean;
|
|
1163
|
-
hotspotIssues?: Issue[];
|
|
1164
|
-
}
|
|
1165
|
-
interface Rule<Context = unknown> {
|
|
1166
|
-
id: string;
|
|
1167
|
-
category: Category;
|
|
1168
|
-
severity: Severity;
|
|
1169
|
-
aiSpecific: boolean;
|
|
1170
|
-
/** Round 25: short description shown by `slopbrick rules` and used in docs. */
|
|
1171
|
-
description?: string;
|
|
1172
|
-
create(context: RuleContext): Context;
|
|
1173
|
-
analyze(context: Context, facts: ScanFacts): Issue[];
|
|
1174
|
-
}
|
|
1175
|
-
interface ResolvedConfig {
|
|
1176
|
-
framework?: string;
|
|
1177
|
-
hasTailwind?: boolean;
|
|
1178
|
-
supportsRsc?: boolean;
|
|
1179
|
-
uiLibraries?: string[];
|
|
1180
|
-
include: string[];
|
|
1181
|
-
exclude: string[];
|
|
1182
|
-
rules: Record<string, RuleSeverity | 'off'>;
|
|
1183
|
-
categoryWeights?: Record<Category, number>;
|
|
1184
|
-
/**
|
|
1185
|
-
* Defaults to { boundary: 0.40, context: 0.35, visual: 0.25 } when unset. */
|
|
1186
|
-
compositeWeights?: {
|
|
1187
|
-
boundary: number;
|
|
1188
|
-
context: number;
|
|
1189
|
-
visual: number;
|
|
1190
|
-
};
|
|
1191
|
-
frameworkMultipliers: Record<string, number>;
|
|
1192
|
-
ruleConfig: Record<string, unknown>;
|
|
1193
|
-
gapTokens?: string[];
|
|
1194
|
-
globalCssTarget?: string;
|
|
1195
|
-
projectMemory?: boolean;
|
|
1196
|
-
telemetry?: boolean;
|
|
1197
|
-
thresholds: {
|
|
1198
|
-
meanSlop: number;
|
|
1199
|
-
p90Slop: number;
|
|
1200
|
-
individualSlopThreshold: number;
|
|
1201
|
-
/** Round 20: per-category thresholds (security fails, typo warns). Optional. */
|
|
1202
|
-
categoryThresholds?: Partial<Record<Category, number>>;
|
|
1203
|
-
};
|
|
1204
|
-
spacingScale?: number[];
|
|
1205
|
-
/**
|
|
1206
|
-
* Declared border-radius scale in rem (Tailwind default) plus the
|
|
1207
|
-
* string 'full' for the 9999px utility. Numeric values that don't
|
|
1208
|
-
* fall on the scale (after px→rem conversion) trigger the
|
|
1209
|
-
* `visual/radius-scale-violation` rule.
|
|
1210
|
-
*/
|
|
1211
|
-
radiusScale?: (number | 'full')[];
|
|
1212
|
-
typographyScale?: string[];
|
|
1213
|
-
arbitraryValueAllowlist: (string | RegExp)[];
|
|
1214
|
-
clampAllowlist?: (string | RegExp)[];
|
|
1215
|
-
/** Phase 2 §10: allowed import paths from brick.config.json. Imports
|
|
1216
|
-
* from `@/components/*` not matching these patterns are flagged by
|
|
1217
|
-
* `context/import-path-mismatch`. */
|
|
1218
|
-
allowedImports?: string[];
|
|
1219
|
-
wcag: {
|
|
1220
|
-
targetSizeExemptSelectors: string[];
|
|
1221
|
-
targetSizeRequireTailwind?: boolean;
|
|
1222
|
-
};
|
|
1223
|
-
/**
|
|
1224
|
-
* Declared repository constitution: "this repo uses Zustand for
|
|
1225
|
-
* state, shadcn for UI, react-query for data fetching" plus an
|
|
1226
|
-
* explicit deny-list of forbidden packages. Drives the `slop_suggest`
|
|
1227
|
-
* and `slop_check_constitution` MCP tools and the
|
|
1228
|
-
* `slopbrick drift` reporter. Auto-detected from package.json when
|
|
1229
|
-
* unset; user declarations always win (including explicit empty
|
|
1230
|
-
* arrays, which mean "we deliberately don't use this category").
|
|
1231
|
-
*/
|
|
1232
|
-
constitution?: Constitution;
|
|
1233
|
-
/**
|
|
1234
|
-
* PR slop score threshold (Phase 11). `slopbrick pr` exits 1 when
|
|
1235
|
-
* the PR introduces more than this many weighted slop points across
|
|
1236
|
-
* the changed files. Default: 20. Lower it to fail PRs that add any
|
|
1237
|
-
* meaningful slop; raise it to be more permissive. Overridden on
|
|
1238
|
-
* the CLI by `--threshold <n>`.
|
|
1239
|
-
*/
|
|
1240
|
-
prScoreThreshold?: number;
|
|
1241
|
-
/**
|
|
1242
|
-
* Phase 5 — Test Intelligence opt-in toggles. The four `test/*`
|
|
1243
|
-
* rules are safe-by-default (each rule short-circuits on non-test
|
|
1244
|
-
* files via `isTestFile()`), but `test/missing-edge-case` walks
|
|
1245
|
-
* production code to find untested branches. Off by default —
|
|
1246
|
-
* opt-in per project.
|
|
1247
|
-
*/
|
|
1248
|
-
testIntelligence?: {
|
|
1249
|
-
/** Walk production AST to find branches without test coverage. */
|
|
1250
|
-
missingEdgeCase?: boolean;
|
|
1251
|
-
};
|
|
1252
|
-
}
|
|
1253
1353
|
|
|
1254
1354
|
type Framework = 'react' | 'vue' | 'svelte' | 'solid' | 'qwik' | 'astro' | 'react-native' | 'expo';
|
|
1255
1355
|
type StylingSolution = 'tailwind' | 'css-modules' | 'styled-components' | 'emotion' | 'panda' | 'other';
|