uidex 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/cli.cjs +4117 -2902
- 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 +55 -16
- package/dist/headless/index.d.ts +55 -16
- package/dist/headless/index.js +3 -0
- package/dist/headless/index.js.map +1 -1
- package/dist/index.cjs +7 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -16
- package/dist/index.d.ts +55 -16
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/playwright/index.cjs +40 -7
- package/dist/playwright/index.cjs.map +1 -1
- package/dist/playwright/index.d.cts +15 -1
- package/dist/playwright/index.d.ts +15 -1
- package/dist/playwright/index.js +39 -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 +7 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +55 -16
- package/dist/react/index.d.ts +55 -16
- package/dist/react/index.js +7 -1
- package/dist/react/index.js.map +1 -1
- package/dist/scan/index.cjs +1533 -294
- package/dist/scan/index.cjs.map +1 -1
- package/dist/scan/index.d.cts +459 -112
- package/dist/scan/index.d.ts +459 -112
- package/dist/scan/index.js +1517 -290
- 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];
|
|
@@ -34,34 +34,60 @@ interface StateDecl {
|
|
|
34
34
|
name: string;
|
|
35
35
|
/** Canonical kind for the state-matrix axis (defaults to `variant`). */
|
|
36
36
|
kind?: StateKind;
|
|
37
|
-
/**
|
|
38
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Acceptance criteria that only this state can evidence. Normalized into the
|
|
39
|
+
* same `{ id, rev, text }` regime as entity-level `acceptance` (one
|
|
40
|
+
* criterion-id namespace per entity), so per-state criteria are claimable by
|
|
41
|
+
* `uidexCovers` and visible to every acceptance gate.
|
|
42
|
+
*/
|
|
43
|
+
acceptance?: AcceptanceCriterion[];
|
|
39
44
|
description?: string;
|
|
40
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* A normalized acceptance criterion. Authored either as a bare string (sugar:
|
|
48
|
+
* `id` is a deterministic kebab slug of the text, `rev` is 1) or as an object
|
|
49
|
+
* `{ id, rev?, text }`; parse-time normalization means everything downstream —
|
|
50
|
+
* audit, scaffold, the devtools — always sees this object shape. `rev` is the
|
|
51
|
+
* pin flows cover against (`uidexCovers("entity", "id@rev")`): bumping it when
|
|
52
|
+
* the criterion's meaning changes is what makes outdated-coverage detectable.
|
|
53
|
+
*/
|
|
54
|
+
interface AcceptanceCriterion {
|
|
55
|
+
id: string;
|
|
56
|
+
rev: number;
|
|
57
|
+
text: string;
|
|
58
|
+
}
|
|
41
59
|
interface Metadata {
|
|
42
60
|
name?: string;
|
|
43
61
|
description?: string;
|
|
44
|
-
acceptance?:
|
|
62
|
+
acceptance?: AcceptanceCriterion[];
|
|
45
63
|
notes?: string;
|
|
46
64
|
composes?: EntityRef[];
|
|
47
65
|
flows?: readonly string[];
|
|
48
66
|
features?: string[];
|
|
49
67
|
widgets?: string[];
|
|
68
|
+
/** Page ids a feature declares it must be mounted on (feature kind only). */
|
|
69
|
+
surfaces?: string[];
|
|
50
70
|
/** Declared render states (see StateDecl); the capture-completeness gate. */
|
|
51
71
|
states?: StateDecl[];
|
|
52
72
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
73
|
+
* The ONE way to say "don't complain about this gap": scope → typed reason.
|
|
74
|
+
*
|
|
75
|
+
* - scope `"*"` covers the whole entity (a redirect page that can never be
|
|
76
|
+
* captured, a component whose states are not gated here);
|
|
77
|
+
* - a core-kind scope covers that one cell of this page's route matrix.
|
|
78
|
+
*
|
|
79
|
+
* `type: "impossible"` is a permanent design statement and needs a `reason`;
|
|
80
|
+
* `type: "backlog"` is debt and does not. Keeping both as values of one field
|
|
81
|
+
* is what makes mislabeling debt as impossible a visible, reviewable edit.
|
|
82
|
+
* `captured-elsewhere` is computed by the workspace and never authored here.
|
|
63
83
|
*/
|
|
64
|
-
|
|
84
|
+
acknowledge?: Partial<Record<AcknowledgeScope, MetaAcknowledgment>>;
|
|
85
|
+
}
|
|
86
|
+
/** Scope of an acknowledgment: one core kind, or the whole entity. */
|
|
87
|
+
type AcknowledgeScope = CoreStateKind | "*";
|
|
88
|
+
interface MetaAcknowledgment {
|
|
89
|
+
type: "impossible" | "backlog";
|
|
90
|
+
reason?: string;
|
|
65
91
|
}
|
|
66
92
|
interface EntityWithMetaBase {
|
|
67
93
|
id: string;
|
|
@@ -78,12 +104,25 @@ interface FlowStep {
|
|
|
78
104
|
entityId: string;
|
|
79
105
|
action?: string;
|
|
80
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* A per-criterion coverage claim a flow makes via
|
|
109
|
+
* `uidexCovers("<entityId>", "<criterionId>@<rev>")`. The rev is REQUIRED at
|
|
110
|
+
* the claim site — a rev-less claim could never go stale, which would defeat
|
|
111
|
+
* the outdated-detection the pin exists for.
|
|
112
|
+
*/
|
|
113
|
+
interface FlowCover {
|
|
114
|
+
entity: string;
|
|
115
|
+
criterion: string;
|
|
116
|
+
rev: number;
|
|
117
|
+
}
|
|
81
118
|
interface Flow {
|
|
82
119
|
kind: "flow";
|
|
83
120
|
id: string;
|
|
84
121
|
loc: Location;
|
|
85
122
|
touches: string[];
|
|
86
123
|
steps: FlowStep[];
|
|
124
|
+
/** Acceptance-criterion coverage claims made inside this flow's describe. */
|
|
125
|
+
covers?: FlowCover[];
|
|
87
126
|
}
|
|
88
127
|
interface Page extends EntityWithMetaBase {
|
|
89
128
|
kind: "page";
|
|
@@ -172,12 +211,38 @@ interface AuditConfig {
|
|
|
172
211
|
scopeLeak?: boolean;
|
|
173
212
|
coverage?: boolean;
|
|
174
213
|
acceptance?: boolean;
|
|
214
|
+
/** Feature `surfaces` mount verification (default on; only fires when a feature declares `surfaces`). */
|
|
215
|
+
surfaces?: boolean;
|
|
216
|
+
/**
|
|
217
|
+
* Require every feature to declare at least one acceptance criterion
|
|
218
|
+
* (default ON — a bare `uidex.feature.ts` is a warning, escalatable to
|
|
219
|
+
* error via `severity` or disabled with `requireAcceptance: false`).
|
|
220
|
+
*/
|
|
221
|
+
requireAcceptance?: boolean;
|
|
175
222
|
/** State-capture completeness gate (default on when a manifest is present). */
|
|
176
223
|
states?: boolean;
|
|
177
224
|
/** Page-capture coverage gate (default on when a manifest is present). */
|
|
178
225
|
pageCoverage?: boolean;
|
|
179
226
|
/** State-matrix (core-kind) gate (default on when a manifest is present). */
|
|
180
227
|
stateMatrix?: boolean;
|
|
228
|
+
/**
|
|
229
|
+
* Per-diagnostic-code severity overrides, e.g.
|
|
230
|
+
* `{ "missing-state-capture": "error" }`. A code's built-in severity is a
|
|
231
|
+
* default, not a policy — this is how a repo escalates an advisory gate into
|
|
232
|
+
* a build-failing ratchet, or silences one it has consciously accepted.
|
|
233
|
+
* `"off"` drops the diagnostic entirely. Unknown codes are rejected by
|
|
234
|
+
* config validation, so a typo can't silently disable a gate.
|
|
235
|
+
*/
|
|
236
|
+
severity?: Record<string, DiagnosticSeverity | "off">;
|
|
237
|
+
}
|
|
238
|
+
/** Declared module-resolution hints for the import-graph checks. */
|
|
239
|
+
interface ResolveConfig {
|
|
240
|
+
/**
|
|
241
|
+
* Specifier-prefix → config-dir-relative path-prefix aliases
|
|
242
|
+
* (e.g. `{ "@/": "src/" }`). Longest matching prefix wins. When declared,
|
|
243
|
+
* these replace the scanner's zero-config `@/` / `~/` → `src/` fallback.
|
|
244
|
+
*/
|
|
245
|
+
aliases: Record<string, string>;
|
|
181
246
|
}
|
|
182
247
|
interface UidexConfig {
|
|
183
248
|
$schema?: string;
|
|
@@ -185,6 +250,7 @@ interface UidexConfig {
|
|
|
185
250
|
exclude?: string[];
|
|
186
251
|
output: string;
|
|
187
252
|
flows?: string[];
|
|
253
|
+
resolve?: ResolveConfig;
|
|
188
254
|
audit?: AuditConfig;
|
|
189
255
|
conventions?: ConventionsConfig;
|
|
190
256
|
/**
|
|
@@ -194,11 +260,21 @@ interface UidexConfig {
|
|
|
194
260
|
*/
|
|
195
261
|
statesManifest?: string;
|
|
196
262
|
/**
|
|
197
|
-
* Path (relative to the config dir) of the
|
|
198
|
-
* tool-managed, shrink-only backlog written by
|
|
199
|
-
* Defaults to `uidex-coverage-baseline.json`.
|
|
263
|
+
* Path (relative to the config dir) of the coverage baseline — the
|
|
264
|
+
* tool-managed, shrink-only `acknowledge` backlog written by
|
|
265
|
+
* `uidex scan --update-baseline`. Defaults to `uidex-coverage-baseline.json`.
|
|
200
266
|
*/
|
|
201
267
|
coverageBaseline?: string;
|
|
268
|
+
/**
|
|
269
|
+
* The workspace this app belongs to — the level that answers "did a SIBLING
|
|
270
|
+
* app capture this shared declaration?" exactly, instead of guessing.
|
|
271
|
+
*
|
|
272
|
+
* - omitted (default): auto-detect by walking up for a workspace marker
|
|
273
|
+
* (`pnpm-workspace.yaml`, `turbo.json`, `.git`, …). Costs a few JSON reads.
|
|
274
|
+
* - a path (relative to the config dir): use exactly this root.
|
|
275
|
+
* - `false`: closed world — never look at siblings.
|
|
276
|
+
*/
|
|
277
|
+
workspace?: string | false;
|
|
202
278
|
}
|
|
203
279
|
interface DiscoveredConfig {
|
|
204
280
|
configPath: string;
|
|
@@ -238,7 +314,8 @@ interface MetadataExport {
|
|
|
238
314
|
kind: MetadataExportKind;
|
|
239
315
|
id: string | false;
|
|
240
316
|
name?: string;
|
|
241
|
-
|
|
317
|
+
/** Entity-level criteria, normalized at parse time (string sugar → object). */
|
|
318
|
+
acceptance?: AcceptanceCriterion[];
|
|
242
319
|
description?: string;
|
|
243
320
|
features?: string[];
|
|
244
321
|
widgets?: string[];
|
|
@@ -253,16 +330,29 @@ interface MetadataExport {
|
|
|
253
330
|
* one adjacent comma) keyed by field name. Used by `--fix` codemods.
|
|
254
331
|
*/
|
|
255
332
|
fieldSpans?: Record<string, Span>;
|
|
333
|
+
/**
|
|
334
|
+
* Spans of top-level fields WITHOUT the comma extension (key start → value
|
|
335
|
+
* end), keyed by field name. `fieldSpans` deletes a property; this one
|
|
336
|
+
* replaces it in place, which is what the `acknowledge` codemod needs.
|
|
337
|
+
*/
|
|
338
|
+
fieldPropSpans?: Record<string, Span>;
|
|
256
339
|
/** Spans of the string-literal items in `features`, parallel to `features`. */
|
|
257
340
|
featureSpans?: Span[];
|
|
258
341
|
/** Spans of the string-literal items in `widgets`, parallel to `widgets`. */
|
|
259
342
|
widgetSpans?: Span[];
|
|
343
|
+
/** Declared surface page ids (feature kind only). */
|
|
344
|
+
surfaces?: string[];
|
|
345
|
+
/** Spans of the string-literal items in `surfaces`, parallel to `surfaces`. */
|
|
346
|
+
surfaceSpans?: Span[];
|
|
260
347
|
/** Declared render states (page/feature/widget); see ParsedState. */
|
|
261
348
|
states?: ParsedState[];
|
|
262
|
-
/**
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
349
|
+
/** Scope (`"*"` | core kind) → typed acknowledgment. See `acknowledge.ts`. */
|
|
350
|
+
acknowledge?: Record<string, ParsedAcknowledgment>;
|
|
351
|
+
}
|
|
352
|
+
/** A parsed `acknowledge` entry from `export const uidex`. */
|
|
353
|
+
interface ParsedAcknowledgment {
|
|
354
|
+
type: "impossible" | "backlog";
|
|
355
|
+
reason?: string;
|
|
266
356
|
}
|
|
267
357
|
/**
|
|
268
358
|
* A parsed `states` entry from `export const uidex`. The authored form is a
|
|
@@ -273,7 +363,8 @@ interface ParsedState {
|
|
|
273
363
|
name: string;
|
|
274
364
|
/** Canonical kind: loading | empty | populated | error | variant. */
|
|
275
365
|
kind?: string;
|
|
276
|
-
acceptance
|
|
366
|
+
/** Per-state criteria, normalized like entity-level `acceptance`. */
|
|
367
|
+
acceptance?: AcceptanceCriterion[];
|
|
277
368
|
description?: string;
|
|
278
369
|
/** Span of the state's name string literal. */
|
|
279
370
|
nameSpan?: Span;
|
|
@@ -290,16 +381,23 @@ interface LandmarkFact {
|
|
|
290
381
|
tag: string;
|
|
291
382
|
line: number;
|
|
292
383
|
}
|
|
293
|
-
/**
|
|
384
|
+
/** An import binding (import / export-from / dynamic `import()`). */
|
|
294
385
|
interface ImportFact {
|
|
295
386
|
specifier: string;
|
|
296
387
|
line: number;
|
|
297
|
-
/** span of the whole import/export statement */
|
|
388
|
+
/** span of the whole import/export statement (or the import() expression) */
|
|
298
389
|
span: Span;
|
|
299
390
|
/** `import type` or an export-from with type-only kind */
|
|
300
391
|
isTypeOnly: boolean;
|
|
301
392
|
/** imported local binding names (default + named + namespace) */
|
|
302
393
|
names: string[];
|
|
394
|
+
/**
|
|
395
|
+
* A dynamic `import("literal")` expression. Dynamic facts are real runtime
|
|
396
|
+
* edges (React.lazy is a mount edge) so the surfaces reach-check uses them,
|
|
397
|
+
* but the scope-leak check skips them — it is about static module
|
|
398
|
+
* dependency structure, which a dynamic import doesn't declare.
|
|
399
|
+
*/
|
|
400
|
+
dynamic?: true;
|
|
303
401
|
}
|
|
304
402
|
/** An interactive host element (`button`, `a`, …) with no data-uidex* attribute. */
|
|
305
403
|
interface InteractiveElementFact {
|
|
@@ -326,6 +424,18 @@ interface FlowCallFact {
|
|
|
326
424
|
interface DynamicFlowCallFact {
|
|
327
425
|
line: number;
|
|
328
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* A parsed `uidexCovers("<entityId>", "<criterionId>@<rev>")` claim inside a
|
|
429
|
+
* tagged flow describe. The rev is REQUIRED at the claim site (parse
|
|
430
|
+
* diagnostic `covers-missing-rev` otherwise) — rev-pinning is what makes
|
|
431
|
+
* outdated-coverage detection possible.
|
|
432
|
+
*/
|
|
433
|
+
interface FlowCoverFact {
|
|
434
|
+
entity: string;
|
|
435
|
+
criterion: string;
|
|
436
|
+
rev: number;
|
|
437
|
+
line: number;
|
|
438
|
+
}
|
|
329
439
|
/** A tagged `test.describe(..., { tag: "@uidex:flow" }, ...)` occurrence. */
|
|
330
440
|
interface FlowFact {
|
|
331
441
|
title: string;
|
|
@@ -333,6 +443,8 @@ interface FlowFact {
|
|
|
333
443
|
calls: FlowCallFact[];
|
|
334
444
|
/** uidex() calls inside this describe whose ids could not be resolved */
|
|
335
445
|
dynamicCalls?: DynamicFlowCallFact[];
|
|
446
|
+
/** uidexCovers() acceptance-coverage claims inside this describe */
|
|
447
|
+
covers?: FlowCoverFact[];
|
|
336
448
|
}
|
|
337
449
|
interface ExtractedFile {
|
|
338
450
|
file: ScannedFile;
|
|
@@ -340,6 +452,8 @@ interface ExtractedFile {
|
|
|
340
452
|
metadata?: MetadataExport[];
|
|
341
453
|
diagnostics?: Diagnostic[];
|
|
342
454
|
flows?: FlowFact[];
|
|
455
|
+
/** true when the file contains any JSX element or fragment */
|
|
456
|
+
hasJsx?: boolean;
|
|
343
457
|
dynamicAttrs?: DynamicAttrFact[];
|
|
344
458
|
unannotatedInteractive?: InteractiveElementFact[];
|
|
345
459
|
landmarks?: LandmarkFact[];
|
|
@@ -405,6 +519,13 @@ declare const CONFIG_FILENAME = ".uidex.json";
|
|
|
405
519
|
interface DiscoverOptions {
|
|
406
520
|
cwd?: string;
|
|
407
521
|
maxDepth?: number;
|
|
522
|
+
/**
|
|
523
|
+
* Keep descending past a config found at `cwd` itself, so the result is EVERY
|
|
524
|
+
* config in the tree rather than the innermost one. The workspace level needs
|
|
525
|
+
* the whole set; a plain `uidex scan` wants the nearest, which is why that
|
|
526
|
+
* stays the default.
|
|
527
|
+
*/
|
|
528
|
+
all?: boolean;
|
|
408
529
|
}
|
|
409
530
|
declare function discover(options?: DiscoverOptions): DiscoveredConfig[];
|
|
410
531
|
|
|
@@ -474,6 +595,142 @@ interface ResolveOutput {
|
|
|
474
595
|
*/
|
|
475
596
|
declare function resolve(ctx: ResolveContext): ResolveOutput;
|
|
476
597
|
|
|
598
|
+
/**
|
|
599
|
+
* ACKNOWLEDGMENT — the single primitive for "don't complain about this".
|
|
600
|
+
*
|
|
601
|
+
* There used to be four mechanisms plus an implicit fifth (`waivers`, the
|
|
602
|
+
* baseline's `missingKinds` / `uncapturedPages`, `capture: false`, and a
|
|
603
|
+
* borrowed-entity inference). They overlapped, lived in unrelated files with
|
|
604
|
+
* unrelated shapes, and the rule keeping them straight — "don't fold capturable
|
|
605
|
+
* debt into a waiver" — was enforced socially, in prose, because nothing
|
|
606
|
+
* structural stopped you reaching for the wrong one.
|
|
607
|
+
*
|
|
608
|
+
* They are now one map from a SCOPE to a typed REASON:
|
|
609
|
+
*
|
|
610
|
+
* acknowledge: {
|
|
611
|
+
* empty: { type: "impossible", reason: "a create form has no empty state" },
|
|
612
|
+
* "*": { type: "backlog" },
|
|
613
|
+
* }
|
|
614
|
+
*
|
|
615
|
+
* SCOPE is a core kind (that kind of this route) or `"*"` (the whole entity —
|
|
616
|
+
* every axis of it). TYPE is why:
|
|
617
|
+
*
|
|
618
|
+
* - `impossible` — human-authored. The surface CANNOT render this; a
|
|
619
|
+
* substantive `reason` is REQUIRED. Contradicted by an actual capture →
|
|
620
|
+
* `stale-acknowledgment`.
|
|
621
|
+
* - `backlog` — debt. It CAN render this and nobody has captured it yet.
|
|
622
|
+
* `reason` is optional. Tool-managed in the coverage baseline
|
|
623
|
+
* (`--update-baseline`, shrink-only), and authorable in source when you want
|
|
624
|
+
* the debt to sit next to the declaration.
|
|
625
|
+
* - `captured-elsewhere` — COMPUTED by the workspace level, never authored: a
|
|
626
|
+
* sibling app that registers this same declaration captured it. Replaces the
|
|
627
|
+
* borrowed-entity guess with an exact answer.
|
|
628
|
+
*
|
|
629
|
+
* The anti-laundering property is now structural rather than social:
|
|
630
|
+
* `impossible` and `backlog` are sibling values of ONE field, so choosing wrongly
|
|
631
|
+
* is a one-token, greppable, reviewable edit — not a choice between two
|
|
632
|
+
* unrelated files. And the reason rule points the lazy path at the honest
|
|
633
|
+
* answer: `impossible` costs you a sentence, `backlog` is free.
|
|
634
|
+
*/
|
|
635
|
+
/** The scope key meaning "the whole entity", not one core kind. */
|
|
636
|
+
declare const ACK_ALL = "*";
|
|
637
|
+
/** Every valid scope key: the four core kinds plus `"*"`. */
|
|
638
|
+
declare const ACK_SCOPES: readonly string[];
|
|
639
|
+
type AcknowledgeType = "impossible" | "backlog" | "captured-elsewhere";
|
|
640
|
+
interface Acknowledgment {
|
|
641
|
+
type: AcknowledgeType;
|
|
642
|
+
/** Why. Required for `impossible`, optional for `backlog`. */
|
|
643
|
+
reason?: string;
|
|
644
|
+
/** `captured-elsewhere` only: the workspace app whose capture covers this. */
|
|
645
|
+
by?: string;
|
|
646
|
+
}
|
|
647
|
+
/** scope (`"*"` | core kind) → why it is acknowledged. */
|
|
648
|
+
type AcknowledgeMap = Record<string, Acknowledgment>;
|
|
649
|
+
/** Where an acknowledgment came from, so diagnostics can point at the right file. */
|
|
650
|
+
type AcknowledgeOrigin = "source" | "baseline" | "workspace";
|
|
651
|
+
interface ResolvedAcknowledgment extends Acknowledgment {
|
|
652
|
+
scope: string;
|
|
653
|
+
origin: AcknowledgeOrigin;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* The committed, tool-managed coverage backlog. Same `Acknowledgment` shape as
|
|
657
|
+
* the source-authored field, keyed by route pattern — so the two really are the
|
|
658
|
+
* same primitive in two homes, not two lookalikes.
|
|
659
|
+
*/
|
|
660
|
+
interface CoverageBaseline {
|
|
661
|
+
version: 2;
|
|
662
|
+
/** route pattern → scope → acknowledgment (always `type: "backlog"`). */
|
|
663
|
+
acknowledge: Record<string, AcknowledgeMap>;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* The acknowledgments in force for one ROUTE: the baseline's entry for the route
|
|
667
|
+
* layered UNDER the page's own `acknowledge`. Source wins — a human statement
|
|
668
|
+
* outranks tool-recorded debt, and `--update-baseline` drops the shadowed entry
|
|
669
|
+
* on its next run.
|
|
670
|
+
*/
|
|
671
|
+
declare function routeAcknowledge(registry: Registry, baseline: CoverageBaseline | undefined, route: string, page: string | undefined): Map<string, ResolvedAcknowledgment>;
|
|
672
|
+
/**
|
|
673
|
+
* The acknowledgments in force for one ENTITY (page/feature/widget). Only `"*"`
|
|
674
|
+
* is meaningful here — the core-kind axis is route-scoped. `capturedElsewhere`
|
|
675
|
+
* is the workspace's computed verdict; source still wins, so an explicit
|
|
676
|
+
* `impossible` is never silently reinterpreted.
|
|
677
|
+
*/
|
|
678
|
+
declare function entityAcknowledge(meta: Metadata | undefined, capturedElsewhereBy?: string): ResolvedAcknowledgment | undefined;
|
|
679
|
+
/**
|
|
680
|
+
* Parse a v2 baseline. Malformed entries are dropped rather than thrown on: a
|
|
681
|
+
* hand-mangled baseline must not crash the scan, and every dropped entry simply
|
|
682
|
+
* re-appears as an unacknowledged gap the gate reports.
|
|
683
|
+
*/
|
|
684
|
+
declare function parseCoverageBaseline(raw: unknown): CoverageBaseline | null;
|
|
685
|
+
/** Serialize a baseline with stable key order, so its diff is reviewable. */
|
|
686
|
+
declare function serializeCoverageBaseline(baseline: CoverageBaseline): string;
|
|
687
|
+
|
|
688
|
+
interface WorkspaceApp {
|
|
689
|
+
/** Display name — the config dir's basename (e.g. "platform-web"). */
|
|
690
|
+
name: string;
|
|
691
|
+
configDir: string;
|
|
692
|
+
configPath: string;
|
|
693
|
+
/** Whether this app's configured sources would register `absFile`. */
|
|
694
|
+
registers: (absFile: string) => boolean;
|
|
695
|
+
/** `captureKey` values this app's manifest captured. */
|
|
696
|
+
captured: ReadonlySet<string>;
|
|
697
|
+
}
|
|
698
|
+
interface Workspace {
|
|
699
|
+
root: string;
|
|
700
|
+
/** Every OTHER app in the workspace; the scanning app is excluded. */
|
|
701
|
+
siblings: WorkspaceApp[];
|
|
702
|
+
}
|
|
703
|
+
interface ResolveWorkspaceOptions {
|
|
704
|
+
configDir: string;
|
|
705
|
+
config: UidexConfig;
|
|
706
|
+
/** Overrides discovery; used by tests to avoid touching the real filesystem. */
|
|
707
|
+
configs?: DiscoveredConfig[];
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Walk up from the app looking for a workspace marker. Returns `undefined` when
|
|
711
|
+
* there is none — a standalone app then keeps today's closed-world behavior
|
|
712
|
+
* rather than silently treating its parent directory as a workspace.
|
|
713
|
+
*/
|
|
714
|
+
declare function findWorkspaceRoot(configDir: string): string | undefined;
|
|
715
|
+
/**
|
|
716
|
+
* Resolve the workspace an app participates in.
|
|
717
|
+
*
|
|
718
|
+
* `config.workspace` selects the root: a path (relative to the config dir),
|
|
719
|
+
* `false` for an explicit closed world, or omitted for auto-detect via
|
|
720
|
+
* `findWorkspaceRoot`. Returns `undefined` when there is no workspace or no
|
|
721
|
+
* sibling in it — callers then behave exactly as a single-app scan does.
|
|
722
|
+
*/
|
|
723
|
+
declare function resolveWorkspace(opts: ResolveWorkspaceOptions): Workspace | undefined;
|
|
724
|
+
/**
|
|
725
|
+
* The sibling app that already captured this declaration, if any — the exact
|
|
726
|
+
* answer that replaces the borrowed-entity guess. `absFile` is the declaring
|
|
727
|
+
* file's real path (a display path cannot be resolved back across a `prefix`).
|
|
728
|
+
*
|
|
729
|
+
* A capture recording no `kind` matches on id alone, mirroring how the
|
|
730
|
+
* state-coverage gate treats an unlabeled capture.
|
|
731
|
+
*/
|
|
732
|
+
declare function capturedElsewhere(workspace: Workspace | undefined, absFile: string | undefined, kind: string, id: string): string | undefined;
|
|
733
|
+
|
|
477
734
|
/**
|
|
478
735
|
* State-capture completeness — the deterministic replacement for a bespoke
|
|
479
736
|
* `check-states.mjs`. Cross-checks the states DECLARED on registry entities
|
|
@@ -487,8 +744,17 @@ declare function resolve(ctx: ResolveContext): ResolveOutput;
|
|
|
487
744
|
* - `orphan-state-capture` — a run captured a state the entity never declared,
|
|
488
745
|
* so the contact sheet shows a state the registry doesn't know about.
|
|
489
746
|
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
747
|
+
* Two things take an entity out of the gate, and both are acknowledgments:
|
|
748
|
+
* a SOURCE `acknowledge: { "*": … }`, or the WORKSPACE observing that a sibling
|
|
749
|
+
* app which registers this same declaration captured it (`captured-elsewhere`).
|
|
750
|
+
*
|
|
751
|
+
* The workspace answer replaces a heuristic this module used to carry: a
|
|
752
|
+
* "borrowed" entity (one declared under a shared source `prefix`) that this app
|
|
753
|
+
* captured nothing for was ASSUMED to be a sibling's responsibility. That guess
|
|
754
|
+
* could not tell a genuinely shared component from an id that merely collided
|
|
755
|
+
* with a sibling's, and it silenced real gaps whenever an app captured nothing.
|
|
756
|
+
* The workspace checks the sibling's actual source set and actual manifest, so
|
|
757
|
+
* the answer is exact and the guess is gone.
|
|
492
758
|
*/
|
|
493
759
|
/** One `entity/state` a capture run recorded. */
|
|
494
760
|
interface CapturedState {
|
|
@@ -507,49 +773,21 @@ interface CapturedState {
|
|
|
507
773
|
}
|
|
508
774
|
interface CapturedStatesManifest {
|
|
509
775
|
captured: CapturedState[];
|
|
776
|
+
/** ISO timestamp the reporter stamped; drives the staleness check. */
|
|
777
|
+
generatedAt?: string;
|
|
510
778
|
}
|
|
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[]>;
|
|
779
|
+
interface StateCoverageOptions {
|
|
780
|
+
/** The workspace, when one is resolved; enables `captured-elsewhere`. */
|
|
781
|
+
workspace?: Workspace;
|
|
782
|
+
/**
|
|
783
|
+
* Maps an entity's `loc.file` (a DISPLAY path, which a `prefix` makes
|
|
784
|
+
* unresolvable on its own) back to its real path on disk. Supplied by
|
|
785
|
+
* `audit()` from the scanned-file list, so the workspace can test the file
|
|
786
|
+
* against a sibling's source roots.
|
|
787
|
+
*/
|
|
788
|
+
resolveSourcePath?: (displayPath: string) => string | undefined;
|
|
532
789
|
}
|
|
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;
|
|
790
|
+
declare function checkStateCoverage(registry: Registry, manifest: CapturedStatesManifest, opts?: StateCoverageOptions): Diagnostic[];
|
|
553
791
|
|
|
554
792
|
interface AuditOptions {
|
|
555
793
|
registry: Registry;
|
|
@@ -581,8 +819,10 @@ interface AuditOptions {
|
|
|
581
819
|
locsOutputPath?: string;
|
|
582
820
|
/** Captured-states manifest for the completeness gate; absent → gate skipped. */
|
|
583
821
|
capturedStates?: CapturedStatesManifest;
|
|
584
|
-
/**
|
|
585
|
-
|
|
822
|
+
/** Coverage baseline (the shrink-only `acknowledge` backlog). */
|
|
823
|
+
coverageBaseline?: CoverageBaseline;
|
|
824
|
+
/** The resolved workspace; enables `captured-elsewhere`. */
|
|
825
|
+
workspace?: Workspace;
|
|
586
826
|
}
|
|
587
827
|
declare function audit(opts: AuditOptions): AuditSummary;
|
|
588
828
|
|
|
@@ -688,11 +928,13 @@ interface RunScanOptions {
|
|
|
688
928
|
configs?: DiscoveredConfig[];
|
|
689
929
|
/** Captured-states manifest for the completeness gate; overrides on-disk load. */
|
|
690
930
|
capturedStates?: CapturedStatesManifest;
|
|
691
|
-
/**
|
|
692
|
-
|
|
931
|
+
/** Coverage baseline (the shrink-only backlog); overrides on-disk load. */
|
|
932
|
+
coverageBaseline?: CoverageBaseline;
|
|
933
|
+
/** Workspace override; skips auto-detection (used by tests). */
|
|
934
|
+
workspace?: Workspace;
|
|
693
935
|
}
|
|
694
|
-
/** Load the
|
|
695
|
-
declare function
|
|
936
|
+
/** Load the coverage baseline from disk, if present + well-formed. */
|
|
937
|
+
declare function loadCoverageBaseline(configDir: string, config: UidexConfig): CoverageBaseline | undefined;
|
|
696
938
|
/** One generated artifact: its bytes plus where it lands (abs + repo-relative). */
|
|
697
939
|
interface ScanArtifact {
|
|
698
940
|
generated: string;
|
|
@@ -724,33 +966,97 @@ declare function runScan(opts?: RunScanOptions): ScanResult[];
|
|
|
724
966
|
* actually moved. Returns `true` if any file was written.
|
|
725
967
|
*/
|
|
726
968
|
declare function writeScanResult(result: ScanResult): boolean;
|
|
727
|
-
/** Absolute path of the
|
|
728
|
-
declare function
|
|
969
|
+
/** Absolute path of the coverage baseline for a scan result. */
|
|
970
|
+
declare function coverageBaselinePath(result: ScanResult): string;
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Page-capture coverage — the coarse gate above the per-state gate. Every
|
|
974
|
+
* derived route must be accounted for: captured (a run drove it), or
|
|
975
|
+
* ACKNOWLEDGED at `"*"` scope — either `impossible` (a redirect, a static
|
|
976
|
+
* notice, an internal target — the old `capture: false`, which now has to say
|
|
977
|
+
* WHY) or `backlog` (tool-managed, shrink-only). A route in none of those is a
|
|
978
|
+
* *new* uncaptured page and fails hard — the ratchet that keeps debt growing.
|
|
979
|
+
*
|
|
980
|
+
* uidex derives the route set (route groups stripped, dynamic segments kept), so
|
|
981
|
+
* unlike a hand-rolled `src/app/**` walk there is no page list to enumerate or
|
|
982
|
+
* keep from rotting: a route exists in the registry or it doesn't. And captures
|
|
983
|
+
* observe the URL they ran against, so which route a capture covers is matched,
|
|
984
|
+
* never hand-declared.
|
|
985
|
+
*/
|
|
986
|
+
/**
|
|
987
|
+
* Turn a Next-style route pattern into an anchored matcher.
|
|
988
|
+
* `/[scope]/products/[productId]/edit` → matches `/acme/products/p1/edit`
|
|
989
|
+
* `/[...slug]` (catch-all) → matches one or more segments
|
|
990
|
+
* Returns the literal-segment count as a specificity score, so the most literal
|
|
991
|
+
* matching pattern wins when several match (e.g. `/x/new` beats `/x/[id]`).
|
|
992
|
+
*/
|
|
993
|
+
declare function compileRoute(pattern: string): {
|
|
994
|
+
test: (url: string) => boolean;
|
|
995
|
+
specificity: number;
|
|
996
|
+
};
|
|
997
|
+
/** The route pattern (from the given set) that most-specifically matches a URL. */
|
|
998
|
+
declare function matchUrlToRoute(url: string, routePaths: string[]): string | null;
|
|
999
|
+
declare function checkPageCoverage(registry: Registry, manifest: CapturedStatesManifest, baseline?: CoverageBaseline): Diagnostic[];
|
|
1000
|
+
/**
|
|
1001
|
+
* The page half of the shrink-only baseline for `--update-baseline`: every route
|
|
1002
|
+
* with no capture and no SOURCE acknowledgment. Seeds on first run and prunes on
|
|
1003
|
+
* later runs; growth only ever happens through this explicit command, visible in
|
|
1004
|
+
* the file's diff.
|
|
1005
|
+
*/
|
|
1006
|
+
declare function computePageBacklog(registry: Registry, manifest: CapturedStatesManifest): Record<string, AcknowledgeMap>;
|
|
729
1007
|
|
|
1008
|
+
declare function checkStateMatrix(registry: Registry, manifest: CapturedStatesManifest, baseline?: CoverageBaseline): Diagnostic[];
|
|
730
1009
|
/**
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
734
|
-
*
|
|
1010
|
+
* The matrix half of the shrink-only baseline for `--update-baseline`: every
|
|
1011
|
+
* (captured route, core kind) that is neither captured nor already acknowledged
|
|
1012
|
+
* in SOURCE. Source acknowledgments are excluded so the same gap is never
|
|
1013
|
+
* recorded twice, in two files, saying two things.
|
|
1014
|
+
*/
|
|
1015
|
+
declare function computeMatrixBacklog(registry: Registry, manifest: CapturedStatesManifest): Record<string, AcknowledgeMap>;
|
|
1016
|
+
|
|
1017
|
+
declare function checkComponentCoverage(registry: Registry, manifest: CapturedStatesManifest): Diagnostic[];
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* Fold a PARTIAL capture manifest into the committed one.
|
|
735
1021
|
*
|
|
736
|
-
*
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
*
|
|
1022
|
+
* A filtered run (`--grep`) legitimately captures a subset, so the reporter's
|
|
1023
|
+
* shrink guard diverts it to `<name>.partial.json` rather than overwriting a
|
|
1024
|
+
* fuller manifest. Without a merge path the only ways forward are re-running the
|
|
1025
|
+
* FULL suite or hand-editing JSON — both of which make scoped re-capture
|
|
1026
|
+
* expensive enough that people skip it and let the manifest drift.
|
|
740
1027
|
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
1028
|
+
* Merge is by `(kind, entity, state)`: rows in the partial REPLACE their
|
|
1029
|
+
* counterparts (that run is the newer evidence) and rows absent from it are
|
|
1030
|
+
* preserved (it never claimed to cover them). So merging is purely additive to
|
|
1031
|
+
* coverage — it can never drop a state, which is what makes it safe to run
|
|
1032
|
+
* without re-reading the whole manifest.
|
|
743
1033
|
*/
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1034
|
+
interface MergeResult {
|
|
1035
|
+
manifest: CapturedStatesManifest;
|
|
1036
|
+
/** `(kind, entity, state)` keys the partial replaced. */
|
|
1037
|
+
updated: string[];
|
|
1038
|
+
/** Keys the partial contributed that the committed manifest lacked. */
|
|
1039
|
+
added: string[];
|
|
1040
|
+
}
|
|
1041
|
+
declare function mergeStates(committed: CapturedStatesManifest, partial: CapturedStatesManifest): MergeResult;
|
|
1042
|
+
|
|
1043
|
+
declare class ConfigError extends Error {
|
|
1044
|
+
constructor(message: string);
|
|
747
1045
|
}
|
|
748
|
-
declare function checkStateMatrix(registry: Registry, manifest: CapturedStatesManifest, baseline?: StateMatrixBaseline): Diagnostic[];
|
|
749
1046
|
/**
|
|
750
|
-
*
|
|
751
|
-
*
|
|
1047
|
+
* Every diagnostic code the scanner can emit — the allow-list `audit.severity`
|
|
1048
|
+
* validates against, so a typo'd key fails loudly instead of silently leaving
|
|
1049
|
+
* the gate it was meant to govern at its default severity.
|
|
1050
|
+
*
|
|
1051
|
+
* Keep in sync when adding a diagnostic; `tests/scan/severity-codes.test.ts`
|
|
1052
|
+
* asserts this set matches the codes actually emitted by the source — a code
|
|
1053
|
+
* missing here cannot be softened/disabled by any repo (validateConfig
|
|
1054
|
+
* rejects unknown `audit.severity` keys).
|
|
752
1055
|
*/
|
|
753
|
-
declare
|
|
1056
|
+
declare const DIAGNOSTIC_CODES: readonly ["acceptance-ambiguous", "acceptance-duplicate-id", "acceptance-orphaned", "acceptance-outdated", "acceptance-uncovered", "acknowledged-elsewhere", "competing-uidex-export", "component-state-uncaptured", "core-kind", "covers-missing-rev", "duplicate-id", "dynamic-attr", "dynamic-flow-reference", "feature-missing-acceptance", "feature-surface-uncaptured", "feature-surface-unreferenced", "gen-missing", "gen-stale", "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"];
|
|
1057
|
+
declare function validateConfig(raw: unknown): UidexConfig;
|
|
1058
|
+
declare function parseConfig(json: string): UidexConfig;
|
|
1059
|
+
declare const DEFAULT_CONVENTIONS: Required<Pick<NonNullable<UidexConfig["conventions"]>, "primitives" | "features" | "pages" | "flows" | "regions">>;
|
|
754
1060
|
|
|
755
1061
|
/**
|
|
756
1062
|
* Applies the machine-generated fixes attached to diagnostics.
|
|
@@ -816,13 +1122,6 @@ interface CliOptions {
|
|
|
816
1122
|
}
|
|
817
1123
|
declare function run(opts: CliOptions): Promise<CliResult>;
|
|
818
1124
|
|
|
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
1125
|
/**
|
|
827
1126
|
* Authoring-surface shape types for `export const uidex = { ... }` declarations.
|
|
828
1127
|
*
|
|
@@ -842,32 +1141,74 @@ declare const DEFAULT_CONVENTIONS: Required<Pick<NonNullable<UidexConfig["conven
|
|
|
842
1141
|
declare namespace Uidex {
|
|
843
1142
|
type CoreStateKind = "loading" | "empty" | "populated" | "error";
|
|
844
1143
|
type StateKind = CoreStateKind | "variant";
|
|
1144
|
+
/**
|
|
1145
|
+
* An acceptance criterion as authored. A bare string is sugar: its `id`
|
|
1146
|
+
* becomes a deterministic kebab slug of the text and `rev` defaults to 1.
|
|
1147
|
+
* The object form pins an explicit `id` (stable across copy edits) and an
|
|
1148
|
+
* optional `rev` (bump it when the criterion's meaning changes so flow
|
|
1149
|
+
* coverage pinned to the old rev surfaces as outdated).
|
|
1150
|
+
*/
|
|
1151
|
+
interface AcceptanceCriterion {
|
|
1152
|
+
id: string;
|
|
1153
|
+
rev?: number;
|
|
1154
|
+
text: string;
|
|
1155
|
+
}
|
|
1156
|
+
type Acceptance = readonly (string | AcceptanceCriterion)[];
|
|
845
1157
|
/** A declared render state, as authored in `states: [{ name, ... }]`. */
|
|
846
1158
|
interface State {
|
|
847
1159
|
name: string;
|
|
848
1160
|
kind?: StateKind;
|
|
849
|
-
|
|
1161
|
+
/** Per-state criteria; same authoring sugar as entity-level `acceptance`. */
|
|
1162
|
+
acceptance?: Acceptance;
|
|
850
1163
|
description?: string;
|
|
851
1164
|
}
|
|
852
|
-
/**
|
|
853
|
-
type
|
|
1165
|
+
/** Scope of an acknowledgment: one core kind, or the whole entity. */
|
|
1166
|
+
type AcknowledgeScope = CoreStateKind | "*";
|
|
1167
|
+
/** A permanent design statement — so it has to say why. */
|
|
1168
|
+
interface Impossible {
|
|
1169
|
+
type: "impossible";
|
|
1170
|
+
reason: string;
|
|
1171
|
+
}
|
|
1172
|
+
/** Debt: capturable, not captured yet. No reason required. */
|
|
1173
|
+
interface Backlog {
|
|
1174
|
+
type: "backlog";
|
|
1175
|
+
reason?: string;
|
|
1176
|
+
}
|
|
1177
|
+
type Acknowledgment = Impossible | Backlog;
|
|
1178
|
+
type Acknowledge = Partial<Record<AcknowledgeScope, Acknowledgment>>;
|
|
1179
|
+
/** A component has no route, so only the `"*"` scope applies to one. */
|
|
1180
|
+
type ComponentAcknowledge = {
|
|
1181
|
+
"*"?: Acknowledgment;
|
|
1182
|
+
};
|
|
854
1183
|
interface Page<PageIds extends string = string, FeatureIds extends string = string, WidgetIds extends string = string> {
|
|
855
1184
|
page: PageIds | false;
|
|
856
1185
|
name?: string;
|
|
857
1186
|
features?: readonly FeatureIds[];
|
|
858
1187
|
widgets?: readonly WidgetIds[];
|
|
859
|
-
acceptance?:
|
|
1188
|
+
acceptance?: Acceptance;
|
|
860
1189
|
states?: readonly (string | State)[];
|
|
861
|
-
|
|
862
|
-
waivers?: Waivers;
|
|
1190
|
+
acknowledge?: Acknowledge;
|
|
863
1191
|
description?: string;
|
|
864
1192
|
}
|
|
865
|
-
interface Feature<FeatureIds extends string = string> {
|
|
1193
|
+
interface Feature<FeatureIds extends string = string, PageIds extends string = string> {
|
|
866
1194
|
feature: FeatureIds | false;
|
|
867
1195
|
name?: string;
|
|
868
1196
|
features?: readonly FeatureIds[];
|
|
869
|
-
|
|
1197
|
+
/**
|
|
1198
|
+
* Pages this feature MUST be mounted on. Each id is verified by the audit
|
|
1199
|
+
* in two halves: the page must exist and its import closure must reference
|
|
1200
|
+
* a component file in the feature's directory (the static
|
|
1201
|
+
* `feature-surface-unreferenced` tripwire), and — when a captured-states
|
|
1202
|
+
* manifest is present — a capture must have rendered the feature on the
|
|
1203
|
+
* page's route (`feature-surface-uncaptured`, the runtime evidence). This
|
|
1204
|
+
* is how a cross-cutting feature (a gate editor, a share panel) states
|
|
1205
|
+
* "every one of these editors owes me a mount" and a forgotten mount fails
|
|
1206
|
+
* the scan instead of shipping.
|
|
1207
|
+
*/
|
|
1208
|
+
surfaces?: readonly PageIds[];
|
|
1209
|
+
acceptance?: Acceptance;
|
|
870
1210
|
states?: readonly (string | State)[];
|
|
1211
|
+
acknowledge?: ComponentAcknowledge;
|
|
871
1212
|
description?: string;
|
|
872
1213
|
}
|
|
873
1214
|
interface Primitive<PrimitiveIds extends string = string> {
|
|
@@ -878,8 +1219,14 @@ declare namespace Uidex {
|
|
|
878
1219
|
interface Widget<WidgetIds extends string = string> {
|
|
879
1220
|
widget: WidgetIds;
|
|
880
1221
|
name?: string;
|
|
881
|
-
acceptance?:
|
|
1222
|
+
acceptance?: Acceptance;
|
|
882
1223
|
states?: readonly (string | State)[];
|
|
1224
|
+
acknowledge?: ComponentAcknowledge;
|
|
1225
|
+
description?: string;
|
|
1226
|
+
}
|
|
1227
|
+
interface Region<RegionIds extends string = string> {
|
|
1228
|
+
region: RegionIds | false;
|
|
1229
|
+
name?: string;
|
|
883
1230
|
description?: string;
|
|
884
1231
|
}
|
|
885
1232
|
interface Flow<FlowIds extends string = string> {
|
|
@@ -892,4 +1239,4 @@ declare namespace Uidex {
|
|
|
892
1239
|
}
|
|
893
1240
|
}
|
|
894
1241
|
|
|
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
|
|
1242
|
+
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, loadCoverageBaseline, matchUrlToRoute, mergeStates, parseConfig, parseCoverageBaseline, pathToId, renameEntity, resolve, resolveGitContext, resolveWorkspace, routeAcknowledge, run as runCli, runScan, scaffoldSpec, scaffoldWidgetSpec, serializeCoverageBaseline, validateConfig, walk, writeScanResult };
|