slopbrick 0.11.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/LICENSE +21 -0
- package/README.md +902 -0
- package/bin/slopbrick.js +6 -0
- package/dist/engine/worker.cjs +7392 -0
- package/dist/engine/worker.js +7366 -0
- package/dist/index.cjs +21516 -0
- package/dist/index.d.cts +1289 -0
- package/dist/index.d.ts +1289 -0
- package/dist/index.js +21508 -0
- package/package.json +97 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,1289 @@
|
|
|
1
|
+
export { CACHE_FILENAME, CONSTITUTION_FILENAME, ComponentFingerprint, ConstitutionFile, FileMtimeEntry, INVENTORY_FILENAME, InventoryFile, MEMORY_SCHEMA_VERSION, MemoryCategory, MemoryPattern, cachePath, constitutionPath, invalidateFile, inventoryPath, isComponentFingerprint, isConstitutionFile, isFileMtimeEntry, isInventoryFile, isInventoryFresh, isMemoryPattern, loadConstitution, loadInventory, saveConstitution, saveInventory } from '@usebrick/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Constitution declared by the user (or auto-detected from package.json).
|
|
5
|
+
* Each allow-list field is a list of canonical names — agents and
|
|
6
|
+
* reviewers can match against import strings without needing the
|
|
7
|
+
* original package name. For example, `@reduxjs/toolkit` and `redux`
|
|
8
|
+
* both surface as `'redux'` under `stateManagement`.
|
|
9
|
+
*
|
|
10
|
+
* `forbidden` is the deny-list: bare package specifiers and `@scope/`
|
|
11
|
+
* prefixes that the project has decided are off-limits. Matched against
|
|
12
|
+
* imports during `slop_check_constitution` and the `slopbrick drift`
|
|
13
|
+
* command.
|
|
14
|
+
*/
|
|
15
|
+
interface Constitution {
|
|
16
|
+
/** State-management libraries, e.g. 'zustand', 'redux', 'jotai'. */
|
|
17
|
+
stateManagement?: string[];
|
|
18
|
+
/** Data-fetching / server-state libraries, e.g. 'react-query', 'swr'. */
|
|
19
|
+
dataFetching?: string[];
|
|
20
|
+
/** UI component libraries, e.g. 'shadcn', 'radix', 'mui'. */
|
|
21
|
+
uiLibrary?: string[];
|
|
22
|
+
/** Form + schema-validation libraries, e.g. 'react-hook-form', 'zod'. */
|
|
23
|
+
forms?: string[];
|
|
24
|
+
/** Styling solutions, e.g. 'tailwind', 'styled-components', 'emotion'. */
|
|
25
|
+
styling?: string[];
|
|
26
|
+
/** Routing libraries, e.g. 'next', 'react-router', 'tanstack-router'. */
|
|
27
|
+
routing?: string[];
|
|
28
|
+
/** Free-form user-declared categories, keyed by category name. */
|
|
29
|
+
custom?: Record<string, string[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Explicit deny-list of bare package specifiers and `@scope/`
|
|
32
|
+
* prefixes. Anything in this list is forbidden — even if it would
|
|
33
|
+
* otherwise match a canonical category. Matching rules (see
|
|
34
|
+
* `matchForbidden`):
|
|
35
|
+
* - `forbidden: ['moment']` matches `moment` exactly.
|
|
36
|
+
* - `forbidden: ['@types/']` matches any `@types/...` import.
|
|
37
|
+
* - `forbidden: ['lodash']` matches `lodash` but not `lodash-es`.
|
|
38
|
+
*/
|
|
39
|
+
forbidden?: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type BusinessLogicCategory = 'pricing' | 'validation' | 'formatting';
|
|
43
|
+
interface BusinessLogicIssue {
|
|
44
|
+
category: BusinessLogicCategory;
|
|
45
|
+
filePath: string;
|
|
46
|
+
line: number;
|
|
47
|
+
column: number;
|
|
48
|
+
ruleId: string;
|
|
49
|
+
message: string;
|
|
50
|
+
advice?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface CategoryDeduction {
|
|
54
|
+
category: string;
|
|
55
|
+
count: number;
|
|
56
|
+
/** Weight per unit (per-extra or per-N-findings). */
|
|
57
|
+
weight: number;
|
|
58
|
+
/** Total deduction applied to the score. */
|
|
59
|
+
deduction: number;
|
|
60
|
+
/** Human-readable summary, e.g. "3 modal systems, 2 beyond baseline". */
|
|
61
|
+
summary: string;
|
|
62
|
+
/** Concrete findings (filenames, category labels, etc.). */
|
|
63
|
+
findings: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface FileMeta {
|
|
67
|
+
/** Absolute path to the file (or relative path from cwd, whichever the
|
|
68
|
+
* caller passed in). */
|
|
69
|
+
path: string;
|
|
70
|
+
/** Lines of code, used by sizeNormalisation in the aggregator. */
|
|
71
|
+
loc: number;
|
|
72
|
+
/** File extension including the leading dot (".tsx", ".vue"). Empty
|
|
73
|
+
* string for extension-less files that were sniffed by `discover.ts`. */
|
|
74
|
+
extension: string;
|
|
75
|
+
/** Derived from extension (or config.framework as fallback). */
|
|
76
|
+
framework: Framework$1;
|
|
77
|
+
}
|
|
78
|
+
interface ImportSpecifier {
|
|
79
|
+
/** Imported symbol name (`Button`, `useState`, `* as Foo`). */
|
|
80
|
+
name: string;
|
|
81
|
+
isDefault: boolean;
|
|
82
|
+
/** `import { X as Y }` — the local alias. */
|
|
83
|
+
alias?: string;
|
|
84
|
+
}
|
|
85
|
+
interface ImportRecord {
|
|
86
|
+
/** Import source as written. `'@/components/ui/button'`, `'react'`, `'./x'`. */
|
|
87
|
+
source: string;
|
|
88
|
+
specifiers: ImportSpecifier[];
|
|
89
|
+
/** True if the source matches a prefix in `config.allowedImports`.
|
|
90
|
+
* Always false for relative or third-party imports. */
|
|
91
|
+
isAllowed: boolean;
|
|
92
|
+
/** Source location. */
|
|
93
|
+
line: number;
|
|
94
|
+
column: number;
|
|
95
|
+
}
|
|
96
|
+
interface ComponentProp {
|
|
97
|
+
name: string;
|
|
98
|
+
/** Inferred type as a string. We don't parse the actual TS type — we
|
|
99
|
+
* surface the literal type annotation when present. */
|
|
100
|
+
type: string;
|
|
101
|
+
isRequired: boolean;
|
|
102
|
+
}
|
|
103
|
+
interface ComponentRecord {
|
|
104
|
+
/** Component name if it can be derived (function name, default export
|
|
105
|
+
* alias). Empty string for anonymous components. */
|
|
106
|
+
name: string;
|
|
107
|
+
isExported: boolean;
|
|
108
|
+
/** Component body line count. */
|
|
109
|
+
loc: number;
|
|
110
|
+
/** True for `'use client'` components and any non-server-compatible
|
|
111
|
+
* component (wrapped in `memo()`, `forwardRef()`, etc.). */
|
|
112
|
+
isClientComponent: boolean;
|
|
113
|
+
/** True if the source has `'use server'` directive. */
|
|
114
|
+
isServerComponent: boolean;
|
|
115
|
+
/** Declared props. Empty array for components without typed props. */
|
|
116
|
+
props: ComponentProp[];
|
|
117
|
+
/**
|
|
118
|
+
* `logic/reactive-hook-soup`'s per-component useEffect count. */
|
|
119
|
+
hookCalls: Array<{
|
|
120
|
+
name: string;
|
|
121
|
+
line: number;
|
|
122
|
+
column: number;
|
|
123
|
+
}>;
|
|
124
|
+
/** Source location of the component declaration. */
|
|
125
|
+
line: number;
|
|
126
|
+
column: number;
|
|
127
|
+
}
|
|
128
|
+
interface JsxElementRecord {
|
|
129
|
+
/** Element tag name: `'div'`, `'Button'`, `'AlertDialog'`. */
|
|
130
|
+
tag: string;
|
|
131
|
+
/** True if the tag is an HTML primitive (`div`, `span`, `button`). False
|
|
132
|
+
* if it's a user component or imported component. */
|
|
133
|
+
isPrimitive: boolean;
|
|
134
|
+
/** Extracted className tokens (split by whitespace, no empty strings). */
|
|
135
|
+
classNames: string[];
|
|
136
|
+
/** Tailwind arbitrary values extracted from classNames (`'p-[13px]'`,
|
|
137
|
+
* `'mt-[10vh]'`, `'bg-[#fff]'`). Empty array if none. */
|
|
138
|
+
arbitraryValues: string[];
|
|
139
|
+
/** Parsed `style={{...}}` props as a flat key→value map. */
|
|
140
|
+
inlineStyles: Record<string, string>;
|
|
141
|
+
/** True for `<button>`, `<a>`, `<input>`, `<select>`, `<textarea>`,
|
|
142
|
+
* and elements with `role="button"`. */
|
|
143
|
+
interactive: boolean;
|
|
144
|
+
/** ARIA-related props on this element (`'aria-label'`, `'role'`). */
|
|
145
|
+
ariaProps: string[];
|
|
146
|
+
/** Raw attributes. String-valued entries hold the attribute value;
|
|
147
|
+
* boolean attributes (e.g. `disabled`) appear as `key: undefined` so
|
|
148
|
+
* `Object.keys(attributes)` exposes them. Rules use truthy checks. */
|
|
149
|
+
attributes: Record<string, string | undefined>;
|
|
150
|
+
/** Source location. */
|
|
151
|
+
line: number;
|
|
152
|
+
column: number;
|
|
153
|
+
}
|
|
154
|
+
interface JsxTree {
|
|
155
|
+
elements: JsxElementRecord[];
|
|
156
|
+
/** Maximum depth of nested JSX elements anywhere in the file. */
|
|
157
|
+
maxNestingDepth: number;
|
|
158
|
+
}
|
|
159
|
+
type HookLocation = 'component-body' | 'useEffect' | 'handler' | 'callback';
|
|
160
|
+
interface HookRecord {
|
|
161
|
+
/** Hook name: `'useState'`, `'useEffect'`, `'useMemo'`, custom hooks. */
|
|
162
|
+
name: string;
|
|
163
|
+
/** Dependency array contents. Empty array means no deps. `undefined`
|
|
164
|
+
* means deps couldn't be inferred (e.g. spread). */
|
|
165
|
+
dependencies: unknown[];
|
|
166
|
+
/** Return type as a string (best-effort inference). */
|
|
167
|
+
returnType: string;
|
|
168
|
+
/** Where the hook was called. */
|
|
169
|
+
location: HookLocation;
|
|
170
|
+
line: number;
|
|
171
|
+
column: number;
|
|
172
|
+
}
|
|
173
|
+
interface StateVariableRecord {
|
|
174
|
+
/** Variable name (`'count'`). */
|
|
175
|
+
name: string;
|
|
176
|
+
/** Setter name (`'setCount'`). Empty string if no setter. */
|
|
177
|
+
setter: string;
|
|
178
|
+
/** True if the value is referenced anywhere in JSX. */
|
|
179
|
+
isUsedInJSX: boolean;
|
|
180
|
+
/** True if the value is never read AND the setter is never called. */
|
|
181
|
+
isZombie: boolean;
|
|
182
|
+
line: number;
|
|
183
|
+
column: number;
|
|
184
|
+
}
|
|
185
|
+
interface DefensiveCheckRecord {
|
|
186
|
+
type: 'nullish' | 'typeof' | 'truthy';
|
|
187
|
+
/** The expression being checked (`'user'`, `'data?.x'`). */
|
|
188
|
+
target: string;
|
|
189
|
+
/** True if the check is redundant because the framework already
|
|
190
|
+
* guarantees the value (e.g. optional chaining on a non-nullable). */
|
|
191
|
+
isGhost: boolean;
|
|
192
|
+
line: number;
|
|
193
|
+
column: number;
|
|
194
|
+
}
|
|
195
|
+
interface ApiCallRecord {
|
|
196
|
+
/** Method or path: `'fetch'`, `'axios.get'`, `'/api/users'`. */
|
|
197
|
+
method: string;
|
|
198
|
+
/** Where the call lives. */
|
|
199
|
+
location: HookLocation;
|
|
200
|
+
/** True for direct calls inside component body (not wrapped in a hook
|
|
201
|
+
* or async handler). Triggers `logic/boundary-violation`. */
|
|
202
|
+
isDirect: boolean;
|
|
203
|
+
line: number;
|
|
204
|
+
column: number;
|
|
205
|
+
}
|
|
206
|
+
interface DesignTokens {
|
|
207
|
+
/** Numeric spacing values used in the file (e.g. `[4, 8, 12, 16, 13]`).
|
|
208
|
+
* Used by entropy rules and `spacing-grid` validation. */
|
|
209
|
+
spacingUsage: number[];
|
|
210
|
+
/** Color values: `'zinc-900'`, `'#fff'`, `'hsl(0 0% 0%)'`, `'rgb(...)'`. */
|
|
211
|
+
colorValues: string[];
|
|
212
|
+
/** Font sizes used in inline styles and classNames. */
|
|
213
|
+
fontSizes: string[];
|
|
214
|
+
/** Border radius values used in the file. */
|
|
215
|
+
borderRadius: string[];
|
|
216
|
+
}
|
|
217
|
+
interface ScanFactsV2 {
|
|
218
|
+
file: FileMeta;
|
|
219
|
+
imports: ImportRecord[];
|
|
220
|
+
components: ComponentRecord[];
|
|
221
|
+
jsx: JsxTree;
|
|
222
|
+
logic: {
|
|
223
|
+
hooks: HookRecord[];
|
|
224
|
+
stateVariables: StateVariableRecord[];
|
|
225
|
+
defensiveChecks: DefensiveCheckRecord[];
|
|
226
|
+
apiCalls: ApiCallRecord[];
|
|
227
|
+
logicalExpressions: LogicalExpressionFact[];
|
|
228
|
+
keyProps: KeyPropFact[];
|
|
229
|
+
optimisticUpdates: OptimisticUpdateFact[];
|
|
230
|
+
};
|
|
231
|
+
designTokens: DesignTokens;
|
|
232
|
+
/**
|
|
233
|
+
* templates. Replaces the synthetic `<template>` elements that the
|
|
234
|
+
* migration injected into `jsx.elements`. */
|
|
235
|
+
templateClassNames: ClassNameFact[];
|
|
236
|
+
/**
|
|
237
|
+
* multiple-components-per-file. */
|
|
238
|
+
componentSizes: ComponentSizeFact[];
|
|
239
|
+
astroComponents: AstroComponentFact[];
|
|
240
|
+
/**
|
|
241
|
+
* comments (and block equivalents). Issues matching these are filtered
|
|
242
|
+
* out before scoring. */
|
|
243
|
+
disabledRules: DisabledLintRuleFact[];
|
|
244
|
+
/** Optional source text (cached for `unified-diff`, `formatAdvice`,
|
|
245
|
+
* and `--suggest` output). Not all rules need this. */
|
|
246
|
+
_source?: string;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
interface SignalStrength {
|
|
250
|
+
/** TP per AI file. Higher = catches more AI tells. */
|
|
251
|
+
recall: number;
|
|
252
|
+
/** FP per human file. Higher = false alarms. */
|
|
253
|
+
fpRate: number;
|
|
254
|
+
/** recall / fpRate (capped at 99 if no FPs observed). Higher = cleaner signal. */
|
|
255
|
+
ratio: number;
|
|
256
|
+
/** recall / (recall + fpRate). 0..1. Higher = more reliable. */
|
|
257
|
+
precision: number;
|
|
258
|
+
/** ISO timestamp of last calibration that produced these numbers. */
|
|
259
|
+
lastCalibratedAt: string;
|
|
260
|
+
/**
|
|
261
|
+
* v0.9.3: when `true`, the scan engine applies `'off'` to this rule by
|
|
262
|
+
* default — user must explicitly opt back in via
|
|
263
|
+
* `slopbrick.config.mjs`'s `rules:` block. Set for:
|
|
264
|
+
* - INVERTED rules (lift < 1.0 — fires MORE on human code than AI;
|
|
265
|
+
* actively misleading when surfaced as AI detectors)
|
|
266
|
+
* - NOISY rules (recall < 0.1 — fires too rarely on AI code to be
|
|
267
|
+
* a useful default; engineers will dismiss after the 3rd false
|
|
268
|
+
* sense of "it doesn't matter")
|
|
269
|
+
* User `rules: { 'rule/id': 'medium' }` overrides the default-off.
|
|
270
|
+
* Omitted (= undefined) means the rule keeps its factory default.
|
|
271
|
+
*/
|
|
272
|
+
defaultOff?: boolean;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
declare const VERSION = "0.10.0";
|
|
276
|
+
/**
|
|
277
|
+
* Categorical bucket for the AI Maintenance Cost score.
|
|
278
|
+
*
|
|
279
|
+
* low — ship as-is, revisit quarterly
|
|
280
|
+
* medium — schedule cleanup work
|
|
281
|
+
* high — block new feature work in the affected subsystem
|
|
282
|
+
* critical — dedicated refactor sprint required
|
|
283
|
+
*
|
|
284
|
+
* Deliberately a categorical label (not numeric). Same reasoning as
|
|
285
|
+
* `aiSecurityRisk`: a single bucket is harder to game than a number,
|
|
286
|
+
* and a manager can read "AI Maintenance Cost: HIGH" in two seconds.
|
|
287
|
+
*
|
|
288
|
+
* The numeric `health` (0-100, higher = better) and `monthlyUSD`
|
|
289
|
+
* (estimated monthly cost to fix the underlying issues) are exposed
|
|
290
|
+
* alongside the label for agents and trend pipelines that need them.
|
|
291
|
+
*/
|
|
292
|
+
type AiMaintenanceCost = 'low' | 'medium' | 'high' | 'critical';
|
|
293
|
+
/** Per-axis contribution to the AI Maintenance Cost score. */
|
|
294
|
+
interface MaintenanceAxisHealth {
|
|
295
|
+
/** Stable axis name (`slopIndex`, `architectureConsistency`, `aiSecurityRisk`, ...). */
|
|
296
|
+
axis: string;
|
|
297
|
+
/** Display label printed by the pretty reporter. */
|
|
298
|
+
label: string;
|
|
299
|
+
/** 0-100, higher = better. Derived from the underlying signal. */
|
|
300
|
+
health: number;
|
|
301
|
+
/** How this was derived, e.g. `100 - slopIndex (inverted)`. */
|
|
302
|
+
source: string;
|
|
303
|
+
}
|
|
304
|
+
/** Inputs to the pure `computeAiMaintenanceCost` function. Every axis is optional. */
|
|
305
|
+
interface MaintenanceAxes {
|
|
306
|
+
/** 0-100, lower = better. The headline `slopIndex` from `ProjectReport`. */
|
|
307
|
+
slopIndex?: number;
|
|
308
|
+
/** 0-100, higher = better. From `buildArchitectureScore`. */
|
|
309
|
+
architectureConsistency?: number;
|
|
310
|
+
/** Categorical — mapped to numeric via `MAINTENANCE_SECURITY_NUMERIC`. */
|
|
311
|
+
aiSecurityRisk?: 'low' | 'medium' | 'high' | 'critical';
|
|
312
|
+
/** Total constitution violations (sum across categories). */
|
|
313
|
+
constitutionViolations?: number;
|
|
314
|
+
/** Spacing + radius scale violation counts. */
|
|
315
|
+
designTokenDrift?: {
|
|
316
|
+
spacing: number;
|
|
317
|
+
radius: number;
|
|
318
|
+
};
|
|
319
|
+
/** Total high-severity issues across the scan. Extra penalty. */
|
|
320
|
+
highSeverityIssueCount?: number;
|
|
321
|
+
/** Approximate LoC; defaults to 0 if unknown (small-project path). */
|
|
322
|
+
linesOfCode?: number;
|
|
323
|
+
/** False when the user has not declared a constitution / design tokens
|
|
324
|
+
* (no drift run). When true, applies the 1.5–2.5× AI multiplier. */
|
|
325
|
+
hasAiSignals?: boolean;
|
|
326
|
+
}
|
|
327
|
+
/** Result returned by `computeAiMaintenanceCost` + `computeAiMaintenanceCostFromReport`. */
|
|
328
|
+
interface AiMaintenanceCostResult {
|
|
329
|
+
/** Categorical headline. */
|
|
330
|
+
cost: AiMaintenanceCost;
|
|
331
|
+
/** Weighted health in 0-100 (higher = better). Bucket is derived from this. */
|
|
332
|
+
health: number;
|
|
333
|
+
/** Estimated monthly USD cost to fix the underlying issues.
|
|
334
|
+
* Anchored to Sonar's $306K/yr/MLoC baseline ($25.50/1k LoC/month)
|
|
335
|
+
* with the CodeClimate grade→minutes mapping for per-issue cost. */
|
|
336
|
+
monthlyUSD: number;
|
|
337
|
+
/** Per-axis breakdown, sorted by health ascending (worst first). */
|
|
338
|
+
axes: MaintenanceAxisHealth[];
|
|
339
|
+
/** Per-bucket advice for managers. */
|
|
340
|
+
advice: string;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Categorical drift band on the doc-freshness score.
|
|
344
|
+
* low — 80-100
|
|
345
|
+
* medium — 60-79
|
|
346
|
+
* high — 40-59
|
|
347
|
+
* critical — 0-39
|
|
348
|
+
*
|
|
349
|
+
* Drives the `--strict` exit code for `slopbrick docs` (high/critical = 1).
|
|
350
|
+
*/
|
|
351
|
+
type DocDriftLevel = 'low' | 'medium' | 'high' | 'critical';
|
|
352
|
+
/** A single doc-drift finding — one rule firing on one line. */
|
|
353
|
+
interface DocFinding {
|
|
354
|
+
ruleId: 'docs/stale-package-reference' | 'docs/stale-function-reference' | 'docs/expired-code-example' | 'docs/broken-link';
|
|
355
|
+
severity: 'low' | 'medium' | 'high';
|
|
356
|
+
docFile: string;
|
|
357
|
+
line: number;
|
|
358
|
+
column: number;
|
|
359
|
+
message: string;
|
|
360
|
+
advice: string;
|
|
361
|
+
/** For `stale-package-reference`: the package the doc references. */
|
|
362
|
+
package?: string;
|
|
363
|
+
/** For `stale-function-reference`: the identifier. */
|
|
364
|
+
identifier?: string;
|
|
365
|
+
/** For `broken-link`: the unresolved link target. */
|
|
366
|
+
link?: string;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Categorical drift band on the db-health score.
|
|
370
|
+
* low — 80-100
|
|
371
|
+
* medium — 60-79
|
|
372
|
+
* high — 40-59
|
|
373
|
+
* critical — 0-39
|
|
374
|
+
*/
|
|
375
|
+
type DbDriftLevel = 'low' | 'medium' | 'high' | 'critical';
|
|
376
|
+
/** A single db-health finding — one rule firing on one table/file. */
|
|
377
|
+
interface DbFinding {
|
|
378
|
+
ruleId: 'db/missing-fk-index' | 'db/duplicate-index' | 'db/missing-not-null' | 'db/enum-sprawl' | 'db/naming-inconsistency' | 'db/sql-concat';
|
|
379
|
+
severity: 'low' | 'medium' | 'high';
|
|
380
|
+
dbFile: string;
|
|
381
|
+
line: number;
|
|
382
|
+
column: number;
|
|
383
|
+
message: string;
|
|
384
|
+
advice: string;
|
|
385
|
+
/** For rules that target a specific table. */
|
|
386
|
+
table?: string;
|
|
387
|
+
/** For column-level rules. */
|
|
388
|
+
columnName?: string;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Letter-grade band on the composite Repository Health score.
|
|
392
|
+
* Drives the `aiDebt` field — same categorical shape as `aiSecurityRisk`.
|
|
393
|
+
*
|
|
394
|
+
* low — 80-100 (clean; safe to ship)
|
|
395
|
+
* medium — 60-79 (manageable; revisit quarterly)
|
|
396
|
+
* high — 40-59 (block new feature work in affected subsystems)
|
|
397
|
+
* critical — 0-39 (dedicated refactor sprint required)
|
|
398
|
+
*/
|
|
399
|
+
type AiDebt = 'low' | 'medium' | 'high' | 'critical';
|
|
400
|
+
/** Numeric mapping for categorical `aiSecurityRisk` used in the composite. */
|
|
401
|
+
declare const AI_SECURITY_NUMERIC: Record<'low' | 'medium' | 'high' | 'critical', number>;
|
|
402
|
+
/** Inputs to the pure `buildRepositoryHealth` function. Every input is optional. */
|
|
403
|
+
interface RepositoryHealthInputs {
|
|
404
|
+
/** 0-100, lower = better. Inverted to 100 - x for the composite. */
|
|
405
|
+
slopIndex?: number;
|
|
406
|
+
/** 0-100, higher = better. */
|
|
407
|
+
architectureConsistency?: number;
|
|
408
|
+
/** Categorical — mapped via AI_SECURITY_NUMERIC. */
|
|
409
|
+
aiSecurityRisk?: 'low' | 'medium' | 'high' | 'critical';
|
|
410
|
+
/** Spacing + radius scale violation counts. */
|
|
411
|
+
designTokenViolations?: {
|
|
412
|
+
spacing: number;
|
|
413
|
+
radius: number;
|
|
414
|
+
};
|
|
415
|
+
/** Total high-severity issue count (extra penalty). */
|
|
416
|
+
highSeverityIssueCount?: number;
|
|
417
|
+
/** 0-100, higher = better. From `slopbrick test` (Phase 5). */
|
|
418
|
+
testQuality?: number;
|
|
419
|
+
/** 0-100, higher = better. From `slopbrick business-logic` (Phase 7). */
|
|
420
|
+
businessLogicCoherence?: number;
|
|
421
|
+
/** 0-100, higher = better. From `slopbrick docs` (Phase 6). */
|
|
422
|
+
docFreshness?: number;
|
|
423
|
+
/** 0-100, higher = better. From `slopbrick db` (Phase 8). */
|
|
424
|
+
dbHealth?: number;
|
|
425
|
+
/** v0.10 — MDL log-likelihood ratio from `computeMDLikelihood`.
|
|
426
|
+
* Positive = rule-firing evidence favors m_ai; negative favors
|
|
427
|
+
* m_human. Surfaced on the result for reporting; NOT folded into
|
|
428
|
+
* the weighted-average composite (which is heuristic and remains
|
|
429
|
+
* unchanged in this phase). Caller computes the ratio from the
|
|
430
|
+
* distinct `ruleId` set across the report's issues. */
|
|
431
|
+
mdlLogRatio?: number;
|
|
432
|
+
}
|
|
433
|
+
/** Result returned by `buildRepositoryHealth` + `buildRepositoryHealthFromReport`. */
|
|
434
|
+
interface RepositoryHealth {
|
|
435
|
+
/** Composite 0-100 score (higher = better). */
|
|
436
|
+
score: number;
|
|
437
|
+
/** Letter-grade band derived from `score`. */
|
|
438
|
+
aiDebt: AiDebt;
|
|
439
|
+
/** Per-axis contribution breakdown (axis → 0-100, higher = better). */
|
|
440
|
+
breakdown: Record<string, number>;
|
|
441
|
+
/** Per-axis weight actually applied (post-renormalization). */
|
|
442
|
+
appliedWeights: Record<string, number>;
|
|
443
|
+
/** Warnings for managers (e.g. "Critical security risk overrides everything"). */
|
|
444
|
+
warnings: string[];
|
|
445
|
+
/** One-line summary. */
|
|
446
|
+
headline: string;
|
|
447
|
+
/** v0.10 — MDL log-likelihood ratio (positive = m_ai evidence).
|
|
448
|
+
* Surfaced when the caller passed `mdlLogRatio` to
|
|
449
|
+
* `buildRepositoryHealth`. Not folded into the weighted-average
|
|
450
|
+
* composite in this phase (kept separate so the heuristic score
|
|
451
|
+
* can be audited independently of the MDL axis). */
|
|
452
|
+
mdlLogRatio?: number;
|
|
453
|
+
}
|
|
454
|
+
/** Default weights — sum to 1.0. Optional axes are skipped, and the
|
|
455
|
+
* remaining weights renormalize to 1.0. Tuned to give `aiSecurityRisk`
|
|
456
|
+
* the heaviest weight (it is the catastrophic single-event dimension)
|
|
457
|
+
* while still letting slopIndex + architecture drive most cases. */
|
|
458
|
+
declare const REPOSITORY_HEALTH_WEIGHTS: {
|
|
459
|
+
readonly slopIndex: 0.2;
|
|
460
|
+
readonly architectureConsistency: 0.2;
|
|
461
|
+
readonly aiSecurityRisk: 0.2;
|
|
462
|
+
readonly designTokenViolations: 0.1;
|
|
463
|
+
readonly testQuality: 0.1;
|
|
464
|
+
readonly businessLogicCoherence: 0.1;
|
|
465
|
+
readonly docFreshness: 0.05;
|
|
466
|
+
readonly dbHealth: 0.05;
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
type Severity = 'low' | 'medium' | 'high';
|
|
470
|
+
type RuleSeverity = Severity | 'auto';
|
|
471
|
+
type Category = 'visual' | 'typo' | 'wcag' | 'layout' | 'component' | 'logic' | 'arch' | 'perf' | 'security' | 'test' | 'docs' | 'db';
|
|
472
|
+
/**
|
|
473
|
+
* `react` covers `.tsx`, `.jsx`, `.ts`, `.js`. Other values are detected
|
|
474
|
+
* by file extension. Unknown extensions fall back to `'react'`.
|
|
475
|
+
*/
|
|
476
|
+
type Framework$1 = 'react' | 'vue' | 'svelte' | 'astro' | 'html';
|
|
477
|
+
interface FixSuggestion {
|
|
478
|
+
kind: 'insert' | 'replace' | 'css-anchor';
|
|
479
|
+
description: string;
|
|
480
|
+
targetFile?: string;
|
|
481
|
+
anchor?: string;
|
|
482
|
+
oldValue?: string;
|
|
483
|
+
newValue?: string;
|
|
484
|
+
}
|
|
485
|
+
interface Issue {
|
|
486
|
+
ruleId: string;
|
|
487
|
+
category: Category;
|
|
488
|
+
severity: Severity;
|
|
489
|
+
aiSpecific: boolean;
|
|
490
|
+
filePath?: string;
|
|
491
|
+
message: string;
|
|
492
|
+
line: number;
|
|
493
|
+
column: number;
|
|
494
|
+
advice?: string;
|
|
495
|
+
fixHint?: string;
|
|
496
|
+
fix?: FixSuggestion;
|
|
497
|
+
fixes?: FixSuggestion[];
|
|
498
|
+
signalStrength?: SignalStrength;
|
|
499
|
+
}
|
|
500
|
+
interface CachedFile {
|
|
501
|
+
hash: string;
|
|
502
|
+
issueCount: number;
|
|
503
|
+
lastScannedAt: string;
|
|
504
|
+
}
|
|
505
|
+
interface ScanCache {
|
|
506
|
+
version: string;
|
|
507
|
+
generatedAt: string;
|
|
508
|
+
files: Record<string, CachedFile>;
|
|
509
|
+
}
|
|
510
|
+
interface ClassNameFact {
|
|
511
|
+
value: string;
|
|
512
|
+
line: number;
|
|
513
|
+
column: number;
|
|
514
|
+
}
|
|
515
|
+
interface ElementFact {
|
|
516
|
+
tag: string;
|
|
517
|
+
attributes: Record<string, string | undefined>;
|
|
518
|
+
classNames: ClassNameFact[];
|
|
519
|
+
eventHandlers: string[];
|
|
520
|
+
line: number;
|
|
521
|
+
column: number;
|
|
522
|
+
}
|
|
523
|
+
interface HookFact {
|
|
524
|
+
name: string;
|
|
525
|
+
line: number;
|
|
526
|
+
column: number;
|
|
527
|
+
hasDependencyArray?: boolean;
|
|
528
|
+
body?: string;
|
|
529
|
+
componentLine?: number;
|
|
530
|
+
dependencies?: string[];
|
|
531
|
+
}
|
|
532
|
+
interface HookCallFact {
|
|
533
|
+
name: string;
|
|
534
|
+
callee: string;
|
|
535
|
+
line: number;
|
|
536
|
+
column: number;
|
|
537
|
+
inConditional: boolean;
|
|
538
|
+
inLoop: boolean;
|
|
539
|
+
inNestedFunction: boolean;
|
|
540
|
+
}
|
|
541
|
+
interface FetchCallFact {
|
|
542
|
+
line: number;
|
|
543
|
+
column: number;
|
|
544
|
+
hasAbortSignal: boolean;
|
|
545
|
+
checksOk: boolean;
|
|
546
|
+
/**
|
|
547
|
+
* The URL argument to fetch(), if it could be extracted as a string literal.
|
|
548
|
+
* Dynamic expressions (template strings, variables) are left undefined.
|
|
549
|
+
*/
|
|
550
|
+
url?: string;
|
|
551
|
+
/**
|
|
552
|
+
* The `credentials` option if explicitly set ('omit' | 'same-origin' | 'include').
|
|
553
|
+
* Undefined when not specified.
|
|
554
|
+
*/
|
|
555
|
+
credentials?: 'omit' | 'same-origin' | 'include';
|
|
556
|
+
/** HTTP method (uppercase) when explicitly set as the second argument. */
|
|
557
|
+
method?: string;
|
|
558
|
+
}
|
|
559
|
+
interface DisabledLintRuleFact {
|
|
560
|
+
ruleId: string;
|
|
561
|
+
line: number;
|
|
562
|
+
column: number;
|
|
563
|
+
scope: 'line' | 'next-line' | 'block';
|
|
564
|
+
}
|
|
565
|
+
interface EvalCallFact {
|
|
566
|
+
kind: 'eval' | 'new-function' | 'function-constructor';
|
|
567
|
+
line: number;
|
|
568
|
+
column: number;
|
|
569
|
+
}
|
|
570
|
+
interface PropMutationFact {
|
|
571
|
+
target: string;
|
|
572
|
+
line: number;
|
|
573
|
+
column: number;
|
|
574
|
+
}
|
|
575
|
+
interface DangerouslySetInnerHtmlFact {
|
|
576
|
+
line: number;
|
|
577
|
+
column: number;
|
|
578
|
+
}
|
|
579
|
+
interface OptimisticUpdateFact {
|
|
580
|
+
setterName: string;
|
|
581
|
+
line: number;
|
|
582
|
+
column: number;
|
|
583
|
+
hasCatchRollback?: boolean;
|
|
584
|
+
}
|
|
585
|
+
interface StateBinding {
|
|
586
|
+
valueName?: string;
|
|
587
|
+
setterName?: string;
|
|
588
|
+
line: number;
|
|
589
|
+
column: number;
|
|
590
|
+
valueReferenced: boolean;
|
|
591
|
+
setterReferenced: boolean;
|
|
592
|
+
}
|
|
593
|
+
interface PropPassThroughFact {
|
|
594
|
+
propName: string;
|
|
595
|
+
toTag: string;
|
|
596
|
+
line: number;
|
|
597
|
+
column: number;
|
|
598
|
+
}
|
|
599
|
+
interface ComponentFacts {
|
|
600
|
+
name?: string;
|
|
601
|
+
line: number;
|
|
602
|
+
column: number;
|
|
603
|
+
endLine: number;
|
|
604
|
+
isServerComponent: boolean;
|
|
605
|
+
isMemoWrapped?: boolean;
|
|
606
|
+
hookCalls: HookFact[];
|
|
607
|
+
stateBindings: StateBinding[];
|
|
608
|
+
propBindings: string[];
|
|
609
|
+
propPassThroughs: PropPassThroughFact[];
|
|
610
|
+
propUsages: string[];
|
|
611
|
+
}
|
|
612
|
+
interface LogicalExpressionFact {
|
|
613
|
+
depth: number;
|
|
614
|
+
line: number;
|
|
615
|
+
column: number;
|
|
616
|
+
text: string;
|
|
617
|
+
isOptionalChainLike: boolean;
|
|
618
|
+
}
|
|
619
|
+
interface StylePropFact {
|
|
620
|
+
source: string;
|
|
621
|
+
line: number;
|
|
622
|
+
column: number;
|
|
623
|
+
}
|
|
624
|
+
interface AstroComponentFact {
|
|
625
|
+
tag: string;
|
|
626
|
+
hasClientDirective: boolean;
|
|
627
|
+
hasEventHandler: boolean;
|
|
628
|
+
line: number;
|
|
629
|
+
column: number;
|
|
630
|
+
}
|
|
631
|
+
interface ConsoleCallFact {
|
|
632
|
+
method: 'log' | 'warn' | 'error' | 'info' | 'debug';
|
|
633
|
+
line: number;
|
|
634
|
+
column: number;
|
|
635
|
+
}
|
|
636
|
+
interface DialogCallFact {
|
|
637
|
+
method: 'alert' | 'confirm' | 'prompt';
|
|
638
|
+
line: number;
|
|
639
|
+
column: number;
|
|
640
|
+
}
|
|
641
|
+
interface StringLiteralFact {
|
|
642
|
+
value: string;
|
|
643
|
+
line: number;
|
|
644
|
+
column: number;
|
|
645
|
+
}
|
|
646
|
+
interface JsxTextLiteralFact {
|
|
647
|
+
value: string;
|
|
648
|
+
line: number;
|
|
649
|
+
column: number;
|
|
650
|
+
parentTag?: string;
|
|
651
|
+
}
|
|
652
|
+
interface CommentFact {
|
|
653
|
+
kind: 'Line' | 'Block';
|
|
654
|
+
value: string;
|
|
655
|
+
line: number;
|
|
656
|
+
column: number;
|
|
657
|
+
}
|
|
658
|
+
interface StateBindingFact {
|
|
659
|
+
valueName: string;
|
|
660
|
+
setterName: string;
|
|
661
|
+
line: number;
|
|
662
|
+
column: number;
|
|
663
|
+
}
|
|
664
|
+
interface JsxAttributeStringLiteralFact {
|
|
665
|
+
value: string;
|
|
666
|
+
attribute: string;
|
|
667
|
+
line: number;
|
|
668
|
+
column: number;
|
|
669
|
+
}
|
|
670
|
+
interface TamaguiStylePropFact {
|
|
671
|
+
name: string;
|
|
672
|
+
value: string;
|
|
673
|
+
line: number;
|
|
674
|
+
column: number;
|
|
675
|
+
}
|
|
676
|
+
interface KeyPropFact {
|
|
677
|
+
tag: string;
|
|
678
|
+
valueType: 'index' | 'missing' | 'stable' | 'unknown';
|
|
679
|
+
line: number;
|
|
680
|
+
column: number;
|
|
681
|
+
}
|
|
682
|
+
interface InlineEventHandlerFact {
|
|
683
|
+
tag: string;
|
|
684
|
+
event: string;
|
|
685
|
+
source: string;
|
|
686
|
+
line: number;
|
|
687
|
+
column: number;
|
|
688
|
+
hasMemoParent?: boolean;
|
|
689
|
+
}
|
|
690
|
+
interface UseEffectBodyFact {
|
|
691
|
+
line: number;
|
|
692
|
+
column: number;
|
|
693
|
+
source: string;
|
|
694
|
+
}
|
|
695
|
+
interface DomQueryFact {
|
|
696
|
+
method: string;
|
|
697
|
+
line: number;
|
|
698
|
+
column: number;
|
|
699
|
+
}
|
|
700
|
+
interface ExplicitAnyFact {
|
|
701
|
+
line: number;
|
|
702
|
+
column: number;
|
|
703
|
+
kind?: 'keyword' | 'missing-annotation';
|
|
704
|
+
}
|
|
705
|
+
interface NonNullAssertionFact {
|
|
706
|
+
line: number;
|
|
707
|
+
column: number;
|
|
708
|
+
}
|
|
709
|
+
interface ComponentSizeFact {
|
|
710
|
+
name?: string;
|
|
711
|
+
lineCount: number;
|
|
712
|
+
jsxBranchCount: number;
|
|
713
|
+
line: number;
|
|
714
|
+
column: number;
|
|
715
|
+
}
|
|
716
|
+
interface HookDependencyArrayFact {
|
|
717
|
+
hookName: string;
|
|
718
|
+
depsSource: string;
|
|
719
|
+
line: number;
|
|
720
|
+
column: number;
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* flat-shape fields have been removed; consumers must read from `v2`.
|
|
724
|
+
* See src/engine/types.ts for the grouped shape definition.
|
|
725
|
+
*/
|
|
726
|
+
interface ScanFacts {
|
|
727
|
+
/** Absolute file path. */
|
|
728
|
+
filePath: string;
|
|
729
|
+
v2: ScanFactsV2;
|
|
730
|
+
}
|
|
731
|
+
interface ImportFact {
|
|
732
|
+
source: string;
|
|
733
|
+
line: number;
|
|
734
|
+
column: number;
|
|
735
|
+
importedNames?: string[];
|
|
736
|
+
}
|
|
737
|
+
interface FileScanResult {
|
|
738
|
+
filePath: string;
|
|
739
|
+
componentCount: number;
|
|
740
|
+
issues: Issue[];
|
|
741
|
+
parseError?: string;
|
|
742
|
+
gapValues?: string[];
|
|
743
|
+
styleSources?: string[];
|
|
744
|
+
elementTags?: string[];
|
|
745
|
+
unmatchedStringLiterals?: string[];
|
|
746
|
+
/**
|
|
747
|
+
* `// slopbrick-disable` directive filtering. */
|
|
748
|
+
facts?: ScanFacts;
|
|
749
|
+
}
|
|
750
|
+
interface ComponentScore {
|
|
751
|
+
filePath: string;
|
|
752
|
+
rawScore: number;
|
|
753
|
+
componentScore: number;
|
|
754
|
+
adjustedScore: number;
|
|
755
|
+
componentCount: number;
|
|
756
|
+
}
|
|
757
|
+
interface BaselineMeta {
|
|
758
|
+
active: boolean;
|
|
759
|
+
version: string;
|
|
760
|
+
baselineRevision: number;
|
|
761
|
+
createdAt: string;
|
|
762
|
+
}
|
|
763
|
+
interface ProjectReport {
|
|
764
|
+
version: string;
|
|
765
|
+
generatedAt: string;
|
|
766
|
+
configPath?: string;
|
|
767
|
+
slopIndex: number;
|
|
768
|
+
assemblyHealth: number;
|
|
769
|
+
totalScore: number;
|
|
770
|
+
categoryScores: Record<Category, number>;
|
|
771
|
+
/** Phase 2 §10: composite subscores. Each is in 0-100 (capped).
|
|
772
|
+
* slopIndex = 0.40 × boundaryScore + 0.35 × contextScore + 0.25 × visualScore. */
|
|
773
|
+
boundaryScore: number;
|
|
774
|
+
contextScore: number;
|
|
775
|
+
visualScore: number;
|
|
776
|
+
/** Phase 2 §10: ruleId → subscore bucket, used by the composite formula.
|
|
777
|
+
* Each issue contributes to exactly one bucket. */
|
|
778
|
+
subscores?: Record<string, number>;
|
|
779
|
+
/** Architecture Consistency Score (0-100). 100 = one modal system, one
|
|
780
|
+
* button variant, one api client, one state lib, one fetch lib, no
|
|
781
|
+
* off-scale values. Optional because it requires a deeper scan and
|
|
782
|
+
* is only computed when the project has source files. */
|
|
783
|
+
architectureConsistency?: number;
|
|
784
|
+
/** Per-category deductions behind the architecture score. */
|
|
785
|
+
architectureDeductions?: CategoryDeduction[];
|
|
786
|
+
/** AI Security Risk — categorical severity for security findings
|
|
787
|
+
* disproportionately introduced by AI-generated code. Independent
|
|
788
|
+
* from slopIndex (mixing them would let a project hide one with
|
|
789
|
+
* the other). Drives the `slopbrick security` subcommand and the
|
|
790
|
+
* security column in scan reports. */
|
|
791
|
+
aiSecurityRisk?: 'low' | 'medium' | 'high' | 'critical';
|
|
792
|
+
/** Count of security findings broken down by severity. */
|
|
793
|
+
aiSecurityFindings?: {
|
|
794
|
+
critical: number;
|
|
795
|
+
high: number;
|
|
796
|
+
medium: number;
|
|
797
|
+
low: number;
|
|
798
|
+
};
|
|
799
|
+
/** Phase 7: Business Logic Coherence (0-100). 100 = no anti-pattern
|
|
800
|
+
* findings across pricing/validation/formatting. Higher is better.
|
|
801
|
+
* Independent of slopIndex + architectureConsistency. Drives the
|
|
802
|
+
* `slopbrick business-logic` subcommand. */
|
|
803
|
+
businessLogicCoherence?: number;
|
|
804
|
+
/** Phase 7: per-issue list backing the business-logic score. */
|
|
805
|
+
businessLogicIssues?: BusinessLogicIssue[];
|
|
806
|
+
/** Phase 5 — Test Quality Score (0-100, lower = more issues). Higher is better.
|
|
807
|
+
* Independent from slopIndex — derived from the four `test/*` rules when
|
|
808
|
+
* `slopbrick test` runs. Surfaced in the JSON report and the dedicated
|
|
809
|
+
* subcommand's pretty output. */
|
|
810
|
+
testQuality?: number;
|
|
811
|
+
/** Phase Memo #4 — AI Maintenance Cost (categorical headline + numeric breakdown).
|
|
812
|
+
* Derived from the existing signals in this report (no new file scanning).
|
|
813
|
+
* Present when `slopbrick scan` or `slopbrick maintenance-cost` runs. */
|
|
814
|
+
aiMaintenanceCost?: AiMaintenanceCostResult;
|
|
815
|
+
/** Phase 6 — Documentation Freshness (0-100, higher = better). 100 = no
|
|
816
|
+
* stale references found in doc files; 0 = every rule fired on every
|
|
817
|
+
* file. Independent of slopIndex — derived from the four `docs/*`
|
|
818
|
+
* rules. Surfaced in `slopbrick scan` and the dedicated `slopbrick
|
|
819
|
+
* docs` subcommand. */
|
|
820
|
+
docFreshness?: number;
|
|
821
|
+
/** Categorical drift band, derived from `docFreshness`. */
|
|
822
|
+
docDrift?: DocDriftLevel;
|
|
823
|
+
/** Per-finding list behind the doc-freshness score. */
|
|
824
|
+
docFindings?: DocFinding[];
|
|
825
|
+
/** Phase 8 — Database Health (0-100, higher = better). Static-only
|
|
826
|
+
* analysis via `pgsql-parser` (libpg_query port). 100 = no anti-patterns
|
|
827
|
+
* found in SQL / Prisma / Drizzle schema files. */
|
|
828
|
+
dbHealth?: number;
|
|
829
|
+
/** Categorical drift band, derived from `dbHealth`. */
|
|
830
|
+
dbDrift?: DbDriftLevel;
|
|
831
|
+
/** Per-finding list behind the db-health score. */
|
|
832
|
+
dbFindings?: DbFinding[];
|
|
833
|
+
/** Phase 12 — Repository Health (composite 0-100). The endgame score
|
|
834
|
+
* that aggregates every prior sub-score into one number a manager
|
|
835
|
+
* reads in two seconds. Always informational; --strict for CI gating. */
|
|
836
|
+
repositoryHealth?: number;
|
|
837
|
+
/** Categorical AI Debt band, derived from `repositoryHealth`. */
|
|
838
|
+
aiDebt?: AiDebt;
|
|
839
|
+
/** Per-axis breakdown of the composite. */
|
|
840
|
+
repositoryHealthBreakdown?: Record<string, number>;
|
|
841
|
+
/** Warnings emitted during composite computation. */
|
|
842
|
+
repositoryHealthWarnings?: string[];
|
|
843
|
+
/** v0.9.1 — Repository Coherence (0-100, higher = better). The headline
|
|
844
|
+
* score under the new "Repository Coherence Scanner" framing. Composite
|
|
845
|
+
* of Architecture Consistency (50%), Pattern Fragmentation (30%),
|
|
846
|
+
* Constitution Violations (10%), and AI Debt (10%). Distinct from
|
|
847
|
+
* `repositoryHealth` (the older catch-all composite) and from
|
|
848
|
+
* `slopIndex` (the per-rule aggregate). */
|
|
849
|
+
coherence?: number;
|
|
850
|
+
/** Per-axis breakdown of the Coherence composite. Each input 0-100. */
|
|
851
|
+
coherenceBreakdown?: {
|
|
852
|
+
/** Architecture Consistency (existing score, fed in unchanged). */
|
|
853
|
+
architectureConsistency: number;
|
|
854
|
+
/** Pattern Fragmentation (inverted to 0-100, higher = better). */
|
|
855
|
+
patternFragmentation: number;
|
|
856
|
+
/** Constitution violations count mapped to 0-100. 100 = none. */
|
|
857
|
+
constitutionMapped: number;
|
|
858
|
+
/** AI Debt letter band mapped to 0-100. A→95, B→85, C→70, D→50, F→25. */
|
|
859
|
+
aiDebtMapped: number;
|
|
860
|
+
};
|
|
861
|
+
/** Weights actually applied to the Coherence composite (post-renormalization). */
|
|
862
|
+
coherenceWeights?: {
|
|
863
|
+
architectureConsistency: number;
|
|
864
|
+
patternFragmentation: number;
|
|
865
|
+
constitutionMapped: number;
|
|
866
|
+
aiDebtMapped: number;
|
|
867
|
+
};
|
|
868
|
+
/** v0.9.1 — Code Hygiene domain score (0-100, higher = better). Aggregates
|
|
869
|
+
* the 31 "Supporting" rules across logic/test/typo/visual/layout that
|
|
870
|
+
* don't fit the Coherence lens but are still useful findings. */
|
|
871
|
+
codeHygiene?: number;
|
|
872
|
+
/** Accessibility domain score (0-100, higher = better). Derived from
|
|
873
|
+
* the four `wcag/*` rules. */
|
|
874
|
+
accessibility?: number;
|
|
875
|
+
/** Performance domain score (0-100, higher = better). Derived from
|
|
876
|
+
* the `perf/*` rules. */
|
|
877
|
+
performance?: number;
|
|
878
|
+
/** Per-domain issue counts (after the v0.9.1 rule classification). */
|
|
879
|
+
domainIssues?: Record<string, number>;
|
|
880
|
+
/** v0.9.2 — Cross-file pattern drift signals. Each signal is a stem
|
|
881
|
+
* (concept) realized as 2+ distinct patterns in the same category
|
|
882
|
+
* across files. The lens answer: "did this code introduce a new
|
|
883
|
+
* pattern when an existing pattern already existed?" */
|
|
884
|
+
crossFileDrift?: Array<{
|
|
885
|
+
category: 'modal' | 'button' | 'api' | 'state' | 'dataFetching' | 'service' | 'route' | 'ormModel';
|
|
886
|
+
stem: string;
|
|
887
|
+
variants: string[];
|
|
888
|
+
files: string[];
|
|
889
|
+
}>;
|
|
890
|
+
/** v0.9.2 — Cross-category drift: stems that appear in 2+ categories
|
|
891
|
+
* with 2+ variants each. E.g. service.User + ormModel.User = same
|
|
892
|
+
* conceptual entity spanning 2 roles. */
|
|
893
|
+
crossCategoryDrift?: Array<{
|
|
894
|
+
stem: string;
|
|
895
|
+
byCategory: Record<string, string[]>;
|
|
896
|
+
files: string[];
|
|
897
|
+
}>;
|
|
898
|
+
p90Score: number;
|
|
899
|
+
peakScore: number;
|
|
900
|
+
componentCount: number;
|
|
901
|
+
fileCount: number;
|
|
902
|
+
components: ComponentScore[];
|
|
903
|
+
issues: Issue[];
|
|
904
|
+
parseErrors?: Array<{
|
|
905
|
+
filePath: string;
|
|
906
|
+
error: string;
|
|
907
|
+
}>;
|
|
908
|
+
baseline?: BaselineMeta;
|
|
909
|
+
thresholds: {
|
|
910
|
+
meanSlop: number;
|
|
911
|
+
p90Slop: number;
|
|
912
|
+
individualSlopThreshold: number;
|
|
913
|
+
};
|
|
914
|
+
/** Research/flywheel snapshot — present when the flywheel directory has artifacts. */
|
|
915
|
+
research?: ResearchMetrics;
|
|
916
|
+
topOffenders?: TopOffender[];
|
|
917
|
+
/**
|
|
918
|
+
* v0.10.1 — PR Slop Score. Aggregate weighted issue count for the
|
|
919
|
+
* `--diff <ref>` mode (the VibeDrift-compatible git-ref filter).
|
|
920
|
+
* Computed as Σ(SEVERITY_WEIGHTS[issue.severity]) for issues in
|
|
921
|
+
* files changed since the ref. SEVERITY_WEIGHTS = { high: 10,
|
|
922
|
+
* medium: 5, low: 1 }. Only present when `diffRef` was supplied.
|
|
923
|
+
* The CI threshold (config.prScoreThreshold) gates on this.
|
|
924
|
+
*/
|
|
925
|
+
prSlopScore?: number;
|
|
926
|
+
/** v0.10.1 — the git ref supplied to --diff <ref>. Undefined for full scans. */
|
|
927
|
+
diffRef?: string;
|
|
928
|
+
}
|
|
929
|
+
interface TopOffender {
|
|
930
|
+
filePath: string;
|
|
931
|
+
/** Count of issues attributed to this file (from per-file results). */
|
|
932
|
+
issueCount: number;
|
|
933
|
+
/** Adjusted score for the file (post-baseline, post-framework-multiplier). */
|
|
934
|
+
adjustedScore: number;
|
|
935
|
+
}
|
|
936
|
+
interface BaselineCache {
|
|
937
|
+
version: string;
|
|
938
|
+
config_hash: string;
|
|
939
|
+
git_head: string;
|
|
940
|
+
baseline_created: string;
|
|
941
|
+
baseline_revision: number;
|
|
942
|
+
totalComponentCount: number;
|
|
943
|
+
scores: Record<string, {
|
|
944
|
+
baselineScore: number;
|
|
945
|
+
componentCount: number;
|
|
946
|
+
}>;
|
|
947
|
+
}
|
|
948
|
+
interface SlopAuditRun {
|
|
949
|
+
timestamp: string;
|
|
950
|
+
version: string;
|
|
951
|
+
slopIndex: number;
|
|
952
|
+
categoryScores: Record<Category, number>;
|
|
953
|
+
topOffenseIds: string[];
|
|
954
|
+
thresholdExceeded: boolean;
|
|
955
|
+
}
|
|
956
|
+
interface AutoTunedRule {
|
|
957
|
+
ruleId: string;
|
|
958
|
+
severity: Severity;
|
|
959
|
+
reason: string;
|
|
960
|
+
}
|
|
961
|
+
interface RuleSuggestion {
|
|
962
|
+
pattern: string;
|
|
963
|
+
example: string;
|
|
964
|
+
count: number;
|
|
965
|
+
suggestedRuleId: string;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Optional research/flywheel metrics. Surfaced in the JSON report and the
|
|
969
|
+
* flywheel summary when present. Generated by the
|
|
970
|
+
* `research generate | analyze | candidates` pipeline.
|
|
971
|
+
*/
|
|
972
|
+
interface ResearchMetrics {
|
|
973
|
+
generatedSampleCount: number;
|
|
974
|
+
generatedRuleCoverage: number;
|
|
975
|
+
/** Number of fingerprint clusters that turned into candidate rules. */
|
|
976
|
+
candidateYield: number;
|
|
977
|
+
/** When the research artifacts were last refreshed. */
|
|
978
|
+
updatedAt: string;
|
|
979
|
+
}
|
|
980
|
+
interface FlywheelState {
|
|
981
|
+
version: string;
|
|
982
|
+
updatedAt: string;
|
|
983
|
+
autoTuned: AutoTunedRule[];
|
|
984
|
+
research?: ResearchMetrics;
|
|
985
|
+
}
|
|
986
|
+
interface FlywheelOutput {
|
|
987
|
+
autoTuned: AutoTunedRule[];
|
|
988
|
+
hotspotIssues: Issue[];
|
|
989
|
+
suggestions: RuleSuggestion[];
|
|
990
|
+
research?: ResearchMetrics;
|
|
991
|
+
}
|
|
992
|
+
interface MagicNumberSpacingConfig {
|
|
993
|
+
ignoreProperties?: string[];
|
|
994
|
+
ignoreZero?: boolean;
|
|
995
|
+
}
|
|
996
|
+
interface RuleContext {
|
|
997
|
+
config: ResolvedConfig;
|
|
998
|
+
filePath: string;
|
|
999
|
+
cwd: string;
|
|
1000
|
+
framework?: string;
|
|
1001
|
+
uiLibraries?: string[];
|
|
1002
|
+
hasTailwind?: boolean;
|
|
1003
|
+
supportsRsc?: boolean;
|
|
1004
|
+
hotspotIssues?: Issue[];
|
|
1005
|
+
}
|
|
1006
|
+
interface Rule<Context = unknown> {
|
|
1007
|
+
id: string;
|
|
1008
|
+
category: Category;
|
|
1009
|
+
severity: Severity;
|
|
1010
|
+
aiSpecific: boolean;
|
|
1011
|
+
/** Round 25: short description shown by `slopbrick rules` and used in docs. */
|
|
1012
|
+
description?: string;
|
|
1013
|
+
create(context: RuleContext): Context;
|
|
1014
|
+
analyze(context: Context, facts: ScanFacts): Issue[];
|
|
1015
|
+
}
|
|
1016
|
+
interface ResolvedConfig {
|
|
1017
|
+
framework?: string;
|
|
1018
|
+
hasTailwind?: boolean;
|
|
1019
|
+
supportsRsc?: boolean;
|
|
1020
|
+
uiLibraries?: string[];
|
|
1021
|
+
include: string[];
|
|
1022
|
+
exclude: string[];
|
|
1023
|
+
rules: Record<string, RuleSeverity | 'off'>;
|
|
1024
|
+
categoryWeights?: Record<Category, number>;
|
|
1025
|
+
/**
|
|
1026
|
+
* Defaults to { boundary: 0.40, context: 0.35, visual: 0.25 } when unset. */
|
|
1027
|
+
compositeWeights?: {
|
|
1028
|
+
boundary: number;
|
|
1029
|
+
context: number;
|
|
1030
|
+
visual: number;
|
|
1031
|
+
};
|
|
1032
|
+
frameworkMultipliers: Record<string, number>;
|
|
1033
|
+
ruleConfig: Record<string, unknown>;
|
|
1034
|
+
gapTokens?: string[];
|
|
1035
|
+
globalCssTarget?: string;
|
|
1036
|
+
projectMemory?: boolean;
|
|
1037
|
+
telemetry?: boolean;
|
|
1038
|
+
thresholds: {
|
|
1039
|
+
meanSlop: number;
|
|
1040
|
+
p90Slop: number;
|
|
1041
|
+
individualSlopThreshold: number;
|
|
1042
|
+
/** Round 20: per-category thresholds (security fails, typo warns). Optional. */
|
|
1043
|
+
categoryThresholds?: Partial<Record<Category, number>>;
|
|
1044
|
+
};
|
|
1045
|
+
spacingScale?: number[];
|
|
1046
|
+
/**
|
|
1047
|
+
* Declared border-radius scale in rem (Tailwind default) plus the
|
|
1048
|
+
* string 'full' for the 9999px utility. Numeric values that don't
|
|
1049
|
+
* fall on the scale (after px→rem conversion) trigger the
|
|
1050
|
+
* `visual/radius-scale-violation` rule.
|
|
1051
|
+
*/
|
|
1052
|
+
radiusScale?: (number | 'full')[];
|
|
1053
|
+
typographyScale?: string[];
|
|
1054
|
+
arbitraryValueAllowlist: (string | RegExp)[];
|
|
1055
|
+
clampAllowlist?: (string | RegExp)[];
|
|
1056
|
+
/** Phase 2 §10: allowed import paths from brick.config.json. Imports
|
|
1057
|
+
* from `@/components/*` not matching these patterns are flagged by
|
|
1058
|
+
* `context/import-path-mismatch`. */
|
|
1059
|
+
allowedImports?: string[];
|
|
1060
|
+
wcag: {
|
|
1061
|
+
targetSizeExemptSelectors: string[];
|
|
1062
|
+
targetSizeRequireTailwind?: boolean;
|
|
1063
|
+
};
|
|
1064
|
+
/**
|
|
1065
|
+
* Declared repository constitution: "this repo uses Zustand for
|
|
1066
|
+
* state, shadcn for UI, react-query for data fetching" plus an
|
|
1067
|
+
* explicit deny-list of forbidden packages. Drives the `slop_suggest`
|
|
1068
|
+
* and `slop_check_constitution` MCP tools and the
|
|
1069
|
+
* `slopbrick drift` reporter. Auto-detected from package.json when
|
|
1070
|
+
* unset; user declarations always win (including explicit empty
|
|
1071
|
+
* arrays, which mean "we deliberately don't use this category").
|
|
1072
|
+
*/
|
|
1073
|
+
constitution?: Constitution;
|
|
1074
|
+
/**
|
|
1075
|
+
* PR slop score threshold (Phase 11). `slopbrick pr` exits 1 when
|
|
1076
|
+
* the PR introduces more than this many weighted slop points across
|
|
1077
|
+
* the changed files. Default: 20. Lower it to fail PRs that add any
|
|
1078
|
+
* meaningful slop; raise it to be more permissive. Overridden on
|
|
1079
|
+
* the CLI by `--threshold <n>`.
|
|
1080
|
+
*/
|
|
1081
|
+
prScoreThreshold?: number;
|
|
1082
|
+
/**
|
|
1083
|
+
* Phase 5 — Test Intelligence opt-in toggles. The four `test/*`
|
|
1084
|
+
* rules are safe-by-default (each rule short-circuits on non-test
|
|
1085
|
+
* files via `isTestFile()`), but `test/missing-edge-case` walks
|
|
1086
|
+
* production code to find untested branches. Off by default —
|
|
1087
|
+
* opt-in per project.
|
|
1088
|
+
*/
|
|
1089
|
+
testIntelligence?: {
|
|
1090
|
+
/** Walk production AST to find branches without test coverage. */
|
|
1091
|
+
missingEdgeCase?: boolean;
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
type Framework = 'react' | 'vue' | 'svelte' | 'solid' | 'qwik' | 'astro' | 'react-native' | 'expo';
|
|
1096
|
+
type StylingSolution = 'tailwind' | 'css-modules' | 'styled-components' | 'emotion' | 'panda' | 'other';
|
|
1097
|
+
type UiLibrary = 'shadcn/ui' | 'mui' | 'chakra' | 'radix' | 'tamagui' | 'nativewind';
|
|
1098
|
+
type Strictness = 'strict' | 'balanced' | 'permissive';
|
|
1099
|
+
interface WizardAnswers {
|
|
1100
|
+
framework: Framework;
|
|
1101
|
+
styling: StylingSolution;
|
|
1102
|
+
uiLibraries: UiLibrary[];
|
|
1103
|
+
strictness: Strictness;
|
|
1104
|
+
}
|
|
1105
|
+
declare const DEFAULT_CONFIG: ResolvedConfig;
|
|
1106
|
+
|
|
1107
|
+
declare function loadConfig(cwd: string): Promise<ResolvedConfig>;
|
|
1108
|
+
|
|
1109
|
+
interface ScanProjectOptions {
|
|
1110
|
+
cwd: string;
|
|
1111
|
+
framework?: string;
|
|
1112
|
+
include?: string[];
|
|
1113
|
+
exclude?: string[];
|
|
1114
|
+
aiOnly?: boolean;
|
|
1115
|
+
humanOnly?: boolean;
|
|
1116
|
+
ignoreWcag22?: boolean;
|
|
1117
|
+
since?: string;
|
|
1118
|
+
staged?: boolean;
|
|
1119
|
+
changed?: boolean;
|
|
1120
|
+
/** v0.10.1: VibeDrift-compatible git-ref filter. When set, only files
|
|
1121
|
+
* changed since this ref are scanned and the report includes a
|
|
1122
|
+
* PR Slop Score. Equivalent to --since <ref> + PR Slop Score. */
|
|
1123
|
+
diffRef?: string;
|
|
1124
|
+
incremental?: boolean;
|
|
1125
|
+
cachePath?: string;
|
|
1126
|
+
tokens?: string;
|
|
1127
|
+
threadCount?: number;
|
|
1128
|
+
tighten?: boolean;
|
|
1129
|
+
workerScript?: string;
|
|
1130
|
+
strict?: boolean;
|
|
1131
|
+
noIncrease?: boolean;
|
|
1132
|
+
cache?: boolean;
|
|
1133
|
+
telemetry?: boolean;
|
|
1134
|
+
}
|
|
1135
|
+
declare function scanProject(options: ScanProjectOptions): Promise<ProjectReport>;
|
|
1136
|
+
|
|
1137
|
+
declare function runInitWizard(cwd: string, detected: Partial<ResolvedConfig>, options?: {
|
|
1138
|
+
input?: NodeJS.ReadableStream;
|
|
1139
|
+
output?: NodeJS.WritableStream;
|
|
1140
|
+
}): Promise<WizardAnswers>;
|
|
1141
|
+
|
|
1142
|
+
declare function runCli({ start }: {
|
|
1143
|
+
start: number;
|
|
1144
|
+
}): Promise<void>;
|
|
1145
|
+
|
|
1146
|
+
declare function thresholdExceeded(report: ProjectReport, config: ResolvedConfig): boolean;
|
|
1147
|
+
declare function failedThresholdCount(report: ProjectReport, config: ResolvedConfig): number;
|
|
1148
|
+
declare function baselineStatusMessage(baseline: BaselineMeta): string;
|
|
1149
|
+
interface StagedGatingResult {
|
|
1150
|
+
failed: boolean;
|
|
1151
|
+
reason?: string;
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Decide whether a staged set of changed files should block the commit.
|
|
1155
|
+
*
|
|
1156
|
+
* Without a baseline, falls back to per-file threshold checks. With a
|
|
1157
|
+
* baseline, simulates the post-change project state (new + modified -
|
|
1158
|
+
* deleted components) and compares the hypothetical mean to the
|
|
1159
|
+
* configured `meanSlop`.
|
|
1160
|
+
*/
|
|
1161
|
+
declare function stagedGating(scores: ComponentScore[], config: ResolvedConfig, baseline: BaselineCache | undefined, cwd: string): StagedGatingResult;
|
|
1162
|
+
interface IssueFilterOptions {
|
|
1163
|
+
aiOnly?: boolean;
|
|
1164
|
+
humanOnly?: boolean;
|
|
1165
|
+
ignoreWcag22?: boolean;
|
|
1166
|
+
rule?: string;
|
|
1167
|
+
}
|
|
1168
|
+
declare function filterIssues(issues: Issue[], options: IssueFilterOptions): Issue[];
|
|
1169
|
+
/**
|
|
1170
|
+
* Drop issues suppressed by inline `// slopbrick-disable[-next-line]`
|
|
1171
|
+
* or block directives at or above the issue's line. Project-level rules
|
|
1172
|
+
* (no filePath) are never suppressed.
|
|
1173
|
+
*/
|
|
1174
|
+
declare function filterByDisabledDirectives(result: FileScanResult, disabledRules: readonly {
|
|
1175
|
+
ruleId: string;
|
|
1176
|
+
scope: 'line' | 'next-line' | 'block';
|
|
1177
|
+
line: number;
|
|
1178
|
+
}[]): void;
|
|
1179
|
+
declare function serializeConfig(config: ResolvedConfig): string;
|
|
1180
|
+
type ReportReadResult = {
|
|
1181
|
+
ok: true;
|
|
1182
|
+
report: ProjectReport;
|
|
1183
|
+
} | {
|
|
1184
|
+
ok: false;
|
|
1185
|
+
error: string;
|
|
1186
|
+
};
|
|
1187
|
+
declare function readReportFile(path: string): ReportReadResult;
|
|
1188
|
+
declare function formatReportFromFile(report: ProjectReport, sourcePath: string): string;
|
|
1189
|
+
|
|
1190
|
+
declare function colorForSlop(slopIndex: number): string;
|
|
1191
|
+
declare function formatBadge(report: ProjectReport): string;
|
|
1192
|
+
/** Render an array of values as a Unicode sparkline (▁▂▃▄▅▆▇█). */
|
|
1193
|
+
declare function formatSparkline(values: number[]): string;
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* v0.10.1: `find_similar_function` engine.
|
|
1197
|
+
*
|
|
1198
|
+
* Given a function/component signature (name + parameter list + hooks
|
|
1199
|
+
* used), find the most similar existing implementations across the
|
|
1200
|
+
* codebase. Foundation for the GIR (Give-Implementation-Reference)
|
|
1201
|
+
* pattern in `slop_suggest` and the BRICK Platform.
|
|
1202
|
+
*
|
|
1203
|
+
* Algorithm (no LLMs, no embeddings — hash-based AST fingerprinting
|
|
1204
|
+
* per Chilowicz 2009, "Syntax Tree Fingerprinting for Code Clone
|
|
1205
|
+
* Detection"; also see Maurer 2017 for a modern take on the same
|
|
1206
|
+
* approach applied to JS/TS):
|
|
1207
|
+
*
|
|
1208
|
+
* 1. Walk the codebase and extract each function/component signature.
|
|
1209
|
+
* Signature = (name, normalized param list, hooks used, props).
|
|
1210
|
+
* 2. Compute a deterministic fingerprint per signature:
|
|
1211
|
+
* fingerprint = sha256(sorted(hooks) | sorted(props) | sorted(params))
|
|
1212
|
+
* 3. Given a query signature, compute Jaccard similarity to every
|
|
1213
|
+
* extracted signature over the union of (hooks ∪ props ∪ params).
|
|
1214
|
+
* 4. Return top-k matches sorted by similarity desc.
|
|
1215
|
+
*
|
|
1216
|
+
* Why this matters: AI agents writing new code ask "does this pattern
|
|
1217
|
+
* already exist?" before inventing new ones. `find_similar_function`
|
|
1218
|
+
* is the deterministic, citation-backed answer — no LLM hallucination,
|
|
1219
|
+
* no embedding dependency, fast even on 100k+ files.
|
|
1220
|
+
*/
|
|
1221
|
+
/**
|
|
1222
|
+
* Normalized signature for a single function/component in the codebase.
|
|
1223
|
+
*/
|
|
1224
|
+
interface ComponentSignature {
|
|
1225
|
+
/** Function/component name (PascalCase for components, camelCase for hooks). */
|
|
1226
|
+
name: string;
|
|
1227
|
+
/** Absolute path of the file the signature lives in. */
|
|
1228
|
+
file: string;
|
|
1229
|
+
/** Path relative to the workspace, for display in results. */
|
|
1230
|
+
fileRel: string;
|
|
1231
|
+
/** Line number (1-indexed) where the signature is defined. */
|
|
1232
|
+
line: number;
|
|
1233
|
+
/** Sorted, deduplicated parameter names (without `:` types). */
|
|
1234
|
+
params: string[];
|
|
1235
|
+
/** React hooks used (useState, useEffect, etc.). */
|
|
1236
|
+
hooks: string[];
|
|
1237
|
+
/** Component props accepted (for React components). */
|
|
1238
|
+
props: string[];
|
|
1239
|
+
}
|
|
1240
|
+
/** A single result from `findSimilarFunctions`. */
|
|
1241
|
+
interface SimilarMatch {
|
|
1242
|
+
/** The matched signature. */
|
|
1243
|
+
signature: ComponentSignature;
|
|
1244
|
+
/** Jaccard similarity in [0, 1]. 1 = identical feature set, 0 = disjoint. */
|
|
1245
|
+
similarity: number;
|
|
1246
|
+
/** Stable fingerprint hash. Two identical signatures always match. */
|
|
1247
|
+
fingerprint: string;
|
|
1248
|
+
}
|
|
1249
|
+
/** Query for `findSimilarFunctions`. */
|
|
1250
|
+
interface FindSimilarQuery {
|
|
1251
|
+
/** Optional name filter (exact match). */
|
|
1252
|
+
name?: string;
|
|
1253
|
+
/** Optional hooks filter (e.g., ['useState', 'useEffect']). */
|
|
1254
|
+
hooks?: string[];
|
|
1255
|
+
/** Optional props filter. */
|
|
1256
|
+
props?: string[];
|
|
1257
|
+
/** Optional params filter. */
|
|
1258
|
+
params?: string[];
|
|
1259
|
+
/** Top-k results to return. Default 10. Capped at 50. */
|
|
1260
|
+
limit?: number;
|
|
1261
|
+
/** Workspace directory to search. */
|
|
1262
|
+
workspaceDir: string;
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Extract every component/function signature from a single source string.
|
|
1266
|
+
* Pure function — no I/O.
|
|
1267
|
+
*/
|
|
1268
|
+
declare function extractSignatures(source: string, filePath: string, workspaceDir: string): ComponentSignature[];
|
|
1269
|
+
/** Stable fingerprint: sha256 over sorted feature set. */
|
|
1270
|
+
declare function fingerprintSignature(sig: Pick<ComponentSignature, 'hooks' | 'props' | 'params'>): string;
|
|
1271
|
+
/**
|
|
1272
|
+
* Jaccard similarity over the union of (hooks ∪ props ∪ params).
|
|
1273
|
+
* Returns 0..1. Identical sets → 1. Disjoint → 0.
|
|
1274
|
+
*/
|
|
1275
|
+
declare function signatureSimilarity(a: Pick<ComponentSignature, 'hooks' | 'props' | 'params'>, b: Pick<ComponentSignature, 'hooks' | 'props' | 'params'>): number;
|
|
1276
|
+
/**
|
|
1277
|
+
* Walk the workspace, extract signatures from every included file, and
|
|
1278
|
+
* return top-k matches sorted by Jaccard similarity desc.
|
|
1279
|
+
*
|
|
1280
|
+
* Pure-ish: the file walking uses async I/O but the similarity math is
|
|
1281
|
+
* synchronous (it's microseconds per signature, even on 10k files).
|
|
1282
|
+
*/
|
|
1283
|
+
declare function findSimilarFunctions(query: FindSimilarQuery, options?: {
|
|
1284
|
+
cwd?: string;
|
|
1285
|
+
include?: string[];
|
|
1286
|
+
exclude?: string[];
|
|
1287
|
+
}): Promise<SimilarMatch[]>;
|
|
1288
|
+
|
|
1289
|
+
export { AI_SECURITY_NUMERIC, type AiDebt, type AiMaintenanceCost, type AiMaintenanceCostResult, type AstroComponentFact, type AutoTunedRule, type BaselineCache, type BaselineMeta, type CachedFile, type Category, type ClassNameFact, type CommentFact, type ComponentFacts, type ComponentScore, type ComponentSignature, type ComponentSizeFact, type ConsoleCallFact, type Constitution, DEFAULT_CONFIG, type DangerouslySetInnerHtmlFact, type DbDriftLevel, type DbFinding, type DialogCallFact, type DisabledLintRuleFact, type DocDriftLevel, type DocFinding, type DomQueryFact, type ElementFact, type EvalCallFact, type ExplicitAnyFact, type FetchCallFact, type FileScanResult, type FindSimilarQuery, type FixSuggestion, type FlywheelOutput, type FlywheelState, type Framework$1 as Framework, type HookCallFact, type HookDependencyArrayFact, type HookFact, type ImportFact, type InlineEventHandlerFact, type Issue, type JsxAttributeStringLiteralFact, type JsxTextLiteralFact, type KeyPropFact, type LogicalExpressionFact, type MagicNumberSpacingConfig, type MaintenanceAxes, type MaintenanceAxisHealth, type NonNullAssertionFact, type OptimisticUpdateFact, type ProjectReport, type PropMutationFact, type PropPassThroughFact, REPOSITORY_HEALTH_WEIGHTS, type ReportReadResult, type RepositoryHealth, type RepositoryHealthInputs, type ResearchMetrics, type ResolvedConfig, type Rule, type RuleContext, type RuleSeverity, type RuleSuggestion, type ScanCache, type ScanFacts, type ScanProjectOptions, type Severity, type SimilarMatch, type SlopAuditRun, type StateBinding, type StateBindingFact, type StringLiteralFact, type StylePropFact, type TamaguiStylePropFact, type TopOffender, type UseEffectBodyFact, VERSION, baselineStatusMessage, colorForSlop, extractSignatures, failedThresholdCount, filterByDisabledDirectives, filterIssues, findSimilarFunctions, fingerprintSignature, formatBadge, formatReportFromFile, formatSparkline, loadConfig, readReportFile, runCli, runInitWizard, scanProject, serializeConfig, signatureSimilarity, stagedGating, thresholdExceeded };
|