uidex 0.8.0 → 0.10.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/dist/cli/cli.cjs +1273 -343
- package/dist/cli/cli.cjs.map +1 -1
- package/dist/headless/index.cjs +3 -0
- package/dist/headless/index.cjs.map +1 -1
- package/dist/headless/index.d.cts +19 -13
- package/dist/headless/index.d.ts +19 -13
- package/dist/headless/index.js +3 -0
- package/dist/headless/index.js.map +1 -1
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -13
- package/dist/index.d.ts +19 -13
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/playwright/index.cjs +36 -7
- package/dist/playwright/index.cjs.map +1 -1
- package/dist/playwright/index.js +36 -7
- package/dist/playwright/index.js.map +1 -1
- package/dist/playwright/states-reporter.cjs +36 -7
- package/dist/playwright/states-reporter.cjs.map +1 -1
- package/dist/playwright/states-reporter.d.cts +15 -0
- package/dist/playwright/states-reporter.d.ts +15 -0
- package/dist/playwright/states-reporter.js +36 -7
- package/dist/playwright/states-reporter.js.map +1 -1
- package/dist/playwright/states.cjs +8 -0
- package/dist/playwright/states.cjs.map +1 -1
- package/dist/playwright/states.d.cts +44 -1
- package/dist/playwright/states.d.ts +44 -1
- package/dist/playwright/states.js +7 -0
- package/dist/playwright/states.js.map +1 -1
- package/dist/react/index.cjs +3 -0
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +19 -13
- package/dist/react/index.d.ts +19 -13
- package/dist/react/index.js +3 -0
- package/dist/react/index.js.map +1 -1
- package/dist/scan/index.cjs +1217 -257
- package/dist/scan/index.cjs.map +1 -1
- package/dist/scan/index.d.cts +410 -104
- package/dist/scan/index.d.ts +410 -104
- package/dist/scan/index.js +1197 -253
- package/dist/scan/index.js.map +1 -1
- package/package.json +17 -17
package/dist/scan/index.d.ts
CHANGED
|
@@ -15,8 +15,8 @@ interface EntityRef {
|
|
|
15
15
|
/**
|
|
16
16
|
* The four kinds every captured route is expected to render — the state-matrix
|
|
17
17
|
* axis. A capture that only shoots the happy path leaves most of the value on
|
|
18
|
-
* the table, so a missing core kind must be captured
|
|
19
|
-
*
|
|
18
|
+
* the table, so a missing core kind must be captured or ACKNOWLEDGED (see
|
|
19
|
+
* `Metadata.acknowledge`).
|
|
20
20
|
*/
|
|
21
21
|
declare const CORE_STATE_KINDS: readonly ["loading", "empty", "populated", "error"];
|
|
22
22
|
type CoreStateKind = (typeof CORE_STATE_KINDS)[number];
|
|
@@ -50,18 +50,24 @@ interface Metadata {
|
|
|
50
50
|
/** Declared render states (see StateDecl); the capture-completeness gate. */
|
|
51
51
|
states?: StateDecl[];
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
53
|
+
* The ONE way to say "don't complain about this gap": scope → typed reason.
|
|
54
|
+
*
|
|
55
|
+
* - scope `"*"` covers the whole entity (a redirect page that can never be
|
|
56
|
+
* captured, a component whose states are not gated here);
|
|
57
|
+
* - a core-kind scope covers that one cell of this page's route matrix.
|
|
58
|
+
*
|
|
59
|
+
* `type: "impossible"` is a permanent design statement and needs a `reason`;
|
|
60
|
+
* `type: "backlog"` is debt and does not. Keeping both as values of one field
|
|
61
|
+
* is what makes mislabeling debt as impossible a visible, reviewable edit.
|
|
62
|
+
* `captured-elsewhere` is computed by the workspace and never authored here.
|
|
56
63
|
*/
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
waivers?: Partial<Record<CoreStateKind, string>>;
|
|
64
|
+
acknowledge?: Partial<Record<AcknowledgeScope, MetaAcknowledgment>>;
|
|
65
|
+
}
|
|
66
|
+
/** Scope of an acknowledgment: one core kind, or the whole entity. */
|
|
67
|
+
type AcknowledgeScope = CoreStateKind | "*";
|
|
68
|
+
interface MetaAcknowledgment {
|
|
69
|
+
type: "impossible" | "backlog";
|
|
70
|
+
reason?: string;
|
|
65
71
|
}
|
|
66
72
|
interface EntityWithMetaBase {
|
|
67
73
|
id: string;
|
|
@@ -153,7 +159,18 @@ interface SourceConfig {
|
|
|
153
159
|
interface ConventionsConfig {
|
|
154
160
|
primitives?: string[] | false;
|
|
155
161
|
features?: string | false;
|
|
156
|
-
|
|
162
|
+
/**
|
|
163
|
+
* How Page entities are derived from route files.
|
|
164
|
+
* - `"auto"` (default): infer the route root from the `app`/`pages`/
|
|
165
|
+
* `routes` directory-name heuristic.
|
|
166
|
+
* - `{ routesDir }`: the route root is exactly this dir (relative to the
|
|
167
|
+
* config dir), detected with the TanStack / React Router `routes/`-style
|
|
168
|
+
* convention. Use for a custom TanStack `routesDirectory`.
|
|
169
|
+
* - `false`: do not derive pages from routes.
|
|
170
|
+
*/
|
|
171
|
+
pages?: "auto" | false | {
|
|
172
|
+
routesDir: string;
|
|
173
|
+
};
|
|
157
174
|
flows?: string[] | false;
|
|
158
175
|
regions?: "landmarks" | false;
|
|
159
176
|
}
|
|
@@ -167,6 +184,15 @@ interface AuditConfig {
|
|
|
167
184
|
pageCoverage?: boolean;
|
|
168
185
|
/** State-matrix (core-kind) gate (default on when a manifest is present). */
|
|
169
186
|
stateMatrix?: boolean;
|
|
187
|
+
/**
|
|
188
|
+
* Per-diagnostic-code severity overrides, e.g.
|
|
189
|
+
* `{ "missing-state-capture": "error" }`. A code's built-in severity is a
|
|
190
|
+
* default, not a policy — this is how a repo escalates an advisory gate into
|
|
191
|
+
* a build-failing ratchet, or silences one it has consciously accepted.
|
|
192
|
+
* `"off"` drops the diagnostic entirely. Unknown codes are rejected by
|
|
193
|
+
* config validation, so a typo can't silently disable a gate.
|
|
194
|
+
*/
|
|
195
|
+
severity?: Record<string, DiagnosticSeverity | "off">;
|
|
170
196
|
}
|
|
171
197
|
interface UidexConfig {
|
|
172
198
|
$schema?: string;
|
|
@@ -183,11 +209,21 @@ interface UidexConfig {
|
|
|
183
209
|
*/
|
|
184
210
|
statesManifest?: string;
|
|
185
211
|
/**
|
|
186
|
-
* Path (relative to the config dir) of the
|
|
187
|
-
* tool-managed, shrink-only backlog written by
|
|
188
|
-
* Defaults to `uidex-coverage-baseline.json`.
|
|
212
|
+
* Path (relative to the config dir) of the coverage baseline — the
|
|
213
|
+
* tool-managed, shrink-only `acknowledge` backlog written by
|
|
214
|
+
* `uidex scan --update-baseline`. Defaults to `uidex-coverage-baseline.json`.
|
|
189
215
|
*/
|
|
190
216
|
coverageBaseline?: string;
|
|
217
|
+
/**
|
|
218
|
+
* The workspace this app belongs to — the level that answers "did a SIBLING
|
|
219
|
+
* app capture this shared declaration?" exactly, instead of guessing.
|
|
220
|
+
*
|
|
221
|
+
* - omitted (default): auto-detect by walking up for a workspace marker
|
|
222
|
+
* (`pnpm-workspace.yaml`, `turbo.json`, `.git`, …). Costs a few JSON reads.
|
|
223
|
+
* - a path (relative to the config dir): use exactly this root.
|
|
224
|
+
* - `false`: closed world — never look at siblings.
|
|
225
|
+
*/
|
|
226
|
+
workspace?: string | false;
|
|
191
227
|
}
|
|
192
228
|
interface DiscoveredConfig {
|
|
193
229
|
configPath: string;
|
|
@@ -242,16 +278,32 @@ interface MetadataExport {
|
|
|
242
278
|
* one adjacent comma) keyed by field name. Used by `--fix` codemods.
|
|
243
279
|
*/
|
|
244
280
|
fieldSpans?: Record<string, Span>;
|
|
281
|
+
/**
|
|
282
|
+
* Spans of top-level fields WITHOUT the comma extension (key start → value
|
|
283
|
+
* end), keyed by field name. `fieldSpans` deletes a property; this one
|
|
284
|
+
* replaces it in place, which is what the `acknowledge` codemod needs.
|
|
285
|
+
*/
|
|
286
|
+
fieldPropSpans?: Record<string, Span>;
|
|
245
287
|
/** Spans of the string-literal items in `features`, parallel to `features`. */
|
|
246
288
|
featureSpans?: Span[];
|
|
247
289
|
/** Spans of the string-literal items in `widgets`, parallel to `widgets`. */
|
|
248
290
|
widgetSpans?: Span[];
|
|
249
291
|
/** Declared render states (page/feature/widget); see ParsedState. */
|
|
250
292
|
states?: ParsedState[];
|
|
251
|
-
/**
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
|
|
293
|
+
/** Scope (`"*"` | core kind) → typed acknowledgment. See `acknowledge.ts`. */
|
|
294
|
+
acknowledge?: Record<string, ParsedAcknowledgment>;
|
|
295
|
+
/**
|
|
296
|
+
* REMOVED fields, parsed only so the `acknowledge` codemod can rewrite them.
|
|
297
|
+
* No gate reads these — an unmigrated declaration is an error with a fix
|
|
298
|
+
* attached, never a silently different meaning.
|
|
299
|
+
*/
|
|
300
|
+
legacyCapture?: boolean;
|
|
301
|
+
legacyWaivers?: Record<string, string>;
|
|
302
|
+
}
|
|
303
|
+
/** A parsed `acknowledge` entry from `export const uidex`. */
|
|
304
|
+
interface ParsedAcknowledgment {
|
|
305
|
+
type: "impossible" | "backlog";
|
|
306
|
+
reason?: string;
|
|
255
307
|
}
|
|
256
308
|
/**
|
|
257
309
|
* A parsed `states` entry from `export const uidex`. The authored form is a
|
|
@@ -394,6 +446,13 @@ declare const CONFIG_FILENAME = ".uidex.json";
|
|
|
394
446
|
interface DiscoverOptions {
|
|
395
447
|
cwd?: string;
|
|
396
448
|
maxDepth?: number;
|
|
449
|
+
/**
|
|
450
|
+
* Keep descending past a config found at `cwd` itself, so the result is EVERY
|
|
451
|
+
* config in the tree rather than the innermost one. The workspace level needs
|
|
452
|
+
* the whole set; a plain `uidex scan` wants the nearest, which is why that
|
|
453
|
+
* stays the default.
|
|
454
|
+
*/
|
|
455
|
+
all?: boolean;
|
|
397
456
|
}
|
|
398
457
|
declare function discover(options?: DiscoverOptions): DiscoveredConfig[];
|
|
399
458
|
|
|
@@ -463,6 +522,151 @@ interface ResolveOutput {
|
|
|
463
522
|
*/
|
|
464
523
|
declare function resolve(ctx: ResolveContext): ResolveOutput;
|
|
465
524
|
|
|
525
|
+
/**
|
|
526
|
+
* ACKNOWLEDGMENT — the single primitive for "don't complain about this".
|
|
527
|
+
*
|
|
528
|
+
* There used to be four mechanisms plus an implicit fifth (`waivers`, the
|
|
529
|
+
* baseline's `missingKinds` / `uncapturedPages`, `capture: false`, and a
|
|
530
|
+
* borrowed-entity inference). They overlapped, lived in unrelated files with
|
|
531
|
+
* unrelated shapes, and the rule keeping them straight — "don't fold capturable
|
|
532
|
+
* debt into a waiver" — was enforced socially, in prose, because nothing
|
|
533
|
+
* structural stopped you reaching for the wrong one.
|
|
534
|
+
*
|
|
535
|
+
* They are now one map from a SCOPE to a typed REASON:
|
|
536
|
+
*
|
|
537
|
+
* acknowledge: {
|
|
538
|
+
* empty: { type: "impossible", reason: "a create form has no empty state" },
|
|
539
|
+
* "*": { type: "backlog" },
|
|
540
|
+
* }
|
|
541
|
+
*
|
|
542
|
+
* SCOPE is a core kind (that kind of this route) or `"*"` (the whole entity —
|
|
543
|
+
* every axis of it). TYPE is why:
|
|
544
|
+
*
|
|
545
|
+
* - `impossible` — human-authored. The surface CANNOT render this; a
|
|
546
|
+
* substantive `reason` is REQUIRED. Contradicted by an actual capture →
|
|
547
|
+
* `stale-acknowledgment`.
|
|
548
|
+
* - `backlog` — debt. It CAN render this and nobody has captured it yet.
|
|
549
|
+
* `reason` is optional. Tool-managed in the coverage baseline
|
|
550
|
+
* (`--update-baseline`, shrink-only), and authorable in source when you want
|
|
551
|
+
* the debt to sit next to the declaration.
|
|
552
|
+
* - `captured-elsewhere` — COMPUTED by the workspace level, never authored: a
|
|
553
|
+
* sibling app that registers this same declaration captured it. Replaces the
|
|
554
|
+
* borrowed-entity guess with an exact answer.
|
|
555
|
+
*
|
|
556
|
+
* The anti-laundering property is now structural rather than social:
|
|
557
|
+
* `impossible` and `backlog` are sibling values of ONE field, so choosing wrongly
|
|
558
|
+
* is a one-token, greppable, reviewable edit — not a choice between two
|
|
559
|
+
* unrelated files. And the reason rule points the lazy path at the honest
|
|
560
|
+
* answer: `impossible` costs you a sentence, `backlog` is free.
|
|
561
|
+
*/
|
|
562
|
+
/** The scope key meaning "the whole entity", not one core kind. */
|
|
563
|
+
declare const ACK_ALL = "*";
|
|
564
|
+
/** Every valid scope key: the four core kinds plus `"*"`. */
|
|
565
|
+
declare const ACK_SCOPES: readonly string[];
|
|
566
|
+
type AcknowledgeType = "impossible" | "backlog" | "captured-elsewhere";
|
|
567
|
+
interface Acknowledgment {
|
|
568
|
+
type: AcknowledgeType;
|
|
569
|
+
/** Why. Required for `impossible`, optional for `backlog`. */
|
|
570
|
+
reason?: string;
|
|
571
|
+
/** `captured-elsewhere` only: the workspace app whose capture covers this. */
|
|
572
|
+
by?: string;
|
|
573
|
+
}
|
|
574
|
+
/** scope (`"*"` | core kind) → why it is acknowledged. */
|
|
575
|
+
type AcknowledgeMap = Record<string, Acknowledgment>;
|
|
576
|
+
/** Where an acknowledgment came from, so diagnostics can point at the right file. */
|
|
577
|
+
type AcknowledgeOrigin = "source" | "baseline" | "workspace";
|
|
578
|
+
interface ResolvedAcknowledgment extends Acknowledgment {
|
|
579
|
+
scope: string;
|
|
580
|
+
origin: AcknowledgeOrigin;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* The committed, tool-managed coverage backlog. Same `Acknowledgment` shape as
|
|
584
|
+
* the source-authored field, keyed by route pattern — so the two really are the
|
|
585
|
+
* same primitive in two homes, not two lookalikes.
|
|
586
|
+
*/
|
|
587
|
+
interface CoverageBaseline {
|
|
588
|
+
version: 2;
|
|
589
|
+
/** route pattern → scope → acknowledgment (always `type: "backlog"`). */
|
|
590
|
+
acknowledge: Record<string, AcknowledgeMap>;
|
|
591
|
+
}
|
|
592
|
+
/** The pre-`acknowledge` baseline shape, recognized only to migrate it. */
|
|
593
|
+
interface LegacyCoverageBaseline {
|
|
594
|
+
uncapturedPages?: string[];
|
|
595
|
+
missingKinds?: Record<string, string[]>;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* The acknowledgments in force for one ROUTE: the baseline's entry for the route
|
|
599
|
+
* layered UNDER the page's own `acknowledge`. Source wins — a human statement
|
|
600
|
+
* outranks tool-recorded debt, and `--update-baseline` drops the shadowed entry
|
|
601
|
+
* on its next run.
|
|
602
|
+
*/
|
|
603
|
+
declare function routeAcknowledge(registry: Registry, baseline: CoverageBaseline | undefined, route: string, page: string | undefined): Map<string, ResolvedAcknowledgment>;
|
|
604
|
+
/**
|
|
605
|
+
* The acknowledgments in force for one ENTITY (page/feature/widget). Only `"*"`
|
|
606
|
+
* is meaningful here — the core-kind axis is route-scoped. `capturedElsewhere`
|
|
607
|
+
* is the workspace's computed verdict; source still wins, so an explicit
|
|
608
|
+
* `impossible` is never silently reinterpreted.
|
|
609
|
+
*/
|
|
610
|
+
declare function entityAcknowledge(meta: Metadata | undefined, capturedElsewhereBy?: string): ResolvedAcknowledgment | undefined;
|
|
611
|
+
/**
|
|
612
|
+
* Parse a v2 baseline. Malformed entries are dropped rather than thrown on: a
|
|
613
|
+
* hand-mangled baseline must not crash the scan, and every dropped entry simply
|
|
614
|
+
* re-appears as an unacknowledged gap the gate reports.
|
|
615
|
+
*/
|
|
616
|
+
declare function parseCoverageBaseline(raw: unknown): CoverageBaseline | null;
|
|
617
|
+
/** Whether a parsed baseline JSON is the pre-`acknowledge` shape. */
|
|
618
|
+
declare function isLegacyBaseline(raw: unknown): raw is LegacyCoverageBaseline;
|
|
619
|
+
/** Convert the pre-`acknowledge` baseline shape to v2. Pure; no I/O. */
|
|
620
|
+
declare function migrateBaseline(legacy: LegacyCoverageBaseline): CoverageBaseline;
|
|
621
|
+
/** Serialize a baseline with stable key order, so its diff is reviewable. */
|
|
622
|
+
declare function serializeCoverageBaseline(baseline: CoverageBaseline): string;
|
|
623
|
+
|
|
624
|
+
interface WorkspaceApp {
|
|
625
|
+
/** Display name — the config dir's basename (e.g. "platform-web"). */
|
|
626
|
+
name: string;
|
|
627
|
+
configDir: string;
|
|
628
|
+
configPath: string;
|
|
629
|
+
/** Whether this app's configured sources would register `absFile`. */
|
|
630
|
+
registers: (absFile: string) => boolean;
|
|
631
|
+
/** `captureKey` values this app's manifest captured. */
|
|
632
|
+
captured: ReadonlySet<string>;
|
|
633
|
+
}
|
|
634
|
+
interface Workspace {
|
|
635
|
+
root: string;
|
|
636
|
+
/** Every OTHER app in the workspace; the scanning app is excluded. */
|
|
637
|
+
siblings: WorkspaceApp[];
|
|
638
|
+
}
|
|
639
|
+
interface ResolveWorkspaceOptions {
|
|
640
|
+
configDir: string;
|
|
641
|
+
config: UidexConfig;
|
|
642
|
+
/** Overrides discovery; used by tests to avoid touching the real filesystem. */
|
|
643
|
+
configs?: DiscoveredConfig[];
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Walk up from the app looking for a workspace marker. Returns `undefined` when
|
|
647
|
+
* there is none — a standalone app then keeps today's closed-world behavior
|
|
648
|
+
* rather than silently treating its parent directory as a workspace.
|
|
649
|
+
*/
|
|
650
|
+
declare function findWorkspaceRoot(configDir: string): string | undefined;
|
|
651
|
+
/**
|
|
652
|
+
* Resolve the workspace an app participates in.
|
|
653
|
+
*
|
|
654
|
+
* `config.workspace` selects the root: a path (relative to the config dir),
|
|
655
|
+
* `false` for an explicit closed world, or omitted for auto-detect via
|
|
656
|
+
* `findWorkspaceRoot`. Returns `undefined` when there is no workspace or no
|
|
657
|
+
* sibling in it — callers then behave exactly as a single-app scan does.
|
|
658
|
+
*/
|
|
659
|
+
declare function resolveWorkspace(opts: ResolveWorkspaceOptions): Workspace | undefined;
|
|
660
|
+
/**
|
|
661
|
+
* The sibling app that already captured this declaration, if any — the exact
|
|
662
|
+
* answer that replaces the borrowed-entity guess. `absFile` is the declaring
|
|
663
|
+
* file's real path (a display path cannot be resolved back across a `prefix`).
|
|
664
|
+
*
|
|
665
|
+
* A capture recording no `kind` matches on id alone, mirroring how the
|
|
666
|
+
* state-coverage gate treats an unlabeled capture.
|
|
667
|
+
*/
|
|
668
|
+
declare function capturedElsewhere(workspace: Workspace | undefined, absFile: string | undefined, kind: string, id: string): string | undefined;
|
|
669
|
+
|
|
466
670
|
/**
|
|
467
671
|
* State-capture completeness — the deterministic replacement for a bespoke
|
|
468
672
|
* `check-states.mjs`. Cross-checks the states DECLARED on registry entities
|
|
@@ -476,8 +680,17 @@ declare function resolve(ctx: ResolveContext): ResolveOutput;
|
|
|
476
680
|
* - `orphan-state-capture` — a run captured a state the entity never declared,
|
|
477
681
|
* so the contact sheet shows a state the registry doesn't know about.
|
|
478
682
|
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
683
|
+
* Two things take an entity out of the gate, and both are acknowledgments:
|
|
684
|
+
* a SOURCE `acknowledge: { "*": … }`, or the WORKSPACE observing that a sibling
|
|
685
|
+
* app which registers this same declaration captured it (`captured-elsewhere`).
|
|
686
|
+
*
|
|
687
|
+
* The workspace answer replaces a heuristic this module used to carry: a
|
|
688
|
+
* "borrowed" entity (one declared under a shared source `prefix`) that this app
|
|
689
|
+
* captured nothing for was ASSUMED to be a sibling's responsibility. That guess
|
|
690
|
+
* could not tell a genuinely shared component from an id that merely collided
|
|
691
|
+
* with a sibling's, and it silenced real gaps whenever an app captured nothing.
|
|
692
|
+
* The workspace checks the sibling's actual source set and actual manifest, so
|
|
693
|
+
* the answer is exact and the guess is gone.
|
|
481
694
|
*/
|
|
482
695
|
/** One `entity/state` a capture run recorded. */
|
|
483
696
|
interface CapturedState {
|
|
@@ -496,49 +709,21 @@ interface CapturedState {
|
|
|
496
709
|
}
|
|
497
710
|
interface CapturedStatesManifest {
|
|
498
711
|
captured: CapturedState[];
|
|
712
|
+
/** ISO timestamp the reporter stamped; drives the staleness check. */
|
|
713
|
+
generatedAt?: string;
|
|
499
714
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
* unlike a hand-rolled `src/app/**` walk there is no page list to enumerate or
|
|
511
|
-
* keep from rotting: a route exists in the registry or it doesn't. And captures
|
|
512
|
-
* observe the URL they ran against, so which route a capture covers is matched,
|
|
513
|
-
* never hand-declared.
|
|
514
|
-
*/
|
|
515
|
-
/** The committed, tool-managed backlog. Shrinks as pages/kinds get captured. */
|
|
516
|
-
interface PageCoverageBaseline {
|
|
517
|
-
/** Route patterns acknowledged as uncaptured (e.g. "/[scope]/collections"). */
|
|
518
|
-
uncapturedPages: string[];
|
|
519
|
-
/** route → core kinds acknowledged as not-yet-captured (the matrix backlog). */
|
|
520
|
-
missingKinds?: Record<string, string[]>;
|
|
715
|
+
interface StateCoverageOptions {
|
|
716
|
+
/** The workspace, when one is resolved; enables `captured-elsewhere`. */
|
|
717
|
+
workspace?: Workspace;
|
|
718
|
+
/**
|
|
719
|
+
* Maps an entity's `loc.file` (a DISPLAY path, which a `prefix` makes
|
|
720
|
+
* unresolvable on its own) back to its real path on disk. Supplied by
|
|
721
|
+
* `audit()` from the scanned-file list, so the workspace can test the file
|
|
722
|
+
* against a sibling's source roots.
|
|
723
|
+
*/
|
|
724
|
+
resolveSourcePath?: (displayPath: string) => string | undefined;
|
|
521
725
|
}
|
|
522
|
-
|
|
523
|
-
* Turn a Next-style route pattern into an anchored matcher.
|
|
524
|
-
* `/[scope]/products/[productId]/edit` → matches `/acme/products/p1/edit`
|
|
525
|
-
* `/[...slug]` (catch-all) → matches one or more segments
|
|
526
|
-
* Returns the literal-segment count as a specificity score, so the most literal
|
|
527
|
-
* matching pattern wins when several match (e.g. `/x/new` beats `/x/[id]`).
|
|
528
|
-
*/
|
|
529
|
-
declare function compileRoute(pattern: string): {
|
|
530
|
-
test: (url: string) => boolean;
|
|
531
|
-
specificity: number;
|
|
532
|
-
};
|
|
533
|
-
/** The route pattern (from the given set) that most-specifically matches a URL. */
|
|
534
|
-
declare function matchUrlToRoute(url: string, routePaths: string[]): string | null;
|
|
535
|
-
declare function checkPageCoverage(registry: Registry, manifest: CapturedStatesManifest, baseline?: PageCoverageBaseline): Diagnostic[];
|
|
536
|
-
/**
|
|
537
|
-
* Compute the shrink-only baseline for `--update-baseline`: every route with no
|
|
538
|
-
* capture and no opt-out. Seeds on first run and prunes on later runs; growth
|
|
539
|
-
* only ever happens through this explicit command, visible in the file's diff.
|
|
540
|
-
*/
|
|
541
|
-
declare function computePageBaseline(registry: Registry, manifest: CapturedStatesManifest): PageCoverageBaseline;
|
|
726
|
+
declare function checkStateCoverage(registry: Registry, manifest: CapturedStatesManifest, opts?: StateCoverageOptions): Diagnostic[];
|
|
542
727
|
|
|
543
728
|
interface AuditOptions {
|
|
544
729
|
registry: Registry;
|
|
@@ -570,8 +755,12 @@ interface AuditOptions {
|
|
|
570
755
|
locsOutputPath?: string;
|
|
571
756
|
/** Captured-states manifest for the completeness gate; absent → gate skipped. */
|
|
572
757
|
capturedStates?: CapturedStatesManifest;
|
|
573
|
-
/**
|
|
574
|
-
|
|
758
|
+
/** Coverage baseline (the shrink-only `acknowledge` backlog). */
|
|
759
|
+
coverageBaseline?: CoverageBaseline;
|
|
760
|
+
/** Migration diagnostic when the baseline on disk is the removed shape. */
|
|
761
|
+
baselineDiagnostic?: Diagnostic;
|
|
762
|
+
/** The resolved workspace; enables `captured-elsewhere`. */
|
|
763
|
+
workspace?: Workspace;
|
|
575
764
|
}
|
|
576
765
|
declare function audit(opts: AuditOptions): AuditSummary;
|
|
577
766
|
|
|
@@ -611,10 +800,26 @@ declare function emit(opts: EmitOptions): EmitResult;
|
|
|
611
800
|
* Detect framework-aware routes:
|
|
612
801
|
* - Next.js App Router: `**\/app/**\/page.tsx` -> `/segments`
|
|
613
802
|
* - Next.js Pages Router: `**\/pages/**\/*.tsx` -> `/segments` (excludes _app, _document, api/)
|
|
614
|
-
* - Vite +
|
|
615
|
-
*
|
|
803
|
+
* - Vite + TanStack Router / React Router: `**\/routes/**\/*.tsx` -> `/segments`.
|
|
804
|
+
* Honors TanStack conventions: pathless layouts (`_layout`) and route
|
|
805
|
+
* groups (`(group)`) drop from the path, the folder route file
|
|
806
|
+
* (`route.tsx`) collapses like `index`, `-`-prefixed and `api/` files are
|
|
807
|
+
* excluded, and flat dot-notation filenames (`a.b.$c.tsx`) split into
|
|
808
|
+
* nested segments. Dynamic segments are normalized to the canonical
|
|
809
|
+
* `[param]` form used everywhere else in the pipeline: `$id` -> `[id]`,
|
|
810
|
+
* the splat `$` -> `[...splat]`, and `[.]`-escaped literals are unwrapped.
|
|
616
811
|
*/
|
|
617
|
-
|
|
812
|
+
interface DetectRoutesOptions {
|
|
813
|
+
/**
|
|
814
|
+
* Explicit routes root, as it appears in `displayPath` (relative to the
|
|
815
|
+
* config dir). When set, only files under this dir are routes and the
|
|
816
|
+
* TanStack / React Router `routes/`-style convention is applied — overriding
|
|
817
|
+
* the `app`/`pages`/`routes` directory-name heuristic. Use for a custom
|
|
818
|
+
* TanStack `routesDirectory`.
|
|
819
|
+
*/
|
|
820
|
+
routesDir?: string;
|
|
821
|
+
}
|
|
822
|
+
declare function detectRoutes(files: ScannedFile[], options?: DetectRoutesOptions): DetectedRoute[];
|
|
618
823
|
declare function pathToId(routePath: string): string;
|
|
619
824
|
|
|
620
825
|
interface GitResolveOptions {
|
|
@@ -661,11 +866,22 @@ interface RunScanOptions {
|
|
|
661
866
|
configs?: DiscoveredConfig[];
|
|
662
867
|
/** Captured-states manifest for the completeness gate; overrides on-disk load. */
|
|
663
868
|
capturedStates?: CapturedStatesManifest;
|
|
664
|
-
/**
|
|
665
|
-
|
|
869
|
+
/** Coverage baseline (the shrink-only backlog); overrides on-disk load. */
|
|
870
|
+
coverageBaseline?: CoverageBaseline;
|
|
871
|
+
/** Workspace override; skips auto-detection (used by tests). */
|
|
872
|
+
workspace?: Workspace;
|
|
666
873
|
}
|
|
667
|
-
|
|
668
|
-
|
|
874
|
+
interface LoadedBaseline {
|
|
875
|
+
baseline?: CoverageBaseline;
|
|
876
|
+
/** Present when the file on disk is the pre-`acknowledge` shape. */
|
|
877
|
+
legacy?: Diagnostic;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Load the coverage baseline from disk. A file in the REMOVED shape does not
|
|
881
|
+
* silently read as "no baseline" — that would turn every acknowledged-backlog
|
|
882
|
+
* route into a hard error — it comes back as a migration diagnostic with a fix.
|
|
883
|
+
*/
|
|
884
|
+
declare function loadCoverageBaseline(configDir: string, config: UidexConfig): LoadedBaseline;
|
|
669
885
|
/** One generated artifact: its bytes plus where it lands (abs + repo-relative). */
|
|
670
886
|
interface ScanArtifact {
|
|
671
887
|
generated: string;
|
|
@@ -697,33 +913,113 @@ declare function runScan(opts?: RunScanOptions): ScanResult[];
|
|
|
697
913
|
* actually moved. Returns `true` if any file was written.
|
|
698
914
|
*/
|
|
699
915
|
declare function writeScanResult(result: ScanResult): boolean;
|
|
700
|
-
/** Absolute path of the
|
|
701
|
-
declare function
|
|
916
|
+
/** Absolute path of the coverage baseline for a scan result. */
|
|
917
|
+
declare function coverageBaselinePath(result: ScanResult): string;
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Page-capture coverage — the coarse gate above the per-state gate. Every
|
|
921
|
+
* derived route must be accounted for: captured (a run drove it), or
|
|
922
|
+
* ACKNOWLEDGED at `"*"` scope — either `impossible` (a redirect, a static
|
|
923
|
+
* notice, an internal target — the old `capture: false`, which now has to say
|
|
924
|
+
* WHY) or `backlog` (tool-managed, shrink-only). A route in none of those is a
|
|
925
|
+
* *new* uncaptured page and fails hard — the ratchet that keeps debt growing.
|
|
926
|
+
*
|
|
927
|
+
* uidex derives the route set (route groups stripped, dynamic segments kept), so
|
|
928
|
+
* unlike a hand-rolled `src/app/**` walk there is no page list to enumerate or
|
|
929
|
+
* keep from rotting: a route exists in the registry or it doesn't. And captures
|
|
930
|
+
* observe the URL they ran against, so which route a capture covers is matched,
|
|
931
|
+
* never hand-declared.
|
|
932
|
+
*/
|
|
933
|
+
/**
|
|
934
|
+
* Turn a Next-style route pattern into an anchored matcher.
|
|
935
|
+
* `/[scope]/products/[productId]/edit` → matches `/acme/products/p1/edit`
|
|
936
|
+
* `/[...slug]` (catch-all) → matches one or more segments
|
|
937
|
+
* Returns the literal-segment count as a specificity score, so the most literal
|
|
938
|
+
* matching pattern wins when several match (e.g. `/x/new` beats `/x/[id]`).
|
|
939
|
+
*/
|
|
940
|
+
declare function compileRoute(pattern: string): {
|
|
941
|
+
test: (url: string) => boolean;
|
|
942
|
+
specificity: number;
|
|
943
|
+
};
|
|
944
|
+
/** The route pattern (from the given set) that most-specifically matches a URL. */
|
|
945
|
+
declare function matchUrlToRoute(url: string, routePaths: string[]): string | null;
|
|
946
|
+
declare function checkPageCoverage(registry: Registry, manifest: CapturedStatesManifest, baseline?: CoverageBaseline): Diagnostic[];
|
|
947
|
+
/**
|
|
948
|
+
* The page half of the shrink-only baseline for `--update-baseline`: every route
|
|
949
|
+
* with no capture and no SOURCE acknowledgment. Seeds on first run and prunes on
|
|
950
|
+
* later runs; growth only ever happens through this explicit command, visible in
|
|
951
|
+
* the file's diff.
|
|
952
|
+
*/
|
|
953
|
+
declare function computePageBacklog(registry: Registry, manifest: CapturedStatesManifest): Record<string, AcknowledgeMap>;
|
|
954
|
+
|
|
955
|
+
declare function checkStateMatrix(registry: Registry, manifest: CapturedStatesManifest, baseline?: CoverageBaseline): Diagnostic[];
|
|
956
|
+
/**
|
|
957
|
+
* The matrix half of the shrink-only baseline for `--update-baseline`: every
|
|
958
|
+
* (captured route, core kind) that is neither captured nor already acknowledged
|
|
959
|
+
* in SOURCE. Source acknowledgments are excluded so the same gap is never
|
|
960
|
+
* recorded twice, in two files, saying two things.
|
|
961
|
+
*/
|
|
962
|
+
declare function computeMatrixBacklog(registry: Registry, manifest: CapturedStatesManifest): Record<string, AcknowledgeMap>;
|
|
963
|
+
|
|
964
|
+
declare function checkComponentCoverage(registry: Registry, manifest: CapturedStatesManifest): Diagnostic[];
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* Migration diagnostics for one file's `export const uidex` blocks. Emitted as
|
|
968
|
+
* errors: the fields are gone, so the declaration means nothing until rewritten.
|
|
969
|
+
*/
|
|
970
|
+
declare function migrateLegacyFields(ef: ExtractedFile): Diagnostic[];
|
|
971
|
+
/**
|
|
972
|
+
* Migration diagnostic for a pre-`acknowledge` coverage baseline. The whole file
|
|
973
|
+
* is replaced, which is safe because the baseline is a generated artifact: the
|
|
974
|
+
* same content `--update-baseline` would produce, just without needing a capture
|
|
975
|
+
* run to regenerate it.
|
|
976
|
+
*/
|
|
977
|
+
declare function migrateLegacyBaseline(opts: {
|
|
978
|
+
path: string;
|
|
979
|
+
raw: string;
|
|
980
|
+
parsed: unknown;
|
|
981
|
+
displayPath: string;
|
|
982
|
+
}): Diagnostic | undefined;
|
|
702
983
|
|
|
703
984
|
/**
|
|
704
|
-
*
|
|
705
|
-
* shoots the happy path leaves most of the harness's value on the table, so
|
|
706
|
-
* every *captured* route must render each of the four core kinds
|
|
707
|
-
* (loading / empty / populated / error), or account for the gap:
|
|
985
|
+
* Fold a PARTIAL capture manifest into the committed one.
|
|
708
986
|
*
|
|
709
|
-
*
|
|
710
|
-
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
987
|
+
* A filtered run (`--grep`) legitimately captures a subset, so the reporter's
|
|
988
|
+
* shrink guard diverts it to `<name>.partial.json` rather than overwriting a
|
|
989
|
+
* fuller manifest. Without a merge path the only ways forward are re-running the
|
|
990
|
+
* FULL suite or hand-editing JSON — both of which make scoped re-capture
|
|
991
|
+
* expensive enough that people skip it and let the manifest drift.
|
|
713
992
|
*
|
|
714
|
-
*
|
|
715
|
-
*
|
|
993
|
+
* Merge is by `(kind, entity, state)`: rows in the partial REPLACE their
|
|
994
|
+
* counterparts (that run is the newer evidence) and rows absent from it are
|
|
995
|
+
* preserved (it never claimed to cover them). So merging is purely additive to
|
|
996
|
+
* coverage — it can never drop a state, which is what makes it safe to run
|
|
997
|
+
* without re-reading the whole manifest.
|
|
716
998
|
*/
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
999
|
+
interface MergeResult {
|
|
1000
|
+
manifest: CapturedStatesManifest;
|
|
1001
|
+
/** `(kind, entity, state)` keys the partial replaced. */
|
|
1002
|
+
updated: string[];
|
|
1003
|
+
/** Keys the partial contributed that the committed manifest lacked. */
|
|
1004
|
+
added: string[];
|
|
1005
|
+
}
|
|
1006
|
+
declare function mergeStates(committed: CapturedStatesManifest, partial: CapturedStatesManifest): MergeResult;
|
|
1007
|
+
|
|
1008
|
+
declare class ConfigError extends Error {
|
|
1009
|
+
constructor(message: string);
|
|
720
1010
|
}
|
|
721
|
-
declare function checkStateMatrix(registry: Registry, manifest: CapturedStatesManifest, baseline?: StateMatrixBaseline): Diagnostic[];
|
|
722
1011
|
/**
|
|
723
|
-
*
|
|
724
|
-
*
|
|
1012
|
+
* Every diagnostic code the scanner can emit — the allow-list `audit.severity`
|
|
1013
|
+
* validates against, so a typo'd key fails loudly instead of silently leaving
|
|
1014
|
+
* the gate it was meant to govern at its default severity.
|
|
1015
|
+
*
|
|
1016
|
+
* Keep in sync when adding a diagnostic; `severity-codes.test.ts` asserts this
|
|
1017
|
+
* set matches the codes actually emitted by the source.
|
|
725
1018
|
*/
|
|
726
|
-
declare
|
|
1019
|
+
declare const DIAGNOSTIC_CODES: readonly ["acceptance-uncovered", "acknowledged-elsewhere", "competing-uidex-export", "component-state-uncaptured", "core-kind", "duplicate-id", "dynamic-attr", "dynamic-flow-reference", "gen-missing", "gen-stale", "legacy-acknowledge-field", "legacy-coverage-baseline", "matrix-backlog", "missing-element-annotation", "missing-state-capture", "orphan-state-capture", "page-backlog", "page-captured", "page-no-capture", "parse-error", "placeholder-reason", "prefer-well-known-file", "rename", "scope-leak", "spread-attr", "stale-acknowledgment", "stale-page-baseline", "uidex-export-ambiguous-kind", "uidex-export-duplicate-field", "uidex-export-duplicate-state", "uidex-export-empty-name", "uidex-export-invalid-field", "uidex-export-invalid-literal", "uidex-export-missing-kind", "uidex-export-satisfies-mismatch", "uidex-export-unknown-field", "unknown-reference", "widget-id-mismatch", "widget-missing-acceptance", "widget-missing-dom"];
|
|
1020
|
+
declare function validateConfig(raw: unknown): UidexConfig;
|
|
1021
|
+
declare function parseConfig(json: string): UidexConfig;
|
|
1022
|
+
declare const DEFAULT_CONVENTIONS: Required<Pick<NonNullable<UidexConfig["conventions"]>, "primitives" | "features" | "pages" | "flows" | "regions">>;
|
|
727
1023
|
|
|
728
1024
|
/**
|
|
729
1025
|
* Applies the machine-generated fixes attached to diagnostics.
|
|
@@ -789,13 +1085,6 @@ interface CliOptions {
|
|
|
789
1085
|
}
|
|
790
1086
|
declare function run(opts: CliOptions): Promise<CliResult>;
|
|
791
1087
|
|
|
792
|
-
declare class ConfigError extends Error {
|
|
793
|
-
constructor(message: string);
|
|
794
|
-
}
|
|
795
|
-
declare function validateConfig(raw: unknown): UidexConfig;
|
|
796
|
-
declare function parseConfig(json: string): UidexConfig;
|
|
797
|
-
declare const DEFAULT_CONVENTIONS: Required<Pick<NonNullable<UidexConfig["conventions"]>, "primitives" | "features" | "pages" | "flows" | "regions">>;
|
|
798
|
-
|
|
799
1088
|
/**
|
|
800
1089
|
* Authoring-surface shape types for `export const uidex = { ... }` declarations.
|
|
801
1090
|
*
|
|
@@ -822,8 +1111,24 @@ declare namespace Uidex {
|
|
|
822
1111
|
acceptance?: readonly string[];
|
|
823
1112
|
description?: string;
|
|
824
1113
|
}
|
|
825
|
-
/**
|
|
826
|
-
type
|
|
1114
|
+
/** Scope of an acknowledgment: one core kind, or the whole entity. */
|
|
1115
|
+
type AcknowledgeScope = CoreStateKind | "*";
|
|
1116
|
+
/** A permanent design statement — so it has to say why. */
|
|
1117
|
+
interface Impossible {
|
|
1118
|
+
type: "impossible";
|
|
1119
|
+
reason: string;
|
|
1120
|
+
}
|
|
1121
|
+
/** Debt: capturable, not captured yet. No reason required. */
|
|
1122
|
+
interface Backlog {
|
|
1123
|
+
type: "backlog";
|
|
1124
|
+
reason?: string;
|
|
1125
|
+
}
|
|
1126
|
+
type Acknowledgment = Impossible | Backlog;
|
|
1127
|
+
type Acknowledge = Partial<Record<AcknowledgeScope, Acknowledgment>>;
|
|
1128
|
+
/** A component has no route, so only the `"*"` scope applies to one. */
|
|
1129
|
+
type ComponentAcknowledge = {
|
|
1130
|
+
"*"?: Acknowledgment;
|
|
1131
|
+
};
|
|
827
1132
|
interface Page<PageIds extends string = string, FeatureIds extends string = string, WidgetIds extends string = string> {
|
|
828
1133
|
page: PageIds | false;
|
|
829
1134
|
name?: string;
|
|
@@ -831,8 +1136,7 @@ declare namespace Uidex {
|
|
|
831
1136
|
widgets?: readonly WidgetIds[];
|
|
832
1137
|
acceptance?: readonly string[];
|
|
833
1138
|
states?: readonly (string | State)[];
|
|
834
|
-
|
|
835
|
-
waivers?: Waivers;
|
|
1139
|
+
acknowledge?: Acknowledge;
|
|
836
1140
|
description?: string;
|
|
837
1141
|
}
|
|
838
1142
|
interface Feature<FeatureIds extends string = string> {
|
|
@@ -841,6 +1145,7 @@ declare namespace Uidex {
|
|
|
841
1145
|
features?: readonly FeatureIds[];
|
|
842
1146
|
acceptance?: readonly string[];
|
|
843
1147
|
states?: readonly (string | State)[];
|
|
1148
|
+
acknowledge?: ComponentAcknowledge;
|
|
844
1149
|
description?: string;
|
|
845
1150
|
}
|
|
846
1151
|
interface Primitive<PrimitiveIds extends string = string> {
|
|
@@ -853,6 +1158,7 @@ declare namespace Uidex {
|
|
|
853
1158
|
name?: string;
|
|
854
1159
|
acceptance?: readonly string[];
|
|
855
1160
|
states?: readonly (string | State)[];
|
|
1161
|
+
acknowledge?: ComponentAcknowledge;
|
|
856
1162
|
description?: string;
|
|
857
1163
|
}
|
|
858
1164
|
interface Flow<FlowIds extends string = string> {
|
|
@@ -865,4 +1171,4 @@ declare namespace Uidex {
|
|
|
865
1171
|
}
|
|
866
1172
|
}
|
|
867
1173
|
|
|
868
|
-
export { type Annotation, type AnnotationKind, type AppliedFix, type ApplyFixesResult, type AuditConfig, type AuditSummary, CONFIG_FILENAME, type CapturedState, type CapturedStatesManifest, type CliOptions, type CliResult, ConfigError, type ConventionsConfig, DEFAULT_CONVENTIONS, type DetectedRoute, type Diagnostic, type DiagnosticFix, type DiagnosticSeverity, type DiscoveredConfig, type ExtractedFile, type FixEdit, type GitContext, type ImportFact, type LandmarkFact, type MetadataExport, type MetadataExportKind, type
|
|
1174
|
+
export { ACK_ALL, ACK_SCOPES, type AcknowledgeMap, type AcknowledgeOrigin, type AcknowledgeType, type Acknowledgment, type Annotation, type AnnotationKind, type AppliedFix, type ApplyFixesResult, type AuditConfig, type AuditSummary, CONFIG_FILENAME, type CapturedState, type CapturedStatesManifest, type CliOptions, type CliResult, ConfigError, type ConventionsConfig, type CoverageBaseline, DEFAULT_CONVENTIONS, DIAGNOSTIC_CODES, type DetectedRoute, type Diagnostic, type DiagnosticFix, type DiagnosticSeverity, type DiscoveredConfig, type ExtractedFile, type FixEdit, type GitContext, type ImportFact, type LandmarkFact, type MergeResult, type MetadataExport, type MetadataExportKind, type ParsedAcknowledgment, type ParsedState, type RenameKind, type RenameOptions, type RenameResult, type ResolvedAcknowledgment, type RunScanOptions, type ScaffoldKind, type ScaffoldOptions, type ScaffoldResult, type ScaffoldSpecOptions, type ScanResult, type ScannedFile, type SourceConfig, type Span, type StateCoverageOptions, Uidex, type UidexConfig, type Workspace, type WorkspaceApp, applyFixes, audit, capturedElsewhere, checkComponentCoverage, checkPageCoverage, checkStateCoverage, checkStateMatrix, compileRoute, computeMatrixBacklog, computePageBacklog, coverageBaselinePath, detectRoutes, discover, emit, entityAcknowledge, extract, extractUidexExports, findWorkspaceRoot, globToRegExp, isLegacyBaseline, loadCoverageBaseline, matchUrlToRoute, mergeStates, migrateBaseline, migrateLegacyBaseline, migrateLegacyFields, parseConfig, parseCoverageBaseline, pathToId, renameEntity, resolve, resolveGitContext, resolveWorkspace, routeAcknowledge, run as runCli, runScan, scaffoldSpec, scaffoldWidgetSpec, serializeCoverageBaseline, validateConfig, walk, writeScanResult };
|