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.
@@ -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 };