uidex 0.9.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 +1190 -335
- 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 +1129 -244
- package/dist/scan/index.cjs.map +1 -1
- package/dist/scan/index.d.cts +379 -100
- package/dist/scan/index.d.ts +379 -100
- package/dist/scan/index.js +1109 -240
- package/dist/scan/index.js.map +1 -1
- package/package.json +1 -1
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;
|
|
@@ -178,6 +184,15 @@ interface AuditConfig {
|
|
|
178
184
|
pageCoverage?: boolean;
|
|
179
185
|
/** State-matrix (core-kind) gate (default on when a manifest is present). */
|
|
180
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">;
|
|
181
196
|
}
|
|
182
197
|
interface UidexConfig {
|
|
183
198
|
$schema?: string;
|
|
@@ -194,11 +209,21 @@ interface UidexConfig {
|
|
|
194
209
|
*/
|
|
195
210
|
statesManifest?: string;
|
|
196
211
|
/**
|
|
197
|
-
* Path (relative to the config dir) of the
|
|
198
|
-
* tool-managed, shrink-only backlog written by
|
|
199
|
-
* 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`.
|
|
200
215
|
*/
|
|
201
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;
|
|
202
227
|
}
|
|
203
228
|
interface DiscoveredConfig {
|
|
204
229
|
configPath: string;
|
|
@@ -253,16 +278,32 @@ interface MetadataExport {
|
|
|
253
278
|
* one adjacent comma) keyed by field name. Used by `--fix` codemods.
|
|
254
279
|
*/
|
|
255
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>;
|
|
256
287
|
/** Spans of the string-literal items in `features`, parallel to `features`. */
|
|
257
288
|
featureSpans?: Span[];
|
|
258
289
|
/** Spans of the string-literal items in `widgets`, parallel to `widgets`. */
|
|
259
290
|
widgetSpans?: Span[];
|
|
260
291
|
/** Declared render states (page/feature/widget); see ParsedState. */
|
|
261
292
|
states?: ParsedState[];
|
|
262
|
-
/**
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
|
|
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;
|
|
266
307
|
}
|
|
267
308
|
/**
|
|
268
309
|
* A parsed `states` entry from `export const uidex`. The authored form is a
|
|
@@ -405,6 +446,13 @@ declare const CONFIG_FILENAME = ".uidex.json";
|
|
|
405
446
|
interface DiscoverOptions {
|
|
406
447
|
cwd?: string;
|
|
407
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;
|
|
408
456
|
}
|
|
409
457
|
declare function discover(options?: DiscoverOptions): DiscoveredConfig[];
|
|
410
458
|
|
|
@@ -474,6 +522,151 @@ interface ResolveOutput {
|
|
|
474
522
|
*/
|
|
475
523
|
declare function resolve(ctx: ResolveContext): ResolveOutput;
|
|
476
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
|
+
|
|
477
670
|
/**
|
|
478
671
|
* State-capture completeness — the deterministic replacement for a bespoke
|
|
479
672
|
* `check-states.mjs`. Cross-checks the states DECLARED on registry entities
|
|
@@ -487,8 +680,17 @@ declare function resolve(ctx: ResolveContext): ResolveOutput;
|
|
|
487
680
|
* - `orphan-state-capture` — a run captured a state the entity never declared,
|
|
488
681
|
* so the contact sheet shows a state the registry doesn't know about.
|
|
489
682
|
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
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.
|
|
492
694
|
*/
|
|
493
695
|
/** One `entity/state` a capture run recorded. */
|
|
494
696
|
interface CapturedState {
|
|
@@ -507,49 +709,21 @@ interface CapturedState {
|
|
|
507
709
|
}
|
|
508
710
|
interface CapturedStatesManifest {
|
|
509
711
|
captured: CapturedState[];
|
|
712
|
+
/** ISO timestamp the reporter stamped; drives the staleness check. */
|
|
713
|
+
generatedAt?: string;
|
|
510
714
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
* unlike a hand-rolled `src/app/**` walk there is no page list to enumerate or
|
|
522
|
-
* keep from rotting: a route exists in the registry or it doesn't. And captures
|
|
523
|
-
* observe the URL they ran against, so which route a capture covers is matched,
|
|
524
|
-
* never hand-declared.
|
|
525
|
-
*/
|
|
526
|
-
/** The committed, tool-managed backlog. Shrinks as pages/kinds get captured. */
|
|
527
|
-
interface PageCoverageBaseline {
|
|
528
|
-
/** Route patterns acknowledged as uncaptured (e.g. "/[scope]/collections"). */
|
|
529
|
-
uncapturedPages: string[];
|
|
530
|
-
/** route → core kinds acknowledged as not-yet-captured (the matrix backlog). */
|
|
531
|
-
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;
|
|
532
725
|
}
|
|
533
|
-
|
|
534
|
-
* Turn a Next-style route pattern into an anchored matcher.
|
|
535
|
-
* `/[scope]/products/[productId]/edit` → matches `/acme/products/p1/edit`
|
|
536
|
-
* `/[...slug]` (catch-all) → matches one or more segments
|
|
537
|
-
* Returns the literal-segment count as a specificity score, so the most literal
|
|
538
|
-
* matching pattern wins when several match (e.g. `/x/new` beats `/x/[id]`).
|
|
539
|
-
*/
|
|
540
|
-
declare function compileRoute(pattern: string): {
|
|
541
|
-
test: (url: string) => boolean;
|
|
542
|
-
specificity: number;
|
|
543
|
-
};
|
|
544
|
-
/** The route pattern (from the given set) that most-specifically matches a URL. */
|
|
545
|
-
declare function matchUrlToRoute(url: string, routePaths: string[]): string | null;
|
|
546
|
-
declare function checkPageCoverage(registry: Registry, manifest: CapturedStatesManifest, baseline?: PageCoverageBaseline): Diagnostic[];
|
|
547
|
-
/**
|
|
548
|
-
* Compute the shrink-only baseline for `--update-baseline`: every route with no
|
|
549
|
-
* capture and no opt-out. Seeds on first run and prunes on later runs; growth
|
|
550
|
-
* only ever happens through this explicit command, visible in the file's diff.
|
|
551
|
-
*/
|
|
552
|
-
declare function computePageBaseline(registry: Registry, manifest: CapturedStatesManifest): PageCoverageBaseline;
|
|
726
|
+
declare function checkStateCoverage(registry: Registry, manifest: CapturedStatesManifest, opts?: StateCoverageOptions): Diagnostic[];
|
|
553
727
|
|
|
554
728
|
interface AuditOptions {
|
|
555
729
|
registry: Registry;
|
|
@@ -581,8 +755,12 @@ interface AuditOptions {
|
|
|
581
755
|
locsOutputPath?: string;
|
|
582
756
|
/** Captured-states manifest for the completeness gate; absent → gate skipped. */
|
|
583
757
|
capturedStates?: CapturedStatesManifest;
|
|
584
|
-
/**
|
|
585
|
-
|
|
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;
|
|
586
764
|
}
|
|
587
765
|
declare function audit(opts: AuditOptions): AuditSummary;
|
|
588
766
|
|
|
@@ -688,11 +866,22 @@ interface RunScanOptions {
|
|
|
688
866
|
configs?: DiscoveredConfig[];
|
|
689
867
|
/** Captured-states manifest for the completeness gate; overrides on-disk load. */
|
|
690
868
|
capturedStates?: CapturedStatesManifest;
|
|
691
|
-
/**
|
|
692
|
-
|
|
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;
|
|
693
873
|
}
|
|
694
|
-
|
|
695
|
-
|
|
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;
|
|
696
885
|
/** One generated artifact: its bytes plus where it lands (abs + repo-relative). */
|
|
697
886
|
interface ScanArtifact {
|
|
698
887
|
generated: string;
|
|
@@ -724,33 +913,113 @@ declare function runScan(opts?: RunScanOptions): ScanResult[];
|
|
|
724
913
|
* actually moved. Returns `true` if any file was written.
|
|
725
914
|
*/
|
|
726
915
|
declare function writeScanResult(result: ScanResult): boolean;
|
|
727
|
-
/** Absolute path of the
|
|
728
|
-
declare function
|
|
916
|
+
/** Absolute path of the coverage baseline for a scan result. */
|
|
917
|
+
declare function coverageBaselinePath(result: ScanResult): string;
|
|
729
918
|
|
|
730
919
|
/**
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
734
|
-
*
|
|
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.
|
|
735
926
|
*
|
|
736
|
-
*
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
*
|
|
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;
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Fold a PARTIAL capture manifest into the committed one.
|
|
740
986
|
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
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.
|
|
992
|
+
*
|
|
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.
|
|
743
998
|
*/
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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);
|
|
747
1010
|
}
|
|
748
|
-
declare function checkStateMatrix(registry: Registry, manifest: CapturedStatesManifest, baseline?: StateMatrixBaseline): Diagnostic[];
|
|
749
1011
|
/**
|
|
750
|
-
*
|
|
751
|
-
*
|
|
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.
|
|
752
1018
|
*/
|
|
753
|
-
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">>;
|
|
754
1023
|
|
|
755
1024
|
/**
|
|
756
1025
|
* Applies the machine-generated fixes attached to diagnostics.
|
|
@@ -816,13 +1085,6 @@ interface CliOptions {
|
|
|
816
1085
|
}
|
|
817
1086
|
declare function run(opts: CliOptions): Promise<CliResult>;
|
|
818
1087
|
|
|
819
|
-
declare class ConfigError extends Error {
|
|
820
|
-
constructor(message: string);
|
|
821
|
-
}
|
|
822
|
-
declare function validateConfig(raw: unknown): UidexConfig;
|
|
823
|
-
declare function parseConfig(json: string): UidexConfig;
|
|
824
|
-
declare const DEFAULT_CONVENTIONS: Required<Pick<NonNullable<UidexConfig["conventions"]>, "primitives" | "features" | "pages" | "flows" | "regions">>;
|
|
825
|
-
|
|
826
1088
|
/**
|
|
827
1089
|
* Authoring-surface shape types for `export const uidex = { ... }` declarations.
|
|
828
1090
|
*
|
|
@@ -849,8 +1111,24 @@ declare namespace Uidex {
|
|
|
849
1111
|
acceptance?: readonly string[];
|
|
850
1112
|
description?: string;
|
|
851
1113
|
}
|
|
852
|
-
/**
|
|
853
|
-
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
|
+
};
|
|
854
1132
|
interface Page<PageIds extends string = string, FeatureIds extends string = string, WidgetIds extends string = string> {
|
|
855
1133
|
page: PageIds | false;
|
|
856
1134
|
name?: string;
|
|
@@ -858,8 +1136,7 @@ declare namespace Uidex {
|
|
|
858
1136
|
widgets?: readonly WidgetIds[];
|
|
859
1137
|
acceptance?: readonly string[];
|
|
860
1138
|
states?: readonly (string | State)[];
|
|
861
|
-
|
|
862
|
-
waivers?: Waivers;
|
|
1139
|
+
acknowledge?: Acknowledge;
|
|
863
1140
|
description?: string;
|
|
864
1141
|
}
|
|
865
1142
|
interface Feature<FeatureIds extends string = string> {
|
|
@@ -868,6 +1145,7 @@ declare namespace Uidex {
|
|
|
868
1145
|
features?: readonly FeatureIds[];
|
|
869
1146
|
acceptance?: readonly string[];
|
|
870
1147
|
states?: readonly (string | State)[];
|
|
1148
|
+
acknowledge?: ComponentAcknowledge;
|
|
871
1149
|
description?: string;
|
|
872
1150
|
}
|
|
873
1151
|
interface Primitive<PrimitiveIds extends string = string> {
|
|
@@ -880,6 +1158,7 @@ declare namespace Uidex {
|
|
|
880
1158
|
name?: string;
|
|
881
1159
|
acceptance?: readonly string[];
|
|
882
1160
|
states?: readonly (string | State)[];
|
|
1161
|
+
acknowledge?: ComponentAcknowledge;
|
|
883
1162
|
description?: string;
|
|
884
1163
|
}
|
|
885
1164
|
interface Flow<FlowIds extends string = string> {
|
|
@@ -892,4 +1171,4 @@ declare namespace Uidex {
|
|
|
892
1171
|
}
|
|
893
1172
|
}
|
|
894
1173
|
|
|
895
|
-
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 };
|