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

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 (40) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/package.json +1 -1
  4. package/references/contracts/a2ui-mcp-surface.md +2 -2
  5. package/skills/a2ui-maintenance/SKILL.md +5 -5
  6. package/skills/a2ui-maintenance/references/anti-patterns.md +2 -2
  7. package/skills/a2ui-maintenance/references/chunk-authoring.md +2 -2
  8. package/skills/a2ui-maintenance/references/eval-diagnostics.md +3 -3
  9. package/skills/a2ui-maintenance/references/format-extension-decisions.md +1 -1
  10. package/skills/a2ui-maintenance/references/mcp-pipeline-ops.md +5 -5
  11. package/skills/a2ui-maintenance/references/mcp-tool-reference.md +3 -3
  12. package/skills/a2ui-maintenance/references/pipeline-overview.md +18 -18
  13. package/skills/a2ui-maintenance/references/semantic-fail-lifting.md +2 -2
  14. package/skills/a2ui-maintenance/references/strategy-engines.md +2 -2
  15. package/skills/a2ui-maintenance/references/zettel-calibration.md +2 -2
  16. package/skills/gen-ui-review/references/corpus-html-patterns.md +1 -1
  17. package/skills/gen-ui-review/references/loop-protocol.md +1 -1
  18. package/skills/llm-client-maintenance/SKILL.md +4 -4
  19. package/skills/llm-client-maintenance/references/adapter-contract.md +3 -3
  20. package/skills/llm-client-maintenance/references/add-a-provider.md +5 -5
  21. package/skills/llm-client-maintenance/references/bridge-facade.md +2 -2
  22. package/skills/llm-client-maintenance/references/browser-proxy-boundary.md +2 -2
  23. package/skills/llm-client-maintenance/references/model-registry.md +2 -2
  24. package/skills/llm-client-maintenance/references/streaming-sse.md +2 -2
  25. package/skills/package-release/SKILL.md +6 -6
  26. package/skills/package-release/references/cut-procedure.md +5 -90
  27. package/skills/package-release/references/gates-catalog.md +4 -4
  28. package/skills/package-release/references/independent-package-release.md +2 -2
  29. package/skills/package-release/references/recovery-paths.md +2 -2
  30. package/skills/package-release/scripts/bump.mjs +31 -28
  31. package/skills/package-release/scripts/insert-stub.mjs +1 -1
  32. package/skills/package-release/scripts/package-paths.mjs +34 -50
  33. package/skills/package-release/scripts/release-pack.mjs +12 -5
  34. package/skills/package-release/scripts/tag-lockstep.mjs +1 -1
  35. package/skills/primitive-authoring/references/common-gotchas.md +1 -1
  36. package/skills/primitive-authoring/references/llm-bridge.md +12 -12
  37. package/skills/primitive-authoring/references/module-promotion.md +2 -2
  38. package/skills/site-deployment/SKILL.md +2 -2
  39. package/skills/site-deployment/references/deploy-playbooks.md +8 -8
  40. package/skills/site-docs-authoring/intent.md +1 -1
@@ -223,7 +223,7 @@ allowlist (never `git add -A`; if peers may have pre-staged files,
223
223
  git -C "$REPO" checkout -b "release/vX.Y.Z"
224
224
  git -C "$REPO" reset HEAD >/dev/null 2>&1
225
225
  git -C "$REPO" add package-lock.json packages/*/package.json packages/*/CHANGELOG.md \
226
- packages/a2ui/*/package.json packages/a2ui/*/CHANGELOG.md \
226
+ packages/gen-ui/a2ui/*/package.json packages/gen-ui/a2ui/*/CHANGELOG.md \
227
227
  packages/genui/adia-catalog/base.json packages/genui/adia-catalog/adia-pack.json \
228
228
  docs/releases/vX.Y.Z.md # + in-scope source + Step-3.0 regen outputs
229
229
  git -C "$REPO" diff --cached --stat | tail -3 # count must match the allowlist
@@ -276,7 +276,7 @@ The operator's initiating instruction covers the whole cycle — no per-step re-
276
276
 
277
277
  | Evidence logged before | Content | Why it's still printed |
278
278
  | --- | --- | --- |
279
- | Tagging (Step 6) | The planned tag list (umbrella + 16 per-package) | The log line a recovery diagnoses from |
279
+ | Tagging (Step 6) | The planned tag list (umbrella + 10 per-package) | The log line a recovery diagnoses from |
280
280
  | Pushing (Step 8) | The Step 7 F-N1 output + tag list + `origin/main..HEAD` count | F-N1's first real evidence — and its ERROR path still hard-stops unconditionally |
281
281
  | Publishing (Step 9) | The current registry snapshot (versions + `dist-tags.latest`) | Ordering is verified against what's LIVE, mechanically |
282
282
  | Deploying (Step 10) | The `deploy-site.yml` dispatch (never a raw rsync) | The workflow carries its own GitHub-environment human gate |
@@ -299,7 +299,7 @@ Log the planned tag list (evidence table above), then tag **at `main`'s post-mer
299
299
 
300
300
  ```bash
301
301
  node "${CLAUDE_PLUGIN_ROOT}/skills/package-release/scripts/tag-lockstep.mjs" \
302
- --version X.Y.Z # umbrella vX.Y.Z + 16 <pkg>-vX.Y.Z (14 npm + 2 plugins)
302
+ --version X.Y.Z # umbrella vX.Y.Z + 10 <pkg>-vX.Y.Z (8 npm + 2 plugins)
303
303
  ```
304
304
 
305
305
  ## §Step 7 — F-N1 release trip-wire
@@ -395,98 +395,13 @@ Any Step-2 stashes: `git stash pop`; flag conflicts to the operator.
395
395
 
396
396
  Always author notes at end-of-cycle without being asked — context is freshest now. Single version → [`notes-authoring.md`](notes-authoring.md); ≥2 versions since the last broadcast → its §Rollup section. Surface the draft inline for copy-paste; the operator owns posting. Skip only on an explicit "no notes".
397
397
 
398
- ## §The 0.8.37 estate-split cut (ADR-0048 P7 — ONE cut, then delete this section)
399
-
400
- Extra steps for the single cut that ships the a2ui/gen-ui split. They ride the
401
- normal procedure above; nothing here replaces a step.
402
-
403
- **It is a PATCH cut, by operator ruling (2026-08-13, gh#1192)** — `0.8.37`, not
404
- the `0.9.0` MINOR the ADR originally proposed. Two consequences to hold in mind
405
- before Step 4:
406
-
407
- - **Invariant 2 (PATCH asymmetry) applies UNCHANGED — no exception.** Internal
408
- `@adia-ai/*` dep ranges stay `^0.8.0` on a patch cut, and that is exactly
409
- right here: `targetCaret("0.8.37")` is `^0.8.0`, the three new names
410
- (`a2ui`, `gen-ui`, `mcp` — retargeted gh#1240, 2026-08-14: this row named
411
- `gen-ui-mcp` before the MCP-distribution fold; that name never published,
412
- `mcp` is its real successor) publish `0.8.37` as their FIRST version, and
413
- `^0.8.0` resolves to it. The shims' deps on their successors are already
414
- `^0.8.0` and need no edit; web-modules' EXACT peer pin moves to `0.8.37` by
415
- the normal peer rule (gh#489), which `bump.mjs` does. So nothing in the split
416
- needs a hand-written range — if `check:lockstep` asks for one, something else
417
- is wrong; do not hand-pin.
418
- - **A patch-shaped number does NOT relax the MIGRATION GUIDE obligation**
419
- (invariant 7). Six published names are being replaced; the guide is owed
420
- regardless of what the version looks like. This is the one failure mode the
421
- small number invites.
422
-
423
- **In the release commit (Step 4/5), not before and not after:**
424
-
425
- 1. **Flip the factory `.mcp.json` pin NAME** — `@adia-ai/a2ui-mcp` →
426
- `@adia-ai/mcp` (retargeted gh#1240, 2026-08-14: this step named
427
- `@adia-ai/gen-ui-mcp` before the MCP-distribution fold folded that planned
428
- name — and `@adia-ai/a2ui-protocol-mcp` — into `@adia-ai/mcp` before either
429
- ever published; the flip target moved, the flip's own timing did not), in
430
- `packages/plugins/adia-ui-factory/.mcp.json` AND its README prose echo
431
- (invariant 8 covers both files; `bump.mjs` moves the VERSION on either
432
- name, so only the name is hand-edited). The pinned args also grow a
433
- subcommand: `["-y", "@adia-ai/mcp@0.8.37", "gen-ui"]` — the fold's bin
434
- dispatches `adia-mcp gen-ui`/`adia-mcp protocol`, not a bare invocation.
435
- **Why here and not at P5:** invariant 8 forces the pin to the CURRENT
436
- lockstep version. Before this cut that is `0.8.36`, and `@adia-ai/mcp`
437
- has no `0.8.36` on npm — flipping early pins every local-marketplace install
438
- to `npx -y @adia-ai/mcp@0.8.36`, i.e. a registry 404. In this commit
439
- the version becomes `0.8.37`, which this cut publishes. (The reasoning is
440
- unchanged by the 0.9.0 → 0.8.37 ruling, and unchanged again by the
441
- gen-ui-mcp → mcp retarget; only the numbers and the name moved.)
442
- `check:lockstep` accepts any name this pin has carried (`MCP_PIN_RE`), so
443
- the flip is gate-clean regardless of which name is live when this runs.
444
-
445
- **Config-KEY standardization rides the same flip (operator ruling
446
- 2026-08-14):** the `mcpServers` key names move with the pin so a tool
447
- call's prefix always names which server answered. Factory `.mcp.json`:
448
- key `a2ui` → `adia-gen-ui`. Repo root `.mcp.json`: key `adia-ui` →
449
- `adia-gen-ui`. New entries added later use `adia-protocol` /
450
- `adia-factory` for the other subcommands. Key renames change consumers'
451
- tool prefixes (`mcp__a2ui__*` → `mcp__adia-gen-ui__*`) — that is the
452
- point, and it ships in the same breaking-named cut as everything else.
453
-
454
- **After Step 9 (publish), once `npm view <old-name> version` shows the shim on
455
- the registry — never before:**
456
-
457
- 2. **Deprecate the six retired names.** The list is projected from the shim
458
- generator's SPEC, so it cannot drift from what actually shipped:
459
-
460
- ```bash
461
- node scripts/build/generate-shims.mjs --deprecate-commands
462
- ```
463
-
464
- It prints the `npm deprecate` lines plus an `npm info <pkg> deprecated`
465
- verify line per package. Print-only by design — read them, then run them.
466
- **Verify by reading the field back**; a zero exit code is not proof.
467
- Deprecating before the shim publishes leaves consumers at a dead end (the
468
- pointer text names a package version that isn't there yet).
469
-
470
- 3. **Verify a shim tarball's CONTENTS, not just the publish exit code** — e.g.
471
- `npm pack @adia-ai/a2ui-runtime@0.8.37 && tar -tzf *.tgz`, then import one
472
- subpath from a scratch dir and confirm it resolves through to
473
- `@adia-ai/a2ui`.
474
-
475
- **Follow-up PR (not this cut):** delete `packages/shims/`, the roster rows
476
- marked `shim: true`, their six `publish-a2ui-*.yml` workflows,
477
- `resolvePackageDir`'s `shims/` fallback, `scripts/build/generate-shims.mjs`,
478
- `scripts/verify/check-shim-bridges.mjs`, the `check:shims` gate, and this
479
- section. Then narrow `MCP_PIN_RE` + `bump.mjs`'s pin pattern back to the single
480
- new name.
481
-
482
- ---
483
-
484
398
  ## §The 0.8.38 cut (the clean-world cut — delete this section after it lands)
485
399
 
486
400
  The first cut AFTER the estate-split bridge. Pre-conditions, all verifiable
487
401
  before Step 1:
488
402
 
489
- 1. **The shim-deletion follow-up PR has MERGED** (the §0.8.37 follow-up above:
403
+ 1. **The shim-deletion follow-up PR has MERGED** (the §0.8.37 follow-up — that
404
+ section deleted itself with the same PR, as designed:
490
405
  `packages/shims/` gone, roster shim rows gone, the six `publish-a2ui-*.yml`
491
406
  workflows gone, `check:shims`/`check-shim-bridges` retired). The roster file
492
407
  is the member count — never transcribe it.
@@ -14,7 +14,7 @@ Row layout per gate: **What** · **Typical failure** · **Recovery**.
14
14
 
15
15
  ### `npm run check:lockstep`
16
16
 
17
- - **What:** all 16 lockstep `@adia-ai/*` packages declare the same `version` (the class-B `adia-plugins` package is excluded — `lockstep: false`, `scripts/package-paths.mjs`); internal `@adia-ai/*` dep ranges match policy (`^X.Y.0` during PATCH cycles, bumped at MINOR).
17
+ - **What:** all 10 lockstep `@adia-ai/*` packages declare the same `version` (the class-B `adia-plugins` package is excluded — `lockstep: false`, `scripts/package-paths.mjs`); internal `@adia-ai/*` dep ranges match policy (`^X.Y.0` during PATCH cycles, bumped at MINOR).
18
18
  - **Typical failure:** one package forgot to bump; a peer edited an internal range mid-PATCH; a `^0.0.x` range slipped in.
19
19
  - **Recovery:** version drift → `` `${CLAUDE_PLUGIN_ROOT}/skills/package-release/scripts/bump.mjs` ``; range drift → `npm run check:lockstep:fix` auto-aligns, then re-run.
20
20
  - **Why `^0.0.x` is forbidden:** npm pre-1.0 semver only widens the caret when major+minor aren't both zero — `^0.0.6` resolves to `>=0.0.6 <0.0.7`, locked to exactly 0.0.6. An internal dep pinned that way silently installs a *stale* sibling on every fresh `npm i` (this shipped a real ~4-day-latent bug before the lockstep policy). The `^X.Y.0` floor (Y≥1) widens correctly across patches; trust the gate, don't reason about caret semantics by hand. Moot at 1.0.0.
@@ -96,8 +96,8 @@ Also: `check:no-self-import-css` (no transitively self-importing barrel) · `che
96
96
 
97
97
  ### `npm run check:browser-safe`
98
98
 
99
- - **What:** no top-level `import 'node:*'` in browser-reachable modules (`packages/a2ui/{compose,retrieval,validator}`, `web-components/`, `web-modules/`, `apps/*/app/*.contents.js`).
100
- - **Recovery:** dual-mode pattern (`IS_NODE` + dynamic `await import(/* @vite-ignore */ 'node:*')` + `import.meta.glob` browser branch); canonical impl: `packages/a2ui/retrieval/component-catalog.js`.
99
+ - **What:** no top-level `import 'node:*'` in browser-reachable modules (`packages/gen-ui/a2ui/{compose,retrieval,validator}`, `web-components/`, `web-modules/`, `apps/*/app/*.contents.js`).
100
+ - **Recovery:** dual-mode pattern (`IS_NODE` + dynamic `await import(/* @vite-ignore */ 'node:*')` + `import.meta.glob` browser branch); canonical impl: `packages/gen-ui/a2ui/retrieval/component-catalog.js`.
101
101
 
102
102
  Siblings: `check:absolute-imports` (no leading-`/` imports — rewrite relative) · `check:template-interp` (template `${expr}` contract) · `check:yaml-events` (yaml `events:` match runtime `dispatchEvent` calls) · `check:yaml-impl-coverage` (every yaml prop implemented in the JS class).
103
103
 
@@ -121,7 +121,7 @@ The two `audit:*` probes are soft-fail pre-cut (exit-1 only on criticals); findi
121
121
 
122
122
  ### `npm run verify:corpus`
123
123
 
124
- - **What:** every chunk in `packages/a2ui/corpus/chunks/*.json` is reachable and well-formed.
124
+ - **What:** every chunk in `packages/gen-ui/a2ui/corpus/chunks/*.json` is reachable and well-formed.
125
125
  - **Recovery:** corpus remediation routes to the A2UI-pipeline skill, not this one.
126
126
 
127
127
  ### `npm run check:chunks-fresh`
@@ -19,7 +19,7 @@
19
19
  | | Class A — lockstep cut | Class B — independent package |
20
20
  | --- | --- | --- |
21
21
  | Versioning | whole set bumps together | own version line per package |
22
- | Lockstep gate | enforced (`check:lockstep` 16/16) | **excluded** — a class-B package is not listed in `check-lockstep.mjs`'s `PACKAGES` (`adia-plugins`, the current member, is filtered out via `lockstep: false` — `scripts/package-paths.mjs`) |
22
+ | Lockstep gate | enforced (`check:lockstep` 10/10) | **excluded** — a class-B package is not listed in `check-lockstep.mjs`'s `PACKAGES` (`adia-plugins`, the current member, is filtered out via `lockstep: false` — `scripts/package-paths.mjs`) |
23
23
  | Umbrella tag | `vX.Y.Z` + per-package | **none** — per-package tags only (versions differ) |
24
24
  | `dist-tag` ordering | cross-package publish order matters | N/A |
25
25
  | Demo-site deploy | yes | **none** |
@@ -47,7 +47,7 @@ Substrate fixes (release scripts / lockfile / CHANGELOG), landed once as a norma
47
47
 
48
48
  ## §Verify target
49
49
 
50
- The published package(s) on the **npm registry** at their independent versions, plus the npm-published `@adia-ai/adia-plugins` manifest referencing the new versions (ADR-0045). NOT "all 16 lockstep packages + the demo site" — that's class A.
50
+ The published package(s) on the **npm registry** at their independent versions, plus the npm-published `@adia-ai/adia-plugins` manifest referencing the new versions (ADR-0045). NOT "all 10 lockstep packages + the demo site" — that's class A.
51
51
 
52
52
  ## §Gotchas (from the first independent cut, factory v0.2.2 + forge v0.1.2)
53
53
 
@@ -17,7 +17,7 @@
17
17
 
18
18
  1. Verify the skip: `npm view <pkg> versions --json | tail` (no X+1 on npm) + `git tag --list 'vX.Y.*'` (no tags). Run pre-flight at the peer's commit to confirm it's shippable.
19
19
  2. Correct the version via a **new commit on top** (not amend — the peer's commit stays for history).
20
- 3. Sweep every occurrence of the wrong version: 16 × package.json, 16 × CHANGELOG (headers + body refs — the lockstep roster, `scripts/package-paths.mjs`), any doc/CSS-comment refs, then regenerate the lockfile. Preserve filename references that intentionally encode the original label.
20
+ 3. Sweep every occurrence of the wrong version: 10 × package.json, 10 × CHANGELOG (headers + body refs — the lockstep roster, `scripts/package-paths.mjs`), any doc/CSS-comment refs, then regenerate the lockfile. Preserve filename references that intentionally encode the original label.
21
21
  4. Commit as `fix(release): correct vX.Y.Z+1 version-skip → vX.Y.Z` documenting the discovery, then resume [`cut-procedure.md`](cut-procedure.md) at Step 5.
22
22
 
23
23
  ## §Scenario 2 — Batch push
@@ -63,7 +63,7 @@
63
63
 
64
64
  ## §Scenario 7 — Tags pushed, ZERO publish workflows fired
65
65
 
66
- **Shape:** all 17 tags exist on origin (umbrella + one per lockstep-roster package — `scripts/package-paths.mjs`), but no `publish-<pkg>.yml` run exists for them; npm latest unchanged; nothing errored.
66
+ **Shape:** all 11 tags exist on origin (umbrella + one per lockstep-roster package — `scripts/package-paths.mjs`), but no `publish-<pkg>.yml` run exists for them; npm latest unchanged; nothing errored.
67
67
 
68
68
  **Root cause:** pushing many tags in **one** `git push` fires a single batched create event that GitHub Actions routinely drops. Re-pushing is a no-op (the tags already exist remotely).
69
69
 
@@ -41,36 +41,34 @@ const SIBLING_MANIFESTS = Object.fromEntries(
41
41
 
42
42
  // Cross-package EXACT pins bumped in the same pass — a different shape from a
43
43
  // package.json "version" field, so SIBLING_MANIFESTS' regex can't reach them.
44
- // The factory plugin's .mcp.json pins @adia-ai/a2ui-mcp to the exact lockstep
44
+ // The factory plugin's .mcp.json pins @adia-ai/mcp to the exact lockstep
45
45
  // version (SKILL invariant 8 — the consumer never floats). bump.mjs left this
46
46
  // untouched through the 0.8.6 + 0.8.7 cuts, forcing a manual edit + amend each
47
47
  // time; check-lockstep's mcp-pin guard now catches a drift, this moves it.
48
48
  //
49
49
  // Each package maps to an ARRAY of pin configs — the factory README's own
50
- // prose echo of the same pin (`server pinned: @adia-ai/a2ui-mcp@X`) is a
50
+ // prose echo of the same pin (`server pinned: @adia-ai/mcp@X`) is a
51
51
  // SECOND file invariant 8 covers, and check:plugin-count-claims' "factory
52
- // README a2ui-mcp pin" EQUALITY check enforces the two agree. Left out of
53
- // this array through the 0.8.35 + 0.8.36 cuts, it needed the same manual
52
+ // README generation-MCP pin" EQUALITY check enforces the two agree. Left out
53
+ // of this array through the 0.8.35 + 0.8.36 cuts, it needed the same manual
54
54
  // hand-commit .mcp.json used to (gh#1198) — same file set, invariant 8's own
55
55
  // scope, so it moves alongside .mcp.json rather than getting its own key.
56
56
  //
57
- // ANY NAME THIS PIN HAS CARRIED (ADR-0048 P5, retargeted gh#1240 2026-08-14):
58
- // the generation MCP is `@adia-ai/mcp` (its `gen-ui` surface) since the
59
- // MCP-distribution fold P2 had planned `@adia-ai/gen-ui-mcp`, which never
60
- // published but the pin keeps naming the old package until the P7 cut
61
- // flips it in the release commit itself (see lockstep-checks.mjs MCP_PIN_RE
62
- // for why an early flip pins consumers to a 404). Matching every name means
63
- // the version bump works before AND after either flip, with no cut-day edit
64
- // to this file.
57
+ // `@adia-ai/mcp` is the single live name since the 0.8.37 estate-split cut
58
+ // flipped the pin in its release commit (ADR-0048 P7). The bridge-window
59
+ // alternation over the retired names (`a2ui-mcp`, the never-published
60
+ // `gen-ui-mcp`) was narrowed back by the shim-deletion follow-up PR, matching
61
+ // lockstep-checks.mjs's MCP_PIN_RE — a retired name in the pin must now fail
62
+ // loudly instead of being bumped along.
65
63
  const A2UI_MCP_PIN = {
66
- label: 'generation-MCP pin (@adia-ai/{a2ui-mcp,gen-ui-mcp,mcp})',
67
- // `@adia-ai/{a2ui-mcp,gen-ui-mcp,mcp}@<from>` (not a prefix of a longer version) → `@<to>`
64
+ label: 'generation-MCP pin (@adia-ai/mcp)',
65
+ // `@adia-ai/mcp@<from>` (not a prefix of a longer version) → `@<to>`
68
66
  // Escape EVERY regex metacharacter in `from`, not just dots: a prerelease or
69
67
  // build-metadata version (`0.9.0-rc+1`) would otherwise inject `+` as a
70
68
  // quantifier and silently match the wrong text. Same class as the truncating
71
69
  // MCP_PIN_RE suffix CodeRabbit found on PR #1229.
72
70
  pattern: (from) =>
73
- new RegExp(`(@adia-ai/(?:(?:a2ui|gen-ui)-mcp|mcp)@)${from.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![\\d.])`),
71
+ new RegExp(`(@adia-ai/mcp@)${from.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![\\d.])`),
74
72
  replace: (to) => `$1${to}`,
75
73
  };
76
74
  const PINNED_REFS = {
@@ -298,38 +296,43 @@ function selftest() {
298
296
  console.error('selftest FAIL: corrupted package.json (no version match) not caught'); process.exit(1);
299
297
  }
300
298
 
301
- // PINNED_REFS — the .mcp.json a2ui-mcp pin (invariant 8) moves in lockstep.
299
+ // PINNED_REFS — the .mcp.json @adia-ai/mcp pin (invariant 8) moves in lockstep.
302
300
  const [pinCfg, readmePinCfg] = PINNED_REFS['packages/plugins/adia-ui-factory'];
303
- const pinBefore = '{"args":["-y","@adia-ai/a2ui-mcp@0.8.6"]}';
301
+ const pinBefore = '{"args":["-y","@adia-ai/mcp@0.8.6","gen-ui"]}';
304
302
  const pinAfter = bumpPinnedRef(pinBefore, pinCfg, '0.8.6', '0.8.7');
305
- if (!pinAfter.includes('@adia-ai/a2ui-mcp@0.8.7') || pinAfter.includes('@0.8.6')) {
306
- console.error('selftest FAIL: PINNED_REFS did not bump the .mcp.json a2ui-mcp pin'); process.exit(1);
303
+ if (!pinAfter.includes('@adia-ai/mcp@0.8.7') || pinAfter.includes('@0.8.6')) {
304
+ console.error('selftest FAIL: PINNED_REFS did not bump the .mcp.json generation-MCP pin'); process.exit(1);
307
305
  }
308
306
  // Must NOT prefix-match a longer version (0.8.6 inside 0.8.60).
309
- if (bumpPinnedRef('@adia-ai/a2ui-mcp@0.8.60', pinCfg, '0.8.6', '0.8.7') !== '@adia-ai/a2ui-mcp@0.8.60') {
307
+ if (bumpPinnedRef('@adia-ai/mcp@0.8.60', pinCfg, '0.8.6', '0.8.7') !== '@adia-ai/mcp@0.8.60') {
310
308
  console.error('selftest FAIL: PINNED_REFS pattern prefix-matched a longer version'); process.exit(1);
311
309
  }
310
+ // A retired bridge name must NOT be bumped along — it should fail the later
311
+ // check-lockstep gate loudly, not be silently kept alive by this pass.
312
+ if (bumpPinnedRef('@adia-ai/a2ui-mcp@0.8.6', pinCfg, '0.8.6', '0.8.7') !== '@adia-ai/a2ui-mcp@0.8.6') {
313
+ console.error('selftest FAIL: pin pattern matched a retired generation-MCP name'); process.exit(1);
314
+ }
312
315
 
313
316
  // gh#1198 — the factory README's OWN prose echo of the same pin
314
- // (`server pinned: @adia-ai/a2ui-mcp@X`) must move alongside .mcp.json;
315
- // check:plugin-count-claims' "factory README a2ui-mcp pin" EQUALITY check
316
- // is what caught this drift on the v0.8.35 + v0.8.36 cuts.
317
+ // (`server pinned: @adia-ai/mcp@X`) must move alongside .mcp.json;
318
+ // check:plugin-count-claims' "factory README generation-MCP pin" EQUALITY
319
+ // check is what caught this drift on the v0.8.35 + v0.8.36 cuts.
317
320
  if (readmePinCfg.file !== 'README.md') {
318
321
  console.error('selftest FAIL: expected the second factory pin config to target README.md'); process.exit(1);
319
322
  }
320
- const readmeBefore = '`a2ui` server pinned: `@adia-ai/a2ui-mcp@0.8.6` (tool SoT: `packages/mcp/TOOLS.md`)';
323
+ const readmeBefore = '`adia-gen-ui` server pinned: `@adia-ai/mcp@0.8.6` (tool SoT: `packages/gen-ui/mcp/TOOLS.md`)';
321
324
  const readmeAfter = bumpPinnedRef(readmeBefore, readmePinCfg, '0.8.6', '0.8.7');
322
- if (!readmeAfter.includes('@adia-ai/a2ui-mcp@0.8.7') || readmeAfter.includes('@0.8.6')) {
323
- console.error('selftest FAIL: PINNED_REFS did not bump the README a2ui-mcp prose pin'); process.exit(1);
325
+ if (!readmeAfter.includes('@adia-ai/mcp@0.8.7') || readmeAfter.includes('@0.8.6')) {
326
+ console.error('selftest FAIL: PINNED_REFS did not bump the README generation-MCP prose pin'); process.exit(1);
324
327
  }
325
- if (bumpPinnedRef('@adia-ai/a2ui-mcp@0.8.60', readmePinCfg, '0.8.6', '0.8.7') !== '@adia-ai/a2ui-mcp@0.8.60') {
328
+ if (bumpPinnedRef('@adia-ai/mcp@0.8.60', readmePinCfg, '0.8.6', '0.8.7') !== '@adia-ai/mcp@0.8.60') {
326
329
  console.error('selftest FAIL: README pin pattern prefix-matched a longer version'); process.exit(1);
327
330
  }
328
331
 
329
332
  // web-modules exact peer pins (gh#489) — all three move in one pass; a
330
333
  // longer version must not prefix-match.
331
334
  const [peerCfg] = PINNED_REFS['packages/web-modules'];
332
- const peersBefore = '{"peerDependencies":{"@adia-ai/web-components": "0.8.22","@adia-ai/a2ui-runtime": "0.8.22","@adia-ai/llm": "0.8.22"}}';
335
+ const peersBefore = '{"peerDependencies":{"@adia-ai/web-components": "0.8.22","@adia-ai/a2ui": "0.8.22","@adia-ai/llm": "0.8.22"}}';
333
336
  const peersAfter = bumpPinnedRef(peersBefore, peerCfg, '0.8.22', '0.8.23');
334
337
  if ((peersAfter.match(/0\.8\.23/g) || []).length !== 3 || peersAfter.includes('0.8.22')) {
335
338
  console.error('selftest FAIL: web-modules peer pins did not all move'); process.exit(1);
@@ -144,7 +144,7 @@ function main() {
144
144
  function selftest() {
145
145
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'insert-stub-selftest-'));
146
146
  try {
147
- const dir = path.join(tmp, 'packages', 'llm');
147
+ const dir = path.join(tmp, 'packages', 'llm', 'core');
148
148
  fs.mkdirSync(dir, { recursive: true });
149
149
  const clPath = path.join(dir, 'CHANGELOG.md');
150
150
  const anchor = '## [0.1.0] — 2026-01-01';
@@ -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
 
@@ -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