@pilotspace/add 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,66 @@
1
+ # {{project}} — Design (DESIGN.md)
2
+
3
+ > The design source of truth for **{{project}}** ({{stage}}) — drafted {{date}}.
4
+ > The AI drafts every UI screen against this doc; the JSON foundation below is what
5
+ > the validators lint.
6
+ >
7
+ > **No UI in this project? This doc is optional — delete it.**
8
+
9
+ This doc is the **prose front-door** to the UDD foundation: it carries the design
10
+ *identity* and *intent* a human owns, then points at the named-set JSON
11
+ (`tokens.json` · `catalog.json` · `prototypes/`) the AI renders from.
12
+
13
+ ## Identity
14
+
15
+ Design identity is **human-owned** — set it here at specify; never let the AI invent
16
+ a brand. Fill each prompt with your real values, then delete the comment.
17
+
18
+ - **Brand color** — <!-- your one primary brand color, as your brand guide names it -->
19
+ - **Palette** — <!-- the supporting roles: accent · surface · text · success/warn/danger -->
20
+ - **Typeface** — <!-- the heading + body type families (and the weights you actually use) -->
21
+ - **Voice / tone** — <!-- 2–3 words, e.g. calm · precise · warm -->
22
+
23
+ Once set, these flow down into the **semantic** layer of `tokens.json` (the AI wires
24
+ the primitives beneath them); see Foundation below.
25
+
26
+ ## Principles
27
+
28
+ The design intent the AI drafts UI against — the persona, the one job, the feel.
29
+
30
+ - **Primary user & their job** — <!-- who this is for and the one thing they hire it to do -->
31
+ - **Design principles** — <!-- 3–5 lines, e.g. "one primary action per screen" · "never hide state" -->
32
+ - **Accessibility floor** — <!-- e.g. WCAG AA contrast · focus-visible · hit-target ≥ 44px -->
33
+
34
+ ## Screens
35
+
36
+ An index of the app's screens — one `design/prototypes/<name>.json` per screen (a flat
37
+ json-render tree the catalog validates). Add a row as each screen is designed.
38
+
39
+ | Screen | Prototype | Status |
40
+ |--------|-----------|--------|
41
+ | (worked example) | `design/prototypes/main.json` ← seed `prototype.sample.json` | sample |
42
+ | <!-- Home --> | <!-- design/prototypes/home.json --> | <!-- draft --> |
43
+
44
+ Start from `prototype.sample.json` (a clean Screen › Card › {Text, Text, Button})
45
+ and adapt it into `design/prototypes/<name>.json` per screen.
46
+
47
+ ## Foundation
48
+
49
+ The named-set JSON the validators lint — lives under `.add/design/`; start from the
50
+ shipped samples, then adapt:
51
+
52
+ - **Tokens** — `design/tokens.json` · the 3-layer design tokens (primitive · semantic ·
53
+ component). Dialect + divergences: `udd-tokens.md`. Start from `tokens.sample.json`.
54
+ - **Catalog** — `design/catalog.json` · the component catalog (typed props + token
55
+ bindings). Render adapter: `udd-catalog.md`. Start from `catalog.sample.json`.
56
+ - **Prototypes** — `design/prototypes/<name>.json` · the flat json-render screen trees
57
+ the catalog validates. Start from `prototype.sample.json`.
58
+
59
+ `add.py check` lints this named set in place — a layer/catalog/tree/cross-file violation
60
+ goes red with a named code; it is silent when there is no `design/` set.
61
+
62
+ ## Render
63
+
64
+ To turn the catalog + a prototype into a live, clickable UI, follow the render recipe
65
+ in **`udd-catalog.md`** (its `## Render recipe` section): `catalog.json` →
66
+ `defineCatalog(...)`, then `catalog.validate(spec)` on the flat tree as-is.
@@ -5,8 +5,13 @@
5
5
  # ADD method vocabulary (domain-standard names; bridges to legacy terms)
6
6
  GOAL: the one durable outcome a project (and each milestone) runs toward — the loop's orientation anchor, declared as the lowercase `goal:` line in PROJECT.md / MILESTONE.md and surfaced by status/guide every session; distinct from a task's §1 Must (a single required behavior, not the whole-project outcome).
7
7
  deep verify: the deepened Verify evidence (v20) required beyond passing tests — for a task that produced code, that every new symbol is referenced (wiring) and no new dead/unused code exists; for prose/non-code, a recorded no-skim semantic read; which path applies is resolver-judged and the engine never classifies (a rubric, not add.py).
8
+ earned green: the build-integrity property — the green is EARNED when the implementation makes the UNCHANGED red suite pass by GENERAL behavior, not by overfitting src to the test fixtures, vacuous/tautological asserts, or stubbing real logic away; task 1's tamper tripwire protects it mechanically and the adversarial refute-read by judgment, and a confirmed earned-green failure is HARD-STOP-class (never auto-passed, never RISK-ACCEPTED).
9
+ adversarial refute-read: the verify-gate, whole-suite specialization of run.md's adversarial verify — an independent reviewer (a subagent under autonomy:auto is recommended; the engine never spawns one) prompted to argue "the green was NOT earned", scoring the earned-green cheats (overfit · vacuous · stub) the mechanical tripwire cannot see.
10
+ bounded self-heal: the verify-gate loop (heal-then-escalate) that gives a CONFIRMED cheat — mechanical tamper (the tripwire) or a reported earned-green failure (`add.py heal --reason`) — a chance to redo honestly before it stops: the engine returns the task to build, COUNTS the attempt, CAPS it at 3 (`HEAL_CAP`), and on the next confirmed cheat forces a HARD-STOP that escalates to the human. The counter is MONOTONIC — it never auto-resets (cmd_phase is unguarded, so a reset would be a zero-human cap bypass). The engine counts/caps/escalates; the AGENT does the honest re-build (never the engine). An honest build passes even at the cap (the cap bites a CONTINUED cheat, never a recovery); a gamed green is never auto-passed, never RISK-ACCEPTED-waived.
8
11
  onboarding: the install -> first-milestone path (formerly "on-ramp").
9
12
  primary flow: the solid forward path of the flow diagram — a phase starts only when its input exists (formerly "forward spine").
13
+ ground: the phase-0 preamble before specify — the AI gathers the real current codebase a task touches (files, symbols, signatures, conventions) into a §0 grounding map; AI-owned, adds no gate (the one approval stays at the contract freeze).
14
+ grounding map / anchors: the §0 GROUND artifact — the real files/symbols/conventions a task touches plus the anchors the frozen contract cites; task-specific delta only (defers to PROJECT.md / CONVENTIONS.md), surfaced by status/check (measure, never block).
10
15
  cross-cutting concern: a concern running through every step rather than being one step — security, testing, observability, cost (formerly "spine / continuous concern").
11
16
  working state: everything an agent loads each session — skill router, active phase, PROJECT/MILESTONE/TASK, state.json (formerly "state surface").
12
17
  audit trail: the reference record read by people, never auto-loaded into agent context (formerly "story surface").
@@ -15,7 +20,8 @@ failing-first suite: the test suite written before code, confirmed red for the r
15
20
  non-functional review: the deliberate post-evidence check of what tests rarely catch — concurrency, security, architecture (formerly "blind-spot checks").
16
21
  change scope: the files a locked run may and may not touch (formerly "touch-boundary"; the <touch_boundary> XML prompt tag keeps its name).
17
22
  automated quality gate: the evidence-based Verify resolver under autonomy auto — may auto-PASS on complete evidence; security always escalates (formerly "evidence auto-gate").
18
- autonomy level: the per-task Verify resolver setting — auto (default) or conservative; declared in the TASK.md header, human-reviewed at the freeze (formerly "autonomy dial").
23
+ autonomy level: the explicit per-task Verify resolver setting on an ordered ladder manual | conservative | auto (manual < conservative < auto): auto (the seeded default) auto-PASSes on complete evidence, conservative keeps a human at the gate, manual is the strict floor (the human owns the gate; nothing auto-resolves); declared in the TASK.md header, human-reviewed at the freeze (formerly "autonomy dial").
24
+ auto-ready goal: a milestone goal whose every exit criterion CITES a verifier `(verify: <test|command|metric>)` — so the engine can self-verify the result against the goal without human judgement. This is the prerequisite by which autonomy is EARNED by goal-clarity (the autonomy level then governs who resolves Verify, but a clarified goal is what makes a self-verifying run meaningful). `add.py check` WARNs (never red) the active milestone until its goal is auto-ready (total >= 1 AND every criterion cited) and `status` surfaces it (`goal-ready: auto-ready ✓` / cited-of-total); a zero-criteria goal is NOT auto-ready and is milestone-shaping's nudge, not this WARN's. The lint forces a citation slot per criterion (raising the floor) but cannot PROVE the citation is real — the human still owns whether it is honest (citation-theater is the accepted, irreducible floor; the freeze gate and autonomy behavior are unchanged by it).
19
25
  living documentation: the durable project artifacts — conventions, glossary, frozen contracts — that outlive any particular code (formerly "survivor layer").
20
26
  scope level: the granularity a decision lives at — intake level (request -> versioned scope), milestone level, setup/foundation level, task level (formerly "altitude").
21
27
  baseline approval: the one human gate that freezes the AI-drafted foundation, first scope, and first contract together — runs as `add.py lock` (formerly "the lock-down").
@@ -7,7 +7,9 @@
7
7
  > that is the re-entrant arrow from the engine down to the foundation.
8
8
 
9
9
  slug: {{project}} · stage: {{stage}} · updated: {{date}}
10
- goal: <the one durable outcome this whole project runs toward set this at setup; status/guide surface it every session>
10
+ autonomy: auto <!-- project default new tasks inherit this rung (manual < conservative < auto); lower a single task in its TASK.md header when it needs a human gate. -->
11
+ goal:
12
+ <!-- set at setup — the outcome this project runs toward; status/guide surface it every session -->
11
13
 
12
14
  ---
13
15
 
@@ -1,9 +1,10 @@
1
1
  # TASK: {{title}}
2
2
 
3
3
  slug: {{slug}} · created: {{date}} · stage: {{stage}}
4
- phase: specify <!-- specify -> scenarios -> contract -> tests -> build -> verify -> observe -> done -->
5
- <!-- high-risk/method-defining scope? declare `risk: high` on the slug line above and lower
6
- the autonomy level with `autonomy: conservative` the engine refuses an unguarded completion
4
+ autonomy: {{autonomy}} <!-- inherited from the project default (PROJECT.md); explicit level: manual < conservative < auto (visible · overridable) — lower below if a high-risk task needs it. -->
5
+ phase: ground <!-- ground -> specify -> scenarios -> contract -> tests -> build -> verify -> observe -> done -->
6
+ <!-- high-risk/method-defining scope? declare `risk: high` on the slug line above and lower the
7
+ autonomy level to `manual` or `conservative` — the engine refuses an unguarded completion
7
8
  (`unguarded_high_risk_auto`, run.md guard). A comment is never a declaration. -->
8
9
 
9
10
  > One file = one task. Fill sections top-to-bottom; the `add` skill drives each phase.
@@ -12,6 +13,15 @@ phase: specify <!-- specify -> scenarios -> contract -> tests -> build -> veri
12
13
 
13
14
  ---
14
15
 
16
+ ## 0 · GROUND — the real codebase ▸ docs/02-the-flow.md
17
+
18
+ Touches (files · symbols · signatures): <path:symbol — what it is / how it is keyed>
19
+ Context (working folder): <docs · todos · config · data the task touches — task-delta only>
20
+ Honors (patterns / conventions): <PROJECT.md / CONVENTIONS.md anchors — task-delta only, never a re-scan>
21
+ Anchors the contract cites: <the symbols §3 will name>
22
+
23
+ ---
24
+
15
25
  ## 1 · SPECIFY — the rules ▸ docs/03-step-1-specify.md
16
26
 
17
27
  Feature: <name>
@@ -96,11 +106,19 @@ Tests live in: `./tests/` · MUST run red (missing implementation) before Build.
96
106
 
97
107
  ## 5 · BUILD — AI writes code ▸ docs/07-step-5-build.md
98
108
 
109
+ Scope (may touch): `./src/` <fill before the §3 freeze — every file the build may write>
110
+ Strategy (ordered batches): <1. … 2. … — the planned build order; guidance, not enforced>
99
111
  Safety rule (feature-specific): <e.g. debit+credit in one atomic transaction>
100
112
  Code lives in: `./src/`
101
113
  Constraints: do NOT change any test or the contract; allow-list packages only; ask if unclear.
102
114
 
103
- <!-- EXIT: all green; coverage held; no test/contract touched; no unlisted dependency. -->
115
+ <!-- Scope tokens, backticked, FIRST declaring line: `./…` = this task dir · a token
116
+ with "/" = project root · a bare name = sibling of the previous token's dir ·
117
+ outside-root resolutions are dropped fail-closed · a DIRECTORY token covers its
118
+ whole subtree (containment — diverges from §4's non-recursive counting) ·
119
+ absent line = UNDECLARED (pre-existing tasks grandfathered, never retro-red) ·
120
+ engine enforcement (touched ⊆ declared) lands in scope-gate-enforce.
121
+ EXIT: all green; coverage held; no test/contract touched; no unlisted dependency. -->
104
122
 
105
123
  ---
106
124
 
@@ -109,6 +127,7 @@ Constraints: do NOT change any test or the contract; allow-list packages only; a
109
127
  - [ ] all tests pass
110
128
  - [ ] coverage did not decrease
111
129
  - [ ] no test or contract was altered during build
130
+ - [ ] the green was EARNED, not gamed — no overfit to fixtures, vacuous asserts, or stubbed-away logic (score with an adversarial refute-read — a subagent recommended under `autonomy: auto`; a confirmed cheat is HARD-STOP)
112
131
  - [ ] concurrency / timing of the risky operation is safe
113
132
  - [ ] no exposed secrets, injection openings, or unexpected dependencies
114
133
  - [ ] layering & dependencies follow CONVENTIONS.md
@@ -0,0 +1,38 @@
1
+ {
2
+ "components": {
3
+ "Screen": {
4
+ "description": "The root viewport container.",
5
+ "hasChildren": true,
6
+ "props": {
7
+ "background": { "type": "token", "token": "color" },
8
+ "padding": { "type": "token", "token": "dimension" }
9
+ }
10
+ },
11
+ "Card": {
12
+ "description": "A surface that groups related content.",
13
+ "hasChildren": true,
14
+ "props": {
15
+ "background": { "type": "token", "token": "color" },
16
+ "gap": { "type": "token", "token": "dimension" },
17
+ "elevation": { "type": "enum", "values": ["none", "sm", "md"] }
18
+ }
19
+ },
20
+ "Text": {
21
+ "description": "A run of styled text.",
22
+ "props": {
23
+ "content": { "type": "string" },
24
+ "color": { "type": "token", "token": "color" },
25
+ "weight": { "type": "enum", "values": ["regular", "bold"] }
26
+ }
27
+ },
28
+ "Button": {
29
+ "description": "A primary call-to-action.",
30
+ "props": {
31
+ "label": { "type": "string" },
32
+ "variant": { "type": "enum", "values": ["primary", "secondary"] },
33
+ "background": { "type": "token", "token": "color" },
34
+ "disabled": { "type": "boolean" }
35
+ }
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "root": "screen",
3
+ "elements": {
4
+ "screen": {
5
+ "type": "Screen",
6
+ "props": {
7
+ "background": "{semantic.color.surface}",
8
+ "padding": "{semantic.space.inset-md}"
9
+ },
10
+ "children": ["welcome-card"]
11
+ },
12
+ "welcome-card": {
13
+ "type": "Card",
14
+ "props": {
15
+ "background": "{semantic.color.surface}",
16
+ "gap": "{semantic.space.inset-sm}",
17
+ "elevation": "sm"
18
+ },
19
+ "children": ["title", "subtitle", "cta"]
20
+ },
21
+ "title": {
22
+ "type": "Text",
23
+ "props": {
24
+ "content": "Welcome to ADD",
25
+ "color": "{semantic.color.text}",
26
+ "weight": "bold"
27
+ }
28
+ },
29
+ "subtitle": {
30
+ "type": "Text",
31
+ "props": {
32
+ "content": "Spec-and-tests-first, render-ready.",
33
+ "color": "{semantic.color.text}",
34
+ "weight": "regular"
35
+ }
36
+ },
37
+ "cta": {
38
+ "type": "Button",
39
+ "props": {
40
+ "label": "Get started",
41
+ "variant": "primary",
42
+ "background": "{semantic.color.accent}",
43
+ "disabled": false
44
+ },
45
+ "on": { "click": [{ "action": "navigate", "to": "onboarding" }] }
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "primitive": {
3
+ "color": {
4
+ "$type": "color",
5
+ "blue-500": { "$value": "#3B82F6" },
6
+ "slate-900": { "$value": "#0F172A" },
7
+ "white": { "$value": "#FFFFFF" }
8
+ },
9
+ "space": {
10
+ "$type": "dimension",
11
+ "2": { "$value": "8px" },
12
+ "4": { "$value": "16px" }
13
+ },
14
+ "font": {
15
+ "family": {
16
+ "$type": "fontFamily",
17
+ "sans": { "$value": ["Inter", "system-ui", "sans-serif"] }
18
+ },
19
+ "weight": {
20
+ "$type": "fontWeight",
21
+ "regular": { "$value": 400 },
22
+ "bold": { "$value": 700 }
23
+ }
24
+ },
25
+ "motion": {
26
+ "$type": "duration",
27
+ "fast": { "$value": "150ms" }
28
+ }
29
+ },
30
+ "semantic": {
31
+ "color": {
32
+ "$type": "color",
33
+ "accent": { "$value": "{primitive.color.blue-500}" },
34
+ "text": { "$value": "{primitive.color.slate-900}" },
35
+ "surface": { "$value": "{primitive.color.white}" }
36
+ },
37
+ "space": {
38
+ "$type": "dimension",
39
+ "inset-sm": { "$value": "{primitive.space.2}" },
40
+ "inset-md": { "$value": "{primitive.space.4}" }
41
+ }
42
+ },
43
+ "component": {
44
+ "button": {
45
+ "bg": { "$type": "color", "$value": "{semantic.color.accent}" },
46
+ "label": { "$type": "color", "$value": "{semantic.color.surface}" },
47
+ "padding": { "$type": "dimension", "$value": "{semantic.space.inset-md}" }
48
+ },
49
+ "card": {
50
+ "bg": { "$type": "color", "$value": "{semantic.color.surface}" },
51
+ "text": { "$type": "color", "$value": "{semantic.color.text}" },
52
+ "gap": { "$type": "dimension", "$value": "{semantic.space.inset-sm}" }
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,122 @@
1
+ # UDD catalog + content trees — the render-ready half
2
+
3
+ The token foundation (`udd-tokens.md`) says what a UI is *made of*; this layer says
4
+ what it is *built from* and *laid out as*. A **catalog** declares the components and
5
+ their typed props; a **content tree** is a flat, json-render-shaped prototype that
6
+ binds those props to semantic tokens. `catalog.sample.json` + `prototype.sample.json`
7
+ are a worked pair that validates clean against each other.
8
+
9
+ ## The foundation is a NAMED SET (Fork A)
10
+
11
+ ```
12
+ .add/design/tokens.json the task-1 dialect (primitive · semantic · component)
13
+ .add/design/catalog.json the component catalog (this doc)
14
+ .add/design/prototypes/<name>.json one flat content tree per prototype screen/flow
15
+ ```
16
+
17
+ Each file lints independently — the token validator never sees the catalog, the
18
+ catalog/tree validator never sees the tokens. The cross-file check (a tree's
19
+ token-prop actually resolving to a semantic token of the right `$type`) is the
20
+ **composer's** job — `add.py check` wires it in the udd-check-lint task.
21
+
22
+ ## CATALOG — components and typed props
23
+
24
+ ```
25
+ { "components": { "<Name>": {
26
+ "description"?: str,
27
+ "hasChildren"?: bool (default false — only containers may hold children),
28
+ "props": { "<prop>": PropSpec } } } }
29
+ ```
30
+
31
+ A **PropSpec** is exactly one of:
32
+
33
+ | PropSpec | the tree value it accepts |
34
+ |----------|---------------------------|
35
+ | `{ "type": "string" }` | a JSON string |
36
+ | `{ "type": "number" }` | a JSON number (not a bool) |
37
+ | `{ "type": "boolean" }` | a JSON bool |
38
+ | `{ "type": "enum", "values": ["a","b"] }` | a string in `values` |
39
+ | `{ "type": "token", "token": "<$type>" }` | a `{semantic.…}` alias (see below) |
40
+
41
+ `<$type>` is a task-1 token type — `color · dimension · number · fontFamily ·
42
+ fontWeight · duration`.
43
+
44
+ ## CONTENT TREE — a flat json-render `Spec`
45
+
46
+ Pinned to **vercel-labs/json-render v0.19.0 / commit `4e4dc46`** (the milestone's
47
+ named top risk: young-project schema drift). The tree mirrors json-render's `Spec`
48
+ exactly, so it is render-ready as-is:
49
+
50
+ ```
51
+ { "root": "<id>",
52
+ "elements": { "<id>": {
53
+ "type": "<Name>", a catalog component name
54
+ "props": { … }, keys ⊆ the component's declared props
55
+ "children"?: ["<id>", …] only if the component hasChildren; ids ∈ elements
56
+ } } }
57
+ ```
58
+
59
+ - A `token` prop's value is the **alias form** `"{semantic.dotted.path}"` from the
60
+ token dialect — it MUST target the `semantic` layer. (Whether that semantic token
61
+ exists and matches the prop's `$type` is resolved by the composer, which has
62
+ `tokens.json`.)
63
+ - json-render's optional `state` / `on` / `visible` / `repeat` are **passed through**
64
+ — render-compatible (a clickable prototype) but NOT linted, keeping the validator
65
+ lean. Interactivity rules stay additive for a later task.
66
+
67
+ ## Validation — the named reds
68
+
69
+ `_catalog_tree_violations(catalog, tree)` returns `[]` for a valid pair, else one
70
+ `(code, path, detail)` per violation, in deterministic order. The eight codes:
71
+
72
+ | code | when |
73
+ |------|------|
74
+ | `tree_cites_uncataloged_component` | an element `type` is not in the catalog |
75
+ | `unknown_prop` | a props key not declared on the element's component |
76
+ | `prop_type_mismatch` | a value's form ≠ its PropSpec (incl. a token prop given a non-alias literal) |
77
+ | `non_semantic_prop_token` | a token-prop alias does not target the `semantic` layer |
78
+ | `dangling_child` | a child id absent from `elements` |
79
+ | `children_not_allowed` | children on a component whose `hasChildren` is false |
80
+ | `missing_root` | `root` absent, or names an id not in `elements` |
81
+ | `malformed_catalog` | a PropSpec with unknown `type`, or a token prop naming an unknown `$type` |
82
+
83
+ The validator lints **shape only** — pure, stdlib, tool-agnostic, no rendering. It is
84
+ SEPARATE from `_token_layer_violations`; udd-check-lint composes both inside
85
+ `add.py check`.
86
+
87
+ ## Render recipe — catalog.json → json-render
88
+
89
+ json-render authors a catalog in TypeScript (`defineCatalog`), not JSON, so our
90
+ `catalog.json` is adapted by a thin (~20-line) loader. The content tree needs no
91
+ adapter — it is a json-render `Spec` already:
92
+
93
+ ```ts
94
+ import { defineCatalog } from "json-render";
95
+ import { z } from "zod";
96
+ import catalogJson from "./catalog.json";
97
+ import spec from "./prototypes/welcome.json";
98
+
99
+ const PROP = { // PropSpec → a Zod field
100
+ string: () => z.string(),
101
+ number: () => z.number(),
102
+ boolean: () => z.boolean(),
103
+ enum: (p) => z.enum(p.values),
104
+ token: () => z.string(), // "{semantic.…}" — resolved to a CSS value at render
105
+ };
106
+
107
+ const components = Object.fromEntries(
108
+ Object.entries(catalogJson.components).map(([name, c]) => [name, {
109
+ hasChildren: c.hasChildren ?? false,
110
+ props: z.object(
111
+ Object.fromEntries(Object.entries(c.props).map(([k, p]) => [k, PROP[p.type](p)])),
112
+ ),
113
+ }]),
114
+ );
115
+
116
+ const catalog = defineCatalog(z, { components });
117
+ catalog.validate(spec); // the flat tree feeds json-render AS-IS
118
+ ```
119
+
120
+ Resolving a `{semantic.…}` alias to a concrete CSS value (via `tokens.json`) is the
121
+ renderer's binding step — out of scope for the foundation, which only guarantees the
122
+ SHAPE is render-ready. The renderer itself is never bundled with the method.
@@ -0,0 +1,79 @@
1
+ # UDD tokens — the compact-DTCG dialect
2
+
3
+ The token foundation a UI project drafts from. Three layers, a fail-closed
4
+ citation rule, and value forms compacted for the AI economy. Aligned with the
5
+ **Design Tokens Format Module 2025.10** (Final Community Group Report, 28 Oct 2025
6
+ — <https://www.designtokens.org/tr/2025.10/format/>); every divergence is NAMED
7
+ below. `tokens.sample.json` is a worked example that validates clean.
8
+
9
+ ## Identity values are human-owned — discuss at specify
10
+
11
+ The dialect checks a token's **shape**; its identity **value** — the brand
12
+ color, the palette seed, the typeface — is design *direction*, not an AI
13
+ default. During **specify**, surface identity tokens for discussion with the
14
+ user; never invent a brand value. Shape is verifiable; identity is a human
15
+ decision.
16
+
17
+ ## The three layers (a convention, not a DTCG feature)
18
+
19
+ A token's **layer is its top-level group name** — `primitive`, `semantic`, or
20
+ `component`. DTCG itself defines groups, `$type`, and aliases but no tier model;
21
+ the layering and the citation rule below are ours.
22
+
23
+ ```
24
+ primitive raw literal values (no alias — the source of truth)
25
+ semantic intent, cites primitives (e.g. color.accent → primitive.color.blue-500)
26
+ component parts, cite semantics (e.g. button.bg → semantic.color.accent)
27
+ ```
28
+
29
+ ## The citation rule (fail-closed)
30
+
31
+ - a `primitive` token's `$value` MUST be a literal — never an alias.
32
+ - a `semantic` token's `$value` MUST be a literal OR an alias to a `primitive` only.
33
+ - a `component` token's `$value` MUST be a literal OR an alias to a `semantic` only.
34
+
35
+ No layer-skipping, no sideways or upward citation. An **alias** is the DTCG curly
36
+ form `"{layer.dotted.path}"` used as a `$value`; chains are allowed (each hop is
37
+ checked); the chain must terminate in a literal.
38
+
39
+ ## Token shape (kept from DTCG)
40
+
41
+ - a **token** is an object with `$value` (required); optional `$type`,
42
+ `$description`.
43
+ - `$type` is optional on a token and **inherited** from the nearest parent group
44
+ that sets it.
45
+ - a **group** is any object without `$value`.
46
+
47
+ ## Value forms — supported `$type`s (NAMED divergences from DTCG 2025.10)
48
+
49
+ | `$type` | compact form (ours) | DTCG 2025.10 form |
50
+ |--------------|----------------------------------------|--------------------------|
51
+ | `color` | `"#RRGGBB"` / `"#RRGGBBAA"` | object `{colorSpace,…}` ⟵ DIVERGES |
52
+ | `dimension` | `"<n><unit>"`, unit ∈ px·rem·em·%·vh·vw | object `{value,unit}` ⟵ DIVERGES |
53
+ | `number` | JSON number | JSON number |
54
+ | `fontWeight` | `100`–`900` or a keyword string | number / keyword |
55
+ | `duration` | `"<n>ms"` / `"<n>s"` | object `{value,unit}` ⟵ DIVERGES |
56
+ | `fontFamily` | string or array of strings | string / array |
57
+
58
+ Composites (`shadow`, `typography`, `border`) and the rarer DTCG types
59
+ (`cubicBezier`, `strokeStyle`, `gradient`, `transition`) are **deferred** —
60
+ additive later without breaking this dialect.
61
+
62
+ ## Validation — the named reds
63
+
64
+ `_token_layer_violations(tokens)` returns `[]` for a valid file, else one
65
+ `(code, path, detail)` per violation. The six codes:
66
+
67
+ | code | when |
68
+ |------|------|
69
+ | `unknown_layer` | a top-level group is not primitive/semantic/component |
70
+ | `unknown_type` | a token's resolved `$type` is outside the supported set |
71
+ | `unresolved_alias` | `{a.b.c}` points at no token bearing a `$value` |
72
+ | `cross_layer_citation` | an alias skips or inverts a layer |
73
+ | `primitive_has_alias` | a primitive token's `$value` is an alias, not a literal |
74
+ | `malformed_value` | a literal `$value` does not match the form for its `$type` |
75
+
76
+ The validator lints **shape only** — stdlib, tool-agnostic, no rendering. The
77
+ `add.py check` wiring (named reds) and the catalog/prototype-tree rules arrive in
78
+ later udd-design-foundation tasks. To render: point a json-render-style renderer
79
+ at the component layer (see the milestone's render recipe).