@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/a2ui-mcp",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "AdiaUI A2UI MCP server. Exposes the compose engine over MCP with an engine selector for monolithic + zettel strategies.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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) {
@@ -72,20 +72,23 @@ try {
72
72
  bad('LLM adapter', e.message);
73
73
  }
74
74
 
75
- // ── Test 2: Pattern library ─────────────────────────────────────────
75
+ // ── Test 2: Composition library (post-§65 chunks-only substrate) ───
76
76
 
77
- console.log('\n2. Pattern library');
77
+ console.log('\n2. Composition library');
78
78
 
79
79
  const { searchBlocks, listPatterns, lookupDomain } = await import('../../compose/core/reference.js');
80
80
 
81
- const allPatterns = listPatterns();
82
- const withTemplates = allPatterns.filter(p => p.template && Array.isArray(p.template));
83
- const domains = [...new Set(allPatterns.map(p => p.domain).filter(Boolean))];
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
- if (allPatterns.length >= 70) {
86
- ok('Pattern count', `${allPatterns.length} total (${withTemplates.length} with templates)`);
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('Pattern count', `only ${allPatterns.length} (expected 70+)`);
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 known compositions (§64 retired pattern-library; reference.js
98
- // now reads from composition-library these are real composition names).
99
- const spotChecks = ['login-form', 'stat-card-dashboard', 'data-table-paginated', 'settings-admin-page'];
100
- const foundAll = spotChecks.every(name => allPatterns.some(p => p.name === name));
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 patterns', spotChecks.join(', '));
107
+ ok('Known chunks', spotChecks.join(', '));
103
108
  } else {
104
- const missing = spotChecks.filter(name => !allPatterns.some(p => p.name === name));
105
- bad('Known patterns', `missing: ${missing.join(', ')}`);
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 form', 'login-form'],
146
- ['nav bar', null], // any match is fine
147
- ['dashboard stats', null],
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
- 'show me a table',
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
- // (The legacy exemplar extract ingest path was retired 2026-04-28 in
242
- // mcp 0.0.5. The chunk corpus is the training surface now.)
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. Hand-authored pattern library should be ≥ 100 entries.
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 >= 100) {
249
- ok('Pattern library', `${patterns.length} hand-authored patterns`);
269
+ if (patterns.length >= 20) {
270
+ ok('Composition library', `${patterns.length} compositions (harvested-chunks substrate)`);
250
271
  } else {
251
- bad('Pattern library', `only ${patterns.length} (expected ≥ 100)`);
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