@adia-ai/adia-ui-forge 0.8.4 → 0.8.6

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 (72) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +27 -2
  3. package/README.md +13 -4
  4. package/agents/a2ui-builder.corpus.json +35 -0
  5. package/agents/a2ui-builder.md +37 -0
  6. package/agents/component-author.corpus.json +40 -0
  7. package/agents/component-author.md +10 -1
  8. package/agents/framework-reviewer.corpus.json +35 -0
  9. package/agents/framework-reviewer.md +42 -0
  10. package/agents/release-builder.corpus.json +39 -0
  11. package/agents/release-builder.md +39 -0
  12. package/agents/routing-corpus.json +38 -38
  13. package/bin/release-pretag-docs-gate +226 -0
  14. package/commands/release.md +5 -3
  15. package/hooks/hooks.json +10 -0
  16. package/package.json +1 -1
  17. package/skills/adia-a2ui/SKILL.md +37 -8
  18. package/skills/adia-a2ui/evals/routing-corpus.json +36 -29
  19. package/skills/adia-a2ui/references/pipeline-overview.md +1 -1
  20. package/skills/adia-a2ui/references/zettel-calibration.md +5 -5
  21. package/skills/adia-author/SKILL.md +16 -8
  22. package/skills/adia-author/evals/routing-corpus.json +39 -218
  23. package/skills/adia-author/references/anti-patterns.md +7 -7
  24. package/skills/adia-author/references/api-contract.md +9 -9
  25. package/skills/adia-author/references/authoring-cycle.md +25 -6
  26. package/skills/adia-author/references/code-style.md +4 -5
  27. package/skills/adia-author/references/lifecycle-patterns.md +8 -8
  28. package/skills/adia-author/references/llm-bridge.md +7 -0
  29. package/skills/adia-author/references/token-contract.md +5 -5
  30. package/skills/adia-author/references/worked-example.md +34 -18
  31. package/skills/adia-author/references/yaml-contract.md +1 -1
  32. package/skills/adia-deploy/SKILL.md +71 -12
  33. package/skills/adia-deploy/evals/routing-corpus.json +37 -0
  34. package/skills/adia-deploy/references/deploy-playbooks.md +1 -1
  35. package/skills/adia-dogfood/SKILL.md +71 -11
  36. package/skills/adia-dogfood/evals/routing-corpus.json +34 -0
  37. package/skills/adia-dogfood/references/admin-shell-anatomy.md +1 -1
  38. package/skills/adia-gen-review/SKILL.md +73 -7
  39. package/skills/adia-gen-review/evals/routing-corpus.json +34 -0
  40. package/skills/adia-gen-review/scripts/gen-review-decompose.mjs +5 -0
  41. package/skills/adia-llm-internals/SKILL.md +10 -6
  42. package/skills/adia-llm-internals/evals/routing-corpus.json +37 -0
  43. package/skills/adia-llm-internals/references/bridge-facade.md +2 -2
  44. package/skills/adia-llm-internals/references/streaming-sse.md +1 -1
  45. package/skills/adia-release/SKILL.md +43 -13
  46. package/skills/adia-release/evals/evals.json +24 -0
  47. package/skills/adia-release/evals/routing-corpus.json +35 -0
  48. package/skills/adia-release/references/changelog-discipline.md +15 -9
  49. package/skills/adia-release/references/cut-procedure.md +78 -24
  50. package/skills/adia-release/references/gates-catalog.md +3 -3
  51. package/skills/adia-release/references/independent-package-release.md +1 -1
  52. package/skills/adia-release/references/recovery-paths.md +4 -2
  53. package/skills/adia-release/scripts/bump.mjs +32 -1
  54. package/skills/adia-release/scripts/dispatch-publish.mjs +49 -11
  55. package/skills/adia-release/scripts/gate-roster.mjs +71 -0
  56. package/skills/adia-release/scripts/insert-stub.mjs +36 -2
  57. package/skills/adia-release/scripts/promote-unreleased.mjs +35 -1
  58. package/skills/adia-release/scripts/release-pack.mjs +315 -56
  59. package/skills/adia-release/scripts/tag-lockstep.mjs +29 -7
  60. package/skills/adia-site-docs/SKILL.md +6 -1
  61. package/skills/adia-site-docs/evals/routing-corpus.json +35 -173
  62. package/skills/adia-ssr/SKILL.md +128 -0
  63. package/skills/adia-ssr/evals/audit-report.md +63 -0
  64. package/skills/adia-ssr/evals/routing-corpus.json +34 -0
  65. package/skills/adia-ssr/references/consumer-workarounds.md +81 -0
  66. package/skills/adia-ssr/references/failure-shapes.md +164 -0
  67. package/skills/adia-ssr/references/guard-patterns.md +183 -0
  68. package/skills/adia-ssr/references/status-ledger.md +33 -0
  69. package/skills/adia-ssr/references/test-without-linkedom.md +129 -0
  70. package/agents/a2ui-engineer.md +0 -26
  71. package/agents/framework-verifier.md +0 -26
  72. package/agents/release-engineer.md +0 -26
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env python3
2
+ """release-pretag-docs-gate — PreToolUse gate on irreversible release verbs.
3
+
4
+ Fires before Bash runs a command that makes a release irreversible — tag
5
+ creation (tag-lockstep without --delete), release-tag pushes, publish
6
+ dispatch, or release-pack's handoff mode — and requires the release docs
7
+ to already be in shape: every lockstep CHANGELOG carries the version's
8
+ section (no leftover [Unreleased] content), entry READMEs exist, and the
9
+ team release notes are generated at docs/releases/v<X>.md. The actual
10
+ checks live in the repo (scripts/release/check-release-docs.mjs) so the
11
+ git pre-push hook shares them; this wrapper only classifies the command,
12
+ derives the version, and converts the gate's findings into a deny with
13
+ the repair commands attached.
14
+
15
+ Born from the v0.8.4 near-miss (2026-07-17): the promotion step was
16
+ skipped, F-N1 fired only after tagging, and the publish shipped tarballs
17
+ with [Unreleased] CHANGELOG headers. This gate runs the same checks at
18
+ the last reversible moment instead.
19
+
20
+ Deliberately quiet: non-Bash events, non-release commands, --delete /
21
+ --dry runs, and repos without the gate script (consumer installs of this
22
+ plugin) all exit 0 with no output.
23
+
24
+ Usage:
25
+ release-pretag-docs-gate --hook # PreToolUse mode: event JSON on stdin
26
+ release-pretag-docs-gate selftest # prove the classifier on fixtures
27
+ """
28
+ import json
29
+ import os
30
+ import re
31
+ import subprocess
32
+ import sys
33
+
34
+ GATE_REL = "scripts/release/check-release-docs.mjs"
35
+
36
+ # Irreversible release verbs -> how to find the version in the command.
37
+ VERSION_FLAG = re.compile(r"--version[= ]['\"]?(\d+\.\d+\.\d+)")
38
+ # A pushed TAG is a bare token like `v0.8.4`, `web-components-v0.8.4`, or
39
+ # the full-form `refs/tags/v0.8.4`; branch refs (`release/v0.8.4`,
40
+ # `refs/heads/…`) stay reversible and deliberately don't match, as do tag
41
+ # DELETIONS (`:refs/tags/v0.8.4` — leading colon).
42
+ TAG_TOKEN = re.compile(r"^(?:[a-z0-9-]+-)?v(\d+\.\d+\.\d+)$")
43
+
44
+ # Sentinel: the command is release-irreversible but no single version can
45
+ # be derived (e.g. `git push --tags`) — deny with the doctrine message
46
+ # rather than fail open.
47
+ AMBIGUOUS = "AMBIGUOUS"
48
+
49
+
50
+ def classify(command):
51
+ """Return the target version string when `command` is an irreversible
52
+ release verb, AMBIGUOUS when it is irreversible but version-less
53
+ (fail closed), else None."""
54
+ if not command:
55
+ return None
56
+ if "tag-lockstep.mjs" in command or "dispatch-publish.mjs" in command \
57
+ or ("release-pack.mjs" in command and "--mode handoff" in command):
58
+ # Reversal / preview / selftest invocations only suppress for these
59
+ # script verbs (selftest earned its row on first live fire: a
60
+ # compound running `tag-lockstep.mjs selftest` next to an unrelated
61
+ # --version flag denied — 2026-07-17).
62
+ if "--delete" in command or "--dry" in command or "selftest" in command:
63
+ return None
64
+ m = VERSION_FLAG.search(command)
65
+ return m.group(1) if m else None
66
+ push_m = re.search(r"\bgit\b[^|;&\n]*\bpush\b", command)
67
+ if push_m:
68
+ # Scan ONLY the push command's own segment — a compound command's
69
+ # unrelated text (a PR body mentioning "v0.8.4", a commit message)
70
+ # must not classify a branch push as a tag push (live false
71
+ # positive 2026-07-17: `git push <branch> … && gh pr create
72
+ # --body "…v0.8.4…"` denied).
73
+ segment = re.split(r"[|;&\n]", command[push_m.start():])[0]
74
+ # `git push --dry-run` / `-n` never moves refs.
75
+ if "--dry-run" in segment or re.search(r"\s-n\b", segment):
76
+ return None
77
+ # Blanket tag pushes can carry release tags AND break the
78
+ # one-tag-per-push publish doctrine — fail closed.
79
+ if "--tags" in segment or "--follow-tags" in segment:
80
+ return AMBIGUOUS
81
+ for token in segment.split():
82
+ token = token.strip("\"'")
83
+ if token.startswith("refs/tags/"):
84
+ token = token[len("refs/tags/"):]
85
+ m = TAG_TOKEN.match(token)
86
+ if m:
87
+ return m.group(1)
88
+ return None
89
+ return None
90
+
91
+
92
+ def find_repo_gate(start):
93
+ """Walk up from `start` to the git root looking for the gate script —
94
+ a release command launched from a subdirectory must not bypass the
95
+ gate just because cwd isn't the repo root."""
96
+ d = os.path.abspath(start or os.getcwd())
97
+ for _ in range(12):
98
+ gate = os.path.join(d, GATE_REL)
99
+ if os.path.isfile(gate):
100
+ return d, gate
101
+ if os.path.isdir(os.path.join(d, ".git")):
102
+ return None, None # repo root reached, no gate — consumer repo
103
+ parent = os.path.dirname(d)
104
+ if parent == d:
105
+ return None, None
106
+ d = parent
107
+ return None, None
108
+
109
+
110
+ def deny(reason):
111
+ print(json.dumps({
112
+ "hookSpecificOutput": {
113
+ "hookEventName": "PreToolUse",
114
+ "permissionDecision": "deny",
115
+ "permissionDecisionReason": reason,
116
+ }
117
+ }))
118
+ sys.exit(0)
119
+
120
+
121
+ def hook_mode():
122
+ try:
123
+ event = json.load(sys.stdin)
124
+ except Exception:
125
+ sys.exit(0)
126
+ if event.get("tool_name") != "Bash":
127
+ sys.exit(0)
128
+ version = classify((event.get("tool_input") or {}).get("command", ""))
129
+ if not version:
130
+ sys.exit(0)
131
+ root, gate = find_repo_gate(event.get("cwd"))
132
+ if not gate:
133
+ sys.exit(0) # consumer repo — nothing to enforce here
134
+ if version == AMBIGUOUS:
135
+ deny(
136
+ "release-pretag-docs-gate · blanket tag push in the release monorepo\n"
137
+ "`git push --tags`/`--follow-tags` can carry release tags unchecked AND\n"
138
+ "batched tag pushes drop the publish-on-tag triggers (invariant 4).\n"
139
+ "Push release tags ONE per `git push origin <tag>` instead."
140
+ )
141
+ try:
142
+ proc = subprocess.run(
143
+ ["node", gate, "--version", version],
144
+ capture_output=True, text=True, cwd=root, timeout=60,
145
+ )
146
+ except (subprocess.TimeoutExpired, OSError) as e:
147
+ # Fail CLOSED on the release path — an erroring gate is not a pass.
148
+ deny(
149
+ "release-pretag-docs-gate · the docs gate itself errored (" + type(e).__name__ + ")\n"
150
+ "Run it directly to diagnose: node " + GATE_REL + " --version " + version
151
+ )
152
+ if proc.returncode == 0:
153
+ sys.exit(0)
154
+ findings = (proc.stderr or proc.stdout).strip()
155
+ deny(
156
+ "release-pretag-docs-gate · release docs not ready for v" + version + "\n"
157
+ + findings + "\n"
158
+ "This command is irreversible (tags/publish) — land the doc fixes via PR first\n"
159
+ "(invariant 3), then re-run. If a finding seems wrong, report it against\n"
160
+ "scripts/release/check-release-docs.mjs; do not bypass the gate."
161
+ )
162
+
163
+
164
+ def selftest():
165
+ cases = [
166
+ ("node .claude/skills/adia-release/scripts/tag-lockstep.mjs --version 0.8.4", "0.8.4"),
167
+ ("node .claude/skills/adia-release/scripts/tag-lockstep.mjs --version 0.8.4 --delete", None),
168
+ ("node scripts/x/dispatch-publish.mjs --version 1.2.3 --verify-triggered", "1.2.3"),
169
+ ("node release-pack.mjs --mode handoff --version 0.9.0 --yes", "0.9.0"),
170
+ ("node release-pack.mjs --mode cut --version 0.9.0", None),
171
+ ("node release-pack.mjs --mode handoff --version 0.9.0 --dry", None),
172
+ ("git push origin web-components-v0.8.4", "0.8.4"),
173
+ ("git push origin v0.8.4", "0.8.4"),
174
+ ('git -C "$REPO" push origin v0.8.4', "0.8.4"),
175
+ ("git push origin refs/tags/v0.8.4", "0.8.4"),
176
+ ("git push origin :refs/tags/v0.8.4", None), # tag DELETION is a reversal
177
+ ("git push origin v0.8.4 --dry-run", None),
178
+ ("git push --tags origin", AMBIGUOUS),
179
+ ("git push --follow-tags origin main", AMBIGUOUS),
180
+ ("node tag-lockstep.mjs --version '0.8.4'", "0.8.4"),
181
+ ("git push origin main", None),
182
+ ("git push -u origin release/v0.8.4", None), # branch push is reversible
183
+ # Compound: branch push + unrelated version text in a later command
184
+ # (PR bodies) must NOT classify as a tag push.
185
+ ('git push -u origin feat/x 2>&1 | tail -1\ngh pr create --body "the four v0.8.4 defects"', None),
186
+ ('git push origin v0.8.4 && echo done', "0.8.4"),
187
+ ("npm run build", None),
188
+ ("", None),
189
+ ]
190
+ for cmd, want in cases:
191
+ got = classify(cmd)
192
+ if got != want:
193
+ print(f"selftest FAIL: classify({cmd!r}) = {got!r}, want {want!r}", file=sys.stderr)
194
+ sys.exit(1)
195
+
196
+ # find_repo_gate walks up from a subdirectory (tempdir fixture — the
197
+ # v1 bug checked cwd only, so any subdir cwd bypassed the gate).
198
+ import tempfile
199
+ with tempfile.TemporaryDirectory() as td:
200
+ os.makedirs(os.path.join(td, ".git"))
201
+ os.makedirs(os.path.join(td, "scripts", "release"))
202
+ open(os.path.join(td, GATE_REL), "w").write("// fixture\n")
203
+ sub = os.path.join(td, "packages", "deep", "nested")
204
+ os.makedirs(sub)
205
+ root, gate = find_repo_gate(sub)
206
+ if root != td or not gate:
207
+ print(f"selftest FAIL: find_repo_gate walk-up ({root!r})", file=sys.stderr)
208
+ sys.exit(1)
209
+ with tempfile.TemporaryDirectory() as td:
210
+ os.makedirs(os.path.join(td, ".git"))
211
+ sub = os.path.join(td, "src")
212
+ os.makedirs(sub)
213
+ root, gate = find_repo_gate(sub)
214
+ if gate is not None:
215
+ print("selftest FAIL: consumer repo must yield no gate", file=sys.stderr)
216
+ sys.exit(1)
217
+ print("selftest OK")
218
+
219
+
220
+ if __name__ == "__main__":
221
+ if len(sys.argv) > 1 and sys.argv[1] == "selftest":
222
+ selftest()
223
+ elif len(sys.argv) > 1 and sys.argv[1] == "--hook":
224
+ hook_mode()
225
+ else:
226
+ print(__doc__)
@@ -1,10 +1,12 @@
1
1
  ---
2
- description: Cut, tag, publish, and deploy an @adia-ai release (11-package lockstep — 9 npm libraries + 2 Claude Code plugins). Stops for sign-off before every irreversible step.
2
+ description: Cut, tag, publish, and deploy an @adia-ai release (11-package lockstep — 9 npm libraries + 2 Claude Code plugins). This invocation IS the authorization for the whole cycle; only gate failures stop it.
3
3
  argument-hint: "[version or package]"
4
4
  ---
5
5
 
6
6
  Run a release. **$ARGUMENTS**
7
7
 
8
8
  Invoke **`adia-release`** and run its cut procedure end-to-end: pre-flight
9
- gates → cut → tag → publish → deploy → notes, stopping for operator sign-off
10
- at each irreversible step. The skill owns the discipline; don't restate it here.
9
+ gates → cut → tag → publish → deploy → notes. This command's invocation is
10
+ the operator's single authorization for the entire cycle (§Authorization,
11
+ operator ruling 2026-07-17) — run inline, don't re-ask per step; stop only
12
+ on a gate failure. The skill owns the discipline; don't restate it here.
package/hooks/hooks.json CHANGED
@@ -9,6 +9,16 @@
9
9
  "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/bin/sidecar-prewrite-guard\" --hook"
10
10
  }
11
11
  ]
12
+ },
13
+ {
14
+ "matcher": "Bash",
15
+ "hooks": [
16
+ {
17
+ "type": "command",
18
+ "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/bin/release-pretag-docs-gate\" --hook",
19
+ "timeout": 90
20
+ }
21
+ ]
12
22
  }
13
23
  ],
14
24
  "PostToolUse": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/adia-ui-forge",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
4
4
  "description": "Maintain the adia-ui (@adia-ai) framework itself \u2014 author primitives and shells, run the A2UI generation pipeline and its corpus, review gen-UI quality, sweep QA, cut releases, deploy. The maintainer counterpart to adia-factory (the consumer/app-author plugin).",
5
5
  "keywords": [
6
6
  "adia-ui",
@@ -1,11 +1,16 @@
1
1
  ---
2
2
  name: adia-a2ui
3
3
  description: >-
4
- Maintains the A2UI pipeline (packages/a2ui/): chunk corpus, compose
5
- strategies, calibration, evals, the a2ui MCP. Use when asked to author/fix
6
- chunks, tune STRONG_MATCH, lift eval fails, change MCP tools, run pipeline
7
- ops, or when a component's A2UI contract can't express a content shape.
8
- NOT for app screens (adia-compose).
4
+ Maintains the A2UI pipeline (packages/a2ui/): the harvested chunk corpus,
5
+ compose strategies (zettel, chunk-zettel, free-form, monolithic), retrieval,
6
+ validator, calibration, evals, the a2ui MCP server. Use when asked to
7
+ author/harvest/fix chunks, tune STRONG_MATCH or zettel thresholds, validate
8
+ an A2UI document, diagnose an eval gap/regression or lift a semantic fail,
9
+ add or change MCP tools (generate_ui, compose_from_chunks,
10
+ check_anti_patterns, refine_composition), scan anti-patterns, run pipeline
11
+ ops, or when a component's A2UI contract can't express a content shape. NOT
12
+ for app screens (adia-compose), runtime gen-UI app features (adia-genui),
13
+ primitive authoring (adia-author), or gallery scoring (adia-gen-review).
9
14
  disable-model-invocation: false
10
15
  user-invocable: true
11
16
  ---
@@ -73,7 +78,7 @@ Unmatched work defaults to pipeline-overview and re-classifies from there.
73
78
  Full structural gate after any pipeline change:
74
79
 
75
80
  ```bash
76
- node scripts/build/components.mjs --verify # "clean — N files up-to-date" (127 component dirs)
81
+ node scripts/build/components.mjs --verify # "clean — N files up-to-date" (the N is the gate; dir count drifts, don't pin it)
77
82
  npm run verify:traits # 100% coverage
78
83
  npm run smoke:engines
79
84
  npm run smoke:register-engine # 11/11
@@ -81,10 +86,18 @@ npm run test:a2ui # 22/22 (+1 skipped OK)
81
86
  npm run eval:diff -- --engine zettel # floors: cov≥87, avg≥85, MRR≥0.94
82
87
  npm run check:zettel-eval-regression -- --latest --strict # mechanical floor gate
83
88
  npm run check:free-form-eval-regression -- --latest # free-form twin
84
- npm run eval:diff -- --engine free-form # floors: cov≥90, avg≥83, F1≥55
89
+ npm run eval:diff -- --engine free-form # floors: cov≥88, avg≥85, F1≥52
85
90
  ```
86
91
 
87
- Monolithic floor: cov=100, avg≥95. Dogfood set: 20/20, avg≥95. A failing gate
92
+ The two `check:*-eval-regression` scripts (`scripts/release/check-zettel-eval-regression.mjs`,
93
+ `scripts/release/check-free-form-eval-regression.mjs`) own the floor numbers — read
94
+ their `ALERT_FLOOR` constants before quoting a number here; this file only mirrors
95
+ them and can drift (it once silently regressed to `cov≥40` before the mechanical
96
+ gate existed).
97
+
98
+ Monolithic floor: cov=100, avg≥95. Dogfood set: 20/20, avg≥95. No mechanical
99
+ regression gate exists for monolithic yet — this floor is convention-only, same
100
+ failure mode the zettel/free-form gates were built to close. A failing gate
88
101
  is the artifact — fix at the source (chunk HTML, engine code, tool schema),
89
102
  re-run the narrowest gate, then the full sequence. A threshold tweak that
90
103
  papers over a failing gate is a regression, not a fix.
@@ -101,3 +114,19 @@ intent → retrieval (chunk / composition search)
101
114
  Every change touches exactly one stage; identify which before patching. History
102
115
  for any constant or decision lives in git and PR descriptions
103
116
  (`git log -S STRONG_MATCH_THRESHOLD -- packages/a2ui`).
117
+
118
+ ## Pipeline Change Record — the output contract
119
+
120
+ Every change reports:
121
+
122
+ | Field | Value |
123
+ | --- | --- |
124
+ | Stage touched | retrieval \| strategy engine (zettel/chunk-zettel/free-form/monolithic) \| composer \| validator \| MCP surface |
125
+ | Narrowest gate run | the specific check for the touched stage, + result |
126
+ | Full sequence | `npm run smoke:engines` + `npm run test:a2ui` result |
127
+ | Floors before → after | cov/avg/MRR (zettel) or cov/avg/F1 (free-form) or cov/avg (monolithic) |
128
+ | Re-baseline | no / yes — if yes, the PR that ratified the new floor |
129
+
130
+ Done when every row is filled and the cited gates are green. NOT done: a
131
+ floor number changed with no before/after comparison, or a threshold tweak
132
+ with no root-cause note for why the floor moved.
@@ -1,31 +1,38 @@
1
1
  {
2
- "skill": "adia-a2ui",
3
- "description": "Routing corpus — does a phrase route to adia-a2ui (the A2UI/gen-ui pipeline maintainer skill) or to a sibling/off-topic?",
4
- "minimums_per_spec": {
5
- "trigger_phrases": 10,
6
- "adversarial_phrases": 5
7
- },
8
- "phrases": [
9
- { "id": "t1", "phrase": "validate this A2UI document", "should_route_to_a2ui": true },
10
- { "id": "t2", "phrase": "harvest a chunk from this HTML demo", "should_route_to_a2ui": true },
11
- { "id": "t3", "phrase": "zettel coverage dropped after the last change", "should_route_to_a2ui": true },
12
- { "id": "t4", "phrase": "should this repeated subtree become its own chunk", "should_route_to_a2ui": true },
13
- { "id": "t5", "phrase": "tune the STRONG_MATCH threshold", "should_route_to_a2ui": true },
14
- { "id": "t6", "phrase": "run check_anti_patterns on this rendered HTML", "should_route_to_a2ui": true },
15
- { "id": "t7", "phrase": "refine composition with an OAuth row", "should_route_to_a2ui": true },
16
- { "id": "t8", "phrase": "MRR dropped in the chunk retrieval eval", "should_route_to_a2ui": true },
17
- { "id": "t9", "phrase": "add an MCP tool to the a2ui server", "should_route_to_a2ui": true },
18
- { "id": "t10", "phrase": "audit the training data corpus for drift", "should_route_to_a2ui": true },
19
- { "id": "t11", "phrase": "diagnose this eval gap and regression", "should_route_to_a2ui": true },
20
- { "id": "t12", "phrase": "run the MCP pipeline: generate_ui then validate_schema", "should_route_to_a2ui": true },
21
- { "id": "t13", "phrase": "lift a semantic fail under sixty", "should_route_to_a2ui": true },
22
- { "id": "t14", "phrase": "why did the composer emit composition-match instead of composition-synthesized", "should_route_to_a2ui": true },
23
- { "id": "a1", "phrase": "build a settings page from existing primitives", "should_route_to_a2ui": false, "expected_alternative": "adia-author" },
24
- { "id": "a2", "phrase": "add a slot to button-ui and update its yaml contract", "should_route_to_a2ui": false, "expected_alternative": "adia-author" },
25
- { "id": "a3", "phrase": "cut a release and bump the version tag", "should_route_to_a2ui": false, "expected_alternative": "adia-release" },
26
- { "id": "a4", "phrase": "score the generated UI quality in apps/genui", "should_route_to_a2ui": false, "expected_alternative": "adia-gen-review" },
27
- { "id": "a5", "phrase": "run a broad visual QA sweep across site and playgrounds", "should_route_to_a2ui": false, "expected_alternative": "adia-dogfood" },
28
- { "id": "a6", "phrase": "fix the @adia-ai/llm SSE adapter", "should_route_to_a2ui": false, "expected_alternative": "adia-llm" },
29
- { "id": "a7", "phrase": "explain how React state management works", "should_route_to_a2ui": false, "expected_alternative": "off-topic" }
30
- ]
2
+ "positives": [
3
+ "validate this A2UI document",
4
+ "harvest a chunk from this HTML demo",
5
+ "zettel coverage dropped after the last change",
6
+ "should this repeated subtree become its own chunk",
7
+ "tune the STRONG_MATCH threshold",
8
+ "run check_anti_patterns on this rendered HTML",
9
+ "refine composition with an OAuth row",
10
+ "MRR dropped in the chunk retrieval eval",
11
+ "add an MCP tool to the a2ui server",
12
+ "audit the training data corpus for drift",
13
+ "diagnose this eval gap and regression",
14
+ "run the MCP pipeline: generate_ui then validate_schema",
15
+ "lift a semantic fail under sixty",
16
+ "why did the composer emit composition-match instead of composition-synthesized"
17
+ ],
18
+ "negatives": [
19
+ "build a settings page from existing primitives",
20
+ "add a slot to button-ui and update its yaml contract",
21
+ "cut a release and bump the version tag",
22
+ "score the generated UI quality in apps/genui",
23
+ "run a broad visual QA sweep across site and playgrounds",
24
+ "fix the @adia-ai/llm SSE adapter",
25
+ "explain how React state management works",
26
+ "compose a settings screen from catalog primitives",
27
+ "mount gen-root and wire data resolvers for end users at runtime",
28
+ "edit the getting-started docs page on the site"
29
+ ],
30
+ "_measured": {
31
+ "as_of": "2026-07-18",
32
+ "scorer": "routing_eval.py (nonoun-plugins/forge)",
33
+ "f1": 0.783,
34
+ "precision": 1.0,
35
+ "recall": 0.643,
36
+ "exit_code": 0
37
+ }
31
38
  }
@@ -14,7 +14,7 @@ lifecycle), `.claude/docs/specs/package-architecture.md` (package relations).
14
14
  | File | Role |
15
15
  | --- | --- |
16
16
  | `packages/a2ui/compose/core/generator.js` | `generate_ui` orchestrator — instant / pro / thinking / stream modes; multi-turn via `executionId` |
17
- | `packages/a2ui/compose/strategies/registry.js` | Engine registry — `registerEngine(name, factory)`. Reserved names: `monolithic`, `zettel`, `mcp`, `monolithic-*` |
17
+ | `packages/a2ui/compose/strategies/registry.js` | Engine registry — `registerEngine(name, factory)`. Reserved names: `monolithic`, `monolithic-instant`, `monolithic-pro`, `monolithic-thinking`, `zettel`, `chunk-zettel`, `free-form` |
18
18
  | `packages/a2ui/compose/strategies/zettel/` | Zettel + chunk-zettel engines — see [strategy-engines](strategy-engines.md) for the per-file map |
19
19
  | `packages/a2ui/compose/strategies/free-form-composer/` | Free-form engine (`index.js`, `system-prompt.js`, `transpile.js`) |
20
20
  | `packages/a2ui/compose/strategies/_shared/chunk-loader.js` | Shared chunk loading for engines |
@@ -8,7 +8,7 @@ is calibrated against the held-out intent set or production telemetry.
8
8
 
9
9
  ## `STRONG_MATCH_THRESHOLD = 40`
10
10
 
11
- - **File**: `packages/a2ui/compose/strategies/zettel/generator-adapter.js:89`
11
+ - **File**: `packages/a2ui/compose/strategies/zettel/generator-adapter.js` (`grep -n STRONG_MATCH_THRESHOLD`)
12
12
  - **Raised**: 22 → 40 post-incident.
13
13
  - **Reason**: at 22, login-form / signup-form played verbatim too often →
14
14
  repetitive output. At 40, only near-perfect retrievals (chart-dashboard=48,
@@ -29,7 +29,7 @@ is calibrated against the held-out intent set or production telemetry.
29
29
 
30
30
  ## `STRONG_RETRIEVAL_SCORE = 8`
31
31
 
32
- - **File**: `chunk-synthesizer.js:31`
32
+ - **File**: `chunk-synthesizer.js` (`grep -n STRONG_RETRIEVAL_SCORE`)
33
33
  - **Scale**: corpus-size-**independent** absolute keyword score from
34
34
  `chunk-library.js#keywordScore()`: first name-word matches a token **+10**,
35
35
  full-query substring **+5**, whole-word token in name **+3**, substring /
@@ -46,7 +46,7 @@ is calibrated against the held-out intent set or production telemetry.
46
46
 
47
47
  ## `PRE_SEARCH_LIMIT = 30`
48
48
 
49
- - **Files**: `chunk-synthesizer.js:32`, `chunk-refiner.js:32`
49
+ - **Files**: `chunk-synthesizer.js` + `chunk-refiner.js` (`grep -n PRE_SEARCH_LIMIT`)
50
50
  - **Reason**: token-budget mitigation — pre-filter the catalog before the LLM
51
51
  sees it; the full catalog per prompt would burn tens of thousands of tokens.
52
52
  - **Synthesizer**: kind-aware allocation —
@@ -61,7 +61,7 @@ is calibrated against the held-out intent set or production telemetry.
61
61
 
62
62
  ## `SCOPE_DRIFT_RATIO = 1.5` + `SCOPE_DRIFT_MIN_ACTUAL = 20`
63
63
 
64
- - **File**: `chunk-synthesizer.js:38-39` (exported)
64
+ - **File**: `chunk-synthesizer.js` (exported; `grep -n SCOPE_DRIFT`)
65
65
  - Composed envelope's component count > 1.5× the sum of bound chunks' counts
66
66
  auto-fires a `scope-drift` issue — catches LLM creative expansion that
67
67
  hallucinates components beyond the bound chunks.
@@ -70,7 +70,7 @@ is calibrated against the held-out intent set or production telemetry.
70
70
 
71
71
  ## `DEFAULT_MAX_ATTEMPTS = 2`
72
72
 
73
- - **Files**: `chunk-refiner.js:31`, `chunk-synthesizer.js:33`
73
+ - **Files**: `chunk-refiner.js` + `chunk-synthesizer.js` (`grep -n DEFAULT_MAX_ATTEMPTS`)
74
74
  - Validator-driven retry budget. After 2 failed validations the engine emits
75
75
  `synthesis-failed`.
76
76
 
@@ -1,11 +1,18 @@
1
1
  ---
2
2
  name: adia-author
3
3
  description: >-
4
- Author or modify code inside the adia-ui monorepo — primitives, shells,
5
- demos, component yaml SoTs, the @adia-ai/llm bridge. Use when asked to add
6
- or edit a primitive, prop, slot, shell, or component CSS, promote inline
7
- UI to a module, or author a demo. NOT for composing app screens from
8
- primitives (adia-compose) or pipeline internals (adia-a2ui).
4
+ Author or modify AdiaUI framework source inside the monorepo — primitives
5
+ (packages/web-components), shells/composites (packages/web-modules), yaml
6
+ SoTs, demos. Use when asked to add a new primitive component, add or fix a prop/slot/
7
+ attribute/CSS variant, update a component's yaml, build or fix a shell
8
+ (chat-shell, admin-shell, editor-shell sidebar/pane/bespoke-tier
9
+ composition), promote or extract repeated inline content into a shared
10
+ module, audit a component's four-axis contract/token usage/lifecycle
11
+ (connected/disconnected symmetry) for drift, or author/write a demo or
12
+ examples.html for a component or composite. NOT for composing app screens
13
+ from primitives (adia-compose), A2UI pipeline internals (adia-a2ui),
14
+ @adia-ai/llm adapter/streaming/bridge internals (adia-llm-internals), or
15
+ site/pages docs pages (adia-site-docs).
9
16
  disable-model-invocation: false
10
17
  user-invocable: true
11
18
  ---
@@ -14,7 +21,9 @@ user-invocable: true
14
21
 
15
22
  Guard rails for code that lands INSIDE the adia-ui monorepo: primitives
16
23
  (`packages/web-components/`), shells and composites (`packages/web-modules/`),
17
- their `<name>.yaml` SoTs, the `@adia-ai/llm` bridge, and composite demos.
24
+ their `<name>.yaml` SoTs, and composite demos. `@adia-ai/llm` internals
25
+ (adapters, streaming, the bridge) are `adia-llm-internals`'s charter, not
26
+ this skill's — route there.
18
27
  Light-DOM is load-bearing: `slot=` attributes are decorative metadata,
19
28
  positioning is CSS by tag + ancestor + DOM order — never `::slotted()`,
20
29
  `::part()`, or shadow DOM. `.claude/docs/specs/component-token-contract.md`
@@ -30,7 +39,6 @@ wins any tie with this skill. Monorepo source read while authoring (yaml, CSS,
30
39
  | Shell / bespoke cluster child (`<admin-*>` `<chat-*>` `<editor-*>` `<simple-*>`) | [shell-patterns.md](references/shell-patterns.md) |
31
40
  | Promote repeated inline UI → shared module | [module-promotion.md](references/module-promotion.md) |
32
41
  | Contract / token / lifecycle drift audit on an existing component | [token-contract.md](references/token-contract.md) + [anti-patterns.md](references/anti-patterns.md) |
33
- | Extend `@adia-ai/llm` (provider, streaming, proxy) | [llm-bridge.md](references/llm-bridge.md) |
34
42
  | Demo for a composite/module — any `packages/web-modules/**/*.{examples,contents}.html` | [composite-demo-protocol.md](references/composite-demo-protocol.md) — NOT the primitive or promotion paths; they lack the canonical-survey discipline |
35
43
  | Convention question ("is this idiomatic?") | [code-style.md](references/code-style.md) — cite the rule, don't expand it inline |
36
44
 
@@ -82,7 +90,6 @@ citation mechanically on every web-modules demo write; the
82
90
  | Shell | render `packages/web-modules/<cluster>/<name>/<name>.html`; `node scripts/dev/audit-native-primitive-leak.mjs --include=<surface>`; `node scripts/dev/audit-shell-composition.mjs --include=<surface>` for `<admin-shell>` composites |
83
91
  | Inline → module promotion | source page renders identically post-refactor (visual sweep + DOM diff) + native-primitive-leak audit on the touched surface |
84
92
  | Drift audit | the findings report itself; gates run once a fix is applied |
85
- | LLM-bridge extension | a real client round-trip parses + returns (`npm run check:lockstep` + `npm run smoke:engines`) |
86
93
  | Composite demo | `npm run audit:demo-pattern-source:strict` + `npm run qa:design-coherence:strict` + (8b) `npm run qa:rendered-dom:emit -- --slug=<slug>` |
87
94
 
88
95
  Full structural-gate sequence after any primitive / shell / promotion work:
@@ -93,6 +100,7 @@ npm run verify:traits # 100% coverage
93
100
  npm run smoke:engines # green
94
101
  node scripts/dev/audit-native-primitive-leak.mjs # 0 critical leaks
95
102
  node scripts/dev/audit-shell-composition.mjs # 0 critical defects
103
+ node scripts/dev/audit-template-child-conflict.mjs # 0 critical (gh#284 shape — non-null template + slots.default)
96
104
  ```
97
105
 
98
106
  A failed gate is the artifact: fix at the source, re-run the narrowest gate,