@forumone/throughline-components 0.0.1 → 0.2.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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +117 -28
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest-source.d.ts +17 -0
- package/dist/manifest-source.d.ts.map +1 -0
- package/dist/manifest-source.js +60 -0
- package/dist/manifest-source.js.map +1 -0
- package/dist/matching/index.d.ts +3 -0
- package/dist/matching/index.d.ts.map +1 -0
- package/dist/matching/index.js +2 -0
- package/dist/matching/index.js.map +1 -0
- package/dist/matching/tfidf.d.ts +13 -0
- package/dist/matching/tfidf.d.ts.map +1 -0
- package/dist/matching/tfidf.js +118 -0
- package/dist/matching/tfidf.js.map +1 -0
- package/dist/matching/types.d.ts +22 -0
- package/dist/matching/types.d.ts.map +1 -0
- package/dist/matching/types.js +2 -0
- package/dist/matching/types.js.map +1 -0
- package/dist/options.d.ts +125 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +42 -0
- package/dist/options.js.map +1 -0
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +83 -0
- package/dist/plugin.js.map +1 -0
- package/dist/tools/find-anti-pattern.d.ts +9 -0
- package/dist/tools/find-anti-pattern.d.ts.map +1 -0
- package/dist/tools/find-anti-pattern.js +37 -0
- package/dist/tools/find-anti-pattern.js.map +1 -0
- package/dist/tools/get-contract.d.ts +4 -0
- package/dist/tools/get-contract.d.ts.map +1 -0
- package/dist/tools/get-contract.js +19 -0
- package/dist/tools/get-contract.js.map +1 -0
- package/dist/tools/get-tokens.d.ts +4 -0
- package/dist/tools/get-tokens.d.ts.map +1 -0
- package/dist/tools/get-tokens.js +19 -0
- package/dist/tools/get-tokens.js.map +1 -0
- package/dist/tools/get-variants.d.ts +4 -0
- package/dist/tools/get-variants.d.ts.map +1 -0
- package/dist/tools/get-variants.js +19 -0
- package/dist/tools/get-variants.js.map +1 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list-components.d.ts +4 -0
- package/dist/tools/list-components.d.ts.map +1 -0
- package/dist/tools/list-components.js +25 -0
- package/dist/tools/list-components.js.map +1 -0
- package/dist/tools/suggest-for-intent.d.ts +12 -0
- package/dist/tools/suggest-for-intent.d.ts.map +1 -0
- package/dist/tools/suggest-for-intent.js +86 -0
- package/dist/tools/suggest-for-intent.js.map +1 -0
- package/dist/tools/validate-composition.d.ts +9 -0
- package/dist/tools/validate-composition.d.ts.map +1 -0
- package/dist/tools/validate-composition.js +37 -0
- package/dist/tools/validate-composition.js.map +1 -0
- package/dist/validation/composition.d.ts +38 -0
- package/dist/validation/composition.d.ts.map +1 -0
- package/dist/validation/composition.js +135 -0
- package/dist/validation/composition.js.map +1 -0
- package/package.json +62 -7
package/dist/options.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const ManifestSourceSchema = z.discriminatedUnion('type', [
|
|
3
|
+
z.object({ type: z.literal('object'), manifest: z.unknown() }),
|
|
4
|
+
z.object({
|
|
5
|
+
type: z.literal('url'),
|
|
6
|
+
url: z.string().url(),
|
|
7
|
+
refreshInterval: z.number().int().positive().optional(),
|
|
8
|
+
}),
|
|
9
|
+
z.object({
|
|
10
|
+
type: z.literal('payload-collection'),
|
|
11
|
+
slug: z.string().min(1),
|
|
12
|
+
documentId: z.string().optional(),
|
|
13
|
+
}),
|
|
14
|
+
]);
|
|
15
|
+
export const ComponentsPluginOptionsSchema = z.object({
|
|
16
|
+
enabled: z.boolean().optional(),
|
|
17
|
+
routePrefix: z.string().optional(),
|
|
18
|
+
manifest: ManifestSourceSchema,
|
|
19
|
+
matching: z
|
|
20
|
+
.object({
|
|
21
|
+
strategy: z.enum(['tfidf']),
|
|
22
|
+
maxRecommendations: z.number().int().positive().optional(),
|
|
23
|
+
})
|
|
24
|
+
.optional(),
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Validates plugin options at load time. Throws with a multi-line message
|
|
28
|
+
* on schema failure or with a targeted message on cross-field violations.
|
|
29
|
+
* Plugins that depend on this server must initialize after `componentsPlugin`
|
|
30
|
+
* because the registry capability check happens during onInit.
|
|
31
|
+
*/
|
|
32
|
+
export function validateOptions(options) {
|
|
33
|
+
const result = ComponentsPluginOptionsSchema.safeParse(options);
|
|
34
|
+
if (!result.success) {
|
|
35
|
+
const issues = result.error.issues
|
|
36
|
+
.map((i) => ` - ${i.path.join('.') || '(root)'}: ${i.message}`)
|
|
37
|
+
.join('\n');
|
|
38
|
+
throw new Error(`Invalid componentsPlugin options:\n${issues}`);
|
|
39
|
+
}
|
|
40
|
+
return options;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AA2BvB,MAAM,oBAAoB,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IACxD,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;IAC9D,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;QACtB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;QACrB,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;KACxD,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC;QACrC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACvB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAClC,CAAC;CACH,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC,MAAM,CAAC;IACpD,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC/B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,QAAQ,EAAE,oBAAoB;IAC9B,QAAQ,EAAE,CAAC;SACR,MAAM,CAAC;QACN,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;QAC3B,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;KAC3D,CAAC;SACD,QAAQ,EAAE;CACd,CAAC,CAAA;AAEF;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgC;IAC9D,MAAM,MAAM,GAAG,6BAA6B,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IAC/D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;aAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aAC/D,IAAI,CAAC,IAAI,CAAC,CAAA;QACb,MAAM,IAAI,KAAK,CAAC,sCAAsC,MAAM,EAAE,CAAC,CAAA;IACjE,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAA;AAQvE,OAAO,EAAE,KAAK,uBAAuB,EAAmB,MAAM,cAAc,CAAA;AAmB5E,eAAO,MAAM,gBAAgB,EAAE,UAAU,CAAC,uBAAuB,CA+E9D,CAAA"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { getPluginRegistry } from '@forumone/throughline-plugin-contract';
|
|
2
|
+
import { createMcpHandler, createNamedLogger, defaultLogger, getAuditWriter, } from '@forumone/throughline-core';
|
|
3
|
+
import { validateOptions } from './options.js';
|
|
4
|
+
import { createManifestLoader } from './manifest-source.js';
|
|
5
|
+
import { createTfidfMatcher } from './matching/index.js';
|
|
6
|
+
import { createFindAntiPatternTool, createGetContractTool, createGetTokensTool, createGetVariantsTool, createListComponentsTool, createSuggestForIntentTool, createValidateCompositionTool, } from './tools/index.js';
|
|
7
|
+
const PLUGIN_ID = '@forumone/throughline-components';
|
|
8
|
+
const PLUGIN_VERSION = '0.1.0';
|
|
9
|
+
const MCP_HANDLER_SYMBOL = Symbol.for('@forumone/throughline/components-mcp-handler');
|
|
10
|
+
export const componentsPlugin = (rawOptions) => (incomingConfig) => {
|
|
11
|
+
if (rawOptions.enabled === false)
|
|
12
|
+
return incomingConfig;
|
|
13
|
+
const options = validateOptions(rawOptions);
|
|
14
|
+
const routePrefix = options.routePrefix ?? '/api/components';
|
|
15
|
+
const logger = createNamedLogger('components', options.logger ?? defaultLogger);
|
|
16
|
+
const maxRecommendations = options.matching?.maxRecommendations ?? 5;
|
|
17
|
+
return {
|
|
18
|
+
...incomingConfig,
|
|
19
|
+
endpoints: [
|
|
20
|
+
...(incomingConfig.endpoints ?? []),
|
|
21
|
+
{
|
|
22
|
+
path: `${routePrefix}/mcp`,
|
|
23
|
+
method: 'post',
|
|
24
|
+
handler: async (req) => {
|
|
25
|
+
const handler = req.payload[MCP_HANDLER_SYMBOL];
|
|
26
|
+
if (!handler) {
|
|
27
|
+
return new Response(JSON.stringify({ error: 'Components MCP not initialized' }), { status: 503, headers: { 'content-type': 'application/json' } });
|
|
28
|
+
}
|
|
29
|
+
return handler(req);
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
onInit: async (payload) => {
|
|
34
|
+
if (incomingConfig.onInit) {
|
|
35
|
+
await incomingConfig.onInit(payload);
|
|
36
|
+
}
|
|
37
|
+
const registry = getPluginRegistry(payload);
|
|
38
|
+
registry.requireCapability('audit-log', PLUGIN_ID);
|
|
39
|
+
const auditWriter = getAuditWriter(payload);
|
|
40
|
+
const loader = createManifestLoader(options.manifest, payload);
|
|
41
|
+
// Eager load: fail fast if the manifest source is broken at deploy
|
|
42
|
+
// time rather than on the first request.
|
|
43
|
+
const manifest = await loader.get();
|
|
44
|
+
const components = Object.values(manifest.raw.components);
|
|
45
|
+
logger.info('Manifest loaded', {
|
|
46
|
+
designSystem: manifest.designSystem.name,
|
|
47
|
+
version: manifest.designSystem.version,
|
|
48
|
+
componentCount: components.length,
|
|
49
|
+
});
|
|
50
|
+
const matcher = createTfidfMatcher(components);
|
|
51
|
+
const tools = [
|
|
52
|
+
createListComponentsTool(loader),
|
|
53
|
+
createGetContractTool(loader),
|
|
54
|
+
createGetVariantsTool(loader),
|
|
55
|
+
createGetTokensTool(loader),
|
|
56
|
+
createSuggestForIntentTool({ loader, matcher, auditWriter, maxRecommendations }),
|
|
57
|
+
createValidateCompositionTool({ loader, auditWriter }),
|
|
58
|
+
createFindAntiPatternTool({ loader, auditWriter }),
|
|
59
|
+
];
|
|
60
|
+
const handler = createMcpHandler({
|
|
61
|
+
payload,
|
|
62
|
+
serverName: 'components',
|
|
63
|
+
tools,
|
|
64
|
+
logger,
|
|
65
|
+
});
|
|
66
|
+
attachMcpHandler(payload, handler);
|
|
67
|
+
registry.register({
|
|
68
|
+
id: PLUGIN_ID,
|
|
69
|
+
version: PLUGIN_VERSION,
|
|
70
|
+
capabilities: ['component-server', 'manifest-loading', 'intent-matching'],
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
function attachMcpHandler(payload, handler) {
|
|
76
|
+
Object.defineProperty(payload, MCP_HANDLER_SYMBOL, {
|
|
77
|
+
value: handler,
|
|
78
|
+
enumerable: false,
|
|
79
|
+
writable: false,
|
|
80
|
+
configurable: false,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { McpToolDefinition } from '@forumone/throughline-plugin-contract';
|
|
2
|
+
import { type AuditWriter } from '@forumone/throughline-core';
|
|
3
|
+
import type { ManifestLoader } from '../manifest-source.js';
|
|
4
|
+
export interface FindAntiPatternDeps {
|
|
5
|
+
loader: ManifestLoader;
|
|
6
|
+
auditWriter: AuditWriter;
|
|
7
|
+
}
|
|
8
|
+
export declare function createFindAntiPatternTool(deps: FindAntiPatternDeps): McpToolDefinition;
|
|
9
|
+
//# sourceMappingURL=find-anti-pattern.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-anti-pattern.d.ts","sourceRoot":"","sources":["../../src/tools/find-anti-pattern.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAC9E,OAAO,EAAE,KAAK,WAAW,EAAY,MAAM,4BAA4B,CAAA;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAG3D,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAA;IACtB,WAAW,EAAE,WAAW,CAAA;CACzB;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,mBAAmB,GAAG,iBAAiB,CAsCtF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { withMeta } from '@forumone/throughline-core';
|
|
3
|
+
import { findAntiPatterns } from '../validation/composition.js';
|
|
4
|
+
export function createFindAntiPatternTool(deps) {
|
|
5
|
+
const inputSchema = withMeta({
|
|
6
|
+
blocks: z
|
|
7
|
+
.array(z.object({
|
|
8
|
+
type: z.string(),
|
|
9
|
+
variant: z.string().optional(),
|
|
10
|
+
}))
|
|
11
|
+
.min(1),
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
name: 'find_anti_pattern',
|
|
15
|
+
description: "Scans a proposed composition for known design anti-patterns (multiple Heroes, Hero at the bottom of a page, etc.). Returns matches with explanation and suggested alternatives. Use before publishing to surface editorial issues.",
|
|
16
|
+
inputSchema,
|
|
17
|
+
handler: async (input, ctx) => {
|
|
18
|
+
const manifest = await deps.loader.get();
|
|
19
|
+
const matches = findAntiPatterns({ blocks: input.blocks }, manifest);
|
|
20
|
+
await deps.auditWriter({
|
|
21
|
+
actor: {
|
|
22
|
+
type: 'user',
|
|
23
|
+
userId: ctx.user?.id,
|
|
24
|
+
userName: ctx.user?.name,
|
|
25
|
+
apiKeyName: ctx.apiKeyName,
|
|
26
|
+
},
|
|
27
|
+
action: 'design.find_anti_pattern',
|
|
28
|
+
mcpServer: 'component',
|
|
29
|
+
mcpTool: 'find_anti_pattern',
|
|
30
|
+
prompt: input._meta?.userPrompt,
|
|
31
|
+
reasoning: input._meta?.reasoning,
|
|
32
|
+
});
|
|
33
|
+
return { matches };
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=find-anti-pattern.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-anti-pattern.js","sourceRoot":"","sources":["../../src/tools/find-anti-pattern.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAoB,QAAQ,EAAE,MAAM,4BAA4B,CAAA;AAEvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAO/D,MAAM,UAAU,yBAAyB,CAAC,IAAyB;IACjE,MAAM,WAAW,GAAG,QAAQ,CAAC;QAC3B,MAAM,EAAE,CAAC;aACN,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;YACP,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;YAChB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SAC/B,CAAC,CACH;aACA,GAAG,CAAC,CAAC,CAAC;KACV,CAAC,CAAA;IAEF,OAAO;QACL,IAAI,EAAE,mBAAmB;QACzB,WAAW,EACT,oOAAoO;QACtO,WAAW;QACX,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC5B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAA;YACxC,MAAM,OAAO,GAAG,gBAAgB,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAA;YAEpE,MAAM,IAAI,CAAC,WAAW,CAAC;gBACrB,KAAK,EAAE;oBACL,IAAI,EAAE,MAAM;oBACZ,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE;oBACpB,QAAQ,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI;oBACxB,UAAU,EAAE,GAAG,CAAC,UAAU;iBAC3B;gBACD,MAAM,EAAE,0BAA0B;gBAClC,SAAS,EAAE,WAAW;gBACtB,OAAO,EAAE,mBAAmB;gBAC5B,MAAM,EAAE,KAAK,CAAC,KAAK,EAAE,UAAU;gBAC/B,SAAS,EAAE,KAAK,CAAC,KAAK,EAAE,SAAS;aAClC,CAAC,CAAA;YAEF,OAAO,EAAE,OAAO,EAAE,CAAA;QACpB,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpToolDefinition } from '@forumone/throughline-plugin-contract';
|
|
2
|
+
import type { ManifestLoader } from '../manifest-source.js';
|
|
3
|
+
export declare function createGetContractTool(loader: ManifestLoader): McpToolDefinition;
|
|
4
|
+
//# sourceMappingURL=get-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-contract.d.ts","sourceRoot":"","sources":["../../src/tools/get-contract.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3D,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,GAAG,iBAAiB,CAiB/E"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function createGetContractTool(loader) {
|
|
3
|
+
return {
|
|
4
|
+
name: 'get_contract',
|
|
5
|
+
description: 'Returns the full contract for a named component, including intent, composition rules, content fields, variants, tokens, accessibility requirements, examples, and anti-examples.',
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
name: z.string().describe('The PascalCase name of the component'),
|
|
8
|
+
}),
|
|
9
|
+
handler: async (input) => {
|
|
10
|
+
const manifest = await loader.get();
|
|
11
|
+
const contract = manifest.getComponent(input.name);
|
|
12
|
+
if (!contract) {
|
|
13
|
+
return { error: `Component "${input.name}" not found in the design system` };
|
|
14
|
+
}
|
|
15
|
+
return contract;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=get-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-contract.js","sourceRoot":"","sources":["../../src/tools/get-contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,MAAM,UAAU,qBAAqB,CAAC,MAAsB;IAC1D,OAAO;QACL,IAAI,EAAE,cAAc;QACpB,WAAW,EACT,kLAAkL;QACpL,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACpB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;SAClE,CAAC;QACF,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YACvB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,CAAA;YACnC,MAAM,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAClD,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO,EAAE,KAAK,EAAE,cAAc,KAAK,CAAC,IAAI,kCAAkC,EAAE,CAAA;YAC9E,CAAC;YACD,OAAO,QAAQ,CAAA;QACjB,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpToolDefinition } from '@forumone/throughline-plugin-contract';
|
|
2
|
+
import type { ManifestLoader } from '../manifest-source.js';
|
|
3
|
+
export declare function createGetTokensTool(loader: ManifestLoader): McpToolDefinition;
|
|
4
|
+
//# sourceMappingURL=get-tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-tokens.d.ts","sourceRoot":"","sources":["../../src/tools/get-tokens.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,iBAAiB,CAiB7E"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function createGetTokensTool(loader) {
|
|
3
|
+
return {
|
|
4
|
+
name: 'get_tokens',
|
|
5
|
+
description: 'Returns the design tokens a component consumes plus the configurable token-backed props and their allowed values.',
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
name: z.string().describe('The PascalCase name of the component'),
|
|
8
|
+
}),
|
|
9
|
+
handler: async (input) => {
|
|
10
|
+
const manifest = await loader.get();
|
|
11
|
+
const contract = manifest.getComponent(input.name);
|
|
12
|
+
if (!contract) {
|
|
13
|
+
return { error: `Component "${input.name}" not found` };
|
|
14
|
+
}
|
|
15
|
+
return contract.tokens;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=get-tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-tokens.js","sourceRoot":"","sources":["../../src/tools/get-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,MAAM,UAAU,mBAAmB,CAAC,MAAsB;IACxD,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,WAAW,EACT,mHAAmH;QACrH,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACpB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;SAClE,CAAC;QACF,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YACvB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,CAAA;YACnC,MAAM,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAClD,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO,EAAE,KAAK,EAAE,cAAc,KAAK,CAAC,IAAI,aAAa,EAAE,CAAA;YACzD,CAAC;YACD,OAAO,QAAQ,CAAC,MAAM,CAAA;QACxB,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpToolDefinition } from '@forumone/throughline-plugin-contract';
|
|
2
|
+
import type { ManifestLoader } from '../manifest-source.js';
|
|
3
|
+
export declare function createGetVariantsTool(loader: ManifestLoader): McpToolDefinition;
|
|
4
|
+
//# sourceMappingURL=get-variants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-variants.d.ts","sourceRoot":"","sources":["../../src/tools/get-variants.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3D,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,GAAG,iBAAiB,CAiB/E"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function createGetVariantsTool(loader) {
|
|
3
|
+
return {
|
|
4
|
+
name: 'get_variants',
|
|
5
|
+
description: 'Returns the available variants for a component, with descriptions and guidance about when to use each.',
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
name: z.string().describe('The PascalCase name of the component'),
|
|
8
|
+
}),
|
|
9
|
+
handler: async (input) => {
|
|
10
|
+
const manifest = await loader.get();
|
|
11
|
+
const contract = manifest.getComponent(input.name);
|
|
12
|
+
if (!contract) {
|
|
13
|
+
return { error: `Component "${input.name}" not found` };
|
|
14
|
+
}
|
|
15
|
+
return { variants: contract.content.variants ?? [] };
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=get-variants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-variants.js","sourceRoot":"","sources":["../../src/tools/get-variants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,MAAM,UAAU,qBAAqB,CAAC,MAAsB;IAC1D,OAAO;QACL,IAAI,EAAE,cAAc;QACpB,WAAW,EACT,wGAAwG;QAC1G,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACpB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;SAClE,CAAC;QACF,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YACvB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,CAAA;YACnC,MAAM,QAAQ,GAAG,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAClD,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,OAAO,EAAE,KAAK,EAAE,cAAc,KAAK,CAAC,IAAI,aAAa,EAAE,CAAA;YACzD,CAAC;YACD,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAA;QACtD,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createListComponentsTool } from './list-components.js';
|
|
2
|
+
export { createGetContractTool } from './get-contract.js';
|
|
3
|
+
export { createGetVariantsTool } from './get-variants.js';
|
|
4
|
+
export { createGetTokensTool } from './get-tokens.js';
|
|
5
|
+
export { createSuggestForIntentTool, type SuggestForIntentDeps, } from './suggest-for-intent.js';
|
|
6
|
+
export { createValidateCompositionTool, type ValidateCompositionDeps, } from './validate-composition.js';
|
|
7
|
+
export { createFindAntiPatternTool, type FindAntiPatternDeps, } from './find-anti-pattern.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAErD,OAAO,EACL,0BAA0B,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,6BAA6B,EAC7B,KAAK,uBAAuB,GAC7B,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACL,yBAAyB,EACzB,KAAK,mBAAmB,GACzB,MAAM,wBAAwB,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createListComponentsTool } from './list-components.js';
|
|
2
|
+
export { createGetContractTool } from './get-contract.js';
|
|
3
|
+
export { createGetVariantsTool } from './get-variants.js';
|
|
4
|
+
export { createGetTokensTool } from './get-tokens.js';
|
|
5
|
+
export { createSuggestForIntentTool, } from './suggest-for-intent.js';
|
|
6
|
+
export { createValidateCompositionTool, } from './validate-composition.js';
|
|
7
|
+
export { createFindAntiPatternTool, } from './find-anti-pattern.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAErD,OAAO,EACL,0BAA0B,GAE3B,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,6BAA6B,GAE9B,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACL,yBAAyB,GAE1B,MAAM,wBAAwB,CAAA"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpToolDefinition } from '@forumone/throughline-plugin-contract';
|
|
2
|
+
import type { ManifestLoader } from '../manifest-source.js';
|
|
3
|
+
export declare function createListComponentsTool(loader: ManifestLoader): McpToolDefinition;
|
|
4
|
+
//# sourceMappingURL=list-components.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-components.d.ts","sourceRoot":"","sources":["../../src/tools/list-components.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3D,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,cAAc,GAAG,iBAAiB,CAuBlF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function createListComponentsTool(loader) {
|
|
3
|
+
return {
|
|
4
|
+
name: 'list_components',
|
|
5
|
+
description: 'Returns the list of components available in the design system. Use this to discover what components exist before composing content.',
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
category: z
|
|
8
|
+
.string()
|
|
9
|
+
.optional()
|
|
10
|
+
.describe('Optional: filter to a single category (hero, section, card, media, cta, navigation, data, form, utility)'),
|
|
11
|
+
}),
|
|
12
|
+
handler: async (input) => {
|
|
13
|
+
const manifest = await loader.get();
|
|
14
|
+
const components = input.category
|
|
15
|
+
? manifest.listByCategory(input.category)
|
|
16
|
+
: Object.values(manifest.raw.components);
|
|
17
|
+
return components.map((c) => ({
|
|
18
|
+
name: c.name,
|
|
19
|
+
category: c.category,
|
|
20
|
+
description: c.description,
|
|
21
|
+
}));
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=list-components.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-components.js","sourceRoot":"","sources":["../../src/tools/list-components.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,MAAM,UAAU,wBAAwB,CAAC,MAAsB;IAC7D,OAAO;QACL,IAAI,EAAE,iBAAiB;QACvB,WAAW,EACT,qIAAqI;QACvI,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACpB,QAAQ,EAAE,CAAC;iBACR,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,0GAA0G,CAAC;SACxH,CAAC;QACF,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YACvB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,CAAA;YACnC,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ;gBAC/B,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC;gBACzC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;YAC1C,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC5B,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,WAAW,EAAE,CAAC,CAAC,WAAW;aAC3B,CAAC,CAAC,CAAA;QACL,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { McpToolDefinition } from '@forumone/throughline-plugin-contract';
|
|
2
|
+
import { type AuditWriter } from '@forumone/throughline-core';
|
|
3
|
+
import type { ManifestLoader } from '../manifest-source.js';
|
|
4
|
+
import type { Matcher } from '../matching/types.js';
|
|
5
|
+
export interface SuggestForIntentDeps {
|
|
6
|
+
loader: ManifestLoader;
|
|
7
|
+
matcher: Matcher;
|
|
8
|
+
auditWriter: AuditWriter;
|
|
9
|
+
maxRecommendations: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function createSuggestForIntentTool(deps: SuggestForIntentDeps): McpToolDefinition;
|
|
12
|
+
//# sourceMappingURL=suggest-for-intent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggest-for-intent.d.ts","sourceRoot":"","sources":["../../src/tools/suggest-for-intent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAC9E,OAAO,EAAE,KAAK,WAAW,EAAY,MAAM,4BAA4B,CAAA;AAEvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAoB,MAAM,sBAAsB,CAAA;AAGrE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,cAAc,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,EAAE,WAAW,CAAA;IACxB,kBAAkB,EAAE,MAAM,CAAA;CAC3B;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,oBAAoB,GAAG,iBAAiB,CAoExF"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { withMeta } from '@forumone/throughline-core';
|
|
3
|
+
import { validateComposition } from '../validation/composition.js';
|
|
4
|
+
export function createSuggestForIntentTool(deps) {
|
|
5
|
+
const inputSchema = withMeta({
|
|
6
|
+
intent: z
|
|
7
|
+
.string()
|
|
8
|
+
.min(5)
|
|
9
|
+
.describe('Natural-language description of what to accomplish (e.g. "introduce a new program")'),
|
|
10
|
+
context: z
|
|
11
|
+
.object({
|
|
12
|
+
existingBlocks: z
|
|
13
|
+
.array(z.string())
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('Component names already on the page, in order'),
|
|
16
|
+
pageType: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Optional page-type hint: landing, article, program, etc.'),
|
|
20
|
+
})
|
|
21
|
+
.optional(),
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
name: 'suggest_for_intent',
|
|
25
|
+
description: "Given a natural-language description of what the author wants to accomplish, returns ranked component recommendations with reasoning. Optionally accepts the existing page context so duplicate Heroes / composition conflicts surface as warnings on the suggestions.",
|
|
26
|
+
inputSchema,
|
|
27
|
+
handler: async (input, ctx) => {
|
|
28
|
+
const manifest = await deps.loader.get();
|
|
29
|
+
const allComponents = Object.values(manifest.raw.components);
|
|
30
|
+
const ranked = deps.matcher.rank(input.intent);
|
|
31
|
+
const recommendations = [];
|
|
32
|
+
for (const { component, score } of ranked) {
|
|
33
|
+
if (recommendations.length >= deps.maxRecommendations)
|
|
34
|
+
break;
|
|
35
|
+
const warnings = collectWarnings(component, input.context?.existingBlocks, manifest);
|
|
36
|
+
recommendations.push({
|
|
37
|
+
component: component.name,
|
|
38
|
+
score,
|
|
39
|
+
reasoning: formatReasoning(component, score, warnings),
|
|
40
|
+
matchedIntent: component.examples[0]?.intent ?? component.intent,
|
|
41
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// Audit (fire-and-forget; never blocks the response).
|
|
45
|
+
await deps.auditWriter({
|
|
46
|
+
actor: {
|
|
47
|
+
type: 'user',
|
|
48
|
+
userId: ctx.user?.id,
|
|
49
|
+
userName: ctx.user?.name,
|
|
50
|
+
apiKeyName: ctx.apiKeyName,
|
|
51
|
+
},
|
|
52
|
+
action: 'design.suggest',
|
|
53
|
+
mcpServer: 'component',
|
|
54
|
+
mcpTool: 'suggest_for_intent',
|
|
55
|
+
prompt: input._meta?.userPrompt ?? input.intent,
|
|
56
|
+
reasoning: input._meta?.reasoning,
|
|
57
|
+
changesSummary: `Suggested components for: ${input.intent.slice(0, 120)}`,
|
|
58
|
+
});
|
|
59
|
+
// Quiet the unused-variable lint when the recommendation set
|
|
60
|
+
// is smaller than the candidate pool — `allComponents` keeps the
|
|
61
|
+
// contract method available for future extensions.
|
|
62
|
+
void allComponents;
|
|
63
|
+
return { recommendations };
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function collectWarnings(component, existingBlocks, manifest) {
|
|
68
|
+
if (!existingBlocks || existingBlocks.length === 0)
|
|
69
|
+
return [];
|
|
70
|
+
const proposed = [
|
|
71
|
+
...existingBlocks.map((type) => ({ type })),
|
|
72
|
+
{ type: component.name },
|
|
73
|
+
];
|
|
74
|
+
const result = validateComposition({ blocks: proposed }, manifest);
|
|
75
|
+
return result.issues
|
|
76
|
+
.filter((i) => i.severity === 'error' && i.blockIndex === proposed.length - 1)
|
|
77
|
+
.map((i) => i.message);
|
|
78
|
+
}
|
|
79
|
+
function formatReasoning(component, score, warnings) {
|
|
80
|
+
const confidence = score > 0.3 ? 'strong match' : score > 0.05 ? 'moderate match' : 'possible match';
|
|
81
|
+
const base = `${component.name}: ${confidence}. ${component.description}`;
|
|
82
|
+
if (warnings.length === 0)
|
|
83
|
+
return base;
|
|
84
|
+
return `${base} Note: ${warnings.join(' ')}`;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=suggest-for-intent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggest-for-intent.js","sourceRoot":"","sources":["../../src/tools/suggest-for-intent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { McpToolDefinition } from '@forumone/throughline-plugin-contract';
|
|
2
|
+
import { type AuditWriter } from '@forumone/throughline-core';
|
|
3
|
+
import type { ManifestLoader } from '../manifest-source.js';
|
|
4
|
+
export interface ValidateCompositionDeps {
|
|
5
|
+
loader: ManifestLoader;
|
|
6
|
+
auditWriter: AuditWriter;
|
|
7
|
+
}
|
|
8
|
+
export declare function createValidateCompositionTool(deps: ValidateCompositionDeps): McpToolDefinition;
|
|
9
|
+
//# sourceMappingURL=validate-composition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-composition.d.ts","sourceRoot":"","sources":["../../src/tools/validate-composition.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAC9E,OAAO,EAAE,KAAK,WAAW,EAAY,MAAM,4BAA4B,CAAA;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAG3D,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,cAAc,CAAA;IACtB,WAAW,EAAE,WAAW,CAAA;CACzB;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,uBAAuB,GAAG,iBAAiB,CAsC9F"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { withMeta } from '@forumone/throughline-core';
|
|
3
|
+
import { validateComposition } from '../validation/composition.js';
|
|
4
|
+
export function createValidateCompositionTool(deps) {
|
|
5
|
+
const inputSchema = withMeta({
|
|
6
|
+
blocks: z
|
|
7
|
+
.array(z.object({
|
|
8
|
+
type: z.string(),
|
|
9
|
+
variant: z.string().optional(),
|
|
10
|
+
}))
|
|
11
|
+
.min(1),
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
name: 'validate_composition',
|
|
15
|
+
description: "Validates a proposed page layout against the design system's composition rules. Returns errors (blocking publish) and warnings (advisory). Call this before recommending a final layout.",
|
|
16
|
+
inputSchema,
|
|
17
|
+
handler: async (input, ctx) => {
|
|
18
|
+
const manifest = await deps.loader.get();
|
|
19
|
+
const result = validateComposition({ blocks: input.blocks }, manifest);
|
|
20
|
+
await deps.auditWriter({
|
|
21
|
+
actor: {
|
|
22
|
+
type: 'user',
|
|
23
|
+
userId: ctx.user?.id,
|
|
24
|
+
userName: ctx.user?.name,
|
|
25
|
+
apiKeyName: ctx.apiKeyName,
|
|
26
|
+
},
|
|
27
|
+
action: 'design.validate',
|
|
28
|
+
mcpServer: 'component',
|
|
29
|
+
mcpTool: 'validate_composition',
|
|
30
|
+
prompt: input._meta?.userPrompt,
|
|
31
|
+
reasoning: input._meta?.reasoning,
|
|
32
|
+
});
|
|
33
|
+
return result;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=validate-composition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-composition.js","sourceRoot":"","sources":["../../src/tools/validate-composition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAoB,QAAQ,EAAE,MAAM,4BAA4B,CAAA;AAEvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAA;AAOlE,MAAM,UAAU,6BAA6B,CAAC,IAA6B;IACzE,MAAM,WAAW,GAAG,QAAQ,CAAC;QAC3B,MAAM,EAAE,CAAC;aACN,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;YACP,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;YAChB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SAC/B,CAAC,CACH;aACA,GAAG,CAAC,CAAC,CAAC;KACV,CAAC,CAAA;IAEF,OAAO;QACL,IAAI,EAAE,sBAAsB;QAC5B,WAAW,EACT,0LAA0L;QAC5L,WAAW;QACX,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC5B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAA;YACxC,MAAM,MAAM,GAAG,mBAAmB,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAA;YAEtE,MAAM,IAAI,CAAC,WAAW,CAAC;gBACrB,KAAK,EAAE;oBACL,IAAI,EAAE,MAAM;oBACZ,MAAM,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE;oBACpB,QAAQ,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI;oBACxB,UAAU,EAAE,GAAG,CAAC,UAAU;iBAC3B;gBACD,MAAM,EAAE,iBAAiB;gBACzB,SAAS,EAAE,WAAW;gBACtB,OAAO,EAAE,sBAAsB;gBAC/B,MAAM,EAAE,KAAK,CAAC,KAAK,EAAE,UAAU;gBAC/B,SAAS,EAAE,KAAK,CAAC,KAAK,EAAE,SAAS;aAClC,CAAC,CAAA;YAEF,OAAO,MAAM,CAAA;QACf,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { LoadedManifest } from '@forumone/throughline-design-contract';
|
|
2
|
+
export interface CompositionBlock {
|
|
3
|
+
type: string;
|
|
4
|
+
variant?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CompositionInput {
|
|
7
|
+
blocks: CompositionBlock[];
|
|
8
|
+
}
|
|
9
|
+
export interface CompositionIssue {
|
|
10
|
+
severity: 'error' | 'warning';
|
|
11
|
+
rule: string;
|
|
12
|
+
message: string;
|
|
13
|
+
blockIndex?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface CompositionResult {
|
|
16
|
+
valid: boolean;
|
|
17
|
+
issues: CompositionIssue[];
|
|
18
|
+
}
|
|
19
|
+
export interface AntiPatternMatch {
|
|
20
|
+
pattern: string;
|
|
21
|
+
why: string;
|
|
22
|
+
useInstead?: string | undefined;
|
|
23
|
+
blockIndex: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Validates a proposed page layout against the design system's composition
|
|
27
|
+
* rules: forbiddenAdjacent, maxPerPage, requiredSiblings, unknown components,
|
|
28
|
+
* unknown variants. Errors block; warnings advise.
|
|
29
|
+
*/
|
|
30
|
+
export declare function validateComposition(input: CompositionInput, manifest: LoadedManifest): CompositionResult;
|
|
31
|
+
/**
|
|
32
|
+
* Surfaces structural anti-patterns inferred from each component's
|
|
33
|
+
* `antiExamples` plus position-aware heuristics. The current rule set is
|
|
34
|
+
* intentionally small and explicit; new rules should be added with care
|
|
35
|
+
* since false positives are worse than missed detections here.
|
|
36
|
+
*/
|
|
37
|
+
export declare function findAntiPatterns(input: CompositionInput, manifest: LoadedManifest): AntiPatternMatch[];
|
|
38
|
+
//# sourceMappingURL=composition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../../src/validation/composition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAA;AAE3E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,gBAAgB,EACvB,QAAQ,EAAE,cAAc,GACvB,iBAAiB,CA+EnB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,gBAAgB,EACvB,QAAQ,EAAE,cAAc,GACvB,gBAAgB,EAAE,CAmDpB"}
|