@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.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +13 -0
- package/package.json +1 -1
- package/references/contracts/a2ui-mcp-surface.md +2 -2
- package/skills/a2ui-maintenance/SKILL.md +5 -5
- package/skills/a2ui-maintenance/references/anti-patterns.md +2 -2
- package/skills/a2ui-maintenance/references/chunk-authoring.md +2 -2
- package/skills/a2ui-maintenance/references/eval-diagnostics.md +3 -3
- package/skills/a2ui-maintenance/references/format-extension-decisions.md +1 -1
- package/skills/a2ui-maintenance/references/mcp-pipeline-ops.md +5 -5
- package/skills/a2ui-maintenance/references/mcp-tool-reference.md +3 -3
- package/skills/a2ui-maintenance/references/pipeline-overview.md +18 -18
- package/skills/a2ui-maintenance/references/semantic-fail-lifting.md +2 -2
- package/skills/a2ui-maintenance/references/strategy-engines.md +2 -2
- package/skills/a2ui-maintenance/references/zettel-calibration.md +2 -2
- package/skills/gen-ui-review/references/corpus-html-patterns.md +1 -1
- package/skills/gen-ui-review/references/loop-protocol.md +1 -1
- package/skills/llm-client-maintenance/SKILL.md +4 -4
- package/skills/llm-client-maintenance/references/adapter-contract.md +3 -3
- package/skills/llm-client-maintenance/references/add-a-provider.md +5 -5
- package/skills/llm-client-maintenance/references/bridge-facade.md +2 -2
- package/skills/llm-client-maintenance/references/browser-proxy-boundary.md +2 -2
- package/skills/llm-client-maintenance/references/model-registry.md +2 -2
- package/skills/llm-client-maintenance/references/streaming-sse.md +2 -2
- package/skills/package-release/SKILL.md +6 -6
- package/skills/package-release/references/cut-procedure.md +5 -90
- package/skills/package-release/references/gates-catalog.md +4 -4
- package/skills/package-release/references/independent-package-release.md +2 -2
- package/skills/package-release/references/recovery-paths.md +2 -2
- package/skills/package-release/scripts/bump.mjs +31 -28
- package/skills/package-release/scripts/insert-stub.mjs +1 -1
- package/skills/package-release/scripts/package-paths.mjs +34 -50
- package/skills/package-release/scripts/release-pack.mjs +12 -5
- package/skills/package-release/scripts/tag-lockstep.mjs +1 -1
- package/skills/primitive-authoring/references/common-gotchas.md +1 -1
- package/skills/primitive-authoring/references/llm-bridge.md +12 -12
- package/skills/primitive-authoring/references/module-promotion.md +2 -2
- package/skills/site-deployment/SKILL.md +2 -2
- package/skills/site-deployment/references/deploy-playbooks.md +8 -8
- 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 +
|
|
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 +
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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:
|
|
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
|
|
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/
|
|
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/
|
|
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
|
|
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
|
-
//
|
|
58
|
-
// the
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
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/
|
|
67
|
-
// `@adia-ai/
|
|
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/
|
|
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
|
|
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/
|
|
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/
|
|
306
|
-
console.error('selftest FAIL: PINNED_REFS did not bump the .mcp.json
|
|
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/
|
|
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/
|
|
315
|
-
// check:plugin-count-claims' "factory README
|
|
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 = '`
|
|
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/
|
|
323
|
-
console.error('selftest FAIL: PINNED_REFS did not bump the README
|
|
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/
|
|
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
|
|
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
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
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-*
|
|
89
|
-
//
|
|
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
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
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
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
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,
|
|
226
|
-
--stub-packages llm,a2ui
|
|
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
|
|
708
|
-
// .mcp.json (SKILL invariant 8 —
|
|
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
|
-
|
|
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;
|
|
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
|