mcp-probe-kit 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/build/lib/__tests__/memory-orchestration.unit.test.js +88 -0
- package/build/lib/__tests__/memory-payload.unit.test.js +35 -0
- package/build/lib/__tests__/quality-constraints.unit.test.d.ts +1 -0
- package/build/lib/__tests__/quality-constraints.unit.test.js +54 -0
- package/build/lib/__tests__/spec-validator.unit.test.js +106 -74
- package/build/lib/agents-md-template.js +32 -32
- package/build/lib/cursor-history-client.d.ts +54 -0
- package/build/lib/cursor-history-client.js +240 -0
- package/build/lib/quality-constraints.d.ts +54 -0
- package/build/lib/quality-constraints.js +155 -0
- package/build/lib/skill-bridge.js +12 -12
- package/build/lib/spec-validator.js +16 -3
- package/build/lib/template-loader.js +83 -23
- package/build/resources/ui-ux-data/guidelines/vercel-web-interface.json +1632 -1632
- package/build/resources/ui-ux-data/metadata.json +30 -30
- package/build/resources/ui-ux-data/shadcn/blocks.json +2541 -2541
- package/build/resources/ui-ux-data/shadcn/components.json +997 -997
- package/build/resources/ui-ux-data/themes/presets.json +483 -483
- package/build/tools/__tests__/cursor-history.unit.test.d.ts +1 -0
- package/build/tools/__tests__/cursor-history.unit.test.js +38 -0
- package/build/tools/check_spec.js +16 -16
- package/build/tools/code_insight.js +41 -41
- package/build/tools/code_review.js +11 -4
- package/build/tools/cursor_read_conversation.d.ts +7 -0
- package/build/tools/cursor_read_conversation.js +36 -0
- package/build/tools/fix_bug.js +161 -161
- package/build/tools/gencommit.js +60 -60
- package/build/tools/init_project_context.js +432 -432
- package/build/tools/start_product.js +1 -1
- package/build/tools/start_ui.js +17 -0
- package/build/tools/ui-ux-tools.d.ts +3 -0
- package/build/tools/ui-ux-tools.js +302 -290
- package/build/utils/__tests__/vercel-guidelines-sync.unit.test.js +12 -12
- package/build/utils/design-reasoning-engine.d.ts +2 -0
- package/build/utils/design-reasoning-engine.js +3 -0
- package/build/utils/themes-sync.js +8 -8
- package/package.json +3 -2
- package/build/resources/index.d.ts +0 -4
- package/build/resources/index.js +0 -4
- package/build/resources/tool-params-guide.d.ts +0 -571
- package/build/resources/tool-params-guide.js +0 -488
- package/build/tools/analyze_project.d.ts +0 -1
- package/build/tools/analyze_project.js +0 -527
- package/build/tools/check_deps.d.ts +0 -13
- package/build/tools/check_deps.js +0 -204
- package/build/tools/convert.d.ts +0 -13
- package/build/tools/convert.js +0 -599
- package/build/tools/css_order.d.ts +0 -13
- package/build/tools/css_order.js +0 -81
- package/build/tools/debug.d.ts +0 -13
- package/build/tools/debug.js +0 -131
- package/build/tools/design2code.d.ts +0 -20
- package/build/tools/design2code.js +0 -426
- package/build/tools/detect_shell.d.ts +0 -6
- package/build/tools/detect_shell.js +0 -151
- package/build/tools/explain.d.ts +0 -13
- package/build/tools/explain.js +0 -390
- package/build/tools/fix.d.ts +0 -13
- package/build/tools/fix.js +0 -303
- package/build/tools/gen_mock.d.ts +0 -22
- package/build/tools/gen_mock.js +0 -269
- package/build/tools/gen_skill.d.ts +0 -13
- package/build/tools/gen_skill.js +0 -560
- package/build/tools/genapi.d.ts +0 -13
- package/build/tools/genapi.js +0 -174
- package/build/tools/genchangelog.d.ts +0 -13
- package/build/tools/genchangelog.js +0 -250
- package/build/tools/gendoc.d.ts +0 -13
- package/build/tools/gendoc.js +0 -232
- package/build/tools/genpr.d.ts +0 -13
- package/build/tools/genpr.js +0 -194
- package/build/tools/genreadme.d.ts +0 -13
- package/build/tools/genreadme.js +0 -626
- package/build/tools/gensql.d.ts +0 -13
- package/build/tools/gensql.js +0 -320
- package/build/tools/genui.d.ts +0 -13
- package/build/tools/genui.js +0 -803
- package/build/tools/init_component_catalog.d.ts +0 -22
- package/build/tools/init_component_catalog.js +0 -809
- package/build/tools/init_setting.d.ts +0 -13
- package/build/tools/init_setting.js +0 -47
- package/build/tools/perf.d.ts +0 -13
- package/build/tools/perf.js +0 -409
- package/build/tools/render_ui.d.ts +0 -22
- package/build/tools/render_ui.js +0 -384
- package/build/tools/resolve_conflict.d.ts +0 -13
- package/build/tools/resolve_conflict.js +0 -349
- package/build/tools/security_scan.d.ts +0 -22
- package/build/tools/security_scan.js +0 -323
- package/build/tools/split.d.ts +0 -13
- package/build/tools/split.js +0 -599
- package/build/tools/start_api.d.ts +0 -13
- package/build/tools/start_api.js +0 -193
- package/build/tools/start_doc.d.ts +0 -13
- package/build/tools/start_doc.js +0 -207
- package/build/tools/start_refactor.d.ts +0 -13
- package/build/tools/start_refactor.js +0 -188
- package/build/tools/start_release.d.ts +0 -13
- package/build/tools/start_release.js +0 -167
- package/build/tools/start_review.d.ts +0 -13
- package/build/tools/start_review.js +0 -175
- /package/build/{utils/design-docs-generator.d.ts → lib/__tests__/memory-orchestration.unit.test.d.ts} +0 -0
- /package/build/{utils/design-docs-generator.js → lib/__tests__/memory-payload.unit.test.d.ts} +0 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import sqlite3 from 'sqlite3';
|
|
5
|
+
import { createToolError } from '../utils/error-handler.js';
|
|
6
|
+
function parseJson(value) {
|
|
7
|
+
if (!value) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(value);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function fileExists(filePath) {
|
|
18
|
+
try {
|
|
19
|
+
return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function resolveCursorGlobalDbPath() {
|
|
26
|
+
const appData = process.env.APPDATA?.trim();
|
|
27
|
+
const homeDir = os.homedir();
|
|
28
|
+
const candidates = [
|
|
29
|
+
appData ? path.join(appData, 'Cursor', 'User', 'globalStorage', 'state.vscdb') : '',
|
|
30
|
+
path.join(homeDir, '.config', 'Cursor', 'User', 'globalStorage', 'state.vscdb'),
|
|
31
|
+
path.join(homeDir, 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'state.vscdb'),
|
|
32
|
+
].filter(Boolean);
|
|
33
|
+
const hit = candidates.find(fileExists);
|
|
34
|
+
if (!hit) {
|
|
35
|
+
throw createToolError('未找到 Cursor 全局状态库 state.vscdb', undefined, {
|
|
36
|
+
candidates,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return hit;
|
|
40
|
+
}
|
|
41
|
+
function openDatabase(dbPath) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READONLY, (error) => {
|
|
44
|
+
if (error) {
|
|
45
|
+
reject(createToolError(`打开 Cursor 数据库失败: ${error.message}`, error, { dbPath }));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
resolve(db);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function closeDatabase(db) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
db.close((error) => {
|
|
55
|
+
if (error) {
|
|
56
|
+
reject(error);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
resolve();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function allRows(db, sql, params = []) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
db.all(sql, params, (error, rows) => {
|
|
66
|
+
if (error) {
|
|
67
|
+
reject(error);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
resolve((rows ?? []));
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function getRow(db, sql, params = []) {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
db.get(sql, params, (error, row) => {
|
|
77
|
+
if (error) {
|
|
78
|
+
reject(error);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
resolve(row);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function asString(value) {
|
|
86
|
+
return typeof value === 'string' ? value : '';
|
|
87
|
+
}
|
|
88
|
+
function asNumber(value) {
|
|
89
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
90
|
+
}
|
|
91
|
+
function asBoolean(value) {
|
|
92
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
93
|
+
}
|
|
94
|
+
function extractConversationsFromHeaders(payload) {
|
|
95
|
+
const items = payload?.allComposers ?? [];
|
|
96
|
+
return items
|
|
97
|
+
.filter((item) => item?.type === 'head')
|
|
98
|
+
.map((item) => {
|
|
99
|
+
const workspace = item.workspaceIdentifier ?? {};
|
|
100
|
+
const uri = workspace.uri ?? {};
|
|
101
|
+
return {
|
|
102
|
+
composerId: asString(item.composerId),
|
|
103
|
+
name: asString(item.name),
|
|
104
|
+
createdAt: asNumber(item.createdAt),
|
|
105
|
+
lastUpdatedAt: asNumber(item.lastUpdatedAt),
|
|
106
|
+
workspaceId: asString(workspace.id) || undefined,
|
|
107
|
+
workspacePath: asString(uri.fsPath) || undefined,
|
|
108
|
+
mode: asString(item.unifiedMode) || undefined,
|
|
109
|
+
contextUsagePercent: asNumber(item.contextUsagePercent),
|
|
110
|
+
subtitle: asString(item.subtitle) || undefined,
|
|
111
|
+
isArchived: asBoolean(item.isArchived) ?? false,
|
|
112
|
+
source: 'composerHeaders',
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function extractBubbleId(key) {
|
|
117
|
+
const parts = key.split(':');
|
|
118
|
+
return parts[parts.length - 1] || key;
|
|
119
|
+
}
|
|
120
|
+
function extractComposerId(key) {
|
|
121
|
+
const parts = key.split(':');
|
|
122
|
+
return parts.length >= 3 ? parts[1] || '' : '';
|
|
123
|
+
}
|
|
124
|
+
export class CursorHistoryClient {
|
|
125
|
+
async withDatabase(runner) {
|
|
126
|
+
const dbPath = resolveCursorGlobalDbPath();
|
|
127
|
+
const db = await openDatabase(dbPath);
|
|
128
|
+
try {
|
|
129
|
+
return await runner(db);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (error instanceof Error) {
|
|
133
|
+
throw createToolError(`读取 Cursor 历史失败: ${error.message}`, error, { dbPath });
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
await closeDatabase(db).catch(() => undefined);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async loadConversationIndex(db) {
|
|
142
|
+
const row = await getRow(db, "select key, value from ItemTable where key = ?", ['composer.composerHeaders']);
|
|
143
|
+
const payload = parseJson(row?.value);
|
|
144
|
+
return extractConversationsFromHeaders(payload);
|
|
145
|
+
}
|
|
146
|
+
async listConversations(params = {}) {
|
|
147
|
+
return this.withDatabase(async (db) => {
|
|
148
|
+
const titleQuery = (params.titleQuery ?? '').trim().toLowerCase();
|
|
149
|
+
const workspaceQuery = (params.workspaceQuery ?? '').trim().toLowerCase();
|
|
150
|
+
const includeArchived = params.includeArchived ?? false;
|
|
151
|
+
const limit = Math.max(1, Math.min(params.limit ?? 20, 200));
|
|
152
|
+
const conversations = await this.loadConversationIndex(db);
|
|
153
|
+
return conversations
|
|
154
|
+
.filter((item) => includeArchived || !item.isArchived)
|
|
155
|
+
.filter((item) => !titleQuery || item.name.toLowerCase().includes(titleQuery))
|
|
156
|
+
.filter((item) => !workspaceQuery || (item.workspacePath ?? '').toLowerCase().includes(workspaceQuery))
|
|
157
|
+
.sort((a, b) => (b.lastUpdatedAt ?? 0) - (a.lastUpdatedAt ?? 0))
|
|
158
|
+
.slice(0, limit);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
async searchHistory(params) {
|
|
162
|
+
return this.withDatabase(async (db) => {
|
|
163
|
+
const query = (params.query ?? '').trim().toLowerCase();
|
|
164
|
+
if (!query) {
|
|
165
|
+
throw new Error('缺少 query');
|
|
166
|
+
}
|
|
167
|
+
const limit = Math.max(1, Math.min(params.limit ?? 20, 200));
|
|
168
|
+
const composerId = (params.composerId ?? '').trim();
|
|
169
|
+
const index = await this.loadConversationIndex(db);
|
|
170
|
+
const names = new Map(index.map((item) => [item.composerId, item.name]));
|
|
171
|
+
const rows = await allRows(db, 'select key, value from cursorDiskKV where key like ?', [composerId ? `bubbleId:${composerId}:%` : 'bubbleId:%']);
|
|
172
|
+
const matches = rows
|
|
173
|
+
.map((row) => {
|
|
174
|
+
const parsed = parseJson(row.value);
|
|
175
|
+
if (!parsed) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
const text = asString(parsed.text);
|
|
179
|
+
const requestId = asString(parsed.requestId);
|
|
180
|
+
const haystack = `${row.key}\n${text}\n${requestId}`.toLowerCase();
|
|
181
|
+
if (!haystack.includes(query)) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
const currentComposerId = extractComposerId(row.key);
|
|
185
|
+
return {
|
|
186
|
+
composerId: currentComposerId,
|
|
187
|
+
conversationName: names.get(currentComposerId) ?? '',
|
|
188
|
+
bubbleId: extractBubbleId(row.key),
|
|
189
|
+
type: asNumber(parsed.type) ?? 0,
|
|
190
|
+
text,
|
|
191
|
+
createdAt: asString(parsed.createdAt) || undefined,
|
|
192
|
+
requestId: requestId || undefined,
|
|
193
|
+
};
|
|
194
|
+
})
|
|
195
|
+
.filter((item) => item !== null)
|
|
196
|
+
.sort((a, b) => (b.createdAt ?? '').localeCompare(a.createdAt ?? ''))
|
|
197
|
+
.slice(0, limit);
|
|
198
|
+
return matches;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
async readConversation(params) {
|
|
202
|
+
return this.withDatabase(async (db) => {
|
|
203
|
+
const composerId = (params.composerId ?? '').trim();
|
|
204
|
+
if (!composerId) {
|
|
205
|
+
throw new Error('缺少 composer_id');
|
|
206
|
+
}
|
|
207
|
+
const limit = Math.max(1, Math.min(params.limit ?? 200, 2000));
|
|
208
|
+
const includeEmpty = params.includeEmpty ?? false;
|
|
209
|
+
const rows = await allRows(db, 'select key, value from cursorDiskKV where key like ?', [`bubbleId:${composerId}:%`]);
|
|
210
|
+
const messages = rows
|
|
211
|
+
.map((row) => {
|
|
212
|
+
const parsed = parseJson(row.value);
|
|
213
|
+
if (!parsed) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
const text = asString(parsed.text);
|
|
217
|
+
if (!includeEmpty && !text) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
bubbleId: extractBubbleId(row.key),
|
|
222
|
+
type: asNumber(parsed.type) ?? 0,
|
|
223
|
+
text,
|
|
224
|
+
createdAt: asString(parsed.createdAt) || undefined,
|
|
225
|
+
requestId: asString(parsed.requestId) || undefined,
|
|
226
|
+
};
|
|
227
|
+
})
|
|
228
|
+
.filter((item) => item !== null)
|
|
229
|
+
.sort((a, b) => (a.createdAt ?? '').localeCompare(b.createdAt ?? ''))
|
|
230
|
+
.slice(0, limit);
|
|
231
|
+
return {
|
|
232
|
+
composerId,
|
|
233
|
+
messages,
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
export function createCursorHistoryClient() {
|
|
239
|
+
return new CursorHistoryClient();
|
|
240
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 质量约束「单一真相源」
|
|
3
|
+
*
|
|
4
|
+
* 目标:把散落在 task 模板、code_review、UI 工具里的软性质量要求,
|
|
5
|
+
* 收敛到一个集中管理的硬约束模块。三个落点工具都从这里 import,
|
|
6
|
+
* 改一处、处处生效,杜绝规则漂移。
|
|
7
|
+
*
|
|
8
|
+
* 设计哲学(借鉴 taste-skill / impeccable):
|
|
9
|
+
* 凡是想让下游 AI 真正照做的规则,必须用「机器可判定的硬约束 +
|
|
10
|
+
* 精确字符串黑名单 + 二元禁令 + 交付前 checklist」,
|
|
11
|
+
* 禁用「尽量 / 建议 / sparingly」这类会被 AI 忽略的软措辞。
|
|
12
|
+
*
|
|
13
|
+
* 本模块为纯常量 + 字符串渲染,无 I/O、零运行时依赖,便于单测。
|
|
14
|
+
*/
|
|
15
|
+
export interface CodeLimits {
|
|
16
|
+
/** 单文件最大行数;超出必须拆分模块/组件 */
|
|
17
|
+
maxFileLines: number;
|
|
18
|
+
/** 单函数最大行数;超出必须拆分方法 */
|
|
19
|
+
maxFunctionLines: number;
|
|
20
|
+
/** 最大嵌套层数;超出用提前返回(early return)展平 */
|
|
21
|
+
maxNestingDepth: number;
|
|
22
|
+
/** 最大参数个数;超出改用参数对象 */
|
|
23
|
+
maxParameters: number;
|
|
24
|
+
}
|
|
25
|
+
export declare const CODE_LIMITS: CodeLimits;
|
|
26
|
+
/**
|
|
27
|
+
* 占位/省略式代码模式——出现即判「未完成的破碎输出」。
|
|
28
|
+
* 措辞二元:零容忍,不存在「少量允许」。
|
|
29
|
+
*/
|
|
30
|
+
export declare const BANNED_CODE_PATTERNS: string[];
|
|
31
|
+
/**
|
|
32
|
+
* 每条都是带数值/比例的硬约束,区别于含糊的「保持一致」。
|
|
33
|
+
* 精选自 impeccable,挑机器可判定、收益最高的条款。
|
|
34
|
+
*/
|
|
35
|
+
export declare const UI_HARD_RULES: string[];
|
|
36
|
+
/**
|
|
37
|
+
* 命中即「AI slop」。这些是 taste-skill / impeccable 生产测试中
|
|
38
|
+
* 最高频的 AI 设计破绽(tells)。
|
|
39
|
+
*/
|
|
40
|
+
export declare const UI_BANNED_LIST: string[];
|
|
41
|
+
/** 代码量硬约束清单 */
|
|
42
|
+
export declare function renderCodeLimits(): string;
|
|
43
|
+
/** 完整性黑名单(占位符/省略式代码) */
|
|
44
|
+
export declare function renderBannedPatterns(): string;
|
|
45
|
+
/** UI 设计硬红线 */
|
|
46
|
+
export declare function renderUiHardRules(): string;
|
|
47
|
+
/** UI 禁用黑名单 */
|
|
48
|
+
export declare function renderUiBannedList(): string;
|
|
49
|
+
/**
|
|
50
|
+
* 交付前自检矩阵(Pre-Flight Checklist)。
|
|
51
|
+
* 把所有约束收口成一个生成结束前必须逐条诚实勾选的闭环。
|
|
52
|
+
* 任意一项不能诚实勾选 = 未完成。
|
|
53
|
+
*/
|
|
54
|
+
export declare function renderPreFlightChecklist(): string;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 质量约束「单一真相源」
|
|
3
|
+
*
|
|
4
|
+
* 目标:把散落在 task 模板、code_review、UI 工具里的软性质量要求,
|
|
5
|
+
* 收敛到一个集中管理的硬约束模块。三个落点工具都从这里 import,
|
|
6
|
+
* 改一处、处处生效,杜绝规则漂移。
|
|
7
|
+
*
|
|
8
|
+
* 设计哲学(借鉴 taste-skill / impeccable):
|
|
9
|
+
* 凡是想让下游 AI 真正照做的规则,必须用「机器可判定的硬约束 +
|
|
10
|
+
* 精确字符串黑名单 + 二元禁令 + 交付前 checklist」,
|
|
11
|
+
* 禁用「尽量 / 建议 / sparingly」这类会被 AI 忽略的软措辞。
|
|
12
|
+
*
|
|
13
|
+
* 本模块为纯常量 + 字符串渲染,无 I/O、零运行时依赖,便于单测。
|
|
14
|
+
*/
|
|
15
|
+
export const CODE_LIMITS = {
|
|
16
|
+
maxFileLines: 500,
|
|
17
|
+
maxFunctionLines: 50,
|
|
18
|
+
maxNestingDepth: 4,
|
|
19
|
+
maxParameters: 3,
|
|
20
|
+
};
|
|
21
|
+
// ============================================================
|
|
22
|
+
// 二、代码完整性黑名单(精确字符串,机械可扫描)
|
|
23
|
+
// ============================================================
|
|
24
|
+
/**
|
|
25
|
+
* 占位/省略式代码模式——出现即判「未完成的破碎输出」。
|
|
26
|
+
* 措辞二元:零容忍,不存在「少量允许」。
|
|
27
|
+
*/
|
|
28
|
+
export const BANNED_CODE_PATTERNS = [
|
|
29
|
+
'// ...',
|
|
30
|
+
'/* ... */',
|
|
31
|
+
'// TODO',
|
|
32
|
+
'// FIXME(遗留未实现)',
|
|
33
|
+
'// rest of code',
|
|
34
|
+
'// rest of the code',
|
|
35
|
+
'// implement here',
|
|
36
|
+
'// your code here',
|
|
37
|
+
'// similar to above',
|
|
38
|
+
'// same as before',
|
|
39
|
+
'裸露的省略号 ...(作为代码占位)',
|
|
40
|
+
'the rest follows the same pattern',
|
|
41
|
+
'and so on',
|
|
42
|
+
'以此类推(作为代码省略)',
|
|
43
|
+
'其余代码省略',
|
|
44
|
+
'此处省略',
|
|
45
|
+
];
|
|
46
|
+
// ============================================================
|
|
47
|
+
// 三、UI 设计硬红线(带数值,可机器判定)
|
|
48
|
+
// ============================================================
|
|
49
|
+
/**
|
|
50
|
+
* 每条都是带数值/比例的硬约束,区别于含糊的「保持一致」。
|
|
51
|
+
* 精选自 impeccable,挑机器可判定、收益最高的条款。
|
|
52
|
+
*/
|
|
53
|
+
export const UI_HARD_RULES = [
|
|
54
|
+
'间距系统:强制 4pt 基准阶梯 [4, 8, 12, 16, 24, 32, 48, 64, 96]px。同组元素 8-12px,跨区块 48-96px。任何不在阶梯内的 padding/margin/gap 判为漂移。',
|
|
55
|
+
'触控目标:≥ 44×44px。视觉元素更小时用 padding 或伪元素扩大命中区。',
|
|
56
|
+
'对比度(WCAG):正文 ≥ 4.5:1;大文本(≥18px 或 bold ≥14px)≥ 3:1;UI 组件/图标 ≥ 3:1;placeholder 同样 ≥ 4.5:1(不可用默认浅灰)。',
|
|
57
|
+
'字阶比例:相邻级别 ≥ 1.25(品牌站)或 1.125-1.2(产品界面)。禁止扁平字阶(14/15/16px 相邻)。最多 5 级。',
|
|
58
|
+
'主正文流字号:≥ 16px / 1rem,且用 rem 而非 px(脚注、表格密集数据、图例等辅助文本可更小)。',
|
|
59
|
+
'Hero 字号天花板:clamp() 的 max ≤ 6rem(96px)。clamp max ≤ 2.5 × min,否则破坏浏览器缩放与回流。',
|
|
60
|
+
'Display 标题字间距:≥ -0.04em,再紧字母会粘连。',
|
|
61
|
+
'色彩空间:用 OKLCH,禁用 HSL。中性色加微着色 chroma 0.005-0.015,朝品牌色(不默认朝暖橙 hue 60 / 冷蓝 hue 250)。',
|
|
62
|
+
'色彩权重:遵守 60-30-10(60% 中性底 / 30% 次要 / 10% 强调)。一个页面锁定单一强调色,逐组件审计一致性。',
|
|
63
|
+
'行高:标题 1.1-1.2,正文 1.5-1.7。深色背景上字重降一档、行高 +0.05-0.1。',
|
|
64
|
+
'交互八态完整性:每个交互元素必须设计 Default / Hover / Focus / Active / Disabled / Loading / Error / Success 八态,缺一即 P1。',
|
|
65
|
+
'Focus 可见性:禁止裸 outline:none。用 :focus-visible,2-3px 粗,对比 ≥ 3:1,outline-offset 在元素外侧。',
|
|
66
|
+
'动效:时长 150-300ms;缓动用 ease-out-expo/quart/quint;禁 bounce/elastic;必须有 @media (prefers-reduced-motion: reduce) 分支。',
|
|
67
|
+
'认知负荷:营销/落地页类界面任意决策点 ≤ 4 个可见选项(导航 ≤ 5 顶级项、表单每组 ≤ 4 字段、定价常见 ≤ 3-4 档)。数据密集型后台(监控大盘、交易看板)按信息需求放宽,不强套此限。',
|
|
68
|
+
'z-index:建立语义层级(dropdown → sticky → modal-backdrop → modal → toast → tooltip)。禁止任意值 999 / 9999。',
|
|
69
|
+
];
|
|
70
|
+
// ============================================================
|
|
71
|
+
// 四、UI 禁用黑名单(match-and-refuse,二元禁令)
|
|
72
|
+
// ============================================================
|
|
73
|
+
/**
|
|
74
|
+
* 命中即「AI slop」。这些是 taste-skill / impeccable 生产测试中
|
|
75
|
+
* 最高频的 AI 设计破绽(tells)。
|
|
76
|
+
*/
|
|
77
|
+
export const UI_BANNED_LIST = [
|
|
78
|
+
'默认字体 Inter / Roboto / Open Sans(无明确理由时禁用)。',
|
|
79
|
+
'AI 默认紫蓝渐变(紫→蓝光辉),最典型的 AI 破绽。',
|
|
80
|
+
'gradient text(background-clip:text + 渐变)作为标题默认效果。',
|
|
81
|
+
'side-stripe 装饰边框(border-left/right > 1px 纯装饰)。',
|
|
82
|
+
'默认 glassmorphism(无真实层次的毛玻璃)。',
|
|
83
|
+
'相同卡片等距网格(无层次的 N 等分 card grid)。',
|
|
84
|
+
'嵌套卡片(nested cards 永远是错的)。',
|
|
85
|
+
'卡片圆角 ≥ 32px(卡片圆角上限 12-16px)。',
|
|
86
|
+
'ghost-card:同元素 border 1px + box-shadow blur ≥16px。',
|
|
87
|
+
'cream / sand / beige 米色正文背景(2026 年的饱和 AI 默认底色)。',
|
|
88
|
+
'hero-metric 模板(巨大数字 + 小标签的套路化英雄区)。',
|
|
89
|
+
'每段都有的 tiny uppercase eyebrow 小标签(每 3 区块最多 1 个)。',
|
|
90
|
+
'01 / 02 / 03 编号式小标题装饰。',
|
|
91
|
+
'英文排版/UI 文案中滥用 em-dash("—"):英文语境最高频的 AI 破绽,禁止用作随意连接符(中文正文的破折号属合法标点,不在此列)。',
|
|
92
|
+
'占位文案 "Lorem Ipsum" / "John Doe" / "Acme"。',
|
|
93
|
+
'AI 陈词滥调文案:Elevate / Seamless / Unleash / Delve / 赋能 / 一站式。',
|
|
94
|
+
'同页重复 CTA 意图(如 "Get in touch" + "Let\'s talk" 并存)。',
|
|
95
|
+
];
|
|
96
|
+
// ============================================================
|
|
97
|
+
// 渲染函数:返回 markdown 片段,供各工具拼进指南
|
|
98
|
+
// ============================================================
|
|
99
|
+
/** 代码量硬约束清单 */
|
|
100
|
+
export function renderCodeLimits() {
|
|
101
|
+
return `**代码量硬约束(超出即判 HIGH,必须重构)**:
|
|
102
|
+
- [ ] 单文件 ≤ ${CODE_LIMITS.maxFileLines} 行:超出必须拆分为多个模块/组件
|
|
103
|
+
- [ ] 单函数 ≤ ${CODE_LIMITS.maxFunctionLines} 行:超出必须拆分方法
|
|
104
|
+
- [ ] 嵌套 ≤ ${CODE_LIMITS.maxNestingDepth} 层:超出用提前返回(early return)展平
|
|
105
|
+
- [ ] 参数 ≤ ${CODE_LIMITS.maxParameters} 个:超出改用参数对象`;
|
|
106
|
+
}
|
|
107
|
+
/** 完整性黑名单(占位符/省略式代码) */
|
|
108
|
+
export function renderBannedPatterns() {
|
|
109
|
+
const list = BANNED_CODE_PATTERNS.map((p) => `- \`${p}\``).join('\n');
|
|
110
|
+
return `**完整性检查(命中即判 CRITICAL:「部分输出 = 破碎输出」)**:
|
|
111
|
+
|
|
112
|
+
下列占位/省略模式零容忍,扫描到任意一个即判定该交付物未完成:
|
|
113
|
+
|
|
114
|
+
${list}
|
|
115
|
+
|
|
116
|
+
> 二元规则:不存在「少量允许」。任何用占位符、省略注释代替真实实现的行为都是破碎输出。`;
|
|
117
|
+
}
|
|
118
|
+
/** UI 设计硬红线 */
|
|
119
|
+
export function renderUiHardRules() {
|
|
120
|
+
const list = UI_HARD_RULES.map((r, i) => `${i + 1}. ${r}`).join('\n');
|
|
121
|
+
return `## UI 设计硬红线(带数值,可逐条核验)
|
|
122
|
+
|
|
123
|
+
${list}`;
|
|
124
|
+
}
|
|
125
|
+
/** UI 禁用黑名单 */
|
|
126
|
+
export function renderUiBannedList() {
|
|
127
|
+
const list = UI_BANNED_LIST.map((b) => `- ❌ ${b}`).join('\n');
|
|
128
|
+
return `## UI 禁用黑名单(命中即 AI slop,二元禁令)
|
|
129
|
+
|
|
130
|
+
${list}`;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 交付前自检矩阵(Pre-Flight Checklist)。
|
|
134
|
+
* 把所有约束收口成一个生成结束前必须逐条诚实勾选的闭环。
|
|
135
|
+
* 任意一项不能诚实勾选 = 未完成。
|
|
136
|
+
*/
|
|
137
|
+
export function renderPreFlightChecklist() {
|
|
138
|
+
return `## 交付前自检矩阵(Pre-Flight Check)
|
|
139
|
+
|
|
140
|
+
> 生成结束前必须逐条诚实勾选。任意一项不能勾选 = 未完成,禁止交付。
|
|
141
|
+
|
|
142
|
+
**完整性**:
|
|
143
|
+
- [ ] 无任何占位符 / 省略注释 / TODO(对照完整性黑名单)
|
|
144
|
+
- [ ] 交付物数量与需求一致(Scope-lock:先数清楚再交付)
|
|
145
|
+
|
|
146
|
+
**代码量**:
|
|
147
|
+
- [ ] 所有文件 ≤ ${CODE_LIMITS.maxFileLines} 行,函数 ≤ ${CODE_LIMITS.maxFunctionLines} 行
|
|
148
|
+
|
|
149
|
+
**UI(若涉及界面)**:
|
|
150
|
+
- [ ] 间距落在 4pt 阶梯内
|
|
151
|
+
- [ ] 对比度达标(正文 ≥ 4.5:1)
|
|
152
|
+
- [ ] 交互元素八态齐全
|
|
153
|
+
- [ ] 未命中 UI 禁用黑名单(无 em-dash、无 AI 紫蓝渐变、无米色底等)
|
|
154
|
+
- [ ] 单一强调色、统一圆角阶梯(一致性锁)`;
|
|
155
|
+
}
|
|
@@ -84,17 +84,17 @@ export function renderSkillBridgeSection(status) {
|
|
|
84
84
|
const conclusion = status.ready
|
|
85
85
|
? "全部 skill 可用,建议按顺序调用后再执行 MCP 工具步骤。"
|
|
86
86
|
: "部分 skill 缺失:继续执行 MCP 主流程,不阻塞;安装缺失 skill 后下次运行可获得更高质量输出。";
|
|
87
|
-
return `## 🧩 Skill Bridge(UI/PRD 增强)
|
|
88
|
-
|
|
89
|
-
按顺序调用(宿主支持时):
|
|
90
|
-
${orderLines}
|
|
91
|
-
|
|
92
|
-
当前状态:
|
|
93
|
-
${stateLines}
|
|
94
|
-
|
|
95
|
-
${conclusion}
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
87
|
+
return `## 🧩 Skill Bridge(UI/PRD 增强)
|
|
88
|
+
|
|
89
|
+
按顺序调用(宿主支持时):
|
|
90
|
+
${orderLines}
|
|
91
|
+
|
|
92
|
+
当前状态:
|
|
93
|
+
${stateLines}
|
|
94
|
+
|
|
95
|
+
${conclusion}
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
99
|
`;
|
|
100
100
|
}
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* 本模块为纯逻辑、无 I/O,便于单测。
|
|
9
9
|
*/
|
|
10
|
-
/**
|
|
11
|
-
const PLACEHOLDER_RE = /\[填写[::][^\]]*\]/g;
|
|
10
|
+
/** 匹配未填写的占位符:`[填写:xxx]` / `[填写:xxx]` / 裸 `[填写]` 都算 */
|
|
11
|
+
const PLACEHOLDER_RE = /\[填写[::]?[^\]]*\]/g;
|
|
12
12
|
function countPlaceholders(content) {
|
|
13
13
|
const matches = content.match(PLACEHOLDER_RE);
|
|
14
14
|
return matches ? matches.length : 0;
|
|
@@ -29,7 +29,7 @@ export function extractFrIds(content) {
|
|
|
29
29
|
const REQUIRED_SECTIONS = {
|
|
30
30
|
requirements: ['功能概述', '需求列表', '非功能需求', '依赖关系'],
|
|
31
31
|
design: ['概述', '技术方案', '文件结构'],
|
|
32
|
-
tasks: ['任务列表', '需求覆盖矩阵'],
|
|
32
|
+
tasks: ['交付物清单', '任务列表', '需求覆盖矩阵', '文件变更清单'],
|
|
33
33
|
};
|
|
34
34
|
/**
|
|
35
35
|
* 校验三份规格文档。传入各文件的全文(不存在传 null)。
|
|
@@ -93,6 +93,19 @@ export function validateSpecDocuments(input) {
|
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
+
// tasks:详细度校验——每条任务应附「证据块」,避免宽泛任务导致 AI 偷懒
|
|
97
|
+
if (tasks && tasks.trim()) {
|
|
98
|
+
const taskItemCount = (tasks.match(/^\s*-\s*\[\s*\]\s*\d+\.\d+/gm) || []).length;
|
|
99
|
+
const evidenceCount = (tasks.match(/证据块/g) || []).length;
|
|
100
|
+
if (taskItemCount > 0 && evidenceCount < taskItemCount) {
|
|
101
|
+
issues.push({
|
|
102
|
+
file: 'tasks',
|
|
103
|
+
severity: 'warning',
|
|
104
|
+
code: 'thin_task',
|
|
105
|
+
message: `tasks.md 有 ${taskItemCount} 条任务,但仅 ${evidenceCount} 条标注「证据块」;过于宽泛的任务易导致实现时偷懒/跳步`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
96
109
|
const errorCount = issues.filter((item) => item.severity === 'error').length;
|
|
97
110
|
const warningCount = issues.filter((item) => item.severity === 'warning').length;
|
|
98
111
|
const passed = errorCount === 0;
|