@adia-ai/adia-ui-forge 0.8.37 → 0.8.39

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 (46) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +23 -0
  3. package/README.md +6 -2
  4. package/agents/a2ui-planner-agent.md +52 -0
  5. package/agents/framework-alignment-agent.md +60 -0
  6. package/agents/framework-planner-agent.md +49 -0
  7. package/package.json +1 -1
  8. package/references/contracts/a2ui-mcp-surface.md +7 -7
  9. package/skills/a2ui-maintenance/SKILL.md +5 -5
  10. package/skills/a2ui-maintenance/references/anti-patterns.md +2 -2
  11. package/skills/a2ui-maintenance/references/chunk-authoring.md +2 -2
  12. package/skills/a2ui-maintenance/references/eval-diagnostics.md +3 -3
  13. package/skills/a2ui-maintenance/references/format-extension-decisions.md +1 -1
  14. package/skills/a2ui-maintenance/references/mcp-pipeline-ops.md +5 -5
  15. package/skills/a2ui-maintenance/references/mcp-tool-reference.md +4 -4
  16. package/skills/a2ui-maintenance/references/pipeline-overview.md +18 -18
  17. package/skills/a2ui-maintenance/references/semantic-fail-lifting.md +2 -2
  18. package/skills/a2ui-maintenance/references/strategy-engines.md +2 -2
  19. package/skills/a2ui-maintenance/references/zettel-calibration.md +2 -2
  20. package/skills/gen-ui-review/references/corpus-html-patterns.md +1 -1
  21. package/skills/gen-ui-review/references/loop-protocol.md +1 -1
  22. package/skills/llm-client-maintenance/SKILL.md +4 -4
  23. package/skills/llm-client-maintenance/references/adapter-contract.md +3 -3
  24. package/skills/llm-client-maintenance/references/add-a-provider.md +5 -5
  25. package/skills/llm-client-maintenance/references/bridge-facade.md +2 -2
  26. package/skills/llm-client-maintenance/references/browser-proxy-boundary.md +2 -2
  27. package/skills/llm-client-maintenance/references/model-registry.md +2 -2
  28. package/skills/llm-client-maintenance/references/streaming-sse.md +2 -2
  29. package/skills/package-release/SKILL.md +8 -8
  30. package/skills/package-release/references/cut-procedure.md +6 -91
  31. package/skills/package-release/references/gates-catalog.md +4 -4
  32. package/skills/package-release/references/independent-package-release.md +2 -2
  33. package/skills/package-release/references/recovery-paths.md +2 -2
  34. package/skills/package-release/scripts/bump.mjs +31 -28
  35. package/skills/package-release/scripts/insert-stub.mjs +1 -1
  36. package/skills/package-release/scripts/package-paths.mjs +34 -50
  37. package/skills/package-release/scripts/release-pack.mjs +12 -5
  38. package/skills/package-release/scripts/tag-lockstep.mjs +1 -1
  39. package/skills/primitive-authoring/references/common-gotchas.md +1 -1
  40. package/skills/primitive-authoring/references/css-patterns.md +1 -1
  41. package/skills/primitive-authoring/references/llm-bridge.md +12 -12
  42. package/skills/primitive-authoring/references/module-promotion.md +2 -2
  43. package/skills/primitive-authoring/references/token-contract.md +20 -1
  44. package/skills/site-deployment/SKILL.md +2 -2
  45. package/skills/site-deployment/references/deploy-playbooks.md +8 -8
  46. package/skills/site-docs-authoring/intent.md +1 -1
@@ -2,21 +2,21 @@
2
2
  // standalone CHANGELOG helpers.
3
3
  //
4
4
  // THE PROBLEM (bit all 6 a2ui packages at the v0.7.14 cut):
5
- // The 6 a2ui packages live on disk under `packages/a2ui/<x>/` but carry the
5
+ // The 6 a2ui packages live on disk under `packages/gen-ui/a2ui/<x>/` but carry the
6
6
  // flat npm-name `a2ui-<x>` everywhere else in the tooling (release-pack.mjs,
7
7
  // dispatch-publish.mjs, tag-lockstep.mjs all use the NAME form). The two
8
8
  // standalone helpers (promote-unreleased.mjs, insert-stub.mjs) joined
9
9
  // `packages/<arg>/CHANGELOG.md` with the raw arg, so calling them with the
10
10
  // name form silently 404'd: `CHANGELOG.md not found at packages/a2ui-corpus/`.
11
- // release-pack.mjs maps internally (`a2ui-corpus` → `packages/a2ui/corpus`),
11
+ // release-pack.mjs maps internally (`a2ui-corpus` → `packages/gen-ui/a2ui/corpus`),
12
12
  // so the two forms diverged: name-form failed standalone, path-form failed
13
13
  // when handed to the orchestrator-style callers.
14
14
  //
15
15
  // THE FIX:
16
16
  // Accept BOTH forms. Try `packages/<arg>` first (path form, backward-compat).
17
17
  // If that directory is absent AND the arg matches `a2ui-<x>`, fall back to
18
- // `packages/a2ui/<x>`. So `a2ui-corpus` (name) and `a2ui/corpus` (path) both
19
- // resolve to `packages/a2ui/corpus/`.
18
+ // `packages/gen-ui/a2ui/<x>`. So `a2ui-corpus` (name) and `a2ui/corpus` (path) both
19
+ // resolve to `packages/gen-ui/a2ui/corpus/`.
20
20
  //
21
21
  // Pure Node, stdlib only — keeps the scripts free of runtime deps.
22
22
 
@@ -55,12 +55,11 @@ import path from 'node:path';
55
55
  // trip-wire (check-release.mjs's per-tag `checkTag` lookup) reads the FULL
56
56
  // roster unfiltered — every pushed tag, lockstep or not, still needs its
57
57
  // package.json/CHANGELOG validated, so it must recognize a class-B tag too.
58
- // `shim` (added ADR-0048 P5): `true` marks an old npm name riding exactly ONE
59
- // cut as a re-export shim before retirement. It changes NOTHING about how the
60
- // cut treats the entry a shim versions, tags, publishes and changelogs like
61
- // any other lockstep member. It exists so P7's retirement PR has a mechanical
62
- // answer to "which rows go away", instead of a name list someone has to
63
- // re-derive from a doc. Absent = a permanent package.
58
+ //
59
+ // `shim: true` (ADR-0048 P5) marked the six retired a2ui-* names for their
60
+ // single 0.8.37 bridge cut; the P7 follow-up PR deleted those rows together
61
+ // with packages/shims/. No row carries the flag anymore, and none may a
62
+ // future one-cut bridge re-introduces the marker with its own retirement PR.
64
63
  //
65
64
  // Every lockstep-scoped consumer must filter `p.lockstep !== false` itself
66
65
  // (this module intentionally does NOT pre-filter PACKAGE_NAMES/PACKAGE_DIRS —
@@ -69,9 +68,9 @@ import path from 'node:path';
69
68
  export const PACKAGE_ROSTER = [
70
69
  { name: 'web-components', dir: 'packages/web-components', lockstep: true },
71
70
  { name: 'web-modules', dir: 'packages/web-modules', lockstep: true },
72
- { name: 'llm', dir: 'packages/llm', lockstep: true },
73
- { name: 'agent', dir: 'packages/agent', lockstep: true },
74
- { name: 'persona', dir: 'packages/persona', lockstep: true },
71
+ { name: 'llm', dir: 'packages/llm/core', lockstep: true },
72
+ { name: 'agent', dir: 'packages/llm/agent', lockstep: true },
73
+ { name: 'persona', dir: 'packages/llm/persona', lockstep: true },
75
74
  // ── ADR-0048 P5 — the estate split's published names ────────────────────
76
75
  //
77
76
  // The successors of the six old a2ui-* names. `a2ui`/`gen-ui` join here,
@@ -83,32 +82,19 @@ export const PACKAGE_ROSTER = [
83
82
  // `mcp` (gh#1240, operator ruling 2026-08-14, roster 17 → 16): folds what
84
83
  // were TWO planned rows — `gen-ui-mcp` (P2 rename target) and
85
84
  // `a2ui-protocol-mcp` (minted fresh at P4) — into ONE published name,
86
- // `@adia-ai/mcp`, at `packages/mcp/`. Neither predecessor name ever
85
+ // `@adia-ai/mcp`, at `packages/gen-ui/mcp/`. Neither predecessor name ever
87
86
  // published a 0.8.x, so this fold has no npm deprecation cycle to run —
88
- // unlike the SIX old a2ui-* shims below, which really did publish and need
89
- // one. The two-SERVER decision (ADR-0048 §3) is unchanged; only the
90
- // npm-distribution row count dropped by one.
91
- { name: 'a2ui', dir: 'packages/a2ui', lockstep: true },
92
- { name: 'gen-ui', dir: 'packages/gen-ui', lockstep: true },
93
- { name: 'mcp', dir: 'packages/mcp', lockstep: true },
94
- // ── ADR-0048 P5 the six old names, riding EXACTLY ONE cut as shims ────
95
- //
96
- // `npm unpublish` is impossible, so each retired name gets one final publish
97
- // as a re-export shim (packages/shims/<name>, generated by
98
- // scripts/build/generate-shims.mjs) plus an `npm deprecate` pointer run
99
- // post-publish. They are on the roster only so that cut versions, tags,
100
- // CHANGELOGs and publish workflows treat them like any other package.
101
- //
102
- // `shim: true` is the retirement marker, not decoration: P7's follow-up PR
103
- // deletes exactly the entries carrying it, together with packages/shims/,
104
- // their publish workflows, and resolvePackageDir's `shims/` fallback below.
105
- // Nothing else in the roster is allowed to carry it.
106
- { name: 'a2ui-runtime', dir: 'packages/shims/a2ui-runtime', lockstep: true, shim: true },
107
- { name: 'a2ui-compose', dir: 'packages/shims/a2ui-compose', lockstep: true, shim: true },
108
- { name: 'a2ui-corpus', dir: 'packages/shims/a2ui-corpus', lockstep: true, shim: true },
109
- { name: 'a2ui-mcp', dir: 'packages/shims/a2ui-mcp', lockstep: true, shim: true },
110
- { name: 'a2ui-retrieval', dir: 'packages/shims/a2ui-retrieval', lockstep: true, shim: true },
111
- { name: 'a2ui-validator', dir: 'packages/shims/a2ui-validator', lockstep: true, shim: true },
87
+ // unlike the SIX old a2ui-* names, which really did publish and got one
88
+ // (shim rows retired below). The two-SERVER decision (ADR-0048 §3) is
89
+ // unchanged; only the npm-distribution row count dropped by one.
90
+ { name: 'a2ui', dir: 'packages/gen-ui/a2ui', lockstep: true },
91
+ { name: 'gen-ui', dir: 'packages/gen-ui/engine', lockstep: true },
92
+ { name: 'mcp', dir: 'packages/gen-ui/mcp', lockstep: true },
93
+ // The six retired a2ui-* names (a2ui-runtime/-compose/-corpus/-mcp/
94
+ // -retrieval/-validator) rode EXACTLY the 0.8.37 cut here as `shim: true`
95
+ // rows under packages/shims/, then the P7 follow-up PR deleted them
96
+ // (cut-procedure.md §0.8.38 precondition 1). Their npm names are
97
+ // deprecated pointers; nothing may re-add them.
112
98
  // Plugins joined the lockstep 2026-07-15 (check-lockstep.mjs POLICY note);
113
99
  // each carries a .claude-plugin/plugin.json that bumps with package.json.
114
100
  { name: 'adia-ui-factory', dir: 'packages/plugins/adia-ui-factory', lockstep: true, plugin: true },
@@ -135,19 +121,17 @@ export const PACKAGE_DIRS = PACKAGE_ROSTER.map((p) => p.dir);
135
121
  // require the directory to exist (so callers can render a legible not-found
136
122
  // error against the resolved path).
137
123
  export function resolvePackageDir(repo, pkg) {
124
+ // Roster name-form first (ADR-0049): `llm` must resolve to the roster's
125
+ // `packages/llm/core`, never to the bare `packages/llm/` FAMILY GROUPING
126
+ // dir that now exists on disk (no package.json, no CHANGELOG) — an
127
+ // existsSync-first probe would hit the grouping dir and silently target
128
+ // a non-package. Same for `gen-ui` → `packages/gen-ui/engine`.
129
+ const row = PACKAGE_ROSTER.find((p) => p.name === pkg);
130
+ if (row) return path.relative('packages', row.dir);
138
131
  if (fs.existsSync(path.join(repo, 'packages', pkg))) return pkg;
139
- // ADR-0048 P3 — the old a2ui-* names now live at packages/shims/<old-name>.
140
- // Tried before the legacy packages/a2ui/<x> nesting below so the shim wins
141
- // while both could exist; both fallbacks die when P5 retires the shims.
142
- if (/^a2ui-/.test(pkg)) {
143
- const shim = path.join('shims', pkg);
144
- if (fs.existsSync(path.join(repo, 'packages', shim))) return shim;
145
- }
146
- const m = /^a2ui-(.+)$/.exec(pkg);
147
- if (m) {
148
- const nested = path.join('a2ui', m[1]);
149
- if (fs.existsSync(path.join(repo, 'packages', nested))) return nested;
150
- }
132
+ // (The historical a2ui-* fallbacks packages/shims/<old-name> and the
133
+ // pre-split packages/gen-ui/a2ui/<x> nesting died with the shim-deletion
134
+ // follow-up PR after the 0.8.37 cut; a2ui-gen-ui-split.md ruled both dead.)
151
135
  // Plugins live under packages/plugins/<name> but carry the flat name form
152
136
  // (adia-ui-forge) in the release tooling — same name-vs-path split as a2ui.
153
137
  if (/^adia-ui-/.test(pkg)) {
@@ -222,8 +222,8 @@ function help() {
222
222
  --gh-notes-file /tmp/release-v$X.Y.Z.md \\
223
223
  --substantive "<one-line>" \\
224
224
  --xref "packages/web-modules/CHANGELOG.md#0$X$Y$Z--YYYY-MM-DD" \\
225
- --substantive-packages web-components,web-modules,a2ui/corpus \\
226
- --stub-packages llm,a2ui-compose,a2ui-mcp,a2ui-retrieval,a2ui-runtime,a2ui-validator
225
+ --substantive-packages web-components,web-modules,gen-ui \\
226
+ --stub-packages llm,agent,persona,a2ui,mcp,adia-ui-factory,adia-ui-forge
227
227
 
228
228
  Flags: --go (single-authorization: the operator's cut-start go covers the
229
229
  whole cycle — auto-confirms ALL checkpoints; stops only on
@@ -704,8 +704,9 @@ function step5Commit(args) {
704
704
  ...LOCKSTEP_ROSTER.flatMap(({ name, dir, plugin }) => {
705
705
  // Roster-driven (H3): each package stages its manifest + CHANGELOG;
706
706
  // plugins also stage .claude-plugin/plugin.json (the /plugin-update
707
- // cache key, moved by bump.mjs); factory additionally pins a2ui-mcp in
708
- // .mcp.json (SKILL invariant 8 — bump.mjs moves the pin too).
707
+ // cache key, moved by bump.mjs); factory additionally pins the
708
+ // generation MCP (@adia-ai/mcp) in .mcp.json (SKILL invariant 8 —
709
+ // bump.mjs moves the pin too).
709
710
  const files = [`${dir}/package.json`, `${dir}/CHANGELOG.md`];
710
711
  if (plugin) files.push(`${dir}/.claude-plugin/plugin.json`);
711
712
  if (name === 'adia-ui-factory') files.push(`${dir}/.mcp.json`);
@@ -1167,7 +1168,13 @@ function selftest() {
1167
1168
  guardFailed = true;
1168
1169
  guardOut = (e.stdout || '') + (e.stderr || '');
1169
1170
  }
1170
- if (!guardFailed || !guardOut.includes('ride-along') || !guardOut.includes('a2ui-validator')) {
1171
+ // The expected ride-alongs are DERIVED from the roster (everything but
1172
+ // the one --substantive-packages entry above), never transcribed — a
1173
+ // hard-coded name here went stale when the shim-deletion follow-up PR
1174
+ // shrank the roster 16 → 10 (the same never-transcribe rule the cut
1175
+ // docs enforce on every other roster projection).
1176
+ const expectedRideAlongs = PACKAGES.filter((p) => p !== 'web-components');
1177
+ if (!guardFailed || !guardOut.includes('ride-along') || !expectedRideAlongs.every((p) => guardOut.includes(p))) {
1171
1178
  console.error('selftest FAIL: from-scratch without --stub-packages must hard-reject at parse time, naming the ride-alongs');
1172
1179
  process.exit(1);
1173
1180
  }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // tag-lockstep.mjs — create the lockstep tag set (umbrella vX.Y.Z + one
3
3
  // per-package <pkg>-vX.Y.Z per roster entry — package-paths.mjs is the
4
- // live census; 17 tags as of gh#1240) at HEAD or at a specified SHA.
4
+ // live census; 11 tags as of gh#1282) at HEAD or at a specified SHA.
5
5
  //
6
6
  // Usage:
7
7
  // node tag-lockstep.mjs --version 0.6.22
@@ -125,7 +125,7 @@ async #loadContent(route) {
125
125
 
126
126
  ## 7. Minting a wrapper-shaped component before its registry.js entry lands — site-a2ui silently deletes the node, not just mis-types it
127
127
 
128
- **Pattern**: a tag is gated first and only, for `*-ui` tags, by `packages/a2ui/registry.js`'s hand-maintained `registry` map — inverted into `reverseRegistry` at `transpiler-maps.js`'s module init, consulted first thing in `compose/transpiler/transpiler.js:149-150`. `registry.js` is hand-edited, not generated by `node scripts/build/components.mjs` (that script writes sidecars/prop-catalog data, consumed only for prop-extraction fidelity on tags the transpiler ALREADY resolved — `transpiler-maps.js:22-26`); a runtime `registerType()` call doesn't rescue a stale row either — `reverseRegistry` is a one-time init snapshot, not live. Regenerate a site-a2ui row for a demo using a component minted in the SAME change, before its `registry.js` line lands, and the tag falls through to `transpiler.js`'s "Unknown → Column" branch (line 180-183) — same mechanism as gh#535's toolbar-group breakage, which at least rendered visibly-wrong. A NEW component is usually wrapper-shaped (one child, author-defined attributes like `anchor="bottom"` the transpiler doesn't map to any real A2UI prop). That shape trips a SECOND, separate rule right after — "single-child container chains flatten" (`transpiler.js:282-285`): a retyped Column with exactly one child and zero recognized props is discarded outright, and its child is spliced directly into the PARENT's children in its place. The wrapper's own id and node are never pushed to the tree at all — not visible-but-wrong, just gone. The row is then internally self-consistent (content hash matches source) so `check:chunks-fresh`/`verify:site-a2ui` both report clean.
128
+ **Pattern**: a tag is gated first and only, for `*-ui` tags, by `packages/gen-ui/a2ui/registry.js`'s hand-maintained `registry` map — inverted into `reverseRegistry` at `transpiler-maps.js`'s module init, consulted first thing in `compose/transpiler/transpiler.js:149-150`. `registry.js` is hand-edited, not generated by `node scripts/build/components.mjs` (that script writes sidecars/prop-catalog data, consumed only for prop-extraction fidelity on tags the transpiler ALREADY resolved — `transpiler-maps.js:22-26`); a runtime `registerType()` call doesn't rescue a stale row either — `reverseRegistry` is a one-time init snapshot, not live. Regenerate a site-a2ui row for a demo using a component minted in the SAME change, before its `registry.js` line lands, and the tag falls through to `transpiler.js`'s "Unknown → Column" branch (line 180-183) — same mechanism as gh#535's toolbar-group breakage, which at least rendered visibly-wrong. A NEW component is usually wrapper-shaped (one child, author-defined attributes like `anchor="bottom"` the transpiler doesn't map to any real A2UI prop). That shape trips a SECOND, separate rule right after — "single-child container chains flatten" (`transpiler.js:282-285`): a retyped Column with exactly one child and zero recognized props is discarded outright, and its child is spliced directly into the PARENT's children in its place. The wrapper's own id and node are never pushed to the tree at all — not visible-but-wrong, just gone. The row is then internally self-consistent (content hash matches source) so `check:chunks-fresh`/`verify:site-a2ui` both report clean.
129
129
 
130
130
  **Example**: minting `anchor-bar-ui` (gh#495, PR #569) and regenerating the `bulk-action-toolbar` pattern's site-a2ui row before the worktree's `registry.js` entry for it existed. Git-verified on the pre-fix commit (`ebf71832d`): `site-a2ui/pages/site__patterns__bulk-action-toolbar.a2ui.json` contains zero occurrences of `pat-bulk-float-bar` (the anchor-bar-ui's own authored id) anywhere — not retyped-and-visible, genuinely absent — while its single child (the toolbar content) survived, reparented one level up. `verify:site-a2ui` reported 321/321 clean.
131
131
 
@@ -223,7 +223,7 @@ AdiaUI is light-DOM. Slotted children are just children. Style them with attribu
223
223
  }
224
224
 
225
225
  :scope > header > [slot="heading"] {
226
- font-weight: var(--a-font-weight-strong);
226
+ font-weight: var(--a-weight-semibold);
227
227
  }
228
228
 
229
229
  /* WRONG — ::slotted() is for shadow DOM */
@@ -7,9 +7,9 @@ are the current, maintained source — this file is kept only as a historical po
7
7
  no longer linked from `primitive-authoring`'s task-shape table or `code-style.md`. Route provider/
8
8
  streaming/bridge work to `llm-client-maintenance`.
9
9
 
10
- Use when adding a 4th provider, modifying `createAdapter()`, changing the streaming chunk shape, or extending `packages/llm/server.js`.
10
+ Use when adding a 4th provider, modifying `createAdapter()`, changing the streaming chunk shape, or extending `packages/llm/core/server.js`.
11
11
 
12
- This reference is about **modifying** the package. For day-to-day consumption (import + call), the README at `packages/llm/README.md` is sufficient.
12
+ This reference is about **modifying** the package. For day-to-day consumption (import + call), the README at `packages/llm/core/README.md` is sufficient.
13
13
 
14
14
  ---
15
15
 
@@ -18,7 +18,7 @@ This reference is about **modifying** the package. For day-to-day consumption (i
18
18
  - Adding a new provider adapter (e.g., DeepSeek, Cohere, Mistral)
19
19
  - Changing `maxTokens`, `temperature`, or another bridge default
20
20
  - Extending the streaming chunk type set (currently: `text` / `thinking` / `done` / `error`)
21
- - Adding a new endpoint to `packages/llm/server.js`
21
+ - Adding a new endpoint to `packages/llm/core/server.js`
22
22
  - Modifying the bridge's `complete()` / `stream()` shape
23
23
  - Debugging "why does my LLM call return truncated JSON?"
24
24
 
@@ -112,7 +112,7 @@ If you add a new endpoint, document it in the npm README under "Browser proxy mo
112
112
 
113
113
  ### Step 1: Create the adapter
114
114
 
115
- `packages/llm/adapters/<name>.js` exports a single object matching the shape used by anthropic / openai / gemini:
115
+ `packages/llm/core/adapters/<name>.js` exports a single object matching the shape used by anthropic / openai / gemini:
116
116
 
117
117
  ```js
118
118
  import { readSSE } from './sse.js';
@@ -139,7 +139,7 @@ export const myProvider = {
139
139
 
140
140
  ### Step 2: Register in the facade
141
141
 
142
- Edit `packages/llm/adapters/index.js`:
142
+ Edit `packages/llm/core/adapters/index.js`:
143
143
 
144
144
  ```js
145
145
  import { myProvider } from './myprovider.js';
@@ -154,7 +154,7 @@ function detectProvider(model) {
154
154
 
155
155
  ### Step 3: Subpath export
156
156
 
157
- Edit `packages/llm/package.json`:
157
+ Edit `packages/llm/core/package.json`:
158
158
 
159
159
  ```json
160
160
  "exports": {
@@ -216,19 +216,19 @@ const DEFAULT_MODELS = {
216
216
  Run the smoke tests against the new provider:
217
217
 
218
218
  ```bash
219
- MYPROVIDER_API_KEY=*** node packages/mcp/gen-ui/scripts/smoke-engine-registry.mjs
219
+ MYPROVIDER_API_KEY=*** node packages/gen-ui/mcp/gen-ui/scripts/smoke-engine-registry.mjs
220
220
  ```
221
221
 
222
222
  Then run real-LLM eval at low intent count:
223
223
 
224
224
  ```bash
225
- LLM_PROVIDER=myprovider node packages/mcp/gen-ui/scripts/eval-diff.mjs --semantic
225
+ LLM_PROVIDER=myprovider node packages/gen-ui/mcp/gen-ui/scripts/eval-diff.mjs --semantic
226
226
  ```
227
227
 
228
228
  ## Pitfalls
229
229
 
230
- - **Two proxy shapes — `proxyUrl` is overloaded.** The bridge supports two architecturally different proxies and the dispatcher in `packages/llm/adapters/index.js` chooses between them by URL shape. Get this wrong and you get silent 401s in the browser even though `node packages/llm/server.js` works fine.
231
- - **Smart proxy** (`packages/llm/server.js`, route `POST /api/chat`): accepts a **provider-neutral body** `{ provider, model, messages, ... }`, holds the API key server-side, and dispatches internally to the right adapter. Headers are just `content-type: application/json`.
230
+ - **Two proxy shapes — `proxyUrl` is overloaded.** The bridge supports two architecturally different proxies and the dispatcher in `packages/llm/core/adapters/index.js` chooses between them by URL shape. Get this wrong and you get silent 401s in the browser even though `node packages/llm/core/server.js` works fine.
231
+ - **Smart proxy** (`packages/llm/core/server.js`, route `POST /api/chat`): accepts a **provider-neutral body** `{ provider, model, messages, ... }`, holds the API key server-side, and dispatches internally to the right adapter. Headers are just `content-type: application/json`.
232
232
  - **Passthrough proxy** (Vite dev `/api/llm/<provider>/<rest>` → real upstream URL `https://api.<provider>.com/<rest>`): expects the **real upstream body shape** (Anthropic-shaped for `/anthropic/`, OpenAI-shaped for `/openai/`, etc.) plus the **adapter's own auth headers** (`x-api-key` + `anthropic-version`, or `Authorization: Bearer …`). The proxy is dumb — it just rewrites the URL and forwards bytes.
233
233
  - Dispatcher: `chat()` and `streamChat()` in `adapters/index.js` call `isPassthroughProxy(proxyUrl)` (regex `/\/api\/llm\/[a-z]+(\/|$)/`). If true, they call `passthroughRequest()` which builds the real upstream body via `adapter.buildRequest()` then swaps in `proxyUrl`. If false, they call `proxyRequest()` with the provider-neutral body.
234
234
  - **When adding a new provider**, make sure: (1) `buildRequest()` omits the API key header when `proxyUrl` is the smart proxy (already handled), (2) `buildRequest()` _includes_ the API key header when called from `passthroughRequest()` (also already handled — passthroughRequest forwards adapter headers). The adapter's `buildRequest()` is the single source of truth for upstream-shape body + headers. Don't fork it for proxy mode.
@@ -246,7 +246,7 @@ After any extension:
246
246
  npm run check:lockstep # @adia-ai/llm version must match the others
247
247
  npm run smoke:engines # engines still register; pick still works
248
248
  npm run test:a2ui # 22 pass / 0 fail / 1 skipped
249
- node packages/llm/server.js # boots cleanly; .env auto-loads
249
+ node packages/llm/core/server.js # boots cleanly; .env auto-loads
250
250
  ```
251
251
 
252
252
  If you added a new SSE chunk type to the streaming protocol, verify all 7 consumers (chat-shell, generator.js, synthesis.js, eval-chunk-synthesis, eval-refine-synthesis, playgrounds/chat, apps/genui) still handle it correctly or fall through gracefully.
@@ -256,4 +256,4 @@ If you added a new SSE chunk type to the streaming protocol, verify all 7 consum
256
256
  - [code-style.md](code-style.md) — general AdiaUI conventions (this is a specialty path; start there if you need broader context)
257
257
  - **a2ui-maintenance** (sibling skill) — generator / MCP / zettel synthesis (consumes the bridge)
258
258
  - Spec: `.claude/docs/specs/package-architecture.md` § 11 (Phase 5 — engine registry)
259
- - README: `packages/llm/README.md` — consumer-facing docs
259
+ - README: `packages/llm/core/README.md` — consumer-facing docs
@@ -119,7 +119,7 @@ The "stripped" consumer. Same structural change minus persistence.
119
119
 
120
120
  ### Phase 4 — Migrate the chunk corpus
121
121
 
122
- The harvested training-corpus chunk (`packages/a2ui/corpus/chunks/<name>.json`) embeds the consumer's HTML as an escaped string. Update it so the LLM sees the new authoring shape.
122
+ The harvested training-corpus chunk (`packages/gen-ui/a2ui/corpus/chunks/<name>.json`) embeds the consumer's HTML as an escaped string. Update it so the LLM sees the new authoring shape.
123
123
 
124
124
  1. **Preferred** — re-run the harvester:
125
125
 
@@ -144,7 +144,7 @@ The harvested training-corpus chunk (`packages/a2ui/corpus/chunks/<name>.json`)
144
144
  3. **Defensive grep across all chunks** for the legacy markers:
145
145
 
146
146
  ```bash
147
- grep -l 'id="<old-id>"' packages/a2ui/corpus/chunks/*.json
147
+ grep -l 'id="<old-id>"' packages/gen-ui/a2ui/corpus/chunks/*.json
148
148
  ```
149
149
 
150
150
  4. **Gotcha — hand-edit + later re-harvest** — if you used the fallback (step 2) AND a peer later adds your consumer's directory to harvester SOURCES, the next `harvest:chunks` run will overwrite your hand-edit with the live consumer HTML. Usually that's _good_ (the re-harvest captures the live state, including any post-edit consumer updates), but diff before assuming. Concrete example: theme-panel's `playground-app-shell.json` was hand-edited, then re-harvested after `playgrounds/` was added to SOURCES — the re-harvest captured the `<aside-ui>` → `<admin-sidebar>` bespoke conversion that the hand-edit missed.
@@ -114,13 +114,32 @@ Every file under `packages/web-components/styles/` answers two questions — whi
114
114
  - Spatial / size / radius / motion / elevation primitives → `styles/foundation/` (one file per dimension)
115
115
  - Type scale (L1/L2), roles (L3), native-element defaults → `styles/type/`
116
116
  - Color layers → `styles/colors/` (parameters · primitives-* · surfaces · scrims · semantics/*)
117
- - The global attribute API (`[size]` `[density]` `[gap]` `[padding]` `[margin]` …) → `styles/api/{sizing,text,layout}.css` — the ONLY home for global `[attr]` selectors
117
+ - The global attribute API (`[size]` `[density]` `[gap]` `[padding]` `[margin]` …) → `styles/api/{sizing,text,layout}.css` — the ONLY home for global `[attr]` selectors (governed by the grammar spec below)
118
118
  - `tokens.css`, `typography.css`, `colors/semantics.css`, etc. remain **compat barrels** at their public paths — they only `@import`; never add declarations to them
119
119
 
120
120
  Enforced by `scripts/release/check-foundation-layer-placement.mjs` (wired into `npm run check`): header tags present, primitive files free of attribute selectors, the attribute API confined to `api/`, no orphan files. Import order inside `colors/semantics/` is load-bearing (`aliases.css` last) and asserted in file headers.
121
121
 
122
122
  Source: ADR-0035.
123
123
 
124
+ ## The global attribute grammar — read the spec before naming an attribute
125
+
126
+ The global attribute API is a *designed system* with its own spec:
127
+ `.claude/docs/specs/attribute-api-system.md` (companion audit:
128
+ `.claude/docs/specs/attribute-api-audit.md`). That spec — not this checklist —
129
+ owns the grammar. Two rules an author must know exist (read the spec for the
130
+ mechanics; never work from this summary alone):
131
+
132
+ - **Explicit vs ambient** (spec §5.2): `[gap]`/`[padding]`/`[margin]` are
133
+ *explicit* per-element overrides (non-inheriting), while `[size]`/`[density]`
134
+ are *ambient* context-setters (inheriting by design). Which axis an attribute
135
+ sits on decides its `@property` registration and how components read it.
136
+ - **No shadowing** (`docs/adr/adr-0053-no-shadowing-global-attributes.md`): a
137
+ component-local attribute may not share a name with any global attribute —
138
+ the global name always means the global thing. Before minting any attribute
139
+ in a component yaml, check it against the spec's attribute inventory; the
140
+ only sanctioned collisions live in the spec's §11 Global-Attribute Exemption
141
+ List (first entries: `swatch-ui[color]` / `noodles-ui[color]`, ADR-0054).
142
+
124
143
  ## When to update this reference
125
144
 
126
145
  If you add a new token category (like `--a-chrome-*` was added), update both this file and `.claude/docs/specs/component-token-contract.md`. The spec doc is the live source of truth; this file is the practitioner's checklist.
@@ -88,9 +88,9 @@ delete-adjudication classes.
88
88
  1. Build from clean, fully-merged `main`. In a fresh worktree,
89
89
  `npm run build -w @adia-ai/llm` runs **before** `build:site` — llm compiles
90
90
  at publish time and its outputs are gitignored, so without it
91
- `/packages/llm/index.js` 404s and component registration breaks site-wide
91
+ `/packages/llm/core/index.js` 404s and component registration breaks site-wide
92
92
  (found live 2026-06-09).
93
- 2. Dry-run `rsync -azni --delete --exclude='packages/a2ui/corpus/feedback/'`
93
+ 2. Dry-run `rsync -azni --delete --exclude='packages/gen-ui/a2ui/corpus/feedback/'`
94
94
  and bucket **every** `*deleting` line into a known-safe class; any
95
95
  unexplained served-content delete aborts the deploy. The exclude protects
96
96
  prod-only runtime-written files (feedback logs wiped 2026-06-10).
@@ -129,8 +129,8 @@ itself is unavailable.
129
129
  ```
130
130
  The `llm` package **compiles its JS at publish time** and its outputs are
131
131
  **gitignored** — so a fresh worktree (or any tree that hasn't published llm
132
- locally) has no `packages/llm/index.js`, `build:site` copies nothing, and
133
- **`/packages/llm/index.js` 404s on the deployed site → component registration
132
+ locally) has no `packages/llm/core/index.js`, `build:site` copies nothing, and
133
+ **`/packages/llm/core/index.js` 404s on the deployed site → component registration
134
134
  breaks on every `/site/components/*` page** (the docs components reference it). Found
135
135
  **live 2026-06-09**; the **0-delete dry-run proved it had never been deployed** (the
136
136
  file was absent on the server, so there was nothing to delete — not a regression, a
@@ -145,22 +145,22 @@ itself is unavailable.
145
145
  deploy gaps). After any package add/rename, check `site.mjs` before deploying.
146
146
  2. **Dry-run first, and adjudicate every delete — BEFORE the real rsync, never after:**
147
147
  ```sh
148
- rsync -azni --delete --exclude='packages/a2ui/corpus/feedback/' \
148
+ rsync -azni --delete --exclude='packages/gen-ui/a2ui/corpus/feedback/' \
149
149
  dist/ <host>.exe.xyz:/srv/<app>/dist/ # -n simulates · -i itemizes
150
150
  ```
151
151
  - **Exclude server-side runtime-written paths.** Some files exist ONLY on prod —
152
152
  written by the running service at runtime, never present in a local build — so
153
153
  `--delete` wipes them on every deploy. Known class on ui-kit:
154
- `packages/a2ui/corpus/feedback/*.jsonl` (the gen-UI canvas training-feedback log).
154
+ `packages/gen-ui/a2ui/corpus/feedback/*.jsonl` (the gen-UI canvas training-feedback log).
155
155
  Found **live 2026-06-10** (the deploy deleted the day's feedback log; restored from
156
156
  the pre-deploy snapshot). Carry the same `--exclude` list on BOTH the dry-run and
157
157
  the real rsync; when a new runtime-written path appears, add it here.
158
158
  - `*deleting` lines = files removed from prod. Bucket **every one** into a known-safe
159
159
  class; **abort if any served-content delete is unexplained.** Safe classes seen on
160
160
  ui-kit: gallery review artifacts (`apps/genui/.../review/cycle-*/`), stale
161
- `packages/a2ui/retrieval/` + eval reports, **content-hash-rotated** CodeMirror
161
+ `packages/gen-ui/a2ui/retrieval/` + eval reports, **content-hash-rotated** CodeMirror
162
162
  chunks (`code/{chunk,dist}-<hash>.js` — old hash deleted, new hash sent = rotation,
163
- not loss), restructured `packages/llm/*.js` dist copies, `node_modules/`
163
+ not loss), restructured `packages/llm/core/*.js` dist copies, `node_modules/`
164
164
  symlink-farm dirs, and refactor-orphaned app files. None are served HTML.
165
165
  - **Don't panic at the send count.** A fresh local build never mtime-aligns with the
166
166
  remote, so `rsync -a` flags ~every file as a send (`<f..t` = mtime-only touch). Only
@@ -176,7 +176,7 @@ itself is unavailable.
176
176
  mutating in place. Restore with `rm -rf dist && mv dist.bak-<date> dist`.
177
177
  4. **Deploy** (the dry-run, minus `-n` — SAME excludes):
178
178
  ```sh
179
- rsync -az --delete --exclude='packages/a2ui/corpus/feedback/' \
179
+ rsync -az --delete --exclude='packages/gen-ui/a2ui/corpus/feedback/' \
180
180
  dist/ <host>.exe.xyz:/srv/<app>/dist/
181
181
  ```
182
182
  5. **Verify the FILE, not the route.** A SPA returns `200` + the app shell for *any*
@@ -205,7 +205,7 @@ itself is unavailable.
205
205
  >
206
206
  > **The one exception — `@adia-ai/llm` (2026-06-09):** llm is NOT a source-tree
207
207
  > component; it **builds its JS at publish** (gitignored outputs), so `build:site` copies
208
- > nothing for it in a fresh worktree and `/packages/llm/index.js` 404s → component
208
+ > nothing for it in a fresh worktree and `/packages/llm/core/index.js` 404s → component
209
209
  > registration breaks site-wide. Run `npm run build -w @adia-ai/llm` **before** `build:site`
210
210
  > (step 1 above). This is exactly why step 5 render-checks a **`/site/components/*`** page,
211
211
  > not just a fixture file — the 404 is invisible to file-presence and to the SPA shell.
@@ -49,7 +49,7 @@ before they start typing.
49
49
 
50
50
  ## fences
51
51
  - NOT for `packages/web-components/components/*/*.examples.html` component demo pages (primitive-authoring)
52
- - NOT for `site/pages/examples/**`, `site/pages/gen-ui/**`, `packages/a2ui/corpus/exemplars/**` — pure primitive-composition training-harvest surfaces with their own no-style-block rule (`composition-and-examples.md`, a2ui-maintenance)
52
+ - NOT for `site/pages/examples/**`, `site/pages/gen-ui/**`, `packages/gen-ui/a2ui/corpus/exemplars/**` — pure primitive-composition training-harvest surfaces with their own no-style-block rule (`composition-and-examples.md`, a2ui-maintenance)
53
53
  - NOT for the A2UI generation pipeline itself, chunk corpus, or MCP tools (a2ui-maintenance)
54
54
 
55
55
  ## assertions