@reqvire-org/ads 0.3.20 → 0.3.23

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.
Files changed (55) hide show
  1. package/README.md +2 -0
  2. package/dist/behaviors.mjs +14 -4
  3. package/dist/bootstrap-ds/README.md +1 -1
  4. package/dist/bootstrap-ds/design-system/tokens/{Foundation.scenario.tsx → Foundation.ads.scenario.tsx} +1 -1
  5. package/dist/bootstrap-ds/tsconfig.build.json +9 -2
  6. package/dist/boundaries.mjs +2 -0
  7. package/dist/check.mjs +25 -5
  8. package/dist/config.mjs +21 -4
  9. package/dist/contract-migration.mjs +110 -0
  10. package/dist/contracts.d.mts +22 -0
  11. package/dist/evidence.mjs +1 -1
  12. package/dist/index.mjs +1 -0
  13. package/dist/init.mjs +1 -1
  14. package/dist/mcp/catalog.mjs +9 -2
  15. package/dist/mcp/context.mjs +1 -1
  16. package/dist/mcp/edit-preparation.mjs +6 -3
  17. package/dist/mcp/preview-widget.bundle.js +41 -39
  18. package/dist/mcp/server.mjs +3 -1
  19. package/dist/paint-contracts.mjs +124 -0
  20. package/dist/preview-resources.mjs +1 -1
  21. package/dist/public-props.mjs +2 -2
  22. package/dist/runtime-contracts.mjs +225 -0
  23. package/dist/scenario-trace.mjs +32 -4
  24. package/dist/scenarios-vite.mjs +1 -1
  25. package/dist/scenarios.mjs +82 -26
  26. package/dist/scoped-tokens.mjs +9 -4
  27. package/dist/skills/ads-break-ui/SKILL.md +4 -0
  28. package/dist/skills/ads-break-ui/references/contracts.md +253 -0
  29. package/dist/skills/ads-designer/SKILL.md +4 -0
  30. package/dist/skills/ads-designer/assets/bootstrap-ds/README.md +1 -1
  31. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/{Foundation.scenario.tsx → Foundation.ads.scenario.tsx} +1 -1
  32. package/dist/skills/ads-designer/assets/bootstrap-ds/tsconfig.build.json +9 -2
  33. package/dist/skills/ads-designer/references/contracts.md +253 -0
  34. package/dist/skills/ads-import/SKILL.md +4 -0
  35. package/dist/skills/ads-import/references/contracts.md +253 -0
  36. package/dist/skills/ads-migrate/SKILL.md +4 -0
  37. package/dist/skills/ads-migrate/references/contracts.md +253 -0
  38. package/dist/skills/ads-motion/SKILL.md +4 -0
  39. package/dist/skills/ads-motion/references/contracts.md +253 -0
  40. package/dist/token-origins.mjs +24 -0
  41. package/dist/tokens.mjs +4 -3
  42. package/dist/unit-contracts.mjs +203 -0
  43. package/dist/units.mjs +34 -3
  44. package/dist/verification/browser.mjs +156 -26
  45. package/dist/verification/playwright-reporter.cjs +13 -3
  46. package/docs/ADS.md +8 -1
  47. package/docs/ARCHITECTURE.md +36 -0
  48. package/docs/BEHAVIORS.md +8 -0
  49. package/docs/CHANGELOG.md +28 -0
  50. package/docs/CONTRACTS.md +253 -0
  51. package/docs/GUIDE.md +26 -1
  52. package/docs/MCP.md +15 -0
  53. package/package.json +4 -1
  54. /package/dist/bootstrap-ds/design-system/tokens/{Foundation.fixture.ts → Foundation.ads.fixture.ts} +0 -0
  55. /package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/{Foundation.fixture.ts → Foundation.ads.fixture.ts} +0 -0
@@ -0,0 +1,253 @@
1
+ # Static owner contracts and development files
2
+
3
+ Use one optional `<Owner>.ads.contract.ts` for owner-local tokens, constrained
4
+ paint inputs, scoped runtime bindings, declarative parts and behavioral checks.
5
+ The import `import type { UnitContract } from "@reqvire-org/ads/contracts"` supplies
6
+ development typing. ADS reads its AST and resolves real symbols without importing
7
+ or executing project modules. It adds no production runtime.
8
+
9
+ ## Files and ownership
10
+
11
+ ```text
12
+ components/Plot/
13
+ Plot.tsx production API/root
14
+ Series.tsx production declarative API
15
+ renderer.tsx production consumer
16
+ paints.ts production binding producer
17
+ types.ts production prop types
18
+ tokens.css optional production token definitions
19
+ Plot.ads.contract.ts static tool declaration
20
+ Plot.ads.scenario.tsx demonstration
21
+ Plot.ads.fixture.ts demonstration data
22
+ Tokens.ads.scenario.tsx real owner-token samples
23
+ Tokens.ads.fixture.ts sample bindings/probes
24
+ Plot.ads.behavior.spec.ts registered browser checks
25
+ Plot.ads.test.tsx optional unit tests
26
+ Plot.ads.helpers.ts development-only adapters
27
+ README.md API/provenance
28
+ ```
29
+
30
+ This is an example, not a required inventory. Use the same roles for patterns,
31
+ controllers and relevant development owners. `review/` remains a separate
32
+ shared development layer; its implementation filenames need no extra marker.
33
+ Runtime components, types and styles cannot import `.ads.` files, tests, fixtures
34
+ or review code through aliases, barrels or helpers. Unknown `.ads.` roles are
35
+ still development-only and are diagnosed. Fixtures/helpers cannot define styles;
36
+ put demo framing in review. Ordinary README/license files retain their names.
37
+
38
+ Scenarios/fixtures/review enter only the selected preview closure. Contracts are
39
+ never executable preview modules; derived audit descriptors are data. Specs run
40
+ only in the configured test runner. Production declaration emission must also
41
+ start from the public runtime entry, rather than include every file under the
42
+ design-system folder. The starter’s `tsconfig.build.json` does this; its normal
43
+ no-emit typecheck still checks development sources. Core import guards protect
44
+ the runtime dependency closure. Review custom emitter configurations through
45
+ the configuration owner before migrating a consuming project. Guards remain tooling. Production prop types
46
+ belong in runtime files, never inferred from a development contract.
47
+
48
+ ## A contract uses actual implementation references
49
+
50
+ ```ts
51
+ import type { UnitContract } from "@reqvire-org/ads/contracts";
52
+ import { Plot } from "./Plot";
53
+ import { Series } from "./Series";
54
+ import { Renderer } from "./renderer";
55
+ import { paints } from "./paints";
56
+
57
+ export default {
58
+ schemaVersion: 1,
59
+ tokens: { namespace: "--plot-", stylesheet: "./tokens.css" },
60
+ paintInputs: [
61
+ { api: Plot, property: "config.*.paint", kind: "paint-reference" },
62
+ { api: Series, property: "fill", kind: "paint-reference", bindings: ["items"] },
63
+ ],
64
+ bindings: {
65
+ items: {
66
+ input: { api: Plot, property: "config" },
67
+ producer: paints,
68
+ valueFrom: "paint",
69
+ variable: "--plot-runtime-${key}",
70
+ scope: { api: Plot, selector: '[data-plot-root=""]' },
71
+ checks: ["isolation"],
72
+ },
73
+ },
74
+ declarativeParts: [
75
+ { api: Series, parent: Plot, consumer: Renderer, checks: ["isolation"] },
76
+ ],
77
+ behavior: {
78
+ subjects: { plot: { api: Plot }, renderer: { implementation: Renderer } },
79
+ guarantees: [{
80
+ id: "scoped-output",
81
+ description: "Changing either instance updates only its own output.",
82
+ subjects: ["plot", "renderer"], checks: ["isolation"],
83
+ }],
84
+ checks: {
85
+ isolation: {
86
+ file: "./Plot.ads.behavior.spec.ts", case: "isolation",
87
+ scenario: "components/Plot", profile: "widget",
88
+ },
89
+ },
90
+ },
91
+ } satisfies UnitContract;
92
+ ```
93
+
94
+ Keep contracts small; most owners need none or one section. Supported data are
95
+ literal objects/arrays, static constants/spreads, `as const` and `satisfies`.
96
+ Symbol references resolve through normal imports, aliases and re-exports.
97
+ Calls, getters, methods, computed metadata and environment-dependent expressions
98
+ are rejected. Function references are opaque identities, never executed.
99
+ Private implementation subjects may be internal module exports without becoming
100
+ public barrel exports. Producers/consumers must belong to the same owner. Required
101
+ checks must exist, back named guarantees and use a browser profile with the
102
+ corresponding scenario. A relationship is registration, not a passing guarantee.
103
+
104
+ ## Typed paints and runtime bindings
105
+
106
+ Production-facing types can use:
107
+
108
+ ```ts
109
+ export type Paint = `var(--${string})` | `url(#${string})` | "none" | "currentColor";
110
+ export interface SeriesProps { fill?: Paint }
111
+ export type Config = Readonly<Record<string, { paint: Paint }>>;
112
+ ```
113
+
114
+ ADS follows the actual property path/type, including nested maps. A name such as
115
+ `Paint`, an arbitrary `string`, `any` or a forged metadata role grants no waiver.
116
+ Supported references are a single accessible token/binding `var(--name)`, a local
117
+ SVG paint `url(#id)`, `none` or `currentColor`. Fallbacks, raw colours, external
118
+ URLs and arbitrary CSS are outside this role. Raw domain colours need a separately
119
+ reviewed validated input boundary; they are not implicitly supported here.
120
+
121
+ A runtime family is instance data, not an infinite design-token inventory. Its
122
+ source proof requires the declared config map, exact key/value mapping and actual
123
+ root attachment. Supported producers use `Object.entries(config)` with a loop,
124
+ or `Object.fromEntries(...map(...))`; same-owner helper returns are traceable.
125
+ Guard each key before the write/return using
126
+ `if (!/^[A-Za-z0-9_-]+$/.test(key)) throw new Error("Invalid key")` (a loop may
127
+ `continue`). Return only the computed map. For example:
128
+
129
+ ```ts
130
+ export function paints(config: Config) {
131
+ const result: Record<string, string> = {};
132
+ for (const [key, entry] of Object.entries(config)) {
133
+ if (!/^[A-Za-z0-9_-]+$/.test(key)) throw new Error("Invalid key");
134
+ result[`--plot-runtime-${key}`] = entry.paint;
135
+ }
136
+ return result;
137
+ }
138
+ ```
139
+
140
+ The runtime root attaches that returned map using `style={paints(config)}` or a
141
+ traceable local/useMemo result and the declared class/exact data attribute.
142
+ Place spreading props before the explicit scope/style so an unknown spread
143
+ cannot replace them. This grants permission to that binding attachment only;
144
+ it does not authorize arbitrary inline styling, CSSProperties APIs or bulk writes.
145
+ Unsupported dynamic producers/attachment paths fail with an analysis diagnostic.
146
+ Keep the implementation project-owned; there is no required ADS binding helper.
147
+
148
+ Families cannot overlap shared tokens, owner-token namespaces, private runtime
149
+ prefixes or other bindings. A broad family such as `--color-${key}` grants no
150
+ shared-token exception. Use a distinct private family where collision freedom
151
+ cannot be established. Registered paint consumers list their allowed binding IDs;
152
+ borrowing a peer's family or private token remains invalid. Bindings are scoped
153
+ to their actual root; portals outside it need deliberate project integration.
154
+
155
+ The App audit checks current mounted instances, accessible values, local SVG
156
+ resources and computed paint-property responses. Visibility and complete rendered
157
+ output still need assertions in the registered browser checks. It rejects missing/duplicate IDs and reports unobserved written values separately. It is bounded to 100 families, 20 instances per family, 100 keys
158
+ and 1000 elements per instance. Exceeding a budget reports limits and complete: false; it does not invalidate a
159
+ working component. An unobserved paint effect also remains unverified until its
160
+ relevant consumer/state is checked. It does
161
+ not prove every future input, visibility state or interaction. In registered
162
+ Playwright checks, use `auditBehaviorPaints(frame)` from
163
+ `@reqvire-org/ads/verification` after updates. Assert `result.ok` and, when claiming complete current-map
164
+ evidence, `result.complete` and the expected `verified` entries.
165
+ Exercise two instances with identical keys/different paints, key updates/removal,
166
+ themes, local SVG IDs and invalid/inaccessible references. Keep normal runner
167
+ profiles; React StrictMode still needs a project development harness.
168
+
169
+ ## Rendered and declarative evidence
170
+
171
+ `covers` names rendered public UI. A null-returning declarative part belongs in
172
+ `exercises`, alongside behavioral APIs:
173
+
174
+ ```tsx
175
+ export const scenario = {
176
+ title: "Plot", covers: ["Plot"], exercises: ["Series"],
177
+ };
178
+ ```
179
+
180
+ The real part must reach its registered consumer through a rendered parent.
181
+ ADS resolves source identity comparisons (`child.type === Series`, including a
182
+ traceable comparison helper) and reads of that same child’s props traced from the consumer’s children input.
183
+ A comparison against a decoy object gives no evidence. An import, unrelated parent, unused
184
+ child or metadata claim gives no consumption credit. Unknown render branches are
185
+ conservative. Browser checks must verify that adding/removing/changing the part
186
+ changes output. Source relationships and current browser evidence are separate.
187
+ Do not add fake DOM, hide public parts or rewrite a valid API to appease tracing.
188
+
189
+ Scenario metadata is validated by field/entry with positions and reasons.
190
+ A bad entry still blocks acceptance while valid independent render evidence is
191
+ retained. Unreadable prerequisites report coverage as not established. Inspect
192
+ the exact source diagnostic and bounded results; do not assume every subsequent
193
+ missing preview means its runtime API is invalid.
194
+
195
+ ## Local tokens and project consistency
196
+
197
+ `tokens.css` remains optional production design data for component/pattern owners,
198
+ imported by the runtime owner. Controllers stay headless; review uses the shared
199
+ vocabulary and its own development presentation state. Only root/theme definitions belong there. Tokens require real sample
200
+ coverage, including independently built owner token scenarios. One namespace
201
+ cannot be shared by peer components. Private references remain private through
202
+ TS constants, re-exports, helper returns, SVG/paint props and property reads/writes.
203
+ Normal composition of a public component using its own tokens remains valid.
204
+ Source analysis covers supported value flows; arbitrary dynamic code is not
205
+ proof of ownership. CSS variables remain browser-visible; namespacing is an
206
+ architecture boundary, not browser encapsulation.
207
+
208
+ Ownership does not establish a project's visual scale:
209
+
210
+ - A component redefining shared `--space-4` fails.
211
+ - Its private `--plot-gap: 7px` can be valid component geometry.
212
+ - `--plot-gap: var(--space-4)` preserves a shared scale with local meaning.
213
+
214
+ Choose project policy explicitly. A registered project check can use tooling-only
215
+ `inspectTokens(root)` and `inspectTokenOrigins(inventory, name, { stopAt: names })`
216
+ from `@reqvire-org/ads`. `aliasOnly` verifies every theme definition follows only
217
+ aliases to the explicitly selected origins; `literals` and `diagnostics` explain
218
+ violations/limits. Select the exact targets and permitted shared origins. Keep
219
+ intentional geometry exceptions exact with documented reasons. ADS neither
220
+ infers spacing meaning from names nor prescribes a global spacing palette.
221
+
222
+ ## Migration and MCP editing
223
+
224
+ Legacy `tokens.json`/`<Owner>.tokens.json` and `behavior.json` remain readable.
225
+ Use either authoring form per section; competing declarations fail. New contracts
226
+ normalize into the same discovery, ownership, source revisions, affected-check
227
+ selection and candidate gates. `ads_units` exposes `contractFile`; `ads_catalog`
228
+ reports constrained paint paths and declarative parent/consumer/check references.
229
+ Use bounded file reads and paged detailed results, not repeated whole-project dumps.
230
+
231
+ Install supporting ADS tooling before migrating a consuming project and reconnect
232
+ MCP before replacing a package used by a running server. Then:
233
+
234
+ 1. Discover one owner with `ads_units`.
235
+ 2. `ads_prepare_patch({ unit, action: "migrate" })` creates a normal local draft.
236
+ 3. Review the converted contract, renamed files/imports and migration notes.
237
+ 4. `ads_prepare_patch({ unit, action: "diff", draft })` emits its Git patchFile.
238
+ 5. Dry-run/apply with `ads_apply_patch`, or a genuinely coupled transaction.
239
+
240
+ Migration never edits live source or bypasses gates. It retains stable scenario
241
+ IDs, check case IDs and directory ownership. A canonical `<Owner>.ads.scenario.tsx` is the same
242
+ development entry as `Scenario.tsx`, even without a production entry or owner
243
+ contract. Explicit flat IDs and existing flat peer families remain separate.
244
+ An optional static `scenario.id` preserves a legacy
245
+ identity when a renamed filename would otherwise be ambiguous; it must stay
246
+ inside the owning unit and be unique. Whole-file or unimportable private behavior subjects stay
247
+ in legacy registration with a note until deliberately resolved. Unknown runner
248
+ globs, custom adapters and documentation references require review. Ambiguous
249
+ flat-to-directory renames stay in the legacy form with a migration note so file
250
+ ownership cannot silently change. Preserve
251
+ licenses, pending drafts and unrelated work; keep transactions within their limits.
252
+ There is no alternate JSON contract editor. Do not loosen a guard or make a valid
253
+ API private merely to complete a migration.
@@ -337,3 +337,7 @@ native integration does not by itself make that material independent.
337
337
 
338
338
  This workflow adapts Emil Kowalski's animation skills. See
339
339
  [README.md](README.md) and [LICENSE.upstream](LICENSE.upstream) for provenance.
340
+
341
+ For static owner contracts, `.ads.` development roles, typed paint/binding APIs,
342
+ declarative evidence, token privacy and guarded migration, read
343
+ [contracts.md](references/contracts.md) when the task touches those contracts.
@@ -0,0 +1,253 @@
1
+ # Static owner contracts and development files
2
+
3
+ Use one optional `<Owner>.ads.contract.ts` for owner-local tokens, constrained
4
+ paint inputs, scoped runtime bindings, declarative parts and behavioral checks.
5
+ The import `import type { UnitContract } from "@reqvire-org/ads/contracts"` supplies
6
+ development typing. ADS reads its AST and resolves real symbols without importing
7
+ or executing project modules. It adds no production runtime.
8
+
9
+ ## Files and ownership
10
+
11
+ ```text
12
+ components/Plot/
13
+ Plot.tsx production API/root
14
+ Series.tsx production declarative API
15
+ renderer.tsx production consumer
16
+ paints.ts production binding producer
17
+ types.ts production prop types
18
+ tokens.css optional production token definitions
19
+ Plot.ads.contract.ts static tool declaration
20
+ Plot.ads.scenario.tsx demonstration
21
+ Plot.ads.fixture.ts demonstration data
22
+ Tokens.ads.scenario.tsx real owner-token samples
23
+ Tokens.ads.fixture.ts sample bindings/probes
24
+ Plot.ads.behavior.spec.ts registered browser checks
25
+ Plot.ads.test.tsx optional unit tests
26
+ Plot.ads.helpers.ts development-only adapters
27
+ README.md API/provenance
28
+ ```
29
+
30
+ This is an example, not a required inventory. Use the same roles for patterns,
31
+ controllers and relevant development owners. `review/` remains a separate
32
+ shared development layer; its implementation filenames need no extra marker.
33
+ Runtime components, types and styles cannot import `.ads.` files, tests, fixtures
34
+ or review code through aliases, barrels or helpers. Unknown `.ads.` roles are
35
+ still development-only and are diagnosed. Fixtures/helpers cannot define styles;
36
+ put demo framing in review. Ordinary README/license files retain their names.
37
+
38
+ Scenarios/fixtures/review enter only the selected preview closure. Contracts are
39
+ never executable preview modules; derived audit descriptors are data. Specs run
40
+ only in the configured test runner. Production declaration emission must also
41
+ start from the public runtime entry, rather than include every file under the
42
+ design-system folder. The starter’s `tsconfig.build.json` does this; its normal
43
+ no-emit typecheck still checks development sources. Core import guards protect
44
+ the runtime dependency closure. Review custom emitter configurations through
45
+ the configuration owner before migrating a consuming project. Guards remain tooling. Production prop types
46
+ belong in runtime files, never inferred from a development contract.
47
+
48
+ ## A contract uses actual implementation references
49
+
50
+ ```ts
51
+ import type { UnitContract } from "@reqvire-org/ads/contracts";
52
+ import { Plot } from "./Plot";
53
+ import { Series } from "./Series";
54
+ import { Renderer } from "./renderer";
55
+ import { paints } from "./paints";
56
+
57
+ export default {
58
+ schemaVersion: 1,
59
+ tokens: { namespace: "--plot-", stylesheet: "./tokens.css" },
60
+ paintInputs: [
61
+ { api: Plot, property: "config.*.paint", kind: "paint-reference" },
62
+ { api: Series, property: "fill", kind: "paint-reference", bindings: ["items"] },
63
+ ],
64
+ bindings: {
65
+ items: {
66
+ input: { api: Plot, property: "config" },
67
+ producer: paints,
68
+ valueFrom: "paint",
69
+ variable: "--plot-runtime-${key}",
70
+ scope: { api: Plot, selector: '[data-plot-root=""]' },
71
+ checks: ["isolation"],
72
+ },
73
+ },
74
+ declarativeParts: [
75
+ { api: Series, parent: Plot, consumer: Renderer, checks: ["isolation"] },
76
+ ],
77
+ behavior: {
78
+ subjects: { plot: { api: Plot }, renderer: { implementation: Renderer } },
79
+ guarantees: [{
80
+ id: "scoped-output",
81
+ description: "Changing either instance updates only its own output.",
82
+ subjects: ["plot", "renderer"], checks: ["isolation"],
83
+ }],
84
+ checks: {
85
+ isolation: {
86
+ file: "./Plot.ads.behavior.spec.ts", case: "isolation",
87
+ scenario: "components/Plot", profile: "widget",
88
+ },
89
+ },
90
+ },
91
+ } satisfies UnitContract;
92
+ ```
93
+
94
+ Keep contracts small; most owners need none or one section. Supported data are
95
+ literal objects/arrays, static constants/spreads, `as const` and `satisfies`.
96
+ Symbol references resolve through normal imports, aliases and re-exports.
97
+ Calls, getters, methods, computed metadata and environment-dependent expressions
98
+ are rejected. Function references are opaque identities, never executed.
99
+ Private implementation subjects may be internal module exports without becoming
100
+ public barrel exports. Producers/consumers must belong to the same owner. Required
101
+ checks must exist, back named guarantees and use a browser profile with the
102
+ corresponding scenario. A relationship is registration, not a passing guarantee.
103
+
104
+ ## Typed paints and runtime bindings
105
+
106
+ Production-facing types can use:
107
+
108
+ ```ts
109
+ export type Paint = `var(--${string})` | `url(#${string})` | "none" | "currentColor";
110
+ export interface SeriesProps { fill?: Paint }
111
+ export type Config = Readonly<Record<string, { paint: Paint }>>;
112
+ ```
113
+
114
+ ADS follows the actual property path/type, including nested maps. A name such as
115
+ `Paint`, an arbitrary `string`, `any` or a forged metadata role grants no waiver.
116
+ Supported references are a single accessible token/binding `var(--name)`, a local
117
+ SVG paint `url(#id)`, `none` or `currentColor`. Fallbacks, raw colours, external
118
+ URLs and arbitrary CSS are outside this role. Raw domain colours need a separately
119
+ reviewed validated input boundary; they are not implicitly supported here.
120
+
121
+ A runtime family is instance data, not an infinite design-token inventory. Its
122
+ source proof requires the declared config map, exact key/value mapping and actual
123
+ root attachment. Supported producers use `Object.entries(config)` with a loop,
124
+ or `Object.fromEntries(...map(...))`; same-owner helper returns are traceable.
125
+ Guard each key before the write/return using
126
+ `if (!/^[A-Za-z0-9_-]+$/.test(key)) throw new Error("Invalid key")` (a loop may
127
+ `continue`). Return only the computed map. For example:
128
+
129
+ ```ts
130
+ export function paints(config: Config) {
131
+ const result: Record<string, string> = {};
132
+ for (const [key, entry] of Object.entries(config)) {
133
+ if (!/^[A-Za-z0-9_-]+$/.test(key)) throw new Error("Invalid key");
134
+ result[`--plot-runtime-${key}`] = entry.paint;
135
+ }
136
+ return result;
137
+ }
138
+ ```
139
+
140
+ The runtime root attaches that returned map using `style={paints(config)}` or a
141
+ traceable local/useMemo result and the declared class/exact data attribute.
142
+ Place spreading props before the explicit scope/style so an unknown spread
143
+ cannot replace them. This grants permission to that binding attachment only;
144
+ it does not authorize arbitrary inline styling, CSSProperties APIs or bulk writes.
145
+ Unsupported dynamic producers/attachment paths fail with an analysis diagnostic.
146
+ Keep the implementation project-owned; there is no required ADS binding helper.
147
+
148
+ Families cannot overlap shared tokens, owner-token namespaces, private runtime
149
+ prefixes or other bindings. A broad family such as `--color-${key}` grants no
150
+ shared-token exception. Use a distinct private family where collision freedom
151
+ cannot be established. Registered paint consumers list their allowed binding IDs;
152
+ borrowing a peer's family or private token remains invalid. Bindings are scoped
153
+ to their actual root; portals outside it need deliberate project integration.
154
+
155
+ The App audit checks current mounted instances, accessible values, local SVG
156
+ resources and computed paint-property responses. Visibility and complete rendered
157
+ output still need assertions in the registered browser checks. It rejects missing/duplicate IDs and reports unobserved written values separately. It is bounded to 100 families, 20 instances per family, 100 keys
158
+ and 1000 elements per instance. Exceeding a budget reports limits and complete: false; it does not invalidate a
159
+ working component. An unobserved paint effect also remains unverified until its
160
+ relevant consumer/state is checked. It does
161
+ not prove every future input, visibility state or interaction. In registered
162
+ Playwright checks, use `auditBehaviorPaints(frame)` from
163
+ `@reqvire-org/ads/verification` after updates. Assert `result.ok` and, when claiming complete current-map
164
+ evidence, `result.complete` and the expected `verified` entries.
165
+ Exercise two instances with identical keys/different paints, key updates/removal,
166
+ themes, local SVG IDs and invalid/inaccessible references. Keep normal runner
167
+ profiles; React StrictMode still needs a project development harness.
168
+
169
+ ## Rendered and declarative evidence
170
+
171
+ `covers` names rendered public UI. A null-returning declarative part belongs in
172
+ `exercises`, alongside behavioral APIs:
173
+
174
+ ```tsx
175
+ export const scenario = {
176
+ title: "Plot", covers: ["Plot"], exercises: ["Series"],
177
+ };
178
+ ```
179
+
180
+ The real part must reach its registered consumer through a rendered parent.
181
+ ADS resolves source identity comparisons (`child.type === Series`, including a
182
+ traceable comparison helper) and reads of that same child’s props traced from the consumer’s children input.
183
+ A comparison against a decoy object gives no evidence. An import, unrelated parent, unused
184
+ child or metadata claim gives no consumption credit. Unknown render branches are
185
+ conservative. Browser checks must verify that adding/removing/changing the part
186
+ changes output. Source relationships and current browser evidence are separate.
187
+ Do not add fake DOM, hide public parts or rewrite a valid API to appease tracing.
188
+
189
+ Scenario metadata is validated by field/entry with positions and reasons.
190
+ A bad entry still blocks acceptance while valid independent render evidence is
191
+ retained. Unreadable prerequisites report coverage as not established. Inspect
192
+ the exact source diagnostic and bounded results; do not assume every subsequent
193
+ missing preview means its runtime API is invalid.
194
+
195
+ ## Local tokens and project consistency
196
+
197
+ `tokens.css` remains optional production design data for component/pattern owners,
198
+ imported by the runtime owner. Controllers stay headless; review uses the shared
199
+ vocabulary and its own development presentation state. Only root/theme definitions belong there. Tokens require real sample
200
+ coverage, including independently built owner token scenarios. One namespace
201
+ cannot be shared by peer components. Private references remain private through
202
+ TS constants, re-exports, helper returns, SVG/paint props and property reads/writes.
203
+ Normal composition of a public component using its own tokens remains valid.
204
+ Source analysis covers supported value flows; arbitrary dynamic code is not
205
+ proof of ownership. CSS variables remain browser-visible; namespacing is an
206
+ architecture boundary, not browser encapsulation.
207
+
208
+ Ownership does not establish a project's visual scale:
209
+
210
+ - A component redefining shared `--space-4` fails.
211
+ - Its private `--plot-gap: 7px` can be valid component geometry.
212
+ - `--plot-gap: var(--space-4)` preserves a shared scale with local meaning.
213
+
214
+ Choose project policy explicitly. A registered project check can use tooling-only
215
+ `inspectTokens(root)` and `inspectTokenOrigins(inventory, name, { stopAt: names })`
216
+ from `@reqvire-org/ads`. `aliasOnly` verifies every theme definition follows only
217
+ aliases to the explicitly selected origins; `literals` and `diagnostics` explain
218
+ violations/limits. Select the exact targets and permitted shared origins. Keep
219
+ intentional geometry exceptions exact with documented reasons. ADS neither
220
+ infers spacing meaning from names nor prescribes a global spacing palette.
221
+
222
+ ## Migration and MCP editing
223
+
224
+ Legacy `tokens.json`/`<Owner>.tokens.json` and `behavior.json` remain readable.
225
+ Use either authoring form per section; competing declarations fail. New contracts
226
+ normalize into the same discovery, ownership, source revisions, affected-check
227
+ selection and candidate gates. `ads_units` exposes `contractFile`; `ads_catalog`
228
+ reports constrained paint paths and declarative parent/consumer/check references.
229
+ Use bounded file reads and paged detailed results, not repeated whole-project dumps.
230
+
231
+ Install supporting ADS tooling before migrating a consuming project and reconnect
232
+ MCP before replacing a package used by a running server. Then:
233
+
234
+ 1. Discover one owner with `ads_units`.
235
+ 2. `ads_prepare_patch({ unit, action: "migrate" })` creates a normal local draft.
236
+ 3. Review the converted contract, renamed files/imports and migration notes.
237
+ 4. `ads_prepare_patch({ unit, action: "diff", draft })` emits its Git patchFile.
238
+ 5. Dry-run/apply with `ads_apply_patch`, or a genuinely coupled transaction.
239
+
240
+ Migration never edits live source or bypasses gates. It retains stable scenario
241
+ IDs, check case IDs and directory ownership. A canonical `<Owner>.ads.scenario.tsx` is the same
242
+ development entry as `Scenario.tsx`, even without a production entry or owner
243
+ contract. Explicit flat IDs and existing flat peer families remain separate.
244
+ An optional static `scenario.id` preserves a legacy
245
+ identity when a renamed filename would otherwise be ambiguous; it must stay
246
+ inside the owning unit and be unique. Whole-file or unimportable private behavior subjects stay
247
+ in legacy registration with a note until deliberately resolved. Unknown runner
248
+ globs, custom adapters and documentation references require review. Ambiguous
249
+ flat-to-directory renames stay in the legacy form with a migration note so file
250
+ ownership cannot silently change. Preserve
251
+ licenses, pending drafts and unrelated work; keep transactions within their limits.
252
+ There is no alternate JSON contract editor. Do not loosen a guard or make a valid
253
+ API private merely to complete a migration.
@@ -0,0 +1,24 @@
1
+ import valueParser from "postcss-value-parser";
2
+
3
+ /** Read-only provenance for project policies. No token-name or design semantics
4
+ * are inferred. stopAt is an explicit project-selected vocabulary. */
5
+ export function inspectTokenOrigins(inventory, name, { stopAt = [] } = {}) {
6
+ const allowed=new Set(stopAt),tokens=new Map(inventory.tokens.map(token=>[token.name,token]));
7
+ const origins=new Set(),literals=[],diagnostics=[];let steps=0;
8
+ function visit(name,seen=new Set()) {
9
+ if(++steps>256){diagnostics.push("Token provenance exceeded 256 branches.");return;}
10
+ const token=tokens.get(name);
11
+ if(!token){diagnostics.push(`Undefined token ${name}.`);return;}
12
+ if(allowed.has(name)){origins.add(name);return;}
13
+ if(seen.has(name)){diagnostics.push(`Cyclic alias through ${name}.`);return;}
14
+ seen=new Set([...seen,name]);
15
+ for(const definition of token.definitions) {
16
+ const nodes=valueParser(definition.value).nodes.filter(node=>!["space","comment"].includes(node.type));
17
+ const ref=nodes.length===1&&nodes[0].type==="function"&&nodes[0].value==="var"&&nodes[0].nodes.filter(node=>!["space","comment"].includes(node.type));
18
+ if(ref&&ref.length===1&&ref[0].type==="word"&&/^--[\w-]+$/.test(ref[0].value))visit(ref[0].value,seen);
19
+ else literals.push({token:name,...definition});
20
+ }
21
+ }
22
+ visit(name);
23
+ return {token:name,origins:[...origins].sort(),literals,diagnostics,aliasOnly:origins.size>0&&!literals.length&&!diagnostics.length};
24
+ }
package/dist/tokens.mjs CHANGED
@@ -45,8 +45,9 @@ function matchesType(value, type) {
45
45
  }
46
46
  }
47
47
 
48
- export function inspectTokens(directory) {
49
- const options = loadConfig(directory);
48
+ export function inspectTokens(directory, suppliedOptions) {
49
+ const options = suppliedOptions ?? loadConfig(directory);
50
+ if(options.tokenInventory)return options.tokenInventory;
50
51
  const { root, dsRoot, config } = options;
51
52
  const definitions = new Map();
52
53
  const diagnostics = [];
@@ -141,7 +142,7 @@ export function inspectTokens(directory) {
141
142
  if (type && resolved !== undefined && !matchesType(resolved, type)) report("token-type", name, entry, `${name} expects ${type}; found ${resolved}.`);
142
143
  }
143
144
  }
144
- return {
145
+ return options.tokenInventory = {
145
146
  contractVersion: tokenContract.version,
146
147
  tokens: [...definitions].sort(([a], [b]) => a.localeCompare(b)).map(([name, entries]) => ({ name, ...tokenContract.tokens[name], ...(scopedTokenOwner(options, name) ? {owner:scopedTokenOwner(options, name).unit, namespace:scopedTokenOwner(options, name).namespace} : {owner:"tokens"}), definitions: entries })),
147
148
  diagnostics,