universal-dev-standards 5.1.0-beta.6 → 5.1.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/README.md +6 -0
- package/bin/uds.js +14 -0
- package/bundled/ai/standards/agent-communication-protocol.ai.yaml +34 -0
- package/bundled/ai/standards/anti-sycophancy-prompting.ai.yaml +111 -0
- package/bundled/ai/standards/capability-declaration.ai.yaml +113 -0
- package/bundled/ai/standards/circuit-breaker.ai.yaml +93 -0
- package/bundled/ai/standards/developer-memory.ai.yaml +13 -0
- package/bundled/ai/standards/dual-phase-output.ai.yaml +108 -0
- package/bundled/ai/standards/failure-source-taxonomy.ai.yaml +115 -0
- package/bundled/ai/standards/frontend-design-standards.ai.yaml +305 -0
- package/bundled/ai/standards/health-check-standards.ai.yaml +140 -0
- package/bundled/ai/standards/immutability-first.ai.yaml +112 -0
- package/bundled/ai/standards/model-selection.ai.yaml +111 -3
- package/bundled/ai/standards/packaging-standards.ai.yaml +142 -0
- package/bundled/ai/standards/recovery-recipe-registry.ai.yaml +200 -0
- package/bundled/ai/standards/retry-standards.ai.yaml +134 -0
- package/bundled/ai/standards/security-decision.ai.yaml +87 -0
- package/bundled/ai/standards/skill-standard-alignment-check.ai.yaml +119 -0
- package/bundled/ai/standards/standard-admission-criteria.ai.yaml +107 -0
- package/bundled/ai/standards/standard-lifecycle-management.ai.yaml +144 -0
- package/bundled/ai/standards/timeout-standards.ai.yaml +104 -0
- package/bundled/ai/standards/token-budget.ai.yaml +108 -0
- package/bundled/ai/standards/translation-lifecycle-standards.ai.yaml +145 -0
- package/bundled/core/anti-sycophancy-prompting.md +184 -0
- package/bundled/core/capability-declaration.md +59 -0
- package/bundled/core/circuit-breaker.md +58 -0
- package/bundled/core/developer-memory.md +29 -1
- package/bundled/core/dual-phase-output.md +56 -0
- package/bundled/core/failure-source-taxonomy.md +72 -0
- package/bundled/core/frontend-design-standards.md +474 -0
- package/bundled/core/health-check-standards.md +72 -0
- package/bundled/core/immutability-first.md +105 -0
- package/bundled/core/model-selection.md +80 -0
- package/bundled/core/packaging-standards.md +216 -0
- package/bundled/core/recovery-recipe-registry.md +69 -0
- package/bundled/core/retry-standards.md +62 -0
- package/bundled/core/security-decision.md +65 -0
- package/bundled/core/skill-standard-alignment-check.md +79 -0
- package/bundled/core/standard-admission-criteria.md +84 -0
- package/bundled/core/standard-lifecycle-management.md +94 -0
- package/bundled/core/timeout-standards.md +63 -0
- package/bundled/core/token-budget.md +58 -0
- package/bundled/core/translation-lifecycle-standards.md +162 -0
- package/bundled/locales/zh-CN/CHANGELOG.md +51 -3
- package/bundled/locales/zh-CN/README.md +1 -1
- package/bundled/locales/zh-CN/core/anti-hallucination.md +22 -3
- package/bundled/locales/zh-CN/core/anti-sycophancy-prompting.md +192 -0
- package/bundled/locales/zh-CN/core/capability-declaration.md +123 -0
- package/bundled/locales/zh-CN/core/circuit-breaker.md +106 -0
- package/bundled/locales/zh-CN/core/dual-phase-output.md +103 -0
- package/bundled/locales/zh-CN/core/failure-source-taxonomy.md +99 -0
- package/bundled/locales/zh-CN/core/frontend-design-standards.md +289 -0
- package/bundled/locales/zh-CN/core/health-check-standards.md +144 -0
- package/bundled/locales/zh-CN/core/immutability-first.md +96 -0
- package/bundled/locales/zh-CN/core/packaging-standards.md +224 -0
- package/bundled/locales/zh-CN/core/recovery-recipe-registry.md +146 -0
- package/bundled/locales/zh-CN/core/retry-standards.md +131 -0
- package/bundled/locales/zh-CN/core/security-decision.md +104 -0
- package/bundled/locales/zh-CN/core/skill-standard-alignment-check.md +112 -0
- package/bundled/locales/zh-CN/core/standard-admission-criteria.md +104 -0
- package/bundled/locales/zh-CN/core/standard-lifecycle-management.md +116 -0
- package/bundled/locales/zh-CN/core/timeout-standards.md +117 -0
- package/bundled/locales/zh-CN/core/token-budget.md +108 -0
- package/bundled/locales/zh-CN/core/translation-lifecycle-standards.md +159 -0
- package/bundled/locales/zh-TW/CHANGELOG.md +51 -3
- package/bundled/locales/zh-TW/README.md +1 -1
- package/bundled/locales/zh-TW/core/anti-sycophancy-prompting.md +192 -0
- package/bundled/locales/zh-TW/core/capability-declaration.md +111 -0
- package/bundled/locales/zh-TW/core/circuit-breaker.md +111 -0
- package/bundled/locales/zh-TW/core/dual-phase-output.md +132 -0
- package/bundled/locales/zh-TW/core/failure-source-taxonomy.md +146 -0
- package/bundled/locales/zh-TW/core/frontend-design-standards.md +460 -0
- package/bundled/locales/zh-TW/core/health-check-standards.md +144 -0
- package/bundled/locales/zh-TW/core/immutability-first.md +159 -0
- package/bundled/locales/zh-TW/core/packaging-standards.md +224 -0
- package/bundled/locales/zh-TW/core/recovery-recipe-registry.md +146 -0
- package/bundled/locales/zh-TW/core/retry-standards.md +140 -0
- package/bundled/locales/zh-TW/core/security-decision.md +120 -0
- package/bundled/locales/zh-TW/core/skill-standard-alignment-check.md +112 -0
- package/bundled/locales/zh-TW/core/standard-admission-criteria.md +104 -0
- package/bundled/locales/zh-TW/core/standard-lifecycle-management.md +116 -0
- package/bundled/locales/zh-TW/core/timeout-standards.md +117 -0
- package/bundled/locales/zh-TW/core/token-budget.md +143 -0
- package/bundled/locales/zh-TW/core/translation-lifecycle-standards.md +159 -0
- package/bundled/skills/e2e-assistant/SKILL.md +19 -5
- package/bundled/skills/testing-guide/SKILL.md +5 -0
- package/bundled/skills/testing-guide/test-skeleton-templates.md +316 -0
- package/package.json +2 -1
- package/src/commands/check.js +6 -0
- package/src/commands/config.js +9 -0
- package/src/commands/init.js +97 -46
- package/src/commands/mcp.js +26 -0
- package/src/commands/run-intent.js +66 -0
- package/src/commands/update.js +41 -4
- package/src/core/command-router.js +85 -0
- package/src/core/project-config.js +91 -0
- package/src/flows/init-flow.js +6 -1
- package/src/i18n/messages.js +6 -6
- package/src/mcp/__tests__/server.test.js +251 -0
- package/src/mcp/server.js +352 -0
- package/src/prompts/init.js +157 -1
- package/src/reconciler/actual-state-scanner.js +24 -0
- package/src/uninstallers/hook-uninstaller.js +32 -1
- package/src/utils/detect-self-adoption.js +173 -0
- package/src/utils/e2e-analyzer.js +88 -5
- package/src/utils/e2e-detector.js +73 -1
- package/src/utils/integration-generator.js +22 -3
- package/standards-registry.json +203 -4
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for UDS MCP Design Standards Server
|
|
3
|
+
* Uses vi.mock('fs') to avoid real filesystem I/O
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Mock 'fs' module — must be declared before importing the module under test
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
vi.mock('fs', () => ({
|
|
12
|
+
readFileSync: vi.fn(),
|
|
13
|
+
existsSync: vi.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
import { readFileSync, existsSync } from 'fs';
|
|
17
|
+
import { McpServer } from '../server.js';
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Fixture data
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
const VALID_DESIGN_MD = `
|
|
23
|
+
# DESIGN.md — My Project
|
|
24
|
+
|
|
25
|
+
## 1. Visual Theme & Mood
|
|
26
|
+
Theme: Minimal
|
|
27
|
+
|
|
28
|
+
## 2. Color Palette
|
|
29
|
+
Primary: #0070f3
|
|
30
|
+
|
|
31
|
+
## 3. Typography
|
|
32
|
+
Font: Inter
|
|
33
|
+
|
|
34
|
+
## 4. Component Styling
|
|
35
|
+
Border-radius: 4px
|
|
36
|
+
|
|
37
|
+
## 5. Layout & Spacing
|
|
38
|
+
Grid: 8px
|
|
39
|
+
|
|
40
|
+
## 6. Design Guidelines
|
|
41
|
+
Keep it clean.
|
|
42
|
+
|
|
43
|
+
> **Version**: 1.0.0
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
const INCOMPLETE_DESIGN_MD = `
|
|
47
|
+
# DESIGN.md — Incomplete Project
|
|
48
|
+
|
|
49
|
+
## 1. Visual Theme & Mood
|
|
50
|
+
Theme: Dark
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
const UDS_TEMPLATE_MD = `# DESIGN.md — [專案名稱] 前端設計規格
|
|
54
|
+
> **版本**: 1.0.0
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
const FRONTEND_STANDARDS_YAML = `id: frontend-design-standards
|
|
58
|
+
meta:
|
|
59
|
+
version: "1.0.0"
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Helpers
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
function makeServer() {
|
|
66
|
+
return new McpServer({
|
|
67
|
+
udsRoot: '/fake/project',
|
|
68
|
+
udsRepoRoot: '/fake/uds-repo',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function makeRequest(method, params = {}, id = 1) {
|
|
73
|
+
return { jsonrpc: '2.0', id, method, params };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Tests
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
describe('McpServer', () => {
|
|
80
|
+
let server;
|
|
81
|
+
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
vi.resetAllMocks();
|
|
84
|
+
server = makeServer();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// -------------------------------------------------------------------------
|
|
88
|
+
// 1. handleInitialize — protocolVersion and serverInfo
|
|
89
|
+
// -------------------------------------------------------------------------
|
|
90
|
+
it('handleInitialize returns correct protocolVersion', () => {
|
|
91
|
+
const req = makeRequest('initialize');
|
|
92
|
+
const res = server.handleInitialize(req);
|
|
93
|
+
expect(res.result.protocolVersion).toBe('2024-11-05');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// -------------------------------------------------------------------------
|
|
97
|
+
// 2. handleInitialize — capabilities contains tools
|
|
98
|
+
// -------------------------------------------------------------------------
|
|
99
|
+
it('handleInitialize includes tools capability', () => {
|
|
100
|
+
const req = makeRequest('initialize');
|
|
101
|
+
const res = server.handleInitialize(req);
|
|
102
|
+
expect(res.result.capabilities).toHaveProperty('tools');
|
|
103
|
+
expect(res.result.serverInfo.name).toBe('uds-design-standards');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// -------------------------------------------------------------------------
|
|
107
|
+
// 3. handleToolsList — returns exactly 3 tools
|
|
108
|
+
// -------------------------------------------------------------------------
|
|
109
|
+
it('handleToolsList returns 3 tools', () => {
|
|
110
|
+
const req = makeRequest('tools/list');
|
|
111
|
+
const res = server.handleToolsList(req);
|
|
112
|
+
expect(res.result.tools).toHaveLength(3);
|
|
113
|
+
const names = res.result.tools.map((t) => t.name);
|
|
114
|
+
expect(names).toContain('get_design_token');
|
|
115
|
+
expect(names).toContain('get_design_standards');
|
|
116
|
+
expect(names).toContain('validate_design_token');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// -------------------------------------------------------------------------
|
|
120
|
+
// 4. handleToolsList — each tool has name, description, inputSchema
|
|
121
|
+
// -------------------------------------------------------------------------
|
|
122
|
+
it('handleToolsList tools each have name, description, inputSchema', () => {
|
|
123
|
+
const req = makeRequest('tools/list');
|
|
124
|
+
const res = server.handleToolsList(req);
|
|
125
|
+
for (const tool of res.result.tools) {
|
|
126
|
+
expect(tool).toHaveProperty('name');
|
|
127
|
+
expect(tool).toHaveProperty('description');
|
|
128
|
+
expect(tool).toHaveProperty('inputSchema');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// -------------------------------------------------------------------------
|
|
133
|
+
// 5. get_design_token — returns project DESIGN.md when it exists
|
|
134
|
+
// -------------------------------------------------------------------------
|
|
135
|
+
it('get_design_token returns project DESIGN.md content when file exists', () => {
|
|
136
|
+
existsSync.mockReturnValue(true);
|
|
137
|
+
readFileSync.mockReturnValue(VALID_DESIGN_MD);
|
|
138
|
+
|
|
139
|
+
const req = makeRequest('tools/call', {
|
|
140
|
+
name: 'get_design_token',
|
|
141
|
+
arguments: { project_path: '/some/project' },
|
|
142
|
+
});
|
|
143
|
+
const res = server.handleToolsCall(req);
|
|
144
|
+
|
|
145
|
+
expect(res.result.content[0].type).toBe('text');
|
|
146
|
+
expect(res.result.content[0].text).toContain('Visual Theme');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// -------------------------------------------------------------------------
|
|
150
|
+
// 6. get_design_token — returns UDS template when DESIGN.md not found
|
|
151
|
+
// -------------------------------------------------------------------------
|
|
152
|
+
it('get_design_token returns UDS template when DESIGN.md is missing', () => {
|
|
153
|
+
existsSync.mockReturnValue(false);
|
|
154
|
+
readFileSync.mockReturnValue(UDS_TEMPLATE_MD);
|
|
155
|
+
|
|
156
|
+
const req = makeRequest('tools/call', {
|
|
157
|
+
name: 'get_design_token',
|
|
158
|
+
arguments: { project_path: '/no/design/md/here' },
|
|
159
|
+
});
|
|
160
|
+
const res = server.handleToolsCall(req);
|
|
161
|
+
|
|
162
|
+
expect(res.result.content[0].text).toContain('UDS DESIGN.md template');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// -------------------------------------------------------------------------
|
|
166
|
+
// 7. get_design_token — returns content array with type "text"
|
|
167
|
+
// -------------------------------------------------------------------------
|
|
168
|
+
it('get_design_token returns content array with type text', () => {
|
|
169
|
+
existsSync.mockReturnValue(true);
|
|
170
|
+
readFileSync.mockReturnValue(VALID_DESIGN_MD);
|
|
171
|
+
|
|
172
|
+
const req = makeRequest('tools/call', {
|
|
173
|
+
name: 'get_design_token',
|
|
174
|
+
arguments: { project_path: '/some/project' },
|
|
175
|
+
});
|
|
176
|
+
const res = server.handleToolsCall(req);
|
|
177
|
+
|
|
178
|
+
expect(Array.isArray(res.result.content)).toBe(true);
|
|
179
|
+
expect(res.result.content[0]).toMatchObject({ type: 'text' });
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// -------------------------------------------------------------------------
|
|
183
|
+
// 8. get_design_standards — returns frontend-design-standards.ai.yaml
|
|
184
|
+
// -------------------------------------------------------------------------
|
|
185
|
+
it('get_design_standards returns YAML standards content', () => {
|
|
186
|
+
readFileSync.mockReturnValue(FRONTEND_STANDARDS_YAML);
|
|
187
|
+
|
|
188
|
+
const req = makeRequest('tools/call', {
|
|
189
|
+
name: 'get_design_standards',
|
|
190
|
+
arguments: {},
|
|
191
|
+
});
|
|
192
|
+
const res = server.handleToolsCall(req);
|
|
193
|
+
|
|
194
|
+
expect(res.result.content[0].type).toBe('text');
|
|
195
|
+
expect(res.result.content[0].text).toContain('frontend-design-standards');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// -------------------------------------------------------------------------
|
|
199
|
+
// 9. validate_design_token — valid DESIGN.md returns valid=true, no missing
|
|
200
|
+
// -------------------------------------------------------------------------
|
|
201
|
+
it('validate_design_token returns valid=true for complete DESIGN.md', () => {
|
|
202
|
+
readFileSync.mockReturnValue(VALID_DESIGN_MD);
|
|
203
|
+
|
|
204
|
+
const req = makeRequest('tools/call', {
|
|
205
|
+
name: 'validate_design_token',
|
|
206
|
+
arguments: { design_md_path: '/some/project/DESIGN.md' },
|
|
207
|
+
});
|
|
208
|
+
const res = server.handleToolsCall(req);
|
|
209
|
+
|
|
210
|
+
const parsed = JSON.parse(res.result.content[0].text);
|
|
211
|
+
expect(parsed.valid).toBe(true);
|
|
212
|
+
expect(parsed.missing_sections).toHaveLength(0);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// -------------------------------------------------------------------------
|
|
216
|
+
// 10. validate_design_token — missing sections returns valid=false + list
|
|
217
|
+
// -------------------------------------------------------------------------
|
|
218
|
+
it('validate_design_token returns valid=false with missing sections listed', () => {
|
|
219
|
+
readFileSync.mockReturnValue(INCOMPLETE_DESIGN_MD);
|
|
220
|
+
|
|
221
|
+
const req = makeRequest('tools/call', {
|
|
222
|
+
name: 'validate_design_token',
|
|
223
|
+
arguments: { design_md_path: '/some/project/DESIGN.md' },
|
|
224
|
+
});
|
|
225
|
+
const res = server.handleToolsCall(req);
|
|
226
|
+
|
|
227
|
+
const parsed = JSON.parse(res.result.content[0].text);
|
|
228
|
+
expect(parsed.valid).toBe(false);
|
|
229
|
+
expect(parsed.missing_sections.length).toBeGreaterThan(0);
|
|
230
|
+
// color-palette should be missing
|
|
231
|
+
expect(parsed.missing_sections).toContain('color-palette');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// -------------------------------------------------------------------------
|
|
235
|
+
// 11. handleRequest — unknown method returns JSON-RPC error code -32601
|
|
236
|
+
// -------------------------------------------------------------------------
|
|
237
|
+
it('handleRequest returns -32601 for unknown method', () => {
|
|
238
|
+
const req = makeRequest('unknown/method');
|
|
239
|
+
const res = server.handleRequest(req);
|
|
240
|
+
expect(res.error.code).toBe(-32601);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// -------------------------------------------------------------------------
|
|
244
|
+
// 12. handleRequest — malformed request (non-object) returns error
|
|
245
|
+
// -------------------------------------------------------------------------
|
|
246
|
+
it('handleRequest handles malformed (non-object) request gracefully', () => {
|
|
247
|
+
const res = server.handleRequest(null);
|
|
248
|
+
expect(res).toHaveProperty('error');
|
|
249
|
+
expect(res.error.code).toBe(-32600);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UDS MCP Design Standards Server
|
|
3
|
+
* Implements MCP (Model Context Protocol) stdio transport using JSON-RPC 2.0
|
|
4
|
+
* No external MCP SDK required — uses Node.js standard library only
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, existsSync } from 'fs';
|
|
8
|
+
import { join, resolve, dirname } from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
// UDS standards root (relative to this file: cli/src/mcp/server.js → repo root is ../../../../)
|
|
14
|
+
const UDS_REPO_ROOT = resolve(__dirname, '../../../../');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Required DESIGN.md sections per frontend-design-standards.
|
|
18
|
+
* Each entry maps a section id (used in validation output) to a search pattern
|
|
19
|
+
* that should appear in the document (case-insensitive).
|
|
20
|
+
*/
|
|
21
|
+
const REQUIRED_DESIGN_SECTIONS = [
|
|
22
|
+
{ id: 'visual-theme', pattern: /visual[\s\-&]+theme/i },
|
|
23
|
+
{ id: 'color-palette', pattern: /color[\s\-&]+palette/i },
|
|
24
|
+
{ id: 'typography', pattern: /typography/i },
|
|
25
|
+
{ id: 'component-styling', pattern: /component[\s\-&]+styling/i },
|
|
26
|
+
{ id: 'layout-spacing', pattern: /layout[\s\S]{0,10}spacing/i },
|
|
27
|
+
{ id: 'design-guidelines', pattern: /design[\s\S]{0,20}guidelines/i },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export class McpServer {
|
|
31
|
+
constructor(options = {}) {
|
|
32
|
+
this.udsRoot = options.udsRoot || process.cwd();
|
|
33
|
+
// For reading UDS bundled files, always use UDS_REPO_ROOT
|
|
34
|
+
this.udsRepoRoot = options.udsRepoRoot || UDS_REPO_ROOT;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Start the MCP server — listens on stdin, writes to stdout
|
|
39
|
+
*/
|
|
40
|
+
start() {
|
|
41
|
+
let buffer = '';
|
|
42
|
+
|
|
43
|
+
process.stdin.setEncoding('utf8');
|
|
44
|
+
process.stdin.on('data', (chunk) => {
|
|
45
|
+
buffer += chunk;
|
|
46
|
+
const lines = buffer.split('\n');
|
|
47
|
+
// Keep the last (potentially incomplete) line in buffer
|
|
48
|
+
buffer = lines.pop();
|
|
49
|
+
|
|
50
|
+
for (const line of lines) {
|
|
51
|
+
const trimmed = line.trim();
|
|
52
|
+
if (!trimmed) continue;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const request = JSON.parse(trimmed);
|
|
56
|
+
const response = this.handleRequest(request);
|
|
57
|
+
if (response !== null && response !== undefined) {
|
|
58
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
// JSON parse error
|
|
62
|
+
const errorResponse = {
|
|
63
|
+
jsonrpc: '2.0',
|
|
64
|
+
id: null,
|
|
65
|
+
error: {
|
|
66
|
+
code: -32700,
|
|
67
|
+
message: 'Parse error',
|
|
68
|
+
data: e.message,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
process.stdout.write(JSON.stringify(errorResponse) + '\n');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
process.stdin.on('end', () => {
|
|
77
|
+
// Process any remaining buffered content
|
|
78
|
+
if (buffer.trim()) {
|
|
79
|
+
try {
|
|
80
|
+
const request = JSON.parse(buffer.trim());
|
|
81
|
+
const response = this.handleRequest(request);
|
|
82
|
+
if (response !== null && response !== undefined) {
|
|
83
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// Ignore final incomplete message
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Route a JSON-RPC 2.0 request to the appropriate handler
|
|
94
|
+
* @param {object} request
|
|
95
|
+
* @returns {object|null} JSON-RPC response (null for notifications)
|
|
96
|
+
*/
|
|
97
|
+
handleRequest(request) {
|
|
98
|
+
if (!request || typeof request !== 'object') {
|
|
99
|
+
return {
|
|
100
|
+
jsonrpc: '2.0',
|
|
101
|
+
id: null,
|
|
102
|
+
error: { code: -32600, message: 'Invalid Request' },
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const { method, id } = request;
|
|
107
|
+
|
|
108
|
+
// Notifications (no id) do not get a response
|
|
109
|
+
if (id === undefined) return null;
|
|
110
|
+
|
|
111
|
+
switch (method) {
|
|
112
|
+
case 'initialize':
|
|
113
|
+
return this.handleInitialize(request);
|
|
114
|
+
case 'tools/list':
|
|
115
|
+
return this.handleToolsList(request);
|
|
116
|
+
case 'tools/call':
|
|
117
|
+
return this.handleToolsCall(request);
|
|
118
|
+
default:
|
|
119
|
+
return {
|
|
120
|
+
jsonrpc: '2.0',
|
|
121
|
+
id,
|
|
122
|
+
error: {
|
|
123
|
+
code: -32601,
|
|
124
|
+
message: `Method not found: ${method}`,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Handle MCP initialize request
|
|
132
|
+
*/
|
|
133
|
+
handleInitialize(request) {
|
|
134
|
+
return {
|
|
135
|
+
jsonrpc: '2.0',
|
|
136
|
+
id: request.id,
|
|
137
|
+
result: {
|
|
138
|
+
protocolVersion: '2024-11-05',
|
|
139
|
+
capabilities: {
|
|
140
|
+
tools: {},
|
|
141
|
+
},
|
|
142
|
+
serverInfo: {
|
|
143
|
+
name: 'uds-design-standards',
|
|
144
|
+
version: '1.0.0',
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Handle tools/list request — return available tools
|
|
152
|
+
*/
|
|
153
|
+
handleToolsList(request) {
|
|
154
|
+
return {
|
|
155
|
+
jsonrpc: '2.0',
|
|
156
|
+
id: request.id,
|
|
157
|
+
result: {
|
|
158
|
+
tools: [
|
|
159
|
+
{
|
|
160
|
+
name: 'get_design_token',
|
|
161
|
+
description:
|
|
162
|
+
'Get the DESIGN.md content for a project, or return the UDS DESIGN.md template if not found',
|
|
163
|
+
inputSchema: {
|
|
164
|
+
type: 'object',
|
|
165
|
+
properties: {
|
|
166
|
+
project_path: {
|
|
167
|
+
type: 'string',
|
|
168
|
+
description: 'Absolute or relative path to the project root',
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
required: ['project_path'],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'get_design_standards',
|
|
176
|
+
description:
|
|
177
|
+
'Get the UDS frontend design standards (frontend-design-standards.ai.yaml)',
|
|
178
|
+
inputSchema: {
|
|
179
|
+
type: 'object',
|
|
180
|
+
properties: {},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: 'validate_design_token',
|
|
185
|
+
description:
|
|
186
|
+
'Validate a DESIGN.md file against UDS frontend design standards',
|
|
187
|
+
inputSchema: {
|
|
188
|
+
type: 'object',
|
|
189
|
+
properties: {
|
|
190
|
+
design_md_path: {
|
|
191
|
+
type: 'string',
|
|
192
|
+
description: 'Path to the DESIGN.md file to validate',
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
required: ['design_md_path'],
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Handle tools/call request — dispatch to specific tool implementation
|
|
205
|
+
* @param {object} request
|
|
206
|
+
* @returns {object} JSON-RPC response
|
|
207
|
+
*/
|
|
208
|
+
handleToolsCall(request) {
|
|
209
|
+
const { id, params } = request;
|
|
210
|
+
const toolName = params?.name;
|
|
211
|
+
const args = params?.arguments || {};
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
let toolResult;
|
|
215
|
+
|
|
216
|
+
switch (toolName) {
|
|
217
|
+
case 'get_design_token':
|
|
218
|
+
toolResult = this._getDesignToken(args);
|
|
219
|
+
break;
|
|
220
|
+
case 'get_design_standards':
|
|
221
|
+
toolResult = this._getDesignStandards();
|
|
222
|
+
break;
|
|
223
|
+
case 'validate_design_token':
|
|
224
|
+
toolResult = this._validateDesignToken(args);
|
|
225
|
+
break;
|
|
226
|
+
default:
|
|
227
|
+
return {
|
|
228
|
+
jsonrpc: '2.0',
|
|
229
|
+
id,
|
|
230
|
+
error: {
|
|
231
|
+
code: -32601,
|
|
232
|
+
message: `Unknown tool: ${toolName}`,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
jsonrpc: '2.0',
|
|
239
|
+
id,
|
|
240
|
+
result: toolResult,
|
|
241
|
+
};
|
|
242
|
+
} catch (err) {
|
|
243
|
+
return {
|
|
244
|
+
jsonrpc: '2.0',
|
|
245
|
+
id,
|
|
246
|
+
error: {
|
|
247
|
+
code: -32000,
|
|
248
|
+
message: err.message,
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Tool: get_design_token
|
|
256
|
+
* Returns DESIGN.md content from the project, or UDS template if not found
|
|
257
|
+
*/
|
|
258
|
+
_getDesignToken({ project_path }) {
|
|
259
|
+
const projectPath = resolve(project_path);
|
|
260
|
+
const designMdPath = join(projectPath, 'DESIGN.md');
|
|
261
|
+
|
|
262
|
+
let text;
|
|
263
|
+
let isTemplate = false;
|
|
264
|
+
|
|
265
|
+
if (existsSync(designMdPath)) {
|
|
266
|
+
text = readFileSync(designMdPath, 'utf8');
|
|
267
|
+
} else {
|
|
268
|
+
// Graceful fallback: return UDS template
|
|
269
|
+
const templatePath = join(this.udsRepoRoot, 'templates', 'DESIGN.md');
|
|
270
|
+
text = readFileSync(templatePath, 'utf8');
|
|
271
|
+
isTemplate = true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const prefix = isTemplate
|
|
275
|
+
? '<!-- This is the UDS DESIGN.md template. No DESIGN.md was found at the specified project path. -->\n\n'
|
|
276
|
+
: '';
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
content: [
|
|
280
|
+
{
|
|
281
|
+
type: 'text',
|
|
282
|
+
text: prefix + text,
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Tool: get_design_standards
|
|
290
|
+
* Returns the UDS frontend-design-standards.ai.yaml content
|
|
291
|
+
*/
|
|
292
|
+
_getDesignStandards() {
|
|
293
|
+
const standardsPath = join(
|
|
294
|
+
this.udsRepoRoot,
|
|
295
|
+
'ai',
|
|
296
|
+
'standards',
|
|
297
|
+
'frontend-design-standards.ai.yaml'
|
|
298
|
+
);
|
|
299
|
+
const text = readFileSync(standardsPath, 'utf8');
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
content: [
|
|
303
|
+
{
|
|
304
|
+
type: 'text',
|
|
305
|
+
text,
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Tool: validate_design_token
|
|
313
|
+
* Validates a DESIGN.md file against required sections
|
|
314
|
+
*/
|
|
315
|
+
_validateDesignToken({ design_md_path }) {
|
|
316
|
+
const filePath = resolve(design_md_path);
|
|
317
|
+
const content = readFileSync(filePath, 'utf8');
|
|
318
|
+
|
|
319
|
+
const missingSections = [];
|
|
320
|
+
const warnings = [];
|
|
321
|
+
|
|
322
|
+
for (const section of REQUIRED_DESIGN_SECTIONS) {
|
|
323
|
+
if (!section.pattern.test(content)) {
|
|
324
|
+
missingSections.push(section.id);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Optional: warn if no version info found
|
|
329
|
+
if (!/version/i.test(content)) {
|
|
330
|
+
warnings.push('No version information found in DESIGN.md');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const valid = missingSections.length === 0;
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
content: [
|
|
337
|
+
{
|
|
338
|
+
type: 'text',
|
|
339
|
+
text: JSON.stringify(
|
|
340
|
+
{
|
|
341
|
+
valid,
|
|
342
|
+
missing_sections: missingSections,
|
|
343
|
+
warnings,
|
|
344
|
+
},
|
|
345
|
+
null,
|
|
346
|
+
2
|
|
347
|
+
),
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|