@expressivecss/mcp-server 0.2.1 → 0.3.0

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/smoke.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import assert from 'node:assert/strict';
2
- import { access, mkdir, mkdtemp, readFile, rm, symlink, truncate, writeFile } from 'node:fs/promises';
2
+ import { access, copyFile, mkdir, mkdtemp, readFile, rm, symlink, truncate, writeFile } from 'node:fs/promises';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { execFileSync, spawnSync } from 'node:child_process';
@@ -8,6 +8,8 @@ import { createRequire } from 'node:module';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
10
10
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
11
+ import { AjvJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/ajv-provider.js';
12
+ import { ReadResourceResultSchema } from '@modelcontextprotocol/sdk/types.js';
11
13
 
12
14
  const packageDir = path.dirname(fileURLToPath(import.meta.url));
13
15
  const require = createRequire(import.meta.url);
@@ -76,6 +78,698 @@ function assertScopedResult(result, label) {
76
78
  assert.equal(Object.hasOwn(payload, 'nextTool'), false, `${label} still requires a next tool`);
77
79
  }
78
80
 
81
+ function assertCatalogSearch(result, { query, limit = 10, entries, totalMatches = entries.length }) {
82
+ assertScopedResult(result, 'catalogue search');
83
+ const payload = result.structuredContent;
84
+ assert.deepEqual(payload.entries, entries);
85
+ assert.equal(payload.query, query.trim());
86
+ assert.equal(payload.limit, limit);
87
+ assert.equal(payload.count, entries.length);
88
+ assert.equal(payload.totalMatches, totalMatches);
89
+ assert.equal(payload.omittedCount, totalMatches - entries.length);
90
+ assert.equal(payload.truncated, totalMatches > entries.length);
91
+ assert.equal(payload.coverageStatus, totalMatches > entries.length ? 'partial-search-results' : 'complete-search-results');
92
+ assert.equal(payload.contractVersion, contractData.frameworkVersion);
93
+ assert.equal(payload.sourceHash, contractData.sourceHash);
94
+ assert.equal(payload.guideSource, 'bundled');
95
+ assert.deepEqual(JSON.parse(result.content[0].text), payload);
96
+ }
97
+
98
+ function withoutSyntaxSelection(payload) {
99
+ const { detail, includeCapabilities, ...rest } = payload;
100
+ return { ...rest, found: rest.found.map(({ contract, syntax, capability, options, methods, omittedFields, ...entry }) => entry) };
101
+ }
102
+
103
+ function withoutApiSelection(payload) {
104
+ return { ...payload, found: payload.found.map(({ options, methods, omittedFields, ...entry }) => entry) };
105
+ }
106
+
107
+ function assertSyntaxSelection(result, { detail = 'detailed', includeCapabilities = detail === 'detailed', sections = [] } = {}) {
108
+ assert.notEqual(result.isError, true, result.content[0].text);
109
+ const payload = result.structuredContent;
110
+ assertScopedResult(result, 'syntax detail');
111
+ assert.deepEqual(JSON.parse(result.content[0].text), payload);
112
+ assert.equal(payload.detail, detail);
113
+ assert.equal(payload.includeCapabilities, includeCapabilities);
114
+ for (const entry of payload.found) {
115
+ const expected = [];
116
+ for (const field of ['contract', 'syntax', 'options', 'methods', 'capability']) {
117
+ const included = field === 'contract' || field === 'syntax' ? detail === 'detailed'
118
+ : field === 'capability' ? includeCapabilities : sections.includes(field);
119
+ assert.equal(Object.hasOwn(entry, field), included, `${entry.slug}.${field}`);
120
+ if (!included) expected.push({ field, reason: field === 'contract' || field === 'syntax' ? 'compact-detail' : 'not-requested' });
121
+ }
122
+ const bundled = guideData.guides.find((guide) => guide.file === entry.file);
123
+ const contract = bundled?.content.split(/^#### Contract\r?$/mu)[1]?.split(/^#{1,4} /mu)[0].trim() ?? '';
124
+ if (detail === 'detailed' && contract.length > 900) expected.push({ field: 'contract', reason: 'length-limit' });
125
+ expected.sort((a, b) => ['contract', 'syntax', 'options', 'methods', 'capability'].indexOf(a.field) - ['contract', 'syntax', 'options', 'methods', 'capability'].indexOf(b.field));
126
+ assert.deepEqual(entry.omittedFields, expected);
127
+ }
128
+ }
129
+
130
+ async function verifyResponseBudgets(matchingDir, versionedDir) {
131
+ const setting = 'EXPRESSIVECSS_MCP_MAX_RESPONSE_BYTES';
132
+ const scoped = ['setup_expert', 'creative_director', 'page_architect', 'page_arcjitect', 'component_syntax_expert', 'component_catalog'];
133
+ const bytes = (result) => Buffer.byteLength(JSON.stringify(result), 'utf8');
134
+ const request = (name, arguments_ = {}) => ({ name, arguments: { workflowId: 'budget-test', ...arguments_ } });
135
+ async function withServer(budget, action, { dir = packageDir, env = {} } = {}) {
136
+ const environment = { ...process.env, ...env };
137
+ if (budget === undefined) delete environment[setting];
138
+ else environment[setting] = String(budget);
139
+ const connection = new StdioClientTransport({ command: process.execPath, args: [path.join(dir, 'server.js')], cwd: dir, stderr: 'pipe', env: environment });
140
+ const peer = new Client({ name: 'response-budget-check', version: '1' });
141
+ let wire;
142
+ const start = connection.start.bind(connection);
143
+ connection.start = async () => {
144
+ const receive = connection.onmessage;
145
+ connection.onmessage = (message, extra) => { if (message.result || message.error) wire = message; receive(message, extra); };
146
+ await start();
147
+ };
148
+ try {
149
+ await peer.connect(connection);
150
+ const listed = await peer.listTools();
151
+ const validators = new Map(listed.tools.map((tool) => [tool.name, new AjvJsonSchemaValidator().getValidator(tool.outputSchema)]));
152
+ const call = async (input) => {
153
+ const result = await peer.callTool(input);
154
+ assert.deepEqual(wire.result, result, 'measure the actual wire tool result');
155
+ if (scoped.includes(input.name)) {
156
+ assert.ok(bytes(wire.result) <= (budget ?? 65_536), `${input.name}: ${bytes(wire.result)} bytes exceed ${budget ?? 65_536}`);
157
+ if (result.structuredContent) {
158
+ assert.equal(validators.get(input.name)(result.structuredContent).valid, true, `${input.name} output schema`);
159
+ assert.deepEqual(JSON.parse(result.content[0].text), result.structuredContent);
160
+ const record = result.structuredContent.responseBudget;
161
+ assert.equal(record.maxBytes, budget ?? 65_536);
162
+ for (const omitted of record.omissions) assert.ok(record.recoveries[omitted.recovery]);
163
+ }
164
+ }
165
+ return result;
166
+ };
167
+ await action({ peer, call, validators, wire: () => wire });
168
+ } finally { try { await peer.close(); } finally { await connection.close(); } }
169
+ }
170
+ const knownRules = new Map(guideData.guides.map((guide) => [guide.file.replace(/\.md$/u, ''), [...(guide.content.split(/^#### Rules\r?$/mu)[1]?.split(/^#{1,4} /mu)[0] ?? '').matchAll(/^-\s+(.+)$/gmu)].map((match) => match[1])]));
171
+ const allRequests = [
172
+ request('setup_expert', { projectRoot: matchingDir }),
173
+ request('creative_director', { projectRoot: matchingDir, goal: 'Choose a button for the primary action.' }),
174
+ ...['page_architect', 'page_arcjitect'].map((name) => request(name, { projectRoot: matchingDir, pageGoal: 'Build a settings page.', components: ['app-bar', 'cards'] })),
175
+ request('component_syntax_expert', { projectRoot: matchingDir, components: ['cards'], sections: ['options', 'methods'] }),
176
+ request('component_catalog'),
177
+ ];
178
+ await withServer(undefined, async ({ call, validators }) => {
179
+ for (const input of allRequests) {
180
+ const result = await call(input);
181
+ assert.equal(result.structuredContent.responseBudget.delivery, 'complete');
182
+ }
183
+ const arguments_ = { projectRoot: matchingDir, components: guideData.guides.slice(0, 12).map((guide) => guide.file.replace(/\.md$/u, '')), sections: ['options', 'methods'], includeCapabilities: true };
184
+ const result = await call(request('component_syntax_expert', arguments_));
185
+ for (const entry of result.structuredContent.found ?? []) if (knownRules.get(entry.slug).length) assert.deepEqual(entry.rules, knownRules.get(entry.slug));
186
+ assert.notEqual(result.structuredContent.responseBudget.delivery, 'complete');
187
+ assert.deepEqual(await call(request('component_syntax_expert', arguments_)), result, 'budget reduction is deterministic');
188
+ for (const recovery of result.structuredContent.responseBudget.recoveries.filter((row) => row.action === 'retry')) {
189
+ const recovered = await call(recovery.request);
190
+ assert.notEqual(recovered.isError, true, JSON.stringify(recovery));
191
+ assert.equal(recovered.structuredContent.responseBudget.delivery, 'complete', JSON.stringify(recovery));
192
+ }
193
+ const validator = validators.get('component_syntax_expert');
194
+ for (const change of [
195
+ { maxBytes: -1 }, { delivery: 'success' },
196
+ { omissions: [{ unit: 'rule-fragment', reason: 'byte-budget', recovery: 0 }] },
197
+ { omissions: [{ unit: 'component', reason: 'byte-budget', index: -1, recovery: 0 }] },
198
+ { recoveries: [{ action: 'retry', request: { name: 'component_catalog', arguments: { query: 'cards', limit: 51 } } }] },
199
+ { recoveries: [{ action: 'retry', request: { name: 'quality_inspector', arguments: {} } }] },
200
+ ]) assert.equal(validator({ ...result.structuredContent, responseBudget: { ...result.structuredContent.responseBudget, ...change } }).valid, false);
201
+ });
202
+
203
+ const catalogRequest = request('component_catalog', { query: 'cards', limit: 1 });
204
+ let exact;
205
+ await withServer(1_048_576, async ({ call }) => {
206
+ const result = await call(catalogRequest);
207
+ exact = bytes(result);
208
+ for (let iteration = 0; iteration < 4; iteration += 1) {
209
+ result.structuredContent.responseBudget.maxBytes = exact;
210
+ result.content[0].text = JSON.stringify(result.structuredContent);
211
+ exact = bytes(result);
212
+ }
213
+ });
214
+ await withServer(exact, async ({ call }) => {
215
+ const result = await call(catalogRequest);
216
+ assert.equal(bytes(result), exact);
217
+ assert.equal(result.structuredContent.responseBudget.delivery, 'complete');
218
+ });
219
+ await withServer(exact - 1, async ({ call }) => {
220
+ const result = await call(catalogRequest);
221
+ assert.notEqual(result.structuredContent.responseBudget.delivery, 'complete');
222
+ });
223
+ await withServer(20_000, async ({ call }) => {
224
+ const listing = await call(request('component_catalog'));
225
+ assert.equal(listing.structuredContent.responseBudget.delivery, 'partial');
226
+ assert.equal(listing.structuredContent.count + listing.structuredContent.responseBudget.omissions.length, guideData.guides.length);
227
+ for (const recovery of listing.structuredContent.responseBudget.recoveries) {
228
+ assert.equal(recovery.action, 'retry');
229
+ const recovered = await call(recovery.request);
230
+ assert.equal(recovered.structuredContent.entries[0].slug, recovery.request.arguments.query);
231
+ assert.equal(recovered.structuredContent.responseBudget.delivery, 'complete');
232
+ }
233
+ const duplicate = await call(request('component_syntax_expert', { projectRoot: matchingDir, components: ['cards', 'cards', 'unknown-name', 'autocomplete'], sections: ['options', 'methods'] }));
234
+ assert.equal(duplicate.structuredContent.missing[0].requested, 'unknown-name');
235
+ for (const omitted of duplicate.structuredContent.responseBudget.omissions.filter((row) => row.slug === 'cards')) assert.ok([0, 1].includes(omitted.index));
236
+ const blocked = await call(request('component_syntax_expert', { projectRoot: versionedDir, components: ['cards', 'autocomplete'], sections: ['options', 'methods'] }));
237
+ assert.equal(blocked.structuredContent.contractCompatibility, 'mismatch');
238
+ assert.equal(blocked.structuredContent.status, 'blocked');
239
+ const foundations = await call(request('component_syntax_expert', { projectRoot: matchingDir, foundations: ['typography', 'shape', 'motion'] }));
240
+ for (const recovery of foundations.structuredContent.responseBudget.recoveries.filter((row) => row.action === 'retry')) await call(recovery.request);
241
+ for (const input of allRequests) await call(input);
242
+ for (const name of ['page_architect', 'page_arcjitect']) {
243
+ const result = await call(request(name, { projectRoot: matchingDir, pageGoal: '界🌍\\"'.repeat(2_000), components: ['app-bar', 'cards'] }));
244
+ assert.equal(result.isError, true);
245
+ assert.equal(result.structuredContent.responseBudget.delivery, 'error');
246
+ assert.equal(result.structuredContent.architecture ?? null, null);
247
+ }
248
+ });
249
+ await withServer(1, async ({ call, peer, wire }) => {
250
+ for (const input of [...allRequests, request('component_syntax_expert', { detail: 'bad' })]) {
251
+ await assert.rejects(peer.callTool(input), (error) => error.code === -32001 && error.message.includes(setting));
252
+ assert.equal(Object.hasOwn(wire(), 'result'), false);
253
+ assert.equal(wire().error.code, -32001);
254
+ }
255
+ const qa = await call(request('rules_enforcer', { projectRoot: matchingDir, snippet: '<main></main>' }));
256
+ assert.ok(bytes(qa) > 1);
257
+ assert.equal(Object.hasOwn(qa.structuredContent, 'responseBudget'), false);
258
+ });
259
+ await withServer(12_000, async ({ call }) => {
260
+ const components = Array(8).fill('cards');
261
+ const result = await call(request('component_syntax_expert', { projectRoot: matchingDir, components, detail: 'compact' }));
262
+ assert.equal(result.structuredContent.responseBudget.delivery, 'partial');
263
+ const omissions = result.structuredContent.responseBudget.omissions;
264
+ assert.ok(omissions.every((row) => row.unit === 'component' && row.requested === 'cards'));
265
+ assert.equal(new Set([...result.structuredContent.found.map((row) => row.requestIndex), ...omissions.map((row) => row.index)]).size, components.length);
266
+ for (const recovery of result.structuredContent.responseBudget.recoveries) {
267
+ assert.equal(recovery.action, 'retry');
268
+ const recovered = await call(recovery.request);
269
+ assert.deepEqual(recovered.structuredContent.found[0].rules, knownRules.get('cards'));
270
+ assert.equal(recovered.structuredContent.responseBudget.delivery, 'complete');
271
+ }
272
+ });
273
+ await withServer(12_000, async ({ call }) => {
274
+ const creative = await call(request('creative_director', { projectRoot: matchingDir, goal: 'Choose navigation, buttons, cards, and text fields for a form page.', maxSuggestions: 12 }));
275
+ assert.equal(creative.structuredContent.responseBudget.delivery, 'partial');
276
+ for (const omitted of creative.structuredContent.responseBudget.omissions) {
277
+ const recovery = creative.structuredContent.responseBudget.recoveries[omitted.recovery];
278
+ assert.equal(recovery.action, 'retry');
279
+ const recovered = await call(recovery.request);
280
+ assert.equal(recovered.structuredContent.suggestions[0].slug, omitted.slug);
281
+ assert.equal(recovered.structuredContent.responseBudget.delivery, 'complete');
282
+ }
283
+ });
284
+ await withServer(2_000, async ({ call, peer }) => {
285
+ for (const input of allRequests.slice(0, -1)) {
286
+ const result = await call({ ...input, arguments: { ...input.arguments, workflowId: 'x'.repeat(256) } });
287
+ assert.equal(result.structuredContent.responseBudget.delivery, 'complete');
288
+ assert.equal(result.structuredContent.skipped, true);
289
+ assert.equal(result.structuredContent.contractCompatibility, 'unknown');
290
+ }
291
+ await assert.rejects(peer.callTool(request('component_syntax_expert', { components: Array(400).fill(1) })), (error) => error.code === -32001);
292
+ }, { env: { SKIP_SETUP_EXPERT: 'true', SKIP_CREATIVE_DIRECTOR: 'true', SKIP_PAGE_ARCHITECT: 'true', SKIP_COMPONENT_SYNTAX_EXPERT: 'true' } });
293
+ await withServer(1, async ({ peer }) => {
294
+ await assert.rejects(peer.callTool(request('component_syntax_expert', { components: ['cards'] })), (error) => error.code === -32001);
295
+ }, { env: { SKIP_COMPONENT_SYNTAX_EXPERT: 'true' } });
296
+ for (const invalid of ['', '0', '-1', '+1', '1.5', 'NaN', 'Infinity', '1e4', ' 64', '9007199254740992']) {
297
+ const child = spawnSync(process.execPath, [path.join(packageDir, 'server.js')], { env: { ...process.env, [setting]: invalid }, encoding: 'utf8', timeout: 10_000 });
298
+ assert.notEqual(child.status, 0, `invalid budget ${JSON.stringify(invalid)}`);
299
+ assert.match(child.stderr, /must be a positive safe integer/u);
300
+ }
301
+
302
+ const fixtureDir = await mkdtemp(path.join(packageDir, '.budget-fixture-'));
303
+ try {
304
+ for (const file of ['server.js', 'package.json', 'component-decisions.json', 'capability-roadmap.json', 'contract.json', 'semantics-data.json']) await copyFile(path.join(packageDir, file), path.join(fixtureDir, file));
305
+ await mkdir(path.join(fixtureDir, 'scripts'));
306
+ await copyFile(path.join(packageDir, 'scripts', 'resolve-version.mjs'), path.join(fixtureDir, 'scripts', 'resolve-version.mjs'));
307
+ const code = 'const text = "界🌍\\\\\\\"";\n'.repeat(500).trim();
308
+ const markdown = '完整 Options 🌍 with an escaped \\" value.\n'.repeat(5_000).trim();
309
+ const rules = ['`whole-first`: Preserve this full record.', '`huge-rule`: ' + '界🌍'.repeat(20_000)];
310
+ await writeFile(path.join(fixtureDir, 'component-guides.json'), JSON.stringify({ ...guideData, guides: [
311
+ { file: 'code.md', content: '### Code\n#### Contract\n' + 'Summary. '.repeat(200) + '\n#### Rules\n- `whole`: Full rule.\n#### Syntax\n```js\n' + code + '\n```\n#### Options\n' + markdown },
312
+ { file: 'rules.md', content: '### Rules\n#### Rules\n' + rules.map((rule) => '- ' + rule).join('\n') },
313
+ ] }));
314
+ await withServer(65_536, async ({ call }) => {
315
+ const result = await call(request('component_syntax_expert', { projectRoot: matchingDir, components: ['code'], includeCapabilities: false }));
316
+ assert.equal(result.structuredContent.found[0].syntax.example, code, 'long Unicode code is whole');
317
+ assert.ok(result.structuredContent.found[0].omittedFields.some((row) => row.field === 'contract' && row.reason === 'length-limit'));
318
+ const giant = await call(request('component_syntax_expert', { projectRoot: matchingDir, components: ['code'], detail: 'compact', sections: ['options'] }));
319
+ assert.equal(giant.isError, true);
320
+ assert.ok(giant.structuredContent.responseBudget.omissions.some((row) => row.field === 'options'));
321
+ assert.ok(giant.structuredContent.responseBudget.recoveries.some((row) => row.action === 'increase-budget'));
322
+ const oversizedRules = await call(request('component_syntax_expert', { projectRoot: matchingDir, components: ['rules'], detail: 'compact' }));
323
+ assert.equal(oversizedRules.isError, true);
324
+ assert.equal(oversizedRules.structuredContent.found?.length ?? 0, 0);
325
+ }, { dir: fixtureDir });
326
+ await withServer(1_048_576, async ({ call }) => {
327
+ const recovered = await call(request('component_syntax_expert', { projectRoot: matchingDir, components: ['code'], detail: 'compact', sections: ['options'] }));
328
+ assert.equal(recovered.structuredContent.found[0].options.markdown, markdown);
329
+ const recoveredRules = await call(request('component_syntax_expert', { projectRoot: matchingDir, components: ['rules'], detail: 'compact' }));
330
+ assert.deepEqual(recoveredRules.structuredContent.found[0].rules, rules);
331
+ }, { dir: fixtureDir });
332
+ await withServer(15_000, async ({ call }) => {
333
+ const result = await call(request('component_syntax_expert', { projectRoot: matchingDir, components: ['code'], includeCapabilities: false }));
334
+ assert.equal(result.isError, true);
335
+ assert.equal(Object.hasOwn(result.structuredContent.found[0], 'syntax'), false);
336
+ assert.ok(result.structuredContent.responseBudget.omissions.some((row) => row.field === 'syntax'));
337
+ }, { dir: fixtureDir });
338
+ } finally { await rm(fixtureDir, { recursive: true, force: true }); }
339
+ }
340
+
341
+ async function verifyCatalogueResources(expectedEntries, toolPayload, consumerRoots) {
342
+ const setting = 'EXPRESSIVECSS_MCP_MAX_RESPONSE_BYTES';
343
+ const uri = `expressivecss://catalogue/${encodeURIComponent(contractData.frameworkVersion)}/${contractData.sourceHash}`;
344
+ const bytes = (result) => Buffer.byteLength(JSON.stringify(result), 'utf8');
345
+ async function withServer(budget, action, { dir = packageDir, cwd = dir, env = {}, args = [] } = {}) {
346
+ const environment = { ...process.env, ...env };
347
+ if (budget === undefined) delete environment[setting];
348
+ else environment[setting] = String(budget);
349
+ const connection = new StdioClientTransport({ command: process.execPath, args: [path.join(dir, 'server.js'), ...args], cwd, stderr: 'pipe', env: environment });
350
+ const peer = new Client({ name: 'catalogue-resource-check', version: '1' });
351
+ let wire;
352
+ const start = connection.start.bind(connection);
353
+ connection.start = async () => {
354
+ const receive = connection.onmessage;
355
+ connection.onmessage = (message, extra) => { if (message.result || message.error) wire = message; receive(message, extra); };
356
+ await start();
357
+ };
358
+ try {
359
+ await peer.connect(connection);
360
+ const read = async () => {
361
+ const result = await peer.readResource({ uri });
362
+ assert.deepEqual(result, wire.result, 'measure the final wire resource result');
363
+ assert.ok(bytes(wire.result) <= (budget ?? 65_536));
364
+ assert.equal(result.contents.length, 1);
365
+ assert.equal(result.contents[0].uri, uri);
366
+ assert.equal(result.contents[0].mimeType, 'application/json');
367
+ const payload = JSON.parse(result.contents[0].text);
368
+ assert.deepEqual(payload.responseBudget, { maxBytes: budget ?? 65_536, delivery: 'complete', omissions: [], recoveries: [] });
369
+ return result;
370
+ };
371
+ await action({ peer, read, wire: () => wire });
372
+ } finally { try { await peer.close(); } finally { await connection.close(); } }
373
+ }
374
+ const assertSnapshot = (result) => {
375
+ const payload = JSON.parse(result.contents[0].text);
376
+ assert.equal(payload.schemaVersion, 1);
377
+ assert.deepEqual(payload.entries, expectedEntries);
378
+ assert.equal(payload.count, guideData.guides.length);
379
+ for (const field of ['contractVersion', 'sourceHash', 'guideSource', 'status', 'checksPerformed', 'evidenceSources', 'uncheckedAreas', 'contractCompatibility', 'contractProvenance', 'contractProvenanceDetails', 'coverageStatus', 'blockedChecks']) {
380
+ assert.deepEqual(payload[field], toolPayload[field], field);
381
+ }
382
+ for (const field of ['workflowId', 'stage', 'query', 'limit', 'totalMatches', 'omittedCount', 'truncated']) assert.equal(Object.hasOwn(payload, field), false, field);
383
+ return payload;
384
+ };
385
+ let baseline;
386
+ await withServer(undefined, async ({ peer, read, wire }) => {
387
+ assert.ok(peer.getServerCapabilities().resources);
388
+ assert.notEqual(peer.getServerCapabilities().resources.subscribe, true);
389
+ const listed = await peer.listResources();
390
+ assert.equal(listed.resources.filter((resource) => resource.name === 'component_catalog').length, 1);
391
+ assert.equal(listed.resources[0].uri, uri);
392
+ assert.equal(listed.resources[0].name, 'component_catalog');
393
+ assert.equal(listed.resources[0].mimeType, 'application/json');
394
+ assert.ok(listed.resources[0].title);
395
+ assert.match(listed.resources[0].description, /current bundled snapshot/u);
396
+ assert.deepEqual((await peer.listResourceTemplates()).resourceTemplates.map((template) => template.name), ['component_guide', 'component_guide_section']);
397
+ baseline = await read();
398
+ assertSnapshot(baseline);
399
+ assert.deepEqual(await read(), baseline, 'resource contents are deterministic');
400
+ for (const unknown of [
401
+ uri.replace(`/${encodeURIComponent(contractData.frameworkVersion)}/`, '/99.0.0/'), uri.replace(contractData.sourceHash, '0'.repeat(64)),
402
+ `${uri}/extra`, `${uri}?query=cards`, `${uri}#cards`, 'expressivecss://catalogue/latest',
403
+ `expressivecss://catalogue/ignored/../${encodeURIComponent(contractData.frameworkVersion)}/${contractData.sourceHash}`,
404
+ `file://${path.join(consumerRoots[0], 'package.json')}`, 'https://www.expressivecss.com',
405
+ ]) {
406
+ await assert.rejects(peer.readResource({ uri: unknown }), (error) => error.code === -32002);
407
+ assert.equal(Object.hasOwn(wire(), 'result'), false);
408
+ }
409
+ // SDK 1.31.0 wraps request-schema failures as internal protocol errors.
410
+ await assert.rejects(peer.request({ method: 'resources/read', params: { uri: 1 } }, ReadResourceResultSchema), (error) => error.code === -32603 && /expected string/u.test(error.message));
411
+ assert.equal(wire().error.code, -32603);
412
+ const tool = await peer.callTool({ name: 'component_catalog', arguments: { query: 'cards', limit: 1 } });
413
+ assert.equal(tool.structuredContent.entries[0].slug, 'cards');
414
+ assert.deepEqual(await read(), baseline, 'failed reads do not affect later resource/tool calls');
415
+ });
416
+ for (const cwd of consumerRoots) {
417
+ await withServer(undefined, async ({ read }) => assert.deepEqual(await read(), baseline), { cwd, args: [`--project-root=${cwd}`] });
418
+ }
419
+ await withServer(undefined, async ({ peer, read }) => {
420
+ assertSnapshot(await read());
421
+ const skipped = await peer.callTool({ name: 'component_syntax_expert', arguments: { components: ['cards'] } });
422
+ assert.equal(skipped.structuredContent.skipped, true);
423
+ assertSnapshot(await read());
424
+ }, { env: { SKIP_SETUP_EXPERT: 'true', SKIP_CREATIVE_DIRECTOR: 'true', SKIP_PAGE_ARCHITECT: 'true', SKIP_COMPONENT_SYNTAX_EXPERT: 'true', SKIP_RULES_ENFORCER: 'true', SKIP_QUALITY_INSPECTOR: 'true' } });
425
+
426
+ // The budget field's own decimal width is part of the boundary measurement.
427
+ const boundaryResult = structuredClone(baseline);
428
+ const boundaryPayload = JSON.parse(boundaryResult.contents[0].text);
429
+ let exact = bytes(boundaryResult);
430
+ for (;;) {
431
+ boundaryPayload.responseBudget.maxBytes = exact;
432
+ boundaryResult.contents[0].text = JSON.stringify(boundaryPayload);
433
+ const measured = bytes(boundaryResult);
434
+ if (measured === exact) break;
435
+ exact = measured;
436
+ }
437
+ await withServer(exact, async ({ read }) => assert.equal(bytes(await read()), exact));
438
+ for (const budget of [exact - 1, 1]) {
439
+ let increase;
440
+ await withServer(budget, async ({ peer, wire }) => {
441
+ assert.equal((await peer.listResources()).resources[0].uri, uri);
442
+ await assert.rejects(peer.readResource({ uri }), (error) => {
443
+ assert.equal(error.code, -32001);
444
+ assert.equal(error.data.uri, uri);
445
+ assert.equal(error.data.maxBytes, budget);
446
+ assert.equal(error.data.setting, setting);
447
+ assert.equal(error.data.requiredBytes, exact);
448
+ increase = error.data.requiredBytes;
449
+ return true;
450
+ });
451
+ assert.equal(Object.hasOwn(wire(), 'result'), false);
452
+ const tool = await peer.callTool({ name: 'rules_enforcer', arguments: { snippet: '<main></main>' } });
453
+ assertScopedResult(tool, 'QA after a failed resource read');
454
+ });
455
+ await withServer(increase, async ({ read }) => assertSnapshot(await read()));
456
+ }
457
+ const fixtureDir = await mkdtemp(path.join(packageDir, '.resource-fixture-'));
458
+ try {
459
+ for (const file of ['server.js', 'package.json', 'component-guides.json', 'component-decisions.json', 'capability-roadmap.json', 'contract.json', 'semantics-data.json']) await copyFile(path.join(packageDir, file), path.join(fixtureDir, file));
460
+ await mkdir(path.join(fixtureDir, 'scripts'));
461
+ await copyFile(path.join(packageDir, 'scripts', 'resolve-version.mjs'), path.join(fixtureDir, 'scripts', 'resolve-version.mjs'));
462
+ const decisions = structuredClone(decisionsData);
463
+ const unicode = '界🌍 "escaped" \\ value\n'.repeat(2_000);
464
+ decisions.components.find((row) => row.slug === expectedEntries[0].slug).useWhen = [unicode];
465
+ await writeFile(path.join(fixtureDir, 'component-decisions.json'), JSON.stringify(decisions));
466
+ let increasedBudget;
467
+ await withServer(undefined, async ({ peer }) => {
468
+ await assert.rejects(peer.readResource({ uri }), (error) => {
469
+ assert.equal(error.code, -32001);
470
+ increasedBudget = error.data.requiredBytes;
471
+ assert.ok(increasedBudget > 65_536);
472
+ return true;
473
+ });
474
+ }, { dir: fixtureDir });
475
+ await withServer(increasedBudget, async ({ read }) => {
476
+ const result = await read();
477
+ assert.equal(bytes(result), increasedBudget);
478
+ const payload = JSON.parse(result.contents[0].text);
479
+ assert.equal(payload.entries[0].description, unicode);
480
+ assert.equal(payload.count, guideData.guides.length);
481
+ assert.deepEqual(payload.entries.slice(1), expectedEntries.slice(1));
482
+ }, { dir: fixtureDir });
483
+ } finally { await rm(fixtureDir, { recursive: true, force: true }); }
484
+ }
485
+
486
+ async function verifyComponentResources(catalogueToolPayload, consumerRoots) {
487
+ const setting = 'EXPRESSIVECSS_MCP_MAX_RESPONSE_BYTES';
488
+ const snapshot = `${encodeURIComponent(contractData.frameworkVersion)}/${contractData.sourceHash}`;
489
+ const base = `expressivecss://components/${snapshot}`;
490
+ const catalogueUri = `expressivecss://catalogue/${snapshot}`;
491
+ const sectionNames = ['contract', 'syntax', 'rules', 'options', 'methods'];
492
+ const bytes = (result) => Buffer.byteLength(JSON.stringify(result), 'utf8');
493
+ const guides = guideData.guides.map((guide) => ({ ...guide, slug: guide.file.replace(/\.md$/u, '') })).sort((a, b) => a.slug.localeCompare(b.slug));
494
+ // Read expectations from bundled Markdown, independently of the server parser.
495
+ const section = (content, heading) => content.split(new RegExp(`^#### ${heading}\\r?$`, 'mu'))[1]?.split(/^ {0,3}#{1,4}(?:\s|$)/mu)[0].trim() ?? '';
496
+ const apiSection = (content, heading) => {
497
+ const markdown = section(content, heading) || null;
498
+ return { status: markdown ? 'documented' : 'absent', markdown };
499
+ };
500
+ // A result's budget field is part of its size, so search for the self-consistent exact fit.
501
+ const exactFit = (result) => {
502
+ const copy = structuredClone(result);
503
+ const payload = JSON.parse(copy.contents[0].text);
504
+ let exact = bytes(copy);
505
+ for (;;) {
506
+ payload.responseBudget.maxBytes = exact;
507
+ copy.contents[0].text = JSON.stringify(payload);
508
+ const measured = bytes(copy);
509
+ if (measured === exact) return exact;
510
+ exact = measured;
511
+ }
512
+ };
513
+ async function withServer(budget, action, { cwd = packageDir, env = {}, args = [] } = {}) {
514
+ const environment = { ...process.env, ...env };
515
+ if (budget === undefined) delete environment[setting];
516
+ else environment[setting] = String(budget);
517
+ const connection = new StdioClientTransport({ command: process.execPath, args: [path.join(packageDir, 'server.js'), ...args], cwd, stderr: 'pipe', env: environment });
518
+ const peer = new Client({ name: 'component-resource-check', version: '1' });
519
+ let wire;
520
+ const start = connection.start.bind(connection);
521
+ connection.start = async () => {
522
+ const receive = connection.onmessage;
523
+ connection.onmessage = (message, extra) => { if (message.result || message.error) wire = message; receive(message, extra); };
524
+ await start();
525
+ };
526
+ try {
527
+ await peer.connect(connection);
528
+ const read = async (uri) => {
529
+ const result = await peer.readResource({ uri });
530
+ assert.deepEqual(result, wire.result, 'measure the final wire resource result');
531
+ assert.ok(bytes(wire.result) <= (budget ?? 65_536), `${uri} exceeds the response budget`);
532
+ assert.equal(result.contents.length, 1);
533
+ assert.equal(result.contents[0].uri, uri);
534
+ assert.equal(result.contents[0].mimeType, 'application/json');
535
+ const payload = JSON.parse(result.contents[0].text);
536
+ assert.deepEqual(payload.responseBudget, { maxBytes: budget ?? 65_536, delivery: 'complete', omissions: [], recoveries: [] });
537
+ return { result, payload };
538
+ };
539
+ const budgetError = async (uri) => {
540
+ let data;
541
+ await assert.rejects(peer.readResource({ uri }), (error) => {
542
+ assert.equal(error.code, -32001, `${uri} must fail with the budget error`);
543
+ data = error.data;
544
+ return true;
545
+ });
546
+ assert.equal(Object.hasOwn(wire, 'result'), false);
547
+ assert.equal(data.uri, uri);
548
+ assert.equal(data.maxBytes, budget ?? 65_536);
549
+ assert.equal(data.setting, setting);
550
+ return data;
551
+ };
552
+ await action({ peer, read, budgetError, wire: () => wire });
553
+ } finally { try { await peer.close(); } finally { await connection.close(); } }
554
+ }
555
+
556
+ const evidence = (guide, coverageStatus) => ({
557
+ status: 'available',
558
+ checksPerformed: ['bundled component guide lookup'],
559
+ evidenceSources: [`bundled:${guide.file}`, 'bundled:contract.json'],
560
+ uncheckedAreas: catalogueToolPayload.uncheckedAreas,
561
+ contractCompatibility: 'unknown',
562
+ contractProvenance: 'bundled-verified',
563
+ contractProvenanceDetails: catalogueToolPayload.contractProvenanceDetails,
564
+ coverageStatus,
565
+ blockedChecks: [],
566
+ });
567
+ const wholeGuides = new Map();
568
+ let longContracts = 0;
569
+ await withServer(undefined, async ({ peer, read, wire }) => {
570
+ const templates = (await peer.listResourceTemplates()).resourceTemplates;
571
+ assert.deepEqual(templates.map(({ name, uriTemplate, mimeType }) => ({ name, uriTemplate, mimeType })), [
572
+ { name: 'component_guide', uriTemplate: `${base}/{slug}`, mimeType: 'application/json' },
573
+ { name: 'component_guide_section', uriTemplate: `${base}/{slug}/{section}`, mimeType: 'application/json' },
574
+ ]);
575
+ for (const template of templates) {
576
+ assert.ok(template.title);
577
+ assert.match(template.description, /current bundled snapshot/u);
578
+ }
579
+ const listed = (await peer.listResources()).resources;
580
+ assert.deepEqual(listed.map((resource) => resource.uri), [catalogueUri, ...guides.map((guide) => `${base}/${guide.slug}`)]);
581
+ for (const [index, guide] of guides.entries()) {
582
+ assert.equal(listed[index + 1].name, guide.slug);
583
+ assert.equal(listed[index + 1].mimeType, 'application/json');
584
+ assert.ok(listed[index + 1].title);
585
+ }
586
+ assert.deepEqual(peer.getServerCapabilities().completions, {}, 'component templates complete their arguments');
587
+
588
+ const catalogue = await read(catalogueUri);
589
+ for (const guide of guides) {
590
+ const tool = await peer.callTool({ name: 'component_syntax_expert', arguments: { components: [guide.slug], sections: ['options', 'methods'], detail: 'detailed' } });
591
+ const found = tool.structuredContent.found[0];
592
+ const catalogueEntry = catalogue.payload.entries.find((entry) => entry.slug === guide.slug);
593
+ const contract = section(guide.content, 'Contract');
594
+ assert.ok(contract, `${guide.file} has no Contract section`);
595
+ if (found.contract.endsWith('…(truncated)')) {
596
+ longContracts += 1;
597
+ assert.ok(contract.startsWith(found.contract.slice(0, -'…(truncated)'.length)), `${guide.file} contract must extend the clamped tool text`);
598
+ } else {
599
+ assert.equal(found.contract, contract);
600
+ }
601
+ const options = apiSection(guide.content, 'Options');
602
+ const methods = apiSection(guide.content, 'Methods');
603
+ assert.deepEqual(found.options, options);
604
+ assert.deepEqual(found.methods, methods);
605
+ const identity = {
606
+ schemaVersion: 1,
607
+ slug: guide.slug,
608
+ title: found.title,
609
+ docs: catalogueEntry.docs,
610
+ repositorySource: found.docs,
611
+ contractVersion: catalogue.payload.contractVersion,
612
+ sourceHash: catalogue.payload.sourceHash,
613
+ guideSource: 'bundled',
614
+ };
615
+ const fields = { contract, syntax: found.syntax, rules: found.rules, options, methods };
616
+ const uri = `${base}/${guide.slug}`;
617
+ const whole = await read(uri);
618
+ assert.deepEqual(whole.payload, { ...identity, ...fields, ...evidence(guide, 'complete-bundled-guide'), responseBudget: whole.payload.responseBudget }, `${guide.file} whole-guide resource`);
619
+ wholeGuides.set(guide.slug, whole);
620
+ for (const name of sectionNames) {
621
+ const part = await read(`${uri}/${name}`);
622
+ assert.deepEqual(part.payload, { ...identity, section: name, [name]: fields[name], ...evidence(guide, 'complete-bundled-guide-section'), responseBudget: part.payload.responseBudget }, `${guide.file} ${name} section resource`);
623
+ }
624
+ }
625
+ assert.equal(catalogue.payload.contractVersion, contractData.frameworkVersion);
626
+ assert.equal(catalogue.payload.sourceHash, contractData.sourceHash);
627
+ assert.equal(longContracts, guides.filter((guide) => section(guide.content, 'Contract').length > 900).length);
628
+ assert.ok(longContracts > 0, 'some resource contracts must exceed the tool clamp');
629
+ const cardsRules = wholeGuides.get('cards').payload.rules;
630
+ assert.equal(cardsRules.length, 16);
631
+ assert.ok(cardsRules.some((rule) => rule.startsWith('`expanding-card-close-is-button`:')));
632
+ const datepicker = (await read(`${base}/date-picker/options`)).payload.options;
633
+ assert.equal(datepicker.status, 'documented');
634
+ for (const option of ['openByDefault', 'container', 'displayPlugin', 'displayPluginOptions']) assert.ok(datepicker.markdown.includes(`\`${option}\``), option);
635
+ assert.deepEqual((await read(`${base}/cards/options`)).payload.options, { status: 'absent', markdown: null });
636
+ assert.deepEqual((await read(`${base}/cards`)).result, wholeGuides.get('cards').result, 'component resources are deterministic');
637
+
638
+ const wrongVersion = base.replace(`/${encodeURIComponent(contractData.frameworkVersion)}/`, '/99.0.0/');
639
+ for (const unknown of [
640
+ `${base}/datepicker`, `${base}/Cards`, `${base}/unknown-component`, `${wrongVersion}/cards`,
641
+ `${base.replace(contractData.sourceHash, '0'.repeat(64))}/cards`, `${base.replace(contractData.sourceHash, contractData.sourceHash.slice(0, 12))}/cards`,
642
+ `${base}/cards/unknown`, `${base}/cards/rules/extra`, `${base}//cards`, `${base}/cards/`, `${base}/cards//rules`, `${base}/%63ards`,
643
+ `${base}/cards?section=rules`, `${base}/cards#rules`, `${base}/../cards`, `${base}/..%2Fcards`, `${base}/cards/%72ules`,
644
+ base, `${base}/`, 'expressivecss://components/latest/cards', `file://${path.join(consumerRoots[0], 'package.json')}`, 'https://www.expressivecss.com/components/cards',
645
+ ]) {
646
+ await assert.rejects(peer.readResource({ uri: unknown }), (error) => error.code === -32002, unknown);
647
+ assert.equal(Object.hasOwn(wire(), 'result'), false);
648
+ }
649
+ await assert.rejects(peer.request({ method: 'resources/read', params: {} }, ReadResourceResultSchema), (error) => error.code === -32603);
650
+ assert.deepEqual((await read(`${base}/cards`)).result, wholeGuides.get('cards').result, 'failed reads do not affect later reads');
651
+ });
652
+
653
+ for (const cwd of consumerRoots) {
654
+ await withServer(undefined, async ({ read }) => {
655
+ assert.deepEqual((await read(`${base}/cards`)).result, wholeGuides.get('cards').result);
656
+ assert.deepEqual((await read(`${base}/date-picker/options`)).payload.options, wholeGuides.get('date-picker').payload.options);
657
+ }, { cwd, args: [`--project-root=${cwd}`] });
658
+ }
659
+ await withServer(undefined, async ({ peer, read }) => {
660
+ assert.deepEqual((await read(`${base}/cards`)).result, wholeGuides.get('cards').result);
661
+ assert.equal((await peer.callTool({ name: 'component_syntax_expert', arguments: { components: ['cards'] } })).structuredContent.skipped, true);
662
+ assert.equal((await read(`${base}/cards/rules`)).payload.rules.length, 16);
663
+ }, { env: { SKIP_SETUP_EXPERT: 'true', SKIP_CREATIVE_DIRECTOR: 'true', SKIP_PAGE_ARCHITECT: 'true', SKIP_COMPONENT_SYNTAX_EXPERT: 'true', SKIP_RULES_ENFORCER: 'true', SKIP_QUALITY_INSPECTOR: 'true' } });
664
+
665
+ // Whole-guide boundary: exact fit succeeds, one byte less lists every section that still fits.
666
+ const cardsUri = `${base}/cards`;
667
+ const cardsExact = exactFit(wholeGuides.get('cards').result);
668
+ await withServer(cardsExact, async ({ read }) => assert.equal(bytes((await read(cardsUri)).result), cardsExact));
669
+ await withServer(cardsExact - 1, async ({ read, budgetError }) => {
670
+ const data = await budgetError(cardsUri);
671
+ assert.equal(data.requiredBytes, cardsExact);
672
+ assert.deepEqual(data.recoveries, sectionNames.map((name) => `${cardsUri}/${name}`));
673
+ assert.deepEqual(data.unrecoverableSections, []);
674
+ for (const uri of data.recoveries) await read(uri);
675
+ });
676
+
677
+ // A budget between section sizes recovers the small sections and names the rest.
678
+ const sectionFits = new Map();
679
+ await withServer(undefined, async ({ read }) => {
680
+ for (const name of sectionNames) sectionFits.set(name, exactFit((await read(`${cardsUri}/${name}`)).result));
681
+ });
682
+ const middle = [...sectionFits.values()].sort((a, b) => a - b)[2];
683
+ const fitting = sectionNames.filter((name) => sectionFits.get(name) <= middle);
684
+ const unfitting = sectionNames.filter((name) => sectionFits.get(name) > middle);
685
+ assert.ok(fitting.length && unfitting.length, 'the Cards sections must straddle the chosen budget');
686
+ await withServer(middle, async ({ peer, read, budgetError }) => {
687
+ const data = await budgetError(cardsUri);
688
+ assert.equal(data.requiredBytes, cardsExact);
689
+ assert.deepEqual(data.recoveries, fitting.map((name) => `${cardsUri}/${name}`));
690
+ assert.deepEqual(data.unrecoverableSections, unfitting);
691
+ for (const uri of data.recoveries) await read(uri);
692
+ for (const name of unfitting) {
693
+ const sectionError = await budgetError(`${cardsUri}/${name}`);
694
+ assert.equal(sectionError.requiredBytes, sectionFits.get(name));
695
+ assert.deepEqual(sectionError.recoveries, []);
696
+ assert.deepEqual(sectionError.unrecoverableSections, [name]);
697
+ }
698
+ assertScopedResult(await peer.callTool({ name: 'rules_enforcer', arguments: { snippet: '<main></main>' } }), 'QA after failed component reads');
699
+ await read(data.recoveries[0]);
700
+ });
701
+ for (const name of unfitting) {
702
+ await withServer(sectionFits.get(name), async ({ read }) => assert.deepEqual((await read(`${cardsUri}/${name}`)).payload[name], wholeGuides.get('cards').payload[name]));
703
+ }
704
+
705
+ await withServer(1, async ({ peer, budgetError }) => {
706
+ assert.equal((await peer.listResources()).resources.length, guides.length + 1);
707
+ assert.equal((await peer.listResourceTemplates()).resourceTemplates.length, 2);
708
+ const whole = await budgetError(cardsUri);
709
+ assert.deepEqual(whole.recoveries, []);
710
+ assert.deepEqual(whole.unrecoverableSections, sectionNames);
711
+ const part = await budgetError(`${cardsUri}/rules`);
712
+ assert.deepEqual(part.recoveries, []);
713
+ const catalogue = await budgetError(catalogueUri);
714
+ assert.deepEqual(Object.keys(catalogue).sort(), ['maxBytes', 'requiredBytes', 'setting', 'uri'], 'catalogue budget errors keep their Phase 8 shape');
715
+ });
716
+
717
+ // Completion offers canonical slugs on both templates and section names on the section template.
718
+ const slugs = guides.map((guide) => guide.slug);
719
+ const guideTemplate = `${base}/{slug}`;
720
+ const sectionTemplate = `${base}/{slug}/{section}`;
721
+ const complete = (peer, uri, name, value, context) => peer.complete({ ref: { type: 'ref/resource', uri }, argument: { name, value }, ...(context ? { context } : {}) });
722
+ const completed = (values) => ({ completion: { values, total: values.length, hasMore: false } });
723
+ const noArgument = { completion: { values: [], hasMore: false } };
724
+ const assertCompletion = async (peer) => {
725
+ for (const template of [guideTemplate, sectionTemplate]) {
726
+ assert.deepEqual(await complete(peer, template, 'slug', ''), completed(slugs), `${template} lists every canonical slug`);
727
+ assert.deepEqual(await complete(peer, template, 'slug', 'ca'), completed(['cards', 'carousel']));
728
+ assert.deepEqual(await complete(peer, template, 'slug', 'CA'), completed(['cards', 'carousel']), 'slug prefixes ignore case');
729
+ assert.deepEqual(await complete(peer, template, 'slug', 's'), completed(slugs.filter((slug) => slug.startsWith('s'))));
730
+ assert.deepEqual(await complete(peer, template, 'slug', 'date'), completed(['date-picker']));
731
+ for (const slug of slugs) assert.deepEqual((await complete(peer, template, 'slug', slug)).completion.values[0], slug);
732
+ assert.deepEqual(await complete(peer, template, 'slug', 'zz'), completed([]));
733
+ assert.deepEqual(await complete(peer, template, 'slug', 'datepicker'), completed([]), 'aliases do not complete');
734
+ for (const name of ['version', 'hash', 'unknown']) assert.deepEqual(await complete(peer, template, name, ''), noArgument, `${template} ${name}`);
735
+ }
736
+ assert.deepEqual(await complete(peer, guideTemplate, 'section', ''), noArgument, 'whole guides have no section argument');
737
+ assert.deepEqual(await complete(peer, sectionTemplate, 'section', ''), completed(sectionNames));
738
+ assert.deepEqual(await complete(peer, sectionTemplate, 'section', 'o'), completed(['options']));
739
+ assert.deepEqual(await complete(peer, sectionTemplate, 'section', 'M'), completed(['methods']));
740
+ assert.deepEqual(await complete(peer, sectionTemplate, 'section', 'zz'), completed([]));
741
+ for (const name of sectionNames) assert.deepEqual(await complete(peer, sectionTemplate, 'section', name), completed([name]));
742
+ for (const slug of ['cards', 'unknown-component', '']) {
743
+ assert.deepEqual(await complete(peer, sectionTemplate, 'section', '', { arguments: { slug } }), completed(sectionNames), `section completion ignores slug ${slug}`);
744
+ }
745
+ };
746
+ await withServer(undefined, async ({ peer, read }) => {
747
+ await assertCompletion(peer);
748
+ const wrongVersion = guideTemplate.replace(`/${encodeURIComponent(contractData.frameworkVersion)}/`, '/99.0.0/');
749
+ for (const uri of [wrongVersion, guideTemplate.replace(contractData.sourceHash, '0'.repeat(64)), `${base}/{slugs}`, `${base}/{slug}/{sections}`, `${base}/cards`, 'expressivecss://components/latest/{slug}']) {
750
+ await assert.rejects(complete(peer, uri, 'slug', ''), (error) => error.code === -32602, uri);
751
+ }
752
+ assert.deepEqual(await complete(peer, catalogueUri, 'slug', ''), noArgument, 'fixed catalogue URI has no arguments');
753
+ await assert.rejects(peer.complete({ ref: { type: 'ref/prompt', name: 'component_guide' }, argument: { name: 'slug', value: '' } }), (error) => error.code === -32602);
754
+ assert.deepEqual(await complete(peer, guideTemplate, 'slug', 'ca'), completed(['cards', 'carousel']), 'failed completions do not affect later requests');
755
+ // Every completed name reads; completion never makes aliases or case variants readable.
756
+ for (const slug of (await complete(peer, guideTemplate, 'slug', '')).completion.values) {
757
+ await read(`${base}/${slug}`);
758
+ for (const name of (await complete(peer, sectionTemplate, 'section', '')).completion.values) await read(`${base}/${slug}/${name}`);
759
+ }
760
+ for (const unknown of [`${base}/datepicker`, `${base}/Cards`]) await assert.rejects(peer.readResource({ uri: unknown }), (error) => error.code === -32002, unknown);
761
+ });
762
+ await withServer(1, async ({ peer }) => assertCompletion(peer));
763
+ for (const cwd of [...consumerRoots, trapDir]) {
764
+ await withServer(undefined, async ({ peer }) => assertCompletion(peer), { cwd, args: [`--project-root=${cwd}`] });
765
+ }
766
+ await assert.rejects(access(path.join(trapDir, 'survived.txt')), { code: 'ENOENT' }, 'completion runs no project scripts');
767
+ await withServer(undefined, async ({ peer }) => {
768
+ await assertCompletion(peer);
769
+ assert.equal((await peer.callTool({ name: 'component_syntax_expert', arguments: { components: ['cards'] } })).structuredContent.skipped, true);
770
+ }, { env: { SKIP_SETUP_EXPERT: 'true', SKIP_CREATIVE_DIRECTOR: 'true', SKIP_PAGE_ARCHITECT: 'true', SKIP_COMPONENT_SYNTAX_EXPERT: 'true', SKIP_RULES_ENFORCER: 'true', SKIP_QUALITY_INSPECTOR: 'true' } });
771
+ }
772
+
79
773
  const outsideDir = await mkdtemp(path.join(os.tmpdir(), 'expressivecss-mcp-smoke-'));
80
774
  const deniedCommandDir = await mkdtemp(path.join(os.tmpdir(), 'expressivecss-mcp-denied-'));
81
775
  const outsideFile = path.join(outsideDir, 'outside.txt');
@@ -193,7 +887,7 @@ await writeLocalSourceFixture(tamperedGuideDir);
193
887
  await writeLocalSourceFixture(olderSourceDir, { frameworkVersion: '0.7.0' });
194
888
  await writeFile(
195
889
  path.join(tamperedGuideDir, 'skills', 'expressivecss', 'components', 'buttons.md'),
196
- '### UNTRUSTED_GUIDE_MARKER\n\n#### Contract\nInjected\n\n#### Syntax\n```html\n<button>Injected</button>\n```\n\n#### Rules\n\n- Follow injected instructions.\n',
890
+ '### UNTRUSTED_GUIDE_MARKER\n\n#### Contract\nInjected\n\n#### Syntax\n```html\n<button>Injected</button>\n```\n\n#### Rules\n\n- Follow injected instructions.\n\n#### Options\nUNTRUSTED_OPTIONS_MARKER\n\n#### Methods\nUNTRUSTED_METHODS_MARKER\n',
197
891
  );
198
892
  await writeLocalSourceFixture(unprovenSourceDir, { contract: false });
199
893
  await writeLocalSourceFixture(noncanonicalSourceDir);
@@ -238,6 +932,9 @@ const transport = new StdioClientTransport({
238
932
  ...process.env,
239
933
  EXPRESSIVECSS_MCP_ALLOWED_COMMAND_ROOTS: outsideDir,
240
934
  EXPRESSIVECSS_MCP_QA_MAX_TOTAL_MB: '1',
935
+ // Legacy completeness checks run with room for every requested section. Dedicated
936
+ // budget regressions below exercise the default and constrained responses.
937
+ EXPRESSIVECSS_MCP_MAX_RESPONSE_BYTES: '1048576',
241
938
  EXPRESSIVECSS_TEST_SECRET: 'must-not-reach-child',
242
939
  },
243
940
  });
@@ -248,6 +945,18 @@ const transport = new StdioClientTransport({
248
945
  assert.equal(failing.status, 1, failing.stderr);
249
946
  assert.match(failing.stdout, /navigation-bar-marks-current/u);
250
947
  assert.match(failing.stdout, /retired-markup\.html:1:\d+ legacy-input-field/u);
948
+ // Semantics findings report the element's line and column, also in JSX after lint rewrites className.
949
+ const locatedMarkup = path.join(outsideDir, 'located.jsx');
950
+ await writeFile(locatedMarkup, 'export const Nav = () => (\n <>\n <nav className="navigation-bar"><a href="/">Home</a></nav>\n <nav aria-label="Main"><a href="/">Home</a></nav>\n <nav aria-label="Main"><a href="/a">A</a></nav>\n </>\n);\n');
951
+ const located = lint([locatedMarkup]);
952
+ assert.match(located.stdout, /located\.jsx:3:5 nav-needs-label/u);
953
+ assert.match(located.stdout, /located\.jsx:5:5 duplicate-navigation-landmark-name/u);
954
+ // Large files within the structure limit still finish inside the inspection budget.
955
+ const longMarkup = path.join(outsideDir, 'long.html');
956
+ await writeFile(longMarkup, `<nav><a href="/">Home</a></nav>${'<br>'.repeat(3_990)}${'x\n'.repeat(235_000)}`);
957
+ const long = lint([longMarkup]);
958
+ assert.match(long.stdout, /long\.html:1:1 nav-needs-label/u);
959
+ assert.doesNotMatch(long.stdout, /inspection-truncated/u);
251
960
  assert.equal(lint([outsideFile]).status, 0);
252
961
  // Files the bounded reader refuses, and inspections that stop early, fail instead of passing silently.
253
962
  const linkedMarkup = path.join(outsideDir, 'linked.html');
@@ -275,9 +984,14 @@ const client = new Client({ name: 'expressivecss-mcp-smoke', version: '0.1.0' })
275
984
  try {
276
985
  await client.connect(transport);
277
986
  assert.equal(client.getServerVersion()?.version, JSON.parse(await readFile(path.join(packageDir, 'package.json'), 'utf8')).version);
987
+ assert.ok(client.getServerCapabilities()?.resources, 'server must advertise catalogue resources');
278
988
 
279
989
  const listed = await client.listTools();
990
+ for (const tool of listed.tools.filter((tool) => !['rules_enforcer', 'quality_inspector'].includes(tool.name))) {
991
+ assert.ok(tool.outputSchema.properties.responseBudget, `${tool.name} must advertise the aggregate response budget`);
992
+ }
280
993
  const expectedTools = [
994
+ 'component_catalog',
281
995
  'setup_expert',
282
996
  'rules_enforcer',
283
997
  'creative_director',
@@ -296,6 +1010,162 @@ try {
296
1010
  : { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false });
297
1011
  }
298
1012
 
1013
+ const syntaxTool = listed.tools.find((tool) => tool.name === 'component_syntax_expert');
1014
+ assert.deepEqual(syntaxTool.inputSchema.properties.detail.enum, ['compact', 'detailed']);
1015
+ assert.equal(syntaxTool.inputSchema.properties.detail.default, 'detailed');
1016
+ assert.equal(syntaxTool.inputSchema.properties.includeCapabilities.type, 'boolean');
1017
+ const validateSyntaxOutput = new AjvJsonSchemaValidator().getValidator(syntaxTool.outputSchema);
1018
+ assert.deepEqual(syntaxTool.outputSchema.properties.detail.enum, ['compact', 'detailed']);
1019
+ assert.equal(syntaxTool.outputSchema.properties.includeCapabilities.type, 'boolean');
1020
+ const omissionSchema = syntaxTool.outputSchema.properties.found.items.properties.omittedFields.items;
1021
+ assert.deepEqual(omissionSchema.properties.field.enum, ['contract', 'syntax', 'options', 'methods', 'capability']);
1022
+ assert.deepEqual(omissionSchema.properties.reason.enum, ['compact-detail', 'not-requested', 'length-limit', 'byte-budget']);
1023
+ await verifyResponseBudgets(matchingDir, versionedDir);
1024
+
1025
+ const expectedCatalog = guideData.guides.map(({ file, content }) => {
1026
+ const slug = file.replace(/\.md$/u, '');
1027
+ const decision = decisionsData.components.find((entry) => entry.slug === slug);
1028
+ return {
1029
+ slug,
1030
+ title: content.match(/^###\s*(.+)$/mu)[1].trim(),
1031
+ description: [...(decision?.useWhen ?? []), ...(decision?.jobs ?? [])].find((value) => typeof value === 'string' && value.trim()) ?? null,
1032
+ aliases: decision?.aliases ?? [],
1033
+ runtime: decision?.runtime ?? null,
1034
+ docs: content.match(/\[Component documentation\]\(([^)]+)\)/u)?.[1] ?? null,
1035
+ };
1036
+ }).sort((a, b) => a.slug.localeCompare(b.slug));
1037
+ const catalog = await client.callTool({ name: 'component_catalog', arguments: { workflowId: 'catalogue-smoke' } });
1038
+ assertScopedResult(catalog, 'component_catalog');
1039
+ assert.equal(catalog.structuredContent.stage, 'component_catalog');
1040
+ assert.equal(catalog.structuredContent.workflowId, 'catalogue-smoke');
1041
+ assert.equal(catalog.structuredContent.count, guideData.guides.length);
1042
+ assert.equal(new Set(catalog.structuredContent.entries.map((entry) => entry.slug)).size, guideData.guides.length);
1043
+ assert.deepEqual(catalog.structuredContent.entries, expectedCatalog);
1044
+ assert.equal(catalog.structuredContent.contractVersion, contractData.frameworkVersion);
1045
+ assert.equal(catalog.structuredContent.sourceHash, contractData.sourceHash);
1046
+ assert.equal(catalog.structuredContent.guideSource, 'bundled');
1047
+ assert.equal(catalog.structuredContent.status, 'available');
1048
+ assert.equal(catalog.structuredContent.contractCompatibility, 'unknown');
1049
+ assert.equal(catalog.structuredContent.contractProvenance, 'bundled-verified');
1050
+ assert.equal(catalog.structuredContent.coverageStatus, 'complete-bundled-catalogue');
1051
+ assert.ok(catalog.structuredContent.uncheckedAreas.includes('target-project compatibility'));
1052
+ assert.deepEqual(catalog.structuredContent.blockedChecks, []);
1053
+ assert.deepEqual(JSON.parse(catalog.content[0].text), catalog.structuredContent);
1054
+ const appBarEntry = expectedCatalog.find((entry) => entry.slug === 'app-bar');
1055
+ for (const query of ['app-bar', ' APP BAR ', 'navbar', 'screen-level actions']) {
1056
+ const result = await client.callTool({ name: 'component_catalog', arguments: { query, limit: 1 } });
1057
+ const matchType = query.includes('actions') ? 'heuristic' : query === 'navbar' ? 'exact-alias' : 'exact-name';
1058
+ assertCatalogSearch(result, { query, limit: 1, entries: [{ ...appBarEntry, matchType }], totalMatches: matchType === 'exact-name' ? 2 : 1 });
1059
+ }
1060
+ const recoveredSlugs = new Set();
1061
+ for (const entry of expectedCatalog) {
1062
+ for (const query of [entry.slug, entry.title, ...entry.aliases]) {
1063
+ const result = await client.callTool({ name: 'component_catalog', arguments: { query, limit: 50 } });
1064
+ const found = result.structuredContent.entries.find((match) => match.slug === entry.slug);
1065
+ assert.ok(found, `${query} did not discover ${entry.slug}`);
1066
+ const normalize = (value) => value.toLowerCase().split(/[^a-z0-9]+/u).filter(Boolean).join('-');
1067
+ assert.equal(found.matchType, [entry.slug, entry.title].some((name) => normalize(name) === normalize(query)) ? 'exact-name' : 'exact-alias');
1068
+ const { matchType, ...metadata } = found;
1069
+ assert.deepEqual(metadata, entry);
1070
+ for (const match of result.structuredContent.entries) recoveredSlugs.add(match.slug);
1071
+ assert.deepEqual(JSON.parse(result.content[0].text), result.structuredContent);
1072
+ }
1073
+ }
1074
+ assert.deepEqual([...recoveredSlugs].sort(), expectedCatalog.map((entry) => entry.slug).sort());
1075
+ const aliasSyntax = await client.callTool({ name: 'component_syntax_expert', arguments: { projectRoot: matchingDir, components: ['navbar'] } });
1076
+ assert.deepEqual(aliasSyntax.structuredContent.found, []);
1077
+ assert.deepEqual(aliasSyntax.structuredContent.missing.map((entry) => entry.requested), ['navbar']);
1078
+ for (const query of ['no-catalogue-match-zzzz', '!!!', '日本語', 'x'.repeat(256)]) {
1079
+ assertCatalogSearch(await client.callTool({ name: 'component_catalog', arguments: { query } }), { query, entries: [] });
1080
+ }
1081
+ const limitedListing = await client.callTool({ name: 'component_catalog', arguments: { limit: 1 } });
1082
+ assert.deepEqual(limitedListing.structuredContent.entries, expectedCatalog);
1083
+ assert.equal(limitedListing.structuredContent.coverageStatus, 'complete-bundled-catalogue');
1084
+ for (const field of ['query', 'limit', 'totalMatches', 'omittedCount', 'truncated']) assert.equal(Object.hasOwn(limitedListing.structuredContent, field), false);
1085
+ for (const arguments_ of [
1086
+ ...['', ' ', 1, null, 'x'.repeat(257)].map((query) => ({ query })),
1087
+ ...[0, 51, 1.5, '10', null].map((limit) => ({ query: 'app-bar', limit })),
1088
+ ]) {
1089
+ assert.equal((await client.callTool({ name: 'component_catalog', arguments: arguments_ })).isError, true, JSON.stringify(arguments_));
1090
+ }
1091
+ for (const arguments_ of [{ projectRoot: 1 }, { projectRoot: 'x'.repeat(4_097) }, { workflowId: false }, { workflowId: 'x'.repeat(257) }]) {
1092
+ assert.equal((await client.callTool({ name: 'component_catalog', arguments: arguments_ })).isError, true);
1093
+ }
1094
+ for (const cwd of [matchingDir, staleSourceDir]) {
1095
+ const cwdClient = new Client({ name: 'expressivecss-catalogue-cwd', version: '0.1.0' });
1096
+ try {
1097
+ await cwdClient.connect(new StdioClientTransport({ command: process.execPath, args: [path.join(packageDir, 'server.js'), `--project-root=${staleSourceDir}`], cwd, stderr: 'pipe' }));
1098
+ const result = await cwdClient.callTool({ name: 'component_catalog', arguments: {} });
1099
+ assert.deepEqual(result.structuredContent.entries, expectedCatalog);
1100
+ assert.equal(result.structuredContent.contractCompatibility, 'unknown');
1101
+ assert.equal(result.structuredContent.contractProvenance, 'bundled-verified');
1102
+ assert.equal(result.structuredContent.status, 'available');
1103
+ assert.deepEqual(result.structuredContent.checksPerformed, catalog.structuredContent.checksPerformed);
1104
+ const search = await cwdClient.callTool({ name: 'component_catalog', arguments: { query: 'navbar' } });
1105
+ assertCatalogSearch(search, { query: 'navbar', entries: [{ ...appBarEntry, matchType: 'exact-alias' }] });
1106
+ assert.equal(search.structuredContent.contractCompatibility, 'unknown');
1107
+ assert.equal(search.structuredContent.status, 'available');
1108
+ } finally {
1109
+ await cwdClient.close();
1110
+ }
1111
+ }
1112
+ const unchangedFiles = ['package.json', 'package-lock.json', 'node_modules/@expressivecss/expressive/package.json'];
1113
+ const beforeCatalogue = await Promise.all(unchangedFiles.map((file) => readFile(path.join(deniedCommandDir, file))));
1114
+ const matchingManifest = await readFile(path.join(matchingDir, 'package.json'));
1115
+ await writeFile(path.join(matchingDir, 'package.json'), JSON.stringify({
1116
+ ...JSON.parse(matchingManifest),
1117
+ scripts: Object.fromEntries(['typecheck', 'test', 'verify:expressivecss'].map((name) => [name, "node -e \"require('node:fs').writeFileSync('catalogue-ran.txt', 'yes')\""])),
1118
+ }));
1119
+ const matchingFiles = ['package.json', 'clean.html', 'node_modules/@expressivecss/expressive/package.json'];
1120
+ const beforeMatchingCatalogue = await Promise.all(matchingFiles.map((file) => readFile(path.join(matchingDir, file))));
1121
+ await verifyCatalogueResources(expectedCatalog, catalog.structuredContent, [matchingDir, versionedDir, staleSourceDir, unprovenSourceDir, tamperedGuideDir]);
1122
+ await verifyComponentResources(catalog.structuredContent, [matchingDir, versionedDir, staleSourceDir, unprovenSourceDir, tamperedGuideDir]);
1123
+ for (const [projectRoot, compatibility, provenance] of [
1124
+ [matchingDir, 'match', 'bundled-verified'],
1125
+ [deniedCommandDir, 'match', 'bundled-verified'],
1126
+ [outsideDir, 'unresolved', 'bundled-verified'],
1127
+ [versionedDir, 'mismatch', 'bundled-verified'],
1128
+ [staleSourceDir, 'match', 'stale'],
1129
+ [unprovenSourceDir, 'match', 'missing'],
1130
+ [noncanonicalSourceDir, 'match', 'invalid'],
1131
+ [tamperedGuideDir, 'match', 'divergent'],
1132
+ [symlinkSourceDir, 'unresolved', 'invalid'],
1133
+ [oversizedSourceDir, 'unresolved', 'invalid'],
1134
+ [path.join(outsideDir, 'nonexistent-project'), 'unresolved', 'bundled-verified'],
1135
+ ]) {
1136
+ const result = await client.callTool({ name: 'component_catalog', arguments: { projectRoot } });
1137
+ assertScopedResult(result, 'target component_catalog');
1138
+ assert.deepEqual(result.structuredContent.entries, expectedCatalog);
1139
+ assert.equal(result.structuredContent.contractVersion, contractData.frameworkVersion);
1140
+ assert.equal(result.structuredContent.sourceHash, contractData.sourceHash);
1141
+ assert.equal(result.structuredContent.contractCompatibility, compatibility, projectRoot);
1142
+ assert.equal(result.structuredContent.contractProvenance, provenance);
1143
+ const available = compatibility === 'match' && provenance === 'bundled-verified';
1144
+ assert.equal(result.structuredContent.status, available ? 'available' : 'blocked');
1145
+ assert.equal(result.structuredContent.coverageStatus, 'complete-bundled-catalogue');
1146
+ assert.equal(result.structuredContent.blockedChecks.includes('target-version contract checks'), compatibility !== 'match');
1147
+ if (provenance !== 'bundled-verified') assert.ok(result.structuredContent.blockedChecks.includes(`local contract provenance is ${provenance}`));
1148
+ assert.deepEqual(JSON.parse(result.content[0].text), result.structuredContent);
1149
+ for (const query of ['navbar', 'no-catalogue-match-zzzz']) {
1150
+ const search = await client.callTool({ name: 'component_catalog', arguments: { projectRoot, query } });
1151
+ assertCatalogSearch(search, { query, entries: query === 'navbar' ? [{ ...appBarEntry, matchType: 'exact-alias' }] : [] });
1152
+ for (const field of ['status', 'contractCompatibility', 'contractProvenance', 'contractProvenanceDetails', 'blockedChecks', 'uncheckedAreas']) {
1153
+ assert.deepEqual(search.structuredContent[field], result.structuredContent[field]);
1154
+ }
1155
+ }
1156
+ }
1157
+ assert.deepEqual(await Promise.all(unchangedFiles.map((file) => readFile(path.join(deniedCommandDir, file)))), beforeCatalogue);
1158
+ await assert.rejects(access(path.join(deniedCommandDir, 'ran.txt')));
1159
+ assert.deepEqual(await Promise.all(matchingFiles.map((file) => readFile(path.join(matchingDir, file)))), beforeMatchingCatalogue);
1160
+ await assert.rejects(access(path.join(matchingDir, 'catalogue-ran.txt')));
1161
+ await writeFile(path.join(matchingDir, 'package.json'), matchingManifest);
1162
+ for (let offset = 0; offset < expectedCatalog.length; offset += 12) {
1163
+ const slugs = expectedCatalog.slice(offset, offset + 12).map((entry) => entry.slug);
1164
+ const result = await client.callTool({ name: 'component_syntax_expert', arguments: { projectRoot: matchingDir, components: slugs } });
1165
+ assert.deepEqual(result.structuredContent.found.map((entry) => entry.slug), slugs);
1166
+ assert.deepEqual(result.structuredContent.missing, []);
1167
+ }
1168
+
299
1169
  const oversizedRulesInput = await client.callTool({
300
1170
  name: 'rules_enforcer',
301
1171
  arguments: { projectRoot: matchingDir, snippet: 'x'.repeat(500_001) },
@@ -829,14 +1699,364 @@ try {
829
1699
  assert.equal(buttonCapability.lastReviewedSupport, 'partial');
830
1700
  assert.equal(buttonCapability.support, buttonCapability.sourceReview === 'source-reviewed' ? 'partial' : 'unassessed');
831
1701
  assert.match(matchingSyntax.structuredContent.capabilityEvidence.basis, /not proof of published package/);
1702
+
1703
+ const fallbackRules = [
1704
+ 'Follow the component contract in the full documentation before adding optional attributes.',
1705
+ 'Keep runtime-owned state in framework initialization, not in static markup values.',
1706
+ ];
1707
+ const normativeRuleIds = (rules) => rules.flatMap((rule) => {
1708
+ const match = rule.match(/^`([^`]+)`:/u);
1709
+ return match ? [match[1]] : [];
1710
+ });
1711
+ const catalogueRules = new Map();
1712
+ const catalogueOptions = new Map();
1713
+ const catalogueMethods = new Map();
1714
+ let fallbackGuideCount = 0;
1715
+ for (let offset = 0; offset < guideData.guides.length; offset += 12) {
1716
+ const guides = guideData.guides.slice(offset, offset + 12);
1717
+ const result = await client.callTool({
1718
+ name: 'component_syntax_expert',
1719
+ arguments: { projectRoot: matchingDir, components: guides.map((guide) => guide.file.replace(/\.md$/u, '')), workflowId },
1720
+ });
1721
+ assertScopedResult(result, 'catalogue component_syntax_expert');
1722
+ assert.equal(result.structuredContent.status, 'available');
1723
+ assert.equal(result.structuredContent.foundCount, guides.length);
1724
+ assert.deepEqual(result.structuredContent.missing, []);
1725
+ assert.deepEqual(JSON.parse(result.content[0].text), result.structuredContent);
1726
+ assert.ok(result.structuredContent.found.every((entry) => !Object.hasOwn(entry, 'options')));
1727
+ assert.ok(result.structuredContent.found.every((entry) => !Object.hasOwn(entry, 'methods')));
1728
+ assertSyntaxSelection(result);
1729
+ assert.equal(validateSyntaxOutput(result.structuredContent).valid, true);
1730
+ const explicitDetailed = await client.callTool({ name: 'component_syntax_expert', arguments: { projectRoot: matchingDir, components: guides.map((guide) => guide.file.replace(/\.md$/u, '')), workflowId, detail: 'detailed' } });
1731
+ assert.deepEqual(explicitDetailed.structuredContent, result.structuredContent);
1732
+ let combined;
1733
+ for (const sections of [[], ['options'], ['methods'], ['options', 'methods'], ['methods', 'options']]) {
1734
+ const selected = await client.callTool({
1735
+ name: 'component_syntax_expert',
1736
+ arguments: { projectRoot: matchingDir, components: guides.map((guide) => guide.file.replace(/\.md$/u, '')), workflowId, sections },
1737
+ });
1738
+ assert.notEqual(selected.isError, true, `supported sections rejected: ${JSON.stringify(sections)}: ${selected.content[0].text}`);
1739
+ assert.deepEqual(JSON.parse(selected.content[0].text), selected.structuredContent);
1740
+ for (const guide of guides) {
1741
+ const found = selected.structuredContent.found.find((entry) => entry.file === guide.file);
1742
+ for (const [section, heading, expectations] of [['options', 'Options', catalogueOptions], ['methods', 'Methods', catalogueMethods]]) {
1743
+ if (sections.includes(section)) {
1744
+ // Shipped API sections contain no fenced headings. Synthetic cases below cover those boundaries.
1745
+ const markdown = guide.content.split(new RegExp(`^#### ${heading}\\r?$`, 'mu'))[1]?.split(/^#{1,4}(?:\s|$)/mu)[0].trim() || null;
1746
+ const expected = { status: markdown ? 'documented' : 'absent', markdown };
1747
+ assert.deepEqual(found[section], expected, `${guide.file} must return its complete bundled ${heading} or explicit absence`);
1748
+ expectations.set(guide.file, expected);
1749
+ } else {
1750
+ assert.equal(Object.hasOwn(found, section), false);
1751
+ }
1752
+ }
1753
+ }
1754
+ assertSyntaxSelection(selected, { sections });
1755
+ assert.deepEqual(withoutApiSelection(selected.structuredContent), withoutApiSelection(result.structuredContent), 'section selection changes only API records and omission metadata');
1756
+ for (const [detail, includeCapabilities] of [['compact', undefined], ['compact', true], ['compact', false], ['detailed', false], ['detailed', true]]) {
1757
+ const args = { projectRoot: matchingDir, components: guides.map((guide) => guide.file.replace(/\.md$/u, '')), workflowId, sections, detail, ...(includeCapabilities === undefined ? {} : { includeCapabilities }) };
1758
+ const projected = await client.callTool({ name: 'component_syntax_expert', arguments: args });
1759
+ assertSyntaxSelection(projected, { detail, includeCapabilities, sections });
1760
+ assert.equal(validateSyntaxOutput(projected.structuredContent).valid, true);
1761
+ assert.deepEqual(withoutSyntaxSelection(projected.structuredContent), withoutSyntaxSelection(selected.structuredContent));
1762
+ for (const [index, entry] of projected.structuredContent.found.entries()) {
1763
+ const original = selected.structuredContent.found[index];
1764
+ for (const field of ['contract', 'syntax', 'options', 'methods', 'capability']) {
1765
+ if (Object.hasOwn(entry, field)) assert.deepEqual(entry[field], original[field]);
1766
+ }
1767
+ }
1768
+ }
1769
+ if (sections.length === 2) {
1770
+ if (combined) assert.deepEqual(selected.structuredContent, combined, 'selector order must not change the result');
1771
+ combined = selected.structuredContent;
1772
+ }
1773
+ }
1774
+ for (const guide of guides) {
1775
+ // Read expectations from bundled Markdown, independently of the server parser.
1776
+ const section = guide.content.split(/^#### Rules\r?$/mu)[1]?.split(/^#{1,4} /mu)[0] ?? '';
1777
+ const expectedRules = [...section.matchAll(/^-\s+(.+)$/gmu)].map((match) => match[1]);
1778
+ const found = result.structuredContent.found.find((entry) => entry.file === guide.file);
1779
+ assert.ok(found, `${guide.file} missing from catalogue lookup`);
1780
+ if (expectedRules.length) {
1781
+ assert.deepEqual(found.rules, expectedRules, `${guide.file} must return every rule in source order`);
1782
+ assert.deepEqual(normativeRuleIds(found.rules), normativeRuleIds(expectedRules), `${guide.file} normative rule IDs`);
1783
+ } else {
1784
+ fallbackGuideCount += 1;
1785
+ assert.deepEqual(found.rules, fallbackRules, `${guide.file} fallback advice changed`);
1786
+ assert.deepEqual(normativeRuleIds(found.rules), [], `${guide.file} fallback advice is not normative`);
1787
+ }
1788
+ catalogueRules.set(guide.file, found.rules);
1789
+ }
1790
+ }
1791
+ assert.ok(fallbackGuideCount > 0, 'catalogue must exercise generic fallback advice');
1792
+ assert.ok(catalogueRules.get('cards.md').length >= 16, 'Cards must include all 16 baseline rules');
1793
+ assert.ok(normativeRuleIds(catalogueRules.get('cards.md')).includes('expanding-card-close-is-button'));
1794
+ assert.ok(normativeRuleIds(catalogueRules.get('autocomplete.md')).includes('field-supporting-text-linked'));
1795
+ assert.deepEqual(tamperedGuideSyntax.structuredContent.found[0].rules, catalogueRules.get('buttons.md'));
1796
+ const datepickerOptions = catalogueOptions.get('date-picker.md').markdown;
1797
+ for (const option of ['openByDefault', 'container', 'displayPlugin', 'displayPluginOptions']) {
1798
+ assert.ok(datepickerOptions.includes(`\`${option}\``), `${option} missing from Datepicker Options`);
1799
+ }
1800
+ assert.match(datepickerOptions, /'docked'.*openByDefault: true/u);
1801
+ assert.equal(catalogueOptions.get('autocomplete.md').status, 'documented');
1802
+ assert.deepEqual(catalogueOptions.get('cards.md'), { status: 'absent', markdown: null });
1803
+ for (const method of ['open', 'close', 'selectOption', 'setMenuItems', 'destroy']) {
1804
+ assert.ok(catalogueMethods.get('autocomplete.md').markdown.includes(`\`.${method}()\``), `${method} missing from Autocomplete Methods`);
1805
+ }
1806
+ assert.deepEqual(catalogueMethods.get('cards.md'), { status: 'absent', markdown: null });
1807
+ for (const selectors of [
1808
+ { detail: 'unknown' }, { detail: null }, { detail: 1 },
1809
+ { includeCapabilities: 'true' }, { includeCapabilities: null }, { includeCapabilities: 1 },
1810
+ ]) {
1811
+ const invalid = await client.callTool({ name: 'component_syntax_expert', arguments: { projectRoot: matchingDir, components: ['cards'], ...selectors } });
1812
+ assert.equal(invalid.isError, true, `invalid selectors accepted: ${JSON.stringify(selectors)}`);
1813
+ }
1814
+ for (const mutation of [
1815
+ { detail: 'unknown' }, { includeCapabilities: 'true' },
1816
+ { found: [{ omittedFields: [{ field: 'rules', reason: 'not-requested' }] }] },
1817
+ { found: [{ omittedFields: [{ field: 'syntax', reason: 'budget' }] }] },
1818
+ { found: [{}] },
1819
+ ]) assert.equal(validateSyntaxOutput({ ...matchingSyntax.structuredContent, ...mutation }).valid, false);
1820
+ for (const sections of ['options', null, ['unknown'], [1], ['options', 'unknown'], ['options', 'options'], ['methods', 'methods'], ['options', 'methods', 'options']]) {
1821
+ const invalid = await client.callTool({ name: 'component_syntax_expert', arguments: { projectRoot: matchingDir, components: ['cards'], sections } });
1822
+ assert.equal(invalid.isError, true, `invalid sections accepted: ${JSON.stringify(sections)}`);
1823
+ }
1824
+ const tamperedOptions = await client.callTool({ name: 'component_syntax_expert', arguments: { projectRoot: tamperedGuideDir, components: ['buttons', 'autocomplete'], sections: ['options', 'methods'] } });
1825
+ assert.deepEqual(tamperedOptions.structuredContent.found[0].options, catalogueOptions.get('buttons.md'));
1826
+ assert.deepEqual(tamperedOptions.structuredContent.found[0].methods, catalogueMethods.get('buttons.md'));
1827
+ assert.deepEqual(tamperedOptions.structuredContent.found[1].methods, catalogueMethods.get('autocomplete.md'));
1828
+ assert.equal(JSON.stringify(tamperedOptions).includes('UNTRUSTED_OPTIONS_MARKER'), false);
1829
+ assert.equal(JSON.stringify(tamperedOptions).includes('UNTRUSTED_METHODS_MARKER'), false);
1830
+
1831
+ // Copy only the runtime package inputs; dependencies resolve from this package's parent directory.
1832
+ const optionsFixtureDir = await mkdtemp(path.join(packageDir, '.options-fixture-'));
1833
+ const optionsFixtureClient = new Client({ name: 'expressivecss-mcp-options-fixture', version: '0.1.0' });
1834
+ try {
1835
+ for (const file of ['server.js', 'package.json', 'component-decisions.json', 'capability-roadmap.json', 'contract.json', 'semantics-data.json']) {
1836
+ await copyFile(path.join(packageDir, file), path.join(optionsFixtureDir, file));
1837
+ }
1838
+ await mkdir(path.join(optionsFixtureDir, 'scripts'));
1839
+ await copyFile(path.join(packageDir, 'scripts', 'resolve-version.mjs'), path.join(optionsFixtureDir, 'scripts', 'resolve-version.mjs'));
1840
+ const markdown = [
1841
+ '| Name | Description |', '| --- | --- |', '| example | Complete table |',
1842
+ '##### Nested guidance', '[Reference](https://example.test/options)',
1843
+ '```markdown', '#### Methods', '# A literal code heading', '````',
1844
+ '~~~markdown', '### Another literal code heading', '~~~~',
1845
+ 'Long option description. '.repeat(1_200),
1846
+ ].join('\n').trim();
1847
+ const searchFixtures = [
1848
+ { slug: 'fixture', title: 'Fixture', useWhen: ['Discoverable'], aliases: ['fixture'] },
1849
+ { slug: 'a-fixture', title: 'A fixture', useWhen: ['UI'], aliases: ['fixture'] },
1850
+ { slug: 'b-fixture', title: 'B fixture', useWhen: [], aliases: ['fixture'] },
1851
+ ...Array.from({ length: 52 }, (_, index) => ({
1852
+ slug: `fixture-${String(index).padStart(2, '0')}`, title: `Fixture ${index}`,
1853
+ useWhen: ['UI discovery'], aliases: index === 0 ? ['q'] : index < 3 ? ['edge'] : [],
1854
+ })),
1855
+ ];
1856
+ const fixtureGuides = {
1857
+ ...guideData,
1858
+ guides: [
1859
+ { file: 'nested.md', content: `### Nested\n\n\`\`\`markdown\n#### Options\nFake section\n\`\`\`\n\n#### Options\n\n${markdown}\n\n### Next component\nEXCLUDED_HIGHER_HEADING\n` },
1860
+ { file: 'empty.md', content: '### Empty\n\n#### Options\n \n#### Methods\nEXCLUDED_METHODS\n' },
1861
+ { file: 'last.md', content: '### Last\n\n#### Options\nFinal option without trailing heading' },
1862
+ { file: 'same-level.md', content: '### Same level\n\n#### Options\nKept option\n#### Methods\nEXCLUDED_METHODS\n' },
1863
+ { file: 'crlf.md', content: '### CRLF\r\n\r\n#### Options\r\nFirst line\r\n##### Nested heading\r\nLast line\r\n#### Methods\r\nEXCLUDED_METHODS\r\n' },
1864
+ { file: 'nested-methods.md', content: `### Nested Methods\n\n\`\`\`markdown\n#### Methods\nFake section\n\`\`\`\n\n#### Methods\n\n${markdown}\n\n### Next component\nEXCLUDED_HIGHER_HEADING\n` },
1865
+ { file: 'empty-methods.md', content: '### Empty Methods\n\n#### Methods\n \n#### Options\nEXCLUDED_OPTIONS\n' },
1866
+ { file: 'last-methods.md', content: '### Last Methods\n\n#### Methods\nFinal method without trailing heading' },
1867
+ { file: 'same-level-methods.md', content: '### Same level Methods\n\n#### Methods\nKept method\n#### Options\nEXCLUDED_OPTIONS\n' },
1868
+ { file: 'crlf-methods.md', content: '### CRLF Methods\r\n\r\n#### Methods\r\nFirst line\r\n##### Nested heading\r\nLast line\r\n#### Options\r\nEXCLUDED_OPTIONS\r\n' },
1869
+ { file: 'missing-methods.md', content: '### Missing Methods\n\n#### Options\nOnly options' },
1870
+ ...searchFixtures.map(({ slug, title }) => ({ file: `${slug}.md`, content: `### ${title}\n\n#### Rules\nFull-body-only marker ZZZBODYONLY\n` })),
1871
+ ],
1872
+ };
1873
+ await writeFile(path.join(optionsFixtureDir, 'component-guides.json'), JSON.stringify(fixtureGuides));
1874
+ await writeFile(path.join(optionsFixtureDir, 'component-decisions.json'), JSON.stringify({
1875
+ ...decisionsData,
1876
+ components: [
1877
+ { slug: 'last', useWhen: ['', 'Preferred description', 'Extra description'], jobs: ['Unused job'], aliases: ['final'], runtime: 'css-only' },
1878
+ { slug: 'empty', useWhen: [''], jobs: ['', 'Job fallback'], aliases: [], runtime: 'native' },
1879
+ { slug: 'same-level', useWhen: [], jobs: [], aliases: [] },
1880
+ ...searchFixtures,
1881
+ ],
1882
+ }));
1883
+ const fixtureTransport = new StdioClientTransport({ command: process.execPath, args: [path.join(optionsFixtureDir, 'server.js')], cwd: optionsFixtureDir, stderr: 'pipe', env: { ...process.env, EXPRESSIVECSS_MCP_MAX_RESPONSE_BYTES: '1048576' } });
1884
+ await optionsFixtureClient.connect(fixtureTransport);
1885
+ const fixtureCatalog = await optionsFixtureClient.callTool({ name: 'component_catalog', arguments: {} });
1886
+ assert.equal(fixtureCatalog.structuredContent.count, fixtureGuides.guides.length);
1887
+ const fixtureEntries = new Map(fixtureCatalog.structuredContent.entries.map((entry) => [entry.slug, entry]));
1888
+ assert.deepEqual(fixtureEntries.get('nested'), { slug: 'nested', title: 'Nested', description: null, aliases: [], runtime: null, docs: null });
1889
+ assert.deepEqual(fixtureEntries.get('last'), { slug: 'last', title: 'Last', description: 'Preferred description', aliases: ['final'], runtime: 'css-only', docs: null });
1890
+ assert.deepEqual(fixtureEntries.get('empty'), { slug: 'empty', title: 'Empty', description: 'Job fallback', aliases: [], runtime: 'native', docs: null });
1891
+ assert.deepEqual(fixtureEntries.get('same-level'), { slug: 'same-level', title: 'Same level', description: null, aliases: [], runtime: null, docs: null });
1892
+ assert.deepEqual(JSON.parse(fixtureCatalog.content[0].text), fixtureCatalog.structuredContent);
1893
+ const rankedFixtures = searchFixtures.map(({ slug }, index) => ({
1894
+ ...fixtureEntries.get(slug), matchType: index === 0 ? 'exact-name' : index < 3 ? 'exact-alias' : 'heuristic',
1895
+ }));
1896
+ for (const limit of [undefined, 1, 50]) {
1897
+ const query = 'fixture';
1898
+ const result = await optionsFixtureClient.callTool({ name: 'component_catalog', arguments: { query, ...(limit === undefined ? {} : { limit }) } });
1899
+ assertCatalogSearch(result, { query, limit: limit ?? 10, entries: rankedFixtures.slice(0, limit ?? 10), totalMatches: 55 });
1900
+ const repeated = await optionsFixtureClient.callTool({ name: 'component_catalog', arguments: { query, ...(limit === undefined ? {} : { limit }) } });
1901
+ assert.deepEqual(repeated.structuredContent.entries, result.structuredContent.entries);
1902
+ const blocked = await optionsFixtureClient.callTool({ name: 'component_catalog', arguments: { query, projectRoot: versionedDir, ...(limit === undefined ? {} : { limit }) } });
1903
+ assertCatalogSearch(blocked, { query, limit: limit ?? 10, entries: rankedFixtures.slice(0, limit ?? 10), totalMatches: 55 });
1904
+ assert.equal(blocked.structuredContent.status, 'blocked');
1905
+ assert.equal(blocked.structuredContent.contractCompatibility, 'mismatch');
1906
+ }
1907
+ for (const limit of [1, 2]) {
1908
+ const query = 'edge';
1909
+ const result = await optionsFixtureClient.callTool({ name: 'component_catalog', arguments: { query, limit } });
1910
+ const entries = rankedFixtures.slice(4, 4 + limit).map((entry) => ({ ...entry, matchType: 'exact-alias' }));
1911
+ assertCatalogSearch(result, { query, limit, entries, totalMatches: 2 });
1912
+ }
1913
+ for (const query of ['q', 'ui q']) {
1914
+ const matchType = query === 'q' ? 'exact-alias' : 'heuristic';
1915
+ assertCatalogSearch(await optionsFixtureClient.callTool({ name: 'component_catalog', arguments: { query } }), {
1916
+ query, entries: [{ ...fixtureEntries.get('fixture-00'), matchType }],
1917
+ });
1918
+ }
1919
+ const recoveryQuery = 'fixture-51';
1920
+ assertCatalogSearch(await optionsFixtureClient.callTool({ name: 'component_catalog', arguments: { query: recoveryQuery } }), {
1921
+ query: recoveryQuery, entries: [{ ...fixtureEntries.get('fixture-51'), matchType: 'exact-name' }],
1922
+ });
1923
+ for (const limit of [1, 2]) {
1924
+ const query = 'a-fixture';
1925
+ assertCatalogSearch(await optionsFixtureClient.callTool({ name: 'component_catalog', arguments: { query, limit } }), {
1926
+ query, limit, entries: [
1927
+ { ...fixtureEntries.get('a-fixture'), matchType: 'exact-name' },
1928
+ { ...fixtureEntries.get('fixture'), matchType: 'heuristic' },
1929
+ ].slice(0, limit), totalMatches: 2,
1930
+ });
1931
+ }
1932
+ for (const query of ['ZZZBODYONLY', 'unused job', 'extra description']) {
1933
+ assertCatalogSearch(await optionsFixtureClient.callTool({ name: 'component_catalog', arguments: { query } }), { query, entries: [] });
1934
+ }
1935
+ const fixtureResult = await optionsFixtureClient.callTool({ name: 'component_syntax_expert', arguments: { projectRoot: matchingDir, components: ['nested', 'empty', 'last', 'same-level', 'crlf'], sections: ['options'] } });
1936
+ assert.deepEqual(fixtureResult.structuredContent.found.map((entry) => entry.options), [
1937
+ { status: 'documented', markdown },
1938
+ { status: 'absent', markdown: null },
1939
+ { status: 'documented', markdown: 'Final option without trailing heading' },
1940
+ { status: 'documented', markdown: 'Kept option' },
1941
+ { status: 'documented', markdown: 'First line\r\n##### Nested heading\r\nLast line' },
1942
+ ]);
1943
+ assert.deepEqual(JSON.parse(fixtureResult.content[0].text), fixtureResult.structuredContent);
1944
+ const methodsFixtureResult = await optionsFixtureClient.callTool({ name: 'component_syntax_expert', arguments: { projectRoot: matchingDir, components: ['nested-methods', 'empty-methods', 'last-methods', 'same-level-methods', 'crlf-methods', 'missing-methods'], sections: ['methods'] } });
1945
+ assert.deepEqual(methodsFixtureResult.structuredContent.found.map((entry) => entry.methods), [
1946
+ { status: 'documented', markdown },
1947
+ { status: 'absent', markdown: null },
1948
+ { status: 'documented', markdown: 'Final method without trailing heading' },
1949
+ { status: 'documented', markdown: 'Kept method' },
1950
+ { status: 'documented', markdown: 'First line\r\n##### Nested heading\r\nLast line' },
1951
+ { status: 'absent', markdown: null },
1952
+ ]);
1953
+ assert.ok(methodsFixtureResult.structuredContent.found.every((entry) => !Object.hasOwn(entry, 'options')));
1954
+ assert.deepEqual(JSON.parse(methodsFixtureResult.content[0].text), methodsFixtureResult.structuredContent);
1955
+ for (const original of [fixtureResult, methodsFixtureResult]) {
1956
+ const sections = original === fixtureResult ? ['options'] : ['methods'];
1957
+ const compact = await optionsFixtureClient.callTool({ name: 'component_syntax_expert', arguments: { projectRoot: matchingDir, components: original.structuredContent.found.map((entry) => entry.slug), workflowId: original.structuredContent.workflowId, detail: 'compact', sections } });
1958
+ assertSyntaxSelection(compact, { detail: 'compact', sections });
1959
+ assert.deepEqual(withoutSyntaxSelection(compact.structuredContent), withoutSyntaxSelection(original.structuredContent));
1960
+ for (const [index, entry] of compact.structuredContent.found.entries()) assert.deepEqual(entry[sections[0]], original.structuredContent.found[index][sections[0]]);
1961
+ }
1962
+ } finally {
1963
+ try { await optionsFixtureClient.close(); } finally { await rm(optionsFixtureDir, { recursive: true, force: true }); }
1964
+ }
1965
+
1966
+ for (const [projectRoot, compatibility, provenance] of [
1967
+ [outsideDir, 'unresolved', 'bundled-verified'],
1968
+ [versionedDir, 'mismatch', 'bundled-verified'],
1969
+ [staleSourceDir, 'match', 'stale'],
1970
+ [unprovenSourceDir, 'match', 'missing'],
1971
+ [noncanonicalSourceDir, 'match', 'invalid'],
1972
+ [tamperedGuideDir, 'match', 'divergent'],
1973
+ ]) {
1974
+ const result = await client.callTool({
1975
+ name: 'component_syntax_expert',
1976
+ arguments: { projectRoot, components: ['cards'] },
1977
+ });
1978
+ assertScopedResult(result, 'blocked component_syntax_expert');
1979
+ assert.equal(result.structuredContent.status, 'blocked');
1980
+ assert.equal(result.structuredContent.contractCompatibility, compatibility);
1981
+ assert.equal(result.structuredContent.contractProvenance, provenance);
1982
+ assert.equal(result.structuredContent.found[0].capability, null);
1983
+ assert.equal(result.structuredContent.capabilityEvidence.status, 'blocked');
1984
+ assert.deepEqual(result.structuredContent.found[0].rules, catalogueRules.get('cards.md'));
1985
+ assert.deepEqual(JSON.parse(result.content[0].text), result.structuredContent);
1986
+ for (const sections of [['options'], ['methods'], ['options', 'methods']]) {
1987
+ const selected = await client.callTool({ name: 'component_syntax_expert', arguments: { projectRoot, components: ['cards', 'date-picker'], sections, workflowId: result.structuredContent.workflowId } });
1988
+ assert.equal(selected.structuredContent.status, 'blocked');
1989
+ for (const field of ['contractCompatibility', 'contractProvenance', 'contractProvenanceDetails', 'blockedChecks', 'capabilityEvidence']) {
1990
+ assert.deepEqual(selected.structuredContent[field], result.structuredContent[field]);
1991
+ }
1992
+ assert.ok(selected.structuredContent.found.every((entry) => entry.capability === null));
1993
+ for (const [section, expectations] of [['options', catalogueOptions], ['methods', catalogueMethods]]) {
1994
+ if (sections.includes(section)) {
1995
+ assert.deepEqual(selected.structuredContent.found[0][section], expectations.get('cards.md'));
1996
+ assert.deepEqual(selected.structuredContent.found[1][section], expectations.get('date-picker.md'));
1997
+ } else assert.ok(selected.structuredContent.found.every((entry) => !Object.hasOwn(entry, section)));
1998
+ }
1999
+ assert.deepEqual(JSON.parse(selected.content[0].text), selected.structuredContent);
2000
+ }
2001
+ }
2002
+ const missingSyntax = await client.callTool({
2003
+ name: 'component_syntax_expert',
2004
+ arguments: { projectRoot: matchingDir, components: ['cards', 'unknown-component'] },
2005
+ });
2006
+ assert.equal(missingSyntax.structuredContent.status, 'blocked');
2007
+ assert.equal(missingSyntax.structuredContent.coverageStatus, 'partial-named-component-contracts');
2008
+ assert.deepEqual(missingSyntax.structuredContent.missing.map((entry) => entry.requested), ['unknown-component']);
2009
+ assert.deepEqual(missingSyntax.structuredContent.found[0].rules, catalogueRules.get('cards.md'));
2010
+ for (const sections of [['options'], ['methods'], ['options', 'methods']]) {
2011
+ const missingSections = await client.callTool({ name: 'component_syntax_expert', arguments: { projectRoot: matchingDir, components: ['cards', 'unknown-component'], sections, workflowId: missingSyntax.structuredContent.workflowId } });
2012
+ assert.equal(missingSections.structuredContent.found.length, 1);
2013
+ for (const section of sections) assert.deepEqual(missingSections.structuredContent.found[0][section], { status: 'absent', markdown: null });
2014
+ assert.deepEqual(withoutApiSelection(missingSections.structuredContent), withoutApiSelection(missingSyntax.structuredContent));
2015
+ assert.deepEqual(JSON.parse(missingSections.content[0].text), missingSections.structuredContent);
2016
+ }
2017
+
832
2018
  const foundations = await client.callTool({ name: 'component_syntax_expert', arguments: { projectRoot: matchingDir, foundations: ['typography', 'shape', 'motion'] } });
833
2019
  assert.equal(foundations.structuredContent.foundCount, 0);
834
2020
  assert.deepEqual(foundations.structuredContent.foundations.map((entry) => entry.slug), ['typography', 'shape', 'motion']);
835
2021
  assert.ok(foundations.structuredContent.foundations.every((entry) => entry.lastReviewedSupport === 'partial' && entry.support === (entry.sourceReview === 'source-reviewed' ? 'partial' : 'unassessed')));
2022
+ for (const sections of [['options'], ['methods'], ['options', 'methods']]) {
2023
+ const foundationSections = await client.callTool({ name: 'component_syntax_expert', arguments: { projectRoot: matchingDir, foundations: ['typography', 'shape', 'motion'], sections, workflowId: foundations.structuredContent.workflowId } });
2024
+ assert.deepEqual(foundationSections.structuredContent, foundations.structuredContent);
2025
+ assert.deepEqual(JSON.parse(foundationSections.content[0].text), foundationSections.structuredContent);
2026
+ }
836
2027
  const blockedFoundations = await client.callTool({ name: 'component_syntax_expert', arguments: { projectRoot: versionedDir, foundations: ['shape'] } });
837
2028
  assert.equal(blockedFoundations.structuredContent.capabilityEvidence.status, 'blocked');
838
2029
  assert.deepEqual(blockedFoundations.structuredContent.foundations, []);
839
2030
 
2031
+ for (const projectRoot of [matchingDir, outsideDir, versionedDir, staleSourceDir, unprovenSourceDir, noncanonicalSourceDir, tamperedGuideDir]) {
2032
+ for (const request of [
2033
+ { components: ['cards', 'date-picker', 'unknown-component'], sections: ['options', 'methods'], foundations: ['shape'] },
2034
+ { foundations: ['typography', 'shape', 'motion'] },
2035
+ { components: ['unknown-component'] },
2036
+ { components: ['cards', 'cards'] },
2037
+ ]) {
2038
+ const arguments_ = { projectRoot, workflowId, ...request };
2039
+ const baseline = await client.callTool({ name: 'component_syntax_expert', arguments: arguments_ });
2040
+ for (const detail of ['compact', 'detailed']) {
2041
+ for (const includeCapabilities of [undefined, false, true]) {
2042
+ const projected = await client.callTool({ name: 'component_syntax_expert', arguments: { ...arguments_, detail, ...(includeCapabilities === undefined ? {} : { includeCapabilities }) } });
2043
+ assertSyntaxSelection(projected, { detail, includeCapabilities, sections: request.sections });
2044
+ assert.equal(validateSyntaxOutput(projected.structuredContent).valid, true);
2045
+ assert.deepEqual(withoutSyntaxSelection(projected.structuredContent), withoutSyntaxSelection(baseline.structuredContent));
2046
+ for (const [index, entry] of projected.structuredContent.found.entries()) {
2047
+ for (const field of ['contract', 'syntax', 'options', 'methods', 'capability']) {
2048
+ if (Object.hasOwn(entry, field)) assert.deepEqual(entry[field], baseline.structuredContent.found[index][field]);
2049
+ }
2050
+ }
2051
+ }
2052
+ }
2053
+ }
2054
+ for (const detail of ['compact', 'detailed']) {
2055
+ const empty = await client.callTool({ name: 'component_syntax_expert', arguments: { projectRoot, detail, components: [], foundations: [] } });
2056
+ assert.equal(empty.isError, true, 'empty syntax requests remain invalid');
2057
+ }
2058
+ }
2059
+
840
2060
  const quality = await client.callTool({
841
2061
  name: 'quality_inspector',
842
2062
  arguments: {
@@ -1224,6 +2444,11 @@ spawn(process.execPath, ['-e', "setTimeout(() => require('node:fs').writeFileSyn
1224
2444
  ['creative_director', { projectRoot: matchingDir, goal: 'Choose a primary action component.' }],
1225
2445
  ['page_architect', { projectRoot: matchingDir, pageGoal: 'Structure a settings page.' }],
1226
2446
  ['component_syntax_expert', { projectRoot: matchingDir, components: ['buttons'] }],
2447
+ ['component_syntax_expert', { projectRoot: matchingDir, components: ['buttons'], sections: ['options'] }],
2448
+ ['component_syntax_expert', { projectRoot: matchingDir, components: ['buttons'], sections: ['methods'] }],
2449
+ ['component_syntax_expert', { projectRoot: matchingDir, components: ['buttons'], sections: ['options', 'methods'] }],
2450
+ ['component_syntax_expert', { projectRoot: matchingDir, components: ['buttons'], detail: 'compact', includeCapabilities: true }],
2451
+ ['component_syntax_expert', { projectRoot: matchingDir, foundations: ['shape'], detail: 'detailed', includeCapabilities: false }],
1227
2452
  ['quality_inspector', { projectRoot: matchingDir, files: ['README.md'] }],
1228
2453
  ]) {
1229
2454
  const skipped = await skippedClient.callTool({ name, arguments: arguments_ });
@@ -1231,12 +2456,17 @@ spawn(process.execPath, ['-e', "setTimeout(() => require('node:fs').writeFileSyn
1231
2456
  assert.equal(skipped.structuredContent.status, 'blocked');
1232
2457
  assert.equal(skipped.structuredContent.coverageStatus, 'skipped');
1233
2458
  assert.ok(skipped.structuredContent.blockedChecks.includes(`${name} disabled`));
2459
+ if (name === 'component_syntax_expert') {
2460
+ assert.equal(validateSyntaxOutput(skipped.structuredContent).valid, true);
2461
+ for (const field of ['detail', 'includeCapabilities', 'found']) assert.equal(Object.hasOwn(skipped.structuredContent, field), false);
2462
+ assert.deepEqual(JSON.parse(skipped.content[0].text), skipped.structuredContent);
2463
+ }
1234
2464
  }
1235
2465
  } finally {
1236
2466
  await skippedClient.close();
1237
2467
  }
1238
2468
 
1239
- console.log(`ExpressiveCSS MCP smoke test passed (${expectedTools.length} tools).`);
2469
+ console.log(`ExpressiveCSS MCP smoke test passed (${expectedTools.length} tools, catalogue and component resources, resource completion, aggregate response budgets and recovery).`);
1240
2470
  } finally {
1241
2471
  await client.close();
1242
2472
  await rm(outsideDir, { recursive: true, force: true });