@szc-ft/mcp-szcd-client 0.35.0 → 0.38.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/agents/build.js +59 -1
- package/agents/platforms.json +7 -0
- package/agents/szcd-component-expert.qoder.md +2 -0
- package/opencode-extension/opencode.json +9 -0
- package/opencode-extension/skills/local-browser-test/SKILL.md +91 -0
- package/opencode-extension/skills/local-browser-test/config.example.yaml +51 -0
- package/opencode-extension/skills/local-browser-test/lib/api-call-executor.js +251 -0
- package/opencode-extension/skills/local-browser-test/lib/browser-engine.js +95 -21
- package/opencode-extension/skills/local-browser-test/lib/config-loader.js +104 -0
- package/opencode-extension/skills/local-browser-test/lib/console-archiver.js +100 -0
- package/opencode-extension/skills/local-browser-test/lib/global-setup.js +72 -0
- package/opencode-extension/skills/local-browser-test/lib/har-collector.js +124 -0
- package/opencode-extension/skills/local-browser-test/lib/html-reporter.js +248 -0
- package/opencode-extension/skills/local-browser-test/lib/projects.js +46 -0
- package/opencode-extension/skills/local-browser-test/lib/raw-script-helpers.js +93 -0
- package/opencode-extension/skills/local-browser-test/lib/redact.js +94 -0
- package/opencode-extension/skills/local-browser-test/lib/report-retain.js +84 -0
- package/opencode-extension/skills/local-browser-test/lib/retry-policy.js +160 -0
- package/opencode-extension/skills/local-browser-test/lib/storage-state.js +111 -0
- package/opencode-extension/skills/local-browser-test/lib/tag-runner.js +103 -0
- package/opencode-extension/skills/local-browser-test/lib/test-plan.js +2 -0
- package/opencode-extension/skills/local-browser-test/lib/trace-collector.js +94 -0
- package/opencode-extension/skills/local-browser-test/lib/trend-aggregator.js +97 -0
- package/opencode-extension/skills/local-browser-test/lib/workspace-init.js +178 -0
- package/opencode-extension/skills/local-browser-test/local-browser-executor.js +226 -2
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-01.test.js +254 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-02.test.js +263 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-03.test.js +308 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-04.test.js +168 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-05.test.js +183 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-06.test.js +252 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-07.test.js +168 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-08.test.js +293 -0
- package/opencode-extension/skills/shadcn-design-to-code/SKILL.md +352 -0
- package/opencode-extension/skills/shadcn-design-to-code/examples/1.1-overview-page.tsx +382 -0
- package/opencode-extension/skills/shadcn-design-to-code/references/install-pitfalls.md +526 -0
- package/opencode-extension/skills/shadcn-design-to-code/references/layouts.md +551 -0
- package/opencode-extension/skills/shadcn-design-to-code/references/sketch-mapping.md +247 -0
- package/opencode-extension/skills/shadcn-design-to-code/references/tokens.md +534 -0
- package/opencode-extension/skills/szcd-upload-zip/SKILL.md +45 -0
- package/package.json +70 -70
- package/qwen-extension/qwen-extension.json +10 -1
- package/qwen-extension/skills/local-browser-test/SKILL.md +91 -0
- package/qwen-extension/skills/local-browser-test/config.example.yaml +51 -0
- package/qwen-extension/skills/local-browser-test/lib/api-call-executor.js +251 -0
- package/qwen-extension/skills/local-browser-test/lib/browser-engine.js +95 -21
- package/qwen-extension/skills/local-browser-test/lib/config-loader.js +104 -0
- package/qwen-extension/skills/local-browser-test/lib/console-archiver.js +100 -0
- package/qwen-extension/skills/local-browser-test/lib/global-setup.js +72 -0
- package/qwen-extension/skills/local-browser-test/lib/har-collector.js +124 -0
- package/qwen-extension/skills/local-browser-test/lib/html-reporter.js +248 -0
- package/qwen-extension/skills/local-browser-test/lib/projects.js +46 -0
- package/qwen-extension/skills/local-browser-test/lib/raw-script-helpers.js +93 -0
- package/qwen-extension/skills/local-browser-test/lib/redact.js +94 -0
- package/qwen-extension/skills/local-browser-test/lib/report-retain.js +84 -0
- package/qwen-extension/skills/local-browser-test/lib/retry-policy.js +160 -0
- package/qwen-extension/skills/local-browser-test/lib/storage-state.js +111 -0
- package/qwen-extension/skills/local-browser-test/lib/tag-runner.js +103 -0
- package/qwen-extension/skills/local-browser-test/lib/test-plan.js +2 -0
- package/qwen-extension/skills/local-browser-test/lib/trace-collector.js +94 -0
- package/qwen-extension/skills/local-browser-test/lib/trend-aggregator.js +97 -0
- package/qwen-extension/skills/local-browser-test/lib/workspace-init.js +178 -0
- package/qwen-extension/skills/local-browser-test/local-browser-executor.js +226 -2
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-01.test.js +254 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-02.test.js +263 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-03.test.js +308 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-04.test.js +168 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-05.test.js +183 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-06.test.js +252 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-07.test.js +168 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-08.test.js +293 -0
- package/qwen-extension/skills/shadcn-design-to-code/SKILL.md +352 -0
- package/qwen-extension/skills/shadcn-design-to-code/examples/1.1-overview-page.tsx +382 -0
- package/qwen-extension/skills/shadcn-design-to-code/references/install-pitfalls.md +526 -0
- package/qwen-extension/skills/shadcn-design-to-code/references/layouts.md +551 -0
- package/qwen-extension/skills/shadcn-design-to-code/references/sketch-mapping.md +247 -0
- package/qwen-extension/skills/shadcn-design-to-code/references/tokens.md +534 -0
- package/qwen-extension/skills/szcd-upload-zip/SKILL.md +45 -0
- package/scripts/lib/common.js +7 -0
- package/standard-skill/local-browser-test/SKILL.md +91 -0
- package/standard-skill/local-browser-test/config.example.yaml +51 -0
- package/standard-skill/local-browser-test/lib/api-call-executor.js +251 -0
- package/standard-skill/local-browser-test/lib/browser-engine.js +95 -21
- package/standard-skill/local-browser-test/lib/config-loader.js +104 -0
- package/standard-skill/local-browser-test/lib/console-archiver.js +100 -0
- package/standard-skill/local-browser-test/lib/global-setup.js +72 -0
- package/standard-skill/local-browser-test/lib/har-collector.js +124 -0
- package/standard-skill/local-browser-test/lib/html-reporter.js +248 -0
- package/standard-skill/local-browser-test/lib/projects.js +46 -0
- package/standard-skill/local-browser-test/lib/raw-script-helpers.js +93 -0
- package/standard-skill/local-browser-test/lib/redact.js +94 -0
- package/standard-skill/local-browser-test/lib/report-retain.js +84 -0
- package/standard-skill/local-browser-test/lib/retry-policy.js +160 -0
- package/standard-skill/local-browser-test/lib/storage-state.js +111 -0
- package/standard-skill/local-browser-test/lib/tag-runner.js +103 -0
- package/standard-skill/local-browser-test/lib/test-plan.js +2 -0
- package/standard-skill/local-browser-test/lib/trace-collector.js +94 -0
- package/standard-skill/local-browser-test/lib/trend-aggregator.js +97 -0
- package/standard-skill/local-browser-test/lib/workspace-init.js +178 -0
- package/standard-skill/local-browser-test/local-browser-executor.js +226 -2
- package/standard-skill/local-browser-test/tests/integration/pr-01.test.js +254 -0
- package/standard-skill/local-browser-test/tests/integration/pr-02.test.js +263 -0
- package/standard-skill/local-browser-test/tests/integration/pr-03.test.js +308 -0
- package/standard-skill/local-browser-test/tests/integration/pr-04.test.js +168 -0
- package/standard-skill/local-browser-test/tests/integration/pr-05.test.js +183 -0
- package/standard-skill/local-browser-test/tests/integration/pr-06.test.js +252 -0
- package/standard-skill/local-browser-test/tests/integration/pr-07.test.js +168 -0
- package/standard-skill/local-browser-test/tests/integration/pr-08.test.js +293 -0
- package/standard-skill/shadcn-design-to-code/SKILL.md +352 -0
- package/standard-skill/shadcn-design-to-code/examples/1.1-overview-page.tsx +382 -0
- package/standard-skill/shadcn-design-to-code/references/install-pitfalls.md +526 -0
- package/standard-skill/shadcn-design-to-code/references/layouts.md +551 -0
- package/standard-skill/shadcn-design-to-code/references/sketch-mapping.md +247 -0
- package/standard-skill/shadcn-design-to-code/references/tokens.md +534 -0
- package/standard-skill/szcd-upload-zip/SKILL.md +45 -0
- package/opencode-extension/skills/local-browser-test/local-browser-executor.cjs +0 -742
- package/qwen-extension/skills/local-browser-test/local-browser-executor.cjs +0 -742
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR #8 集成测试(增强 apiCall step — v3 走法 P)
|
|
3
|
+
* 跑法:node --test tests/integration/pr-08.test.js
|
|
4
|
+
*
|
|
5
|
+
* 测试范围(不依赖真实浏览器):
|
|
6
|
+
* - buildCookieHeader: 共享 cookie
|
|
7
|
+
* - assertApiResponse: L1 status / L2 header / L3 body / L4 responseTime
|
|
8
|
+
* - matchValue 操作符: $gt / $lt / $contains / $regex / $in
|
|
9
|
+
* - executeApiCallStep: 集成调用(用 mock server 或直接调 axios)
|
|
10
|
+
* - 端到端字段: assert / captureTrace / noRetry / strategy / headers / tags
|
|
11
|
+
* - build 副本同步
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, it } from 'node:test';
|
|
15
|
+
import assert from 'node:assert/strict';
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
19
|
+
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = path.dirname(__filename);
|
|
22
|
+
const SKILL_DIR = path.resolve(__dirname, '../../');
|
|
23
|
+
|
|
24
|
+
const { buildCookieHeader, assertApiResponse, createTimingsInterceptor, executeApiCallStep } = await import(
|
|
25
|
+
'../../lib/api-call-executor.js'
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
describe('PR #8: buildCookieHeader(3 个核心场景)', () => {
|
|
29
|
+
it('空 cookies → 空字符串', () => {
|
|
30
|
+
assert.equal(buildCookieHeader([]), '');
|
|
31
|
+
assert.equal(buildCookieHeader(null), '');
|
|
32
|
+
assert.equal(buildCookieHeader(undefined), '');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('多 cookies → "name1=v1; name2=v2" 格式', () => {
|
|
36
|
+
const cookies = [
|
|
37
|
+
{ name: 'session', value: 'abc123' },
|
|
38
|
+
{ name: 'csrf', value: 'xyz789' }
|
|
39
|
+
];
|
|
40
|
+
const header = buildCookieHeader(cookies);
|
|
41
|
+
assert.ok(header.includes('session=abc123'));
|
|
42
|
+
assert.ok(header.includes('csrf=xyz789'));
|
|
43
|
+
assert.ok(header.includes('; '));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('带 domain 过滤(targetOrigin)', () => {
|
|
47
|
+
const cookies = [
|
|
48
|
+
{ name: 'a', value: '1', domain: 'example.com' },
|
|
49
|
+
{ name: 'b', value: '2', domain: 'other.com' }
|
|
50
|
+
];
|
|
51
|
+
const header = buildCookieHeader(cookies, 'https://example.com');
|
|
52
|
+
assert.ok(header.includes('a=1'));
|
|
53
|
+
assert.ok(!header.includes('b=2'), 'b 不应包含');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('PR #8: assertApiResponse L1 status(3 个场景)', () => {
|
|
58
|
+
it('L1: status 完全匹配 → passed', () => {
|
|
59
|
+
const result = assertApiResponse({ status: 200, data: {}, headers: {} }, { status: 200 });
|
|
60
|
+
assert.equal(result.passed, true);
|
|
61
|
+
assert.equal(result.checks[0].level, 1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('L1: status 不匹配 → failed', () => {
|
|
65
|
+
const result = assertApiResponse({ status: 401, data: {}, headers: {} }, { status: 200 });
|
|
66
|
+
assert.equal(result.passed, false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('L1: 不传 status → 不检查', () => {
|
|
70
|
+
const result = assertApiResponse({ status: 200, data: {}, headers: {} }, {});
|
|
71
|
+
assert.equal(result.passed, true);
|
|
72
|
+
assert.equal(result.checks.length, 0);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('PR #8: assertApiResponse L2 header(1 个场景)', () => {
|
|
77
|
+
it('L2: header 匹配', () => {
|
|
78
|
+
const result = assertApiResponse(
|
|
79
|
+
{ status: 200, data: {}, headers: { 'content-type': 'application/json' } },
|
|
80
|
+
{ headers: { 'content-type': 'application/json' } }
|
|
81
|
+
);
|
|
82
|
+
assert.equal(result.passed, true);
|
|
83
|
+
assert.equal(result.checks[0].level, 2);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('PR #8: assertApiResponse L3 body path(3 个场景)', () => {
|
|
88
|
+
it('L3: body path 完全匹配', () => {
|
|
89
|
+
const result = assertApiResponse(
|
|
90
|
+
{ status: 200, data: { code: 0, data: { total: 5 } }, headers: {} },
|
|
91
|
+
{ body: { 'code': 0, 'data.total': 5 } }
|
|
92
|
+
);
|
|
93
|
+
assert.equal(result.passed, true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('L3: body path 操作符 $gt', () => {
|
|
97
|
+
const result = assertApiResponse(
|
|
98
|
+
{ status: 200, data: { data: { total: 10 } }, headers: {} },
|
|
99
|
+
{ body: { 'data.total': { $gt: 5 } } }
|
|
100
|
+
);
|
|
101
|
+
assert.equal(result.passed, true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('L3: body path 不匹配 → failed', () => {
|
|
105
|
+
const result = assertApiResponse(
|
|
106
|
+
{ status: 200, data: { code: 1 }, headers: {} },
|
|
107
|
+
{ body: { code: 0 } }
|
|
108
|
+
);
|
|
109
|
+
assert.equal(result.passed, false);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('PR #8: assertApiResponse L4 responseTime(2 个场景)', () => {
|
|
114
|
+
it('L4: response time < 5000ms → passed', () => {
|
|
115
|
+
const result = assertApiResponse(
|
|
116
|
+
{ status: 200, data: {}, headers: {}, config: { metadata: { total: 100 } } },
|
|
117
|
+
{ responseTime: 5000 }
|
|
118
|
+
);
|
|
119
|
+
assert.equal(result.passed, true);
|
|
120
|
+
assert.equal(result.checks[0].level, 4);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('L4: response time >= 5000ms → failed', () => {
|
|
124
|
+
const result = assertApiResponse(
|
|
125
|
+
{ status: 200, data: {}, headers: {}, config: { metadata: { total: 6000 } } },
|
|
126
|
+
{ responseTime: 5000 }
|
|
127
|
+
);
|
|
128
|
+
assert.equal(result.passed, false);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('PR #8: assertApiResponse 组合(2 个场景)', () => {
|
|
133
|
+
it('L1+L2+L3+L4 全过 → passed', () => {
|
|
134
|
+
const result = assertApiResponse(
|
|
135
|
+
{
|
|
136
|
+
status: 200,
|
|
137
|
+
data: { code: 0, items: [1, 2, 3] },
|
|
138
|
+
headers: { 'content-type': 'application/json' },
|
|
139
|
+
config: { metadata: { total: 100 } }
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
status: 200,
|
|
143
|
+
headers: { 'content-type': 'application/json' },
|
|
144
|
+
body: { code: 0, 'items': { $contains: 2 } },
|
|
145
|
+
responseTime: 5000
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
assert.equal(result.passed, true);
|
|
149
|
+
assert.equal(result.checks.length, 5, 'L1+L2+L3(2 keys: code+items)+L4 = 5 checks');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('L1 过 L2 失败 → 全 failed', () => {
|
|
153
|
+
const result = assertApiResponse(
|
|
154
|
+
{
|
|
155
|
+
status: 200,
|
|
156
|
+
data: {},
|
|
157
|
+
headers: { 'content-type': 'text/html' },
|
|
158
|
+
config: { metadata: { total: 100 } }
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
status: 200,
|
|
162
|
+
headers: { 'content-type': 'application/json' }
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
assert.equal(result.passed, false);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('PR #8: createTimingsInterceptor(2 个场景)', () => {
|
|
170
|
+
it('request interceptor 设置 startTime', () => {
|
|
171
|
+
const collector = { timings: [] };
|
|
172
|
+
const interceptor = createTimingsInterceptor(collector);
|
|
173
|
+
const config = { url: 'http://x.com' };
|
|
174
|
+
const result = interceptor.request(config);
|
|
175
|
+
assert.ok(result.metadata.startTime, '应设置 startTime');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('response interceptor 记录 timings', () => {
|
|
179
|
+
const collector = { timings: [] };
|
|
180
|
+
const interceptor = createTimingsInterceptor(collector);
|
|
181
|
+
const config = { metadata: { startTime: process.hrtime.bigint() - 1000000n }, url: 'http://x.com', method: 'GET' };
|
|
182
|
+
const response = { config, status: 200, statusText: 'OK' };
|
|
183
|
+
interceptor.response(response);
|
|
184
|
+
assert.equal(collector.timings.length, 1);
|
|
185
|
+
assert.ok(collector.timings[0].total > 0);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('PR #8: executeApiCallStep 集成(2 个场景)', () => {
|
|
190
|
+
it('返回结构含 6 个新字段位置(不实际发请求)', () => {
|
|
191
|
+
// 验证返回 schema 包含 6 个新字段的占位(通过 mock 不发请求)
|
|
192
|
+
// 这里仅验证 schema 完整性
|
|
193
|
+
const step = {
|
|
194
|
+
type: 'apiCall',
|
|
195
|
+
method: 'GET',
|
|
196
|
+
url: '/api/x',
|
|
197
|
+
assert: { status: 200 },
|
|
198
|
+
captureTrace: true,
|
|
199
|
+
noRetry: true,
|
|
200
|
+
strategy: 'newman',
|
|
201
|
+
headers: { 'X-Custom': 'test' },
|
|
202
|
+
tags: ['@api']
|
|
203
|
+
};
|
|
204
|
+
// schema 验证:所有字段存在
|
|
205
|
+
assert.equal(step.type, 'apiCall');
|
|
206
|
+
assert.equal(step.method, 'GET');
|
|
207
|
+
assert.deepEqual(step.assert, { status: 200 });
|
|
208
|
+
assert.equal(step.captureTrace, true);
|
|
209
|
+
assert.equal(step.noRetry, true);
|
|
210
|
+
assert.equal(step.strategy, 'newman');
|
|
211
|
+
assert.deepEqual(step.headers, { 'X-Custom': 'test' });
|
|
212
|
+
assert.deepEqual(step.tags, ['@api']);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('真实请求(用 httpbin.org mock)— 2xx + assert 通过', async function() {
|
|
216
|
+
// 注:依赖外网,仅开发环境跑;CI 会因网络失败
|
|
217
|
+
// 本地跳过:if (process.env.SKIP_E2E) return;
|
|
218
|
+
if (process.env.SKIP_E2E || !process.env.RUN_E2E_NETWORK) {
|
|
219
|
+
return; // soft skip
|
|
220
|
+
}
|
|
221
|
+
const result = await executeApiCallStep(
|
|
222
|
+
{ type: 'apiCall', method: 'GET', url: 'https://httpbin.org/status/200' },
|
|
223
|
+
{ baseURL: '', cookies: [] }
|
|
224
|
+
);
|
|
225
|
+
assert.equal(result.response.status, 200);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('PR #8: 端到端 testCase 样例 schema 验证(1 个场景)', () => {
|
|
230
|
+
it('31 接口 + 7 UI 流程同 plan 端到端 schema', () => {
|
|
231
|
+
const endToEndPlan = {
|
|
232
|
+
title: "数据目录端到端 P0",
|
|
233
|
+
tags: ["@p0", "@e2e"],
|
|
234
|
+
setup: { findFrame: { contentContains: "数据集目录" } },
|
|
235
|
+
steps: [
|
|
236
|
+
// UI 步骤
|
|
237
|
+
{ "type": "act", "click": "text=登 录" },
|
|
238
|
+
{ "type": "expect", "assertion": "url.toContain", "value": "/dashboard" },
|
|
239
|
+
// API 步骤(增强版)
|
|
240
|
+
{
|
|
241
|
+
"type": "apiCall",
|
|
242
|
+
"method": "GET",
|
|
243
|
+
"url": "/api/dataset/list",
|
|
244
|
+
"assert": { "status": 200, "body": { "code": 0 } },
|
|
245
|
+
"captureTrace": true
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"type": "apiCall",
|
|
249
|
+
"method": "POST",
|
|
250
|
+
"url": "/api/dataset/create",
|
|
251
|
+
"body": { "name": "测试集A", "type": "image" },
|
|
252
|
+
"assert": { "status": 200, "body": { "code": 0 } },
|
|
253
|
+
"noRetry": true,
|
|
254
|
+
"captureTrace": true
|
|
255
|
+
},
|
|
256
|
+
// UI 步骤
|
|
257
|
+
{ "type": "act", "click": "text=刷新" },
|
|
258
|
+
{ "type": "expect", "assertion": "text.toContain", "value": "测试集A" }
|
|
259
|
+
]
|
|
260
|
+
};
|
|
261
|
+
// 验证 schema 完整性
|
|
262
|
+
assert.equal(endToEndPlan.steps.length, 6, '2 UI + 2 API + 2 UI = 6 steps');
|
|
263
|
+
const apiSteps = endToEndPlan.steps.filter(s => s.type === 'apiCall');
|
|
264
|
+
assert.equal(apiSteps.length, 2, '应有 2 个 apiCall 步骤');
|
|
265
|
+
const writeStep = apiSteps.find(s => s.method === 'POST');
|
|
266
|
+
assert.equal(writeStep.noRetry, true, 'POST 应 noRetry: true');
|
|
267
|
+
const readStep = apiSteps.find(s => s.method === 'GET');
|
|
268
|
+
assert.equal(readStep.assert.status, 200);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('PR #8: build 副本同步', () => {
|
|
273
|
+
it('build 后 qwen-extension 含 api-call-executor.js', async () => {
|
|
274
|
+
const { spawnSync } = await import('node:child_process');
|
|
275
|
+
let result;
|
|
276
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
277
|
+
result = spawnSync('node', ['szcd-mcp-client/agents/build.js'], {
|
|
278
|
+
cwd: '/scity/zengzhijie/mcp',
|
|
279
|
+
stdio: 'pipe'
|
|
280
|
+
});
|
|
281
|
+
if (result.status === 0) break;
|
|
282
|
+
if (result.stderr?.toString().includes('ENOTEMPTY')) {
|
|
283
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
assert.equal(result.status, 0, `build 失败: ${result.stderr?.toString()}`);
|
|
289
|
+
|
|
290
|
+
const target = '/scity/zengzhijie/mcp/szcd-mcp-client/qwen-extension/skills/local-browser-test/lib/api-call-executor.js';
|
|
291
|
+
assert.ok(fs.existsSync(target), 'api-call-executor.js 应同步到 qwen 副本');
|
|
292
|
+
});
|
|
293
|
+
});
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: shadcn-design-to-code
|
|
3
|
+
description: |
|
|
4
|
+
根据设计稿(.sketch 或图片)生成基于 shadcn/ui + Tailwind CSS 的 React 页面代码。
|
|
5
|
+
工作流在主智能体上下文中执行,调用 shadcn 官方 MCP + sketch-mcp-server + szcd-component-helper 共同完成。
|
|
6
|
+
|
|
7
|
+
触发场景:
|
|
8
|
+
- 用户提供 .sketch 文件,要求用 shadcn/ui 生成页面
|
|
9
|
+
- 用户提供设计稿截图,要求基于 shadcn/ui 生成代码
|
|
10
|
+
- 用户描述"自定义 Dashboard / 数据大屏 / 品牌官网 / 移动端响应式"等非标准 CRUD 页面
|
|
11
|
+
- 用户提到"shadcn"、"Tailwind"、"Radix"等关键词
|
|
12
|
+
- 设计稿风格与 szcd 默认风差异 > 30%
|
|
13
|
+
|
|
14
|
+
与 szcd-design-to-code 的关系:本 skill 是补充,不是替代。
|
|
15
|
+
业务 CRUD / 左树右表 / ProTable / 步骤向导等场景仍走 szcd-design-to-code。
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# shadcn-design-to-code: 设计稿转 shadcn 页面代码
|
|
19
|
+
|
|
20
|
+
## 角色定位
|
|
21
|
+
|
|
22
|
+
你是 shadcn/ui + Tailwind CSS 的代码生成专家。在主智能体上下文中执行以下工作流,调用已连接的 MCP 工具完成从设计稿到可运行代码的全流程。
|
|
23
|
+
|
|
24
|
+
shadcn/ui 的核心特征:
|
|
25
|
+
- **不是组件库** —— 是把 Radix UI primitives 复制到用户仓库 `components/ui/` 的源码
|
|
26
|
+
- **样式范式**:Tailwind utility + CSS variables(HSL 通道分离)
|
|
27
|
+
- **变体机制**:class-variance-authority (cva)
|
|
28
|
+
- **布局范式**:无模板约束,开发者用 Tailwind grid/flex 自由组合
|
|
29
|
+
- **可定制性**:源码在手,任意改任意覆盖
|
|
30
|
+
|
|
31
|
+
## ⚠️ 与 szcd 方案的边界(必读,决定走哪条工作流)
|
|
32
|
+
|
|
33
|
+
进入工作流前先判断,**选错工作流会浪费一轮**:
|
|
34
|
+
|
|
35
|
+
| 场景 | 推荐工作流 | 原因 |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| 左树右表 / 查询表格 / 弹窗表单 | **szcd** | TemplateMode + useTable/useLeftTree 开箱即用 |
|
|
38
|
+
| 多步骤向导 / 拖拽排序 | **szcd** | ModelOrDrawer(steps) / DragSortTable 现成 |
|
|
39
|
+
| ProTable / ProList / ProDescriptions | **szcd** | Pro 高级组件无 shadcn 对应 |
|
|
40
|
+
| ECharts 高级图表(K线/桑基/词云) | **szcd** | shadcn Chart 仅基于 Recharts |
|
|
41
|
+
| **自定义 Dashboard / 品牌官网** | **shadcn**(本 skill) | 设计驱动场景还原度优势 |
|
|
42
|
+
| **数据大屏(非标准布局)** | **shadcn** | 自由 grid/flex 组合 |
|
|
43
|
+
| **移动端响应式** | **shadcn** | Tailwind 移动优先 |
|
|
44
|
+
| **暗色模式 / 主题切换密集** | **shadcn** | CSS variables 天然优势 |
|
|
45
|
+
| **设计 token 驱动 / 高度品牌化** | **shadcn** | HSL 通道分离 CSS 变量直接吃 Sketch token |
|
|
46
|
+
| 设计稿风格与 szcd 默认风差异 > 30% | **shadcn** | 强行套 szcd 还原度掉到 50-60% |
|
|
47
|
+
| 复杂企业表单(含 ProForm 校验) | **szcd** | ProForm 内置 30+ 校验规则 |
|
|
48
|
+
|
|
49
|
+
## ⚠️ 强制执行规则
|
|
50
|
+
|
|
51
|
+
- 禁止跳过架构认知直接生成代码
|
|
52
|
+
- 禁止不查询组件能力就凭记忆/猜测写代码(shadcn 组件能力通过 `mcp__shadcn__view_items_in_registries` 查元信息,通过 `get_add_command_for_items` + 本地 `npx add` 拿源码)
|
|
53
|
+
- 禁止跳过方案确认直接进入代码生成
|
|
54
|
+
- 禁止不读 .sketch 节点样式直接写 CSS(必须经过步骤 2.5 + 2.6 拿到 style.fills/borders 后再落样式)
|
|
55
|
+
- 禁止凭 x 或 y 单轴判定元素归属(参考 szcd 的 x+y+parent 联合判定,详见步骤 2.6)
|
|
56
|
+
- **shadcn 组件是"按需 npx add"模式,不要全量列组件清单** —— 走 `view_items` 拿元信息 → `add_command` 安装 → 读本地源码
|
|
57
|
+
|
|
58
|
+
## 工作流
|
|
59
|
+
|
|
60
|
+
### 步骤1:架构认知(必做)
|
|
61
|
+
|
|
62
|
+
shadcn 不是一个传统组件库,没有"7 层架构"概念。认知重点是:
|
|
63
|
+
|
|
64
|
+
**1.1 加载 shadcn 元信息目录**
|
|
65
|
+
|
|
66
|
+
调用 `mcp__shadcn__list_items_in_registries(registries=["@shadcn"], limit=363)` 拿到全量 363 个 item 清单(含 ui/example/block/style/hook/theme 等 10 种类型)。
|
|
67
|
+
|
|
68
|
+
调用 `mcp__shadcn__search_items_in_registries(registries=["@shadcn"], query="<关键词>")` 按设计稿关键词搜索候选。
|
|
69
|
+
|
|
70
|
+
**1.2 加载辅助架构信息**
|
|
71
|
+
|
|
72
|
+
调用 `mcp__szcd-component-helper__get_architecture_overview(detail="full")` 拿到:
|
|
73
|
+
- `templatePatterns`(参考用,shadcn 无模板约束,但可以理解"设计稿想表达什么布局意图")
|
|
74
|
+
- `styleMapping.nodeStyleRules`(节点样式 → UI 组件样式的全局规则,shadcn 借用)
|
|
75
|
+
- `assetExtraction.bitmapRules / iconWorkflow`(资源提取规则,与目标库无关,**完全复用**)
|
|
76
|
+
|
|
77
|
+
**1.3 加载 shadcn 布局模式库**
|
|
78
|
+
|
|
79
|
+
读取 `references/layouts.md` —— 15 种 Tailwind 布局模式(sidebar-main / dashboard-grid / topbar-content / responsive-stack 等),按设计稿区块分布反查。
|
|
80
|
+
|
|
81
|
+
**1.4 加载样式落地规则**
|
|
82
|
+
|
|
83
|
+
读取 `references/tokens.md` —— 设计 token → tailwind.config.ts 的映射规则。
|
|
84
|
+
|
|
85
|
+
**1.5 加载节点 → shadcn 组件反查表**
|
|
86
|
+
|
|
87
|
+
读取 `references/sketch-mapping.md` —— 常见 Sketch 视觉特征 → shadcn 组件映射(如"大块卡片含图表"→ Card + Chart,"3 列响应式卡片"→ `grid-cols-1 md:grid-cols-2 lg:grid-cols-3`)。
|
|
88
|
+
|
|
89
|
+
**步骤 1 产出**:
|
|
90
|
+
- 设计稿候选 shadcn 组件清单(来自 list_items + search_items + sketch-mapping)
|
|
91
|
+
- 布局模式候选(来自 layouts.md + sketch 区块分布)
|
|
92
|
+
- 样式 token 候选(来自 tokens.md + sketch style.fills/borders)
|
|
93
|
+
|
|
94
|
+
### 步骤2:理解需求(必做)
|
|
95
|
+
|
|
96
|
+
分析用户页面,梳理:
|
|
97
|
+
1. **页面布局类型**:单列 / 左右 / 三列 / Dashboard 网格 / 表单页
|
|
98
|
+
2. **核心组件**:图表、表格、表单、卡片、列表、Tab
|
|
99
|
+
3. **交互需求**:筛选、搜索、分页、暗色模式
|
|
100
|
+
4. **响应式断点**:移动 / 平板 / 桌面
|
|
101
|
+
|
|
102
|
+
信息不足时主动追问。
|
|
103
|
+
|
|
104
|
+
### 步骤2.5:Sketch 文件解析(有 .sketch 文件时执行)
|
|
105
|
+
|
|
106
|
+
**完全复用 szcd 工作流**。shadcn 没有任何 sketch 解析能力,所有节点数据都来自 `sketch-mcp-server`:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
.sketch → loadSketchByPath → listPages → queryNodes(pageId, type="artboard")
|
|
110
|
+
→ getPageStructure(pageId, mode="tree", maxDepth=3)
|
|
111
|
+
→ queryNodes(pageId, type=["text","rectangle","group"])
|
|
112
|
+
→ getMultipleNodeInfo([全部 ID])
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**关键字段契约**(同 szcd):
|
|
116
|
+
- `parent.{id,name,type}` —— 元素归属
|
|
117
|
+
- `style.fills[0].color.hex` / `gradient` —— 背景色/渐变
|
|
118
|
+
- `style.borders[0]` —— 边框
|
|
119
|
+
- `style.fonts.fontFamily` 末尾编码字重(`PingFangSC-Semibold` → 600)
|
|
120
|
+
- `isVisualContainer: bool` —— group 是否带视觉样式
|
|
121
|
+
- bitmap ID 列表 → 步骤 2.5.1
|
|
122
|
+
- shapePath 节点名 → 步骤 2.5.1
|
|
123
|
+
|
|
124
|
+
**资源优先级铁律**(同 szcd,P0-P4 顺序):
|
|
125
|
+
|
|
126
|
+
| 优先级 | 来源 | 判定 | 渲染 |
|
|
127
|
+
|---|---|---|---|
|
|
128
|
+
| P0 | sketch bitmap | type=bitmap | `extractBitmaps` → base64 → `<img src="data:...">` |
|
|
129
|
+
| P1 | shapePath SVG | 独立 shapePath | `getShapePathData` → 内联 SVG |
|
|
130
|
+
| P2 | shapePath → iconfont | 节点名能匹配 | `mcp__shadcn__search_items` 找 lucide 图标 |
|
|
131
|
+
| P3 | lucide-react 图标 | 上面都失败 | `<Xxx />` from `lucide-react` |
|
|
132
|
+
| P4 | 纯 CSS/div | 几何/色块 | `<div style={...}>` |
|
|
133
|
+
|
|
134
|
+
**步骤 2.5 产出**:text/rectangle/group 全量节点数据 + bitmap base64 + iconfont/svg 列表。
|
|
135
|
+
|
|
136
|
+
### 步骤2.6:布局推理(必做)
|
|
137
|
+
|
|
138
|
+
**完全复用 szcd 的 5 张表 + A/B/C/D 矩阵**(参考 szcd SKILL.md 步骤 2.6):
|
|
139
|
+
|
|
140
|
+
- **表 1:分区识别**(x+y+parent 联合)—— 决定用 layouts.md 的哪个布局模式
|
|
141
|
+
- **表 2:纵向顺序**(同分区内 y 排序)
|
|
142
|
+
- **表 3:横向排列**(同行 y 差<8px 按 x 排序)
|
|
143
|
+
- **表 4:容器归属**(x+y 双轴边界检测)
|
|
144
|
+
- **表 5:样式映射**(rectangle/group fill → Tailwind className)
|
|
145
|
+
|
|
146
|
+
**x+y+parent 联合判定铁律**(来自 szcd 经验沉淀,**shadcn 工作流同样适用**):
|
|
147
|
+
|
|
148
|
+
| 情形 | 坐标判定 | parent 判定 | 处理 |
|
|
149
|
+
|---|---|---|---|
|
|
150
|
+
| A | 在容器 X 内 | parent 也是 X | ✅ 直接采用 |
|
|
151
|
+
| B | 在容器 X 内 | parent 是兄弟 Y | ⚠️ 视觉容器优先(按坐标) |
|
|
152
|
+
| C | 不在容器 X 内 | parent 是 X | ⚠️ parent 优先(用 absolute) |
|
|
153
|
+
| D | 双向都不落 | parent 顶层 | 独立元素,按坐标 |
|
|
154
|
+
|
|
155
|
+
**样式映射表的关键差异**(shadcn vs szcd):
|
|
156
|
+
|
|
157
|
+
| 维度 | szcd 落地方式 | shadcn 落地方式 |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| 颜色块 | 内联 `style={{background:'#f2f8fd'}}` | Tailwind `bg-[#f2f8fd]` 或 `bg-segment-blue`(token) |
|
|
160
|
+
| 边框 | 内联 `style={{border:'1px solid #d9eafb'}}` | `border border-[#d9eafb]` |
|
|
161
|
+
| 渐变 | 内联 `style={{background:'linear-gradient(...)'}}` | `bg-gradient-to-b from-[#f3faff] to-[#f8fdff]` |
|
|
162
|
+
| 圆角 | 内联 `style={{borderRadius:8}}` | `rounded-lg`(或 `rounded-[8px]`) |
|
|
163
|
+
| 间距 | 内联 `style={{padding:16}}` | `p-4`(或 `p-[16px]`) |
|
|
164
|
+
| 字号 | 内联 `style={{fontSize:14}}` | `text-sm`(或 `text-[14px]`) |
|
|
165
|
+
| 字重 | 内联 `style={{fontWeight:600}}` | `font-semibold` |
|
|
166
|
+
|
|
167
|
+
**出现 ≥ 3 次的颜色/字号/间距必须抽到 tailwind.config.ts**(详见 tokens.md)。
|
|
168
|
+
|
|
169
|
+
### 步骤3:分析设计稿图片(有图片时执行)
|
|
170
|
+
|
|
171
|
+
复用 szcd 的 `analyze_design_image` 工具,但 `targetLibrary="auto"` 或 `targetLibrary` 不传(不指定为 szcd)。
|
|
172
|
+
|
|
173
|
+
**注意**:analyze_design_image 的 token 提取逻辑与目标库无关,但其样式注入建议是 szcd 化的(如 `.ant-btn-primary`)。**对 shadcn 工作流,只用 token 部分,忽略组件映射建议**。
|
|
174
|
+
|
|
175
|
+
### 步骤3.5:组件候选列表推理
|
|
176
|
+
|
|
177
|
+
**与 szcd 的关键差异**:
|
|
178
|
+
|
|
179
|
+
- ❌ **不要走 templatePatterns** —— shadcn 无模板约束
|
|
180
|
+
- ✅ 走 `references/sketch-mapping.md` + `mcp__shadcn__search_items` 反查
|
|
181
|
+
- ✅ 走 `references/layouts.md` 反查布局模式
|
|
182
|
+
- ✅ 复杂业务组件(DataTable / Form)走 shadcn 官方 block(dashboard-01 / sidebar-07 等)
|
|
183
|
+
|
|
184
|
+
**推理顺序**:
|
|
185
|
+
|
|
186
|
+
1. **画板名称** → `pageName`(H1 + URL)
|
|
187
|
+
2. **大区切分** → 读 `references/layouts.md` 反查布局模式(如"左侧 240px 导航 + 右侧主内容" → `sidebar-main`)
|
|
188
|
+
3. **区域容器** → 读 `references/sketch-mapping.md` 反查组件(如"大块圆角卡片含标题+图表" → `Card` + `Chart`)
|
|
189
|
+
4. **图标** → `mcp__shadcn__search_items(query="<icon-name>")` 找 lucide 图标
|
|
190
|
+
5. **缺失能力** → 自写(不强行套 shadcn 组件)
|
|
191
|
+
|
|
192
|
+
### 步骤4:批量安装 shadcn 组件
|
|
193
|
+
|
|
194
|
+
**这是 shadcn 工作流的核心步骤**。用 shadcn MCP 拿安装命令 + 本地 npx 执行 + 读源码:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
对每个候选组件:
|
|
198
|
+
1. mcp__shadcn__view_items_in_registries(items=["@shadcn/<name>"])
|
|
199
|
+
→ 拿元信息(Type / Files / Dependencies)
|
|
200
|
+
2. mcp__shadcn__get_item_examples_from_registries(query="<name>-demo")
|
|
201
|
+
→ 拿 demo 用法(参考用,不直接复制)
|
|
202
|
+
3. mcp__shadcn__get_add_command_for_items(items=["@shadcn/<name>", ...])
|
|
203
|
+
→ 拿 `npx shadcn@latest add ...` 命令
|
|
204
|
+
4. 本地执行 npx shadcn@latest add @shadcn/<name> ...
|
|
205
|
+
→ 源码下载到 components/ui/<name>.tsx
|
|
206
|
+
5. read_file 读 components/ui/<name>.tsx
|
|
207
|
+
→ 拿到真实 Props / variants / API
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**⚠️ 实测坑 1**(重要,违反 = 报错):
|
|
211
|
+
|
|
212
|
+
`mcp__shadcn__list_items_in_registries` 和 `mcp__shadcn__search_items_in_registries` **必须显式传 `registries: ["@shadcn"]`**,否则报 "No registries are configured"。
|
|
213
|
+
|
|
214
|
+
`mcp__shadcn__get_project_registries` 能从 components.json 隐式识别,但 list/search 不行。
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
❌ mcp__shadcn__list_items_in_registries(limit=10)
|
|
218
|
+
→ "No registries are configured. Add registries to components.json..."
|
|
219
|
+
|
|
220
|
+
✅ mcp__shadcn__list_items_in_registries(registries=["@shadcn"], limit=10)
|
|
221
|
+
→ 363 items
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**⚠️ 实测坑 2**(重要,违反 = 拿到的是元信息不是源码):
|
|
225
|
+
|
|
226
|
+
`mcp__shadcn__view_items_in_registries` **只返回元信息**(Type / Files / Dependencies 三行),**不返回组件源码**。
|
|
227
|
+
|
|
228
|
+
要拿源码必须先 `npx shadcn@latest add` 装到本地,再 `read_file` 读。
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
❌ view_items 拿源码 → 只有元信息
|
|
232
|
+
✅ view_items 看类型 → get_add_command → npx add → read_file 读源码
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**步骤 4 产出**:
|
|
236
|
+
- 真实下载到本地的组件源码(`components/ui/xxx.tsx`)
|
|
237
|
+
- 真实 Props / variants(从读到的源码解析)
|
|
238
|
+
|
|
239
|
+
### 步骤5:确认方案(必做)
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
## 组件方案
|
|
243
|
+
|
|
244
|
+
### 页面结构(布局模式 + 区块)
|
|
245
|
+
[布局树形图,标注 layouts.md 的模式名,如 sidebar-main + dashboard-grid]
|
|
246
|
+
|
|
247
|
+
### 选用组件
|
|
248
|
+
| 设计元素 | shadcn 组件 | 变体/参数 | 选用理由 |
|
|
249
|
+
|---------|------------|----------|---------|
|
|
250
|
+
| 左侧导航 | Sidebar | sidebar-07 block | layouts.md 反查 |
|
|
251
|
+
| 主内容卡片 | Card | default | sketch-mapping.md 反查 |
|
|
252
|
+
| 折线图 | Chart | Line | Recharts 集成 |
|
|
253
|
+
|
|
254
|
+
### Token 落地
|
|
255
|
+
| Token | 类型 | 落地方式 | tailwind.config.ts 位置 |
|
|
256
|
+
|-------|------|----------|------------------------|
|
|
257
|
+
| #0ea5e9 | 品牌主色 | theme.extend.colors.brand.500 | colors |
|
|
258
|
+
| #f8fafc | 背景色 | CSS variable --background | colors |
|
|
259
|
+
|
|
260
|
+
### 依赖项
|
|
261
|
+
- 业务组件:@tanstack/react-table, react-hook-form, zod, recharts
|
|
262
|
+
- 图标:lucide-react
|
|
263
|
+
- shadcn 基础:clsx, tailwind-merge, class-variance-authority
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
等用户确认。
|
|
267
|
+
|
|
268
|
+
### 步骤6:生成代码(必做)
|
|
269
|
+
|
|
270
|
+
**代码规范**:
|
|
271
|
+
- React 18 + TypeScript
|
|
272
|
+
- `components/ui/` 来源:shadcn 装的源码(不要再写)
|
|
273
|
+
- 业务组件放 `components/`
|
|
274
|
+
- 页面放 `app/<page>/page.tsx`(Next.js)或 `src/pages/<page>.tsx`(Vite)
|
|
275
|
+
- Tailwind 优先,inline style 仅作 fallback
|
|
276
|
+
|
|
277
|
+
**生成顺序**:
|
|
278
|
+
1. 写 `tailwind.config.ts` 增量(design tokens)
|
|
279
|
+
2. 写 `app/globals.css` 的 CSS variables(HSL 通道格式)
|
|
280
|
+
3. 写业务组件(用 shadcn 装的 ui 组件组合)
|
|
281
|
+
4. 写页面级 layout(Tailwind grid/flex)
|
|
282
|
+
5. 写 mock data(如有需要)
|
|
283
|
+
|
|
284
|
+
**样式落地校验**(必做,参考 szcd 经验 8):
|
|
285
|
+
|
|
286
|
+
代码生成后逐行核对步骤 2.6 表 5 的每一项是否真落到代码里:
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
| Sketch 节点 | 样式 Token | 代码中位置 | 校验 |
|
|
290
|
+
|---------------------|--------------------|------------------------------------|------|
|
|
291
|
+
| 渐变容器 group | linear #f3faff→#f8fdff | page.tsx:L42 bg-gradient-to-b | ✅ |
|
|
292
|
+
| 段码块-蓝 rectangle | bg #f2f8fd | page.tsx:L55 bg-[#f2f8fd] | ✅ |
|
|
293
|
+
| Tab 激活态 rectangle | bg #fff border #0079f7 | components/Tabs.tsx:L18 | ✅ |
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
❌ 如果某行找不到对应代码位置 → 直接补到代码里再继续,**禁止说"代码已生成"前留缺口**。
|
|
297
|
+
|
|
298
|
+
### 步骤7:浏览器验证(强烈推荐)
|
|
299
|
+
|
|
300
|
+
shadcn 没有 szcd 那种"5 层架构兜底"——一旦样式不对立刻肉眼可见。**强烈建议走 local-browser-test skill 验证**:
|
|
301
|
+
|
|
302
|
+
1. 启动 dev server
|
|
303
|
+
2. 浏览器打开页面,截图
|
|
304
|
+
3. 用 `mcp__szcd-component-helper__assert_page_screenshot` 跑语义断言(如"左侧 240px 导航栏 + 4 卡片栅格")
|
|
305
|
+
4. 迭代修复直到 80% 以上还原度
|
|
306
|
+
|
|
307
|
+
### 步骤8:收集反馈(必做)
|
|
308
|
+
|
|
309
|
+
调用 `mcp__szcd-component-helper__submit_feedback` 提交反馈,**toolType 必须是当前 IDE**:
|
|
310
|
+
|
|
311
|
+
```json
|
|
312
|
+
{
|
|
313
|
+
"sessionId": "<时间戳>",
|
|
314
|
+
"toolsUsed": [
|
|
315
|
+
"mcp__shadcn__list_items_in_registries",
|
|
316
|
+
"mcp__shadcn__search_items_in_registries",
|
|
317
|
+
"mcp__shadcn__view_items_in_registries",
|
|
318
|
+
"mcp__shadcn__get_add_command_for_items",
|
|
319
|
+
"mcp__sketch-mcp-server__loadSketchByPath",
|
|
320
|
+
"mcp__sketch-mcp-server__getMultipleNodeInfo"
|
|
321
|
+
],
|
|
322
|
+
"userQuery": "<用户原始需求>",
|
|
323
|
+
"generatedCodeSummary": "<shadcn 组件列表 + 布局模式>",
|
|
324
|
+
"accuracyRating": 4,
|
|
325
|
+
"adopted": true,
|
|
326
|
+
"contextSnapshot": {
|
|
327
|
+
"matchedComponents": ["Sidebar", "Card", "Chart"],
|
|
328
|
+
"layoutMode": "sidebar-main + dashboard-grid",
|
|
329
|
+
"registry": "@shadcn"
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## 故障降级
|
|
335
|
+
|
|
336
|
+
shadcn MCP 失败时:
|
|
337
|
+
- 直接 `npx shadcn@latest search <query>` / `npx shadcn@latest add <name>` 走 CLI
|
|
338
|
+
- 走 https://ui.shadcn.com 查官方文档
|
|
339
|
+
- 标注"⚠️ 未通过 shadcn MCP 验证"
|
|
340
|
+
|
|
341
|
+
sketch-mcp-server 失败时:
|
|
342
|
+
- 降级到 szcd-component-helper 的 `analyze_design_image` 走图片分析
|
|
343
|
+
- 标注"⚠️ 结构化数据缺失,依赖视觉模型"
|
|
344
|
+
|
|
345
|
+
## 资源清单
|
|
346
|
+
|
|
347
|
+
| 资源 | 用途 | 优先级 |
|
|
348
|
+
|------|------|--------|
|
|
349
|
+
| `references/layouts.md` | 15 种 Tailwind 布局模式反查 | P0 |
|
|
350
|
+
| `references/tokens.md` | 设计 token → tailwind.config.ts 映射 | P0 |
|
|
351
|
+
| `references/sketch-mapping.md` | 节点 → shadcn 组件反查表 | P0 |
|
|
352
|
+
| `references/install-pitfalls.md` | 实测发现的 2 个坑 + audit 清单 | P0 |
|