@adia-ai/a2ui-mcp 0.4.6 → 0.4.7
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/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/scripts/smoke-engine-registry.mjs +6 -1
- package/scripts/test-a2ui.mjs +53 -32
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,12 @@ zettel strategies.
|
|
|
11
11
|
|
|
12
12
|
_No pending changes._
|
|
13
13
|
|
|
14
|
+
## [0.4.7] - 2026-05-12
|
|
15
|
+
|
|
16
|
+
### Changed — smoke + test scripts aligned to post-§72 retrieval surface
|
|
17
|
+
|
|
18
|
+
`scripts/smoke-engine-registry.mjs` retrieval probe set + `scripts/test-a2ui.mjs` (composition-count threshold + spot-checks + intent-gate keyword surface) now exercise the harvested-chunks substrate that survives §72's `corpus/patterns/` + `corpus/compositions/` retirement. Probe for "pricing tiers" dropped (no pricing surface in shipped `/site/`); replaced with "admin dashboard with kpi cards" matching `dashboard-admin-page`. Spot-check names updated to real chunk names (`auth-signin-card-password`, `auth-signup-entry`, `dashboard-admin-page`, `settings-admin-page`). No tool / API change — internal scripts only.
|
|
19
|
+
|
|
14
20
|
## [0.4.6] - 2026-05-12
|
|
15
21
|
|
|
16
22
|
### Changed — patterns surface retired (§64 step 5, 2026-05-12)
|
package/package.json
CHANGED
|
@@ -44,10 +44,15 @@ console.log(`\n[smoke] shape invariants: ${ok ? 'ok' : 'FAIL'}`);
|
|
|
44
44
|
// component tree's text content must overlap the intent's keywords.
|
|
45
45
|
// This catches retrieval regressions (wrong-domain top hit) that pure
|
|
46
46
|
// shape-validation gates miss.
|
|
47
|
+
// Probes pick intents that match the post-§65 harvested-chunks
|
|
48
|
+
// substrate (auth flows, dashboard variants, settings, errors).
|
|
49
|
+
// Removed: 'pricing tiers' (no pricing surface in shipped /site/ —
|
|
50
|
+
// retrieval honestly returns synthesis-failed; LLM fallback handles
|
|
51
|
+
// the intent at ~9s vs ~25ms).
|
|
47
52
|
const RETRIEVAL_PROBES = [
|
|
48
53
|
{ intent: 'login form with email and password', engine: 'zettel', expectKeywords: ['sign in', 'login', 'email', 'password'] },
|
|
49
|
-
{ intent: 'pricing tiers with three plans', engine: 'zettel', expectKeywords: ['pricing', 'tier', 'plan', 'starter', 'pro', 'enterprise', '$'] },
|
|
50
54
|
{ intent: 'sign up form for a new account', engine: 'zettel', expectKeywords: ['sign up', 'register', 'create account', 'email'] },
|
|
55
|
+
{ intent: 'admin dashboard with kpi cards', engine: 'zettel', expectKeywords: ['dashboard', 'kpi', 'metric', 'revenue', 'users', 'orders', 'conversion'] },
|
|
51
56
|
];
|
|
52
57
|
|
|
53
58
|
function extractText(messages) {
|
package/scripts/test-a2ui.mjs
CHANGED
|
@@ -72,20 +72,23 @@ try {
|
|
|
72
72
|
bad('LLM adapter', e.message);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
// ── Test 2:
|
|
75
|
+
// ── Test 2: Composition library (post-§65 chunks-only substrate) ───
|
|
76
76
|
|
|
77
|
-
console.log('\n2.
|
|
77
|
+
console.log('\n2. Composition library');
|
|
78
78
|
|
|
79
79
|
const { searchBlocks, listPatterns, lookupDomain } = await import('../../compose/core/reference.js');
|
|
80
80
|
|
|
81
|
-
const
|
|
82
|
-
const withTemplates =
|
|
83
|
-
const domains = [...new Set(
|
|
81
|
+
const allCompositions = listPatterns();
|
|
82
|
+
const withTemplates = allCompositions.filter(p => p.template && Array.isArray(p.template));
|
|
83
|
+
const domains = [...new Set(allCompositions.map(p => p.domain).filter(Boolean))];
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// Post-§65: retrieval surface is the harvested-chunks substrate
|
|
86
|
+
// (~28-32 annotated chunks at the time of v0.4.7). Threshold sized
|
|
87
|
+
// for that floor; grows naturally as more source HTML gets annotated.
|
|
88
|
+
if (allCompositions.length >= 20) {
|
|
89
|
+
ok('Composition count', `${allCompositions.length} total (${withTemplates.length} with templates)`);
|
|
87
90
|
} else {
|
|
88
|
-
bad('
|
|
91
|
+
bad('Composition count', `only ${allCompositions.length} (expected 20+)`);
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
if (domains.length >= 3) {
|
|
@@ -94,15 +97,17 @@ if (domains.length >= 3) {
|
|
|
94
97
|
bad('Domains', `only ${domains.length}: ${domains.join(', ')}`);
|
|
95
98
|
}
|
|
96
99
|
|
|
97
|
-
// Spot-check
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
// Spot-check chunk names that exist in the harvested substrate. These
|
|
101
|
+
// are real chunk names from /apps/user-flow/, /apps/saas/, etc. —
|
|
102
|
+
// post-§65 the test asserts on actual product surfaces, not on
|
|
103
|
+
// curated composition JSON that's no longer the canonical source.
|
|
104
|
+
const spotChecks = ['auth-signin-card-password', 'auth-signup-entry', 'dashboard-admin-page', 'settings-admin-page'];
|
|
105
|
+
const foundAll = spotChecks.every(name => allCompositions.some(p => p.name === name));
|
|
101
106
|
if (foundAll) {
|
|
102
|
-
ok('Known
|
|
107
|
+
ok('Known chunks', spotChecks.join(', '));
|
|
103
108
|
} else {
|
|
104
|
-
const missing = spotChecks.filter(name => !
|
|
105
|
-
bad('Known
|
|
109
|
+
const missing = spotChecks.filter(name => !allCompositions.some(p => p.name === name));
|
|
110
|
+
bad('Known chunks', `missing: ${missing.join(', ')}`);
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
// ── Test 3: Instant mode gate ───────────────────────────────────────
|
|
@@ -119,14 +124,20 @@ function testGate(intent) {
|
|
|
119
124
|
const intentWords = intent.toLowerCase().split(/\s+/).filter(w => w.length > 2 && !GATE_STOPS.has(w));
|
|
120
125
|
const nameWords = best.name.toLowerCase().split(/[-_\s]+/);
|
|
121
126
|
const matchTags = (best.tags || []).map(t => t.toLowerCase());
|
|
127
|
+
// Post-§65: harvested chunks carry semantic intent in `keywords` more
|
|
128
|
+
// than in `tags` (which became {complexity, layout} slots). Include
|
|
129
|
+
// keywords in the gate so `login → auth-signin-card-password` strong-hits
|
|
130
|
+
// off the chunk's `keywords: ["login", ...]` field.
|
|
131
|
+
const matchKeywords = (best.keywords || []).map(k => k.toLowerCase());
|
|
122
132
|
const matchDomain = (best.domain || '').toLowerCase();
|
|
123
133
|
|
|
124
134
|
const hasStrongHit = intentWords.some(w => {
|
|
125
135
|
if (w.length < 3) return false;
|
|
126
|
-
if (nameWords.includes(w) || matchTags.includes(w)) return true;
|
|
136
|
+
if (nameWords.includes(w) || matchTags.includes(w) || matchKeywords.includes(w)) return true;
|
|
127
137
|
if (w.length >= 4) {
|
|
128
138
|
return nameWords.some(n => n.length >= 3 && (w.startsWith(n) || n.startsWith(w))) ||
|
|
129
|
-
matchTags.some(t => t.length >= 3 && (w.startsWith(t) || t.startsWith(w)))
|
|
139
|
+
matchTags.some(t => t.length >= 3 && (w.startsWith(t) || t.startsWith(w))) ||
|
|
140
|
+
matchKeywords.some(k => k.length >= 3 && (w.startsWith(k) || k.startsWith(w)));
|
|
130
141
|
}
|
|
131
142
|
return false;
|
|
132
143
|
});
|
|
@@ -134,19 +145,26 @@ function testGate(intent) {
|
|
|
134
145
|
const hasWeakHit = !hasStrongHit && intentWords.some(w => {
|
|
135
146
|
return nameWords.some(n => n.length >= 3 && (n.includes(w) || w.includes(n))) ||
|
|
136
147
|
matchTags.some(t => t.length >= 3 && (t.includes(w) || w.includes(t))) ||
|
|
148
|
+
matchKeywords.some(k => k.length >= 3 && (k.includes(w) || w.includes(k))) ||
|
|
137
149
|
matchDomain.includes(w);
|
|
138
150
|
});
|
|
139
151
|
|
|
140
152
|
return { gate: hasStrongHit ? 'STRONG' : hasWeakHit ? 'WEAK' : 'REJECTED', pattern: best.name };
|
|
141
153
|
}
|
|
142
154
|
|
|
143
|
-
// Should STRONG match
|
|
155
|
+
// Should STRONG match — restricted to intents covered by the
|
|
156
|
+
// harvested-chunks substrate (auth, dashboard, settings, error pages).
|
|
157
|
+
// Intents previously tested ("pricing table", "chat interface",
|
|
158
|
+
// "todo list", etc.) dropped because §65 retired the curated
|
|
159
|
+
// composition surface — LLM fallback handles those now.
|
|
160
|
+
// Intents need ≥2 content-token hits OR a direct name-token match —
|
|
161
|
+
// short 1-content-word intents (e.g. just "login form") get gated out
|
|
162
|
+
// by composition-library's anti-spurious-match logic. Use intents that
|
|
163
|
+
// land naturally — they're what real users type anyway.
|
|
144
164
|
const strongTests = [
|
|
145
|
-
['login
|
|
146
|
-
['
|
|
147
|
-
['
|
|
148
|
-
['pricing table', null],
|
|
149
|
-
['chat interface', null],
|
|
165
|
+
['login with email and password', null], // → auth-signin-card-password (3 keyword hits)
|
|
166
|
+
['admin dashboard kpi', null], // → dashboard-admin-page
|
|
167
|
+
['workspace admin settings', null], // → settings-admin-page
|
|
150
168
|
];
|
|
151
169
|
for (const [intent, expected] of strongTests) {
|
|
152
170
|
const { gate, pattern } = testGate(intent);
|
|
@@ -159,10 +177,9 @@ for (const [intent, expected] of strongTests) {
|
|
|
159
177
|
|
|
160
178
|
// Should NOT be REJECTED (STRONG or WEAK both acceptable)
|
|
161
179
|
const passTests = [
|
|
162
|
-
'
|
|
163
|
-
'create a todo list',
|
|
164
|
-
'user profile card',
|
|
180
|
+
'sign up for an account',
|
|
165
181
|
'settings page',
|
|
182
|
+
'404 not found error',
|
|
166
183
|
];
|
|
167
184
|
for (const intent of passTests) {
|
|
168
185
|
const { gate, pattern } = testGate(intent);
|
|
@@ -238,17 +255,21 @@ if (!THINKING) {
|
|
|
238
255
|
}
|
|
239
256
|
|
|
240
257
|
// ── Test 6: Training corpus surfaces ────────────────────────────────
|
|
241
|
-
//
|
|
242
|
-
//
|
|
258
|
+
// Post-§65: `compositions/` retired alongside the hand-authored
|
|
259
|
+
// pattern library. The harvested-chunks substrate is the sole
|
|
260
|
+
// retrieval surface; everything else falls through to LLM.
|
|
261
|
+
// (Legacy exemplar extract → ingest path retired 2026-04-28 mcp 0.0.5.)
|
|
243
262
|
|
|
244
263
|
console.log('\n6. Training corpus surfaces');
|
|
245
264
|
|
|
246
|
-
// 6a.
|
|
265
|
+
// 6a. Composition library (harvested chunks via composition-library).
|
|
266
|
+
// Threshold sized for the post-§65 floor (~28 annotated chunks at
|
|
267
|
+
// v0.4.7); grows as more source HTML gets annotated.
|
|
247
268
|
const patterns = listPatterns();
|
|
248
|
-
if (patterns.length >=
|
|
249
|
-
ok('
|
|
269
|
+
if (patterns.length >= 20) {
|
|
270
|
+
ok('Composition library', `${patterns.length} compositions (harvested-chunks substrate)`);
|
|
250
271
|
} else {
|
|
251
|
-
bad('
|
|
272
|
+
bad('Composition library', `only ${patterns.length} (expected ≥ 20)`);
|
|
252
273
|
}
|
|
253
274
|
|
|
254
275
|
// 6b. Gen-UI chunk corpus — should be ≥ 500 unique chunks across
|