sillyspec 3.6.0 → 3.7.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 +42 -19
- package/SKILL.md +57 -15
- package/package.json +1 -1
- package/src/index.js +15 -2
- package/src/init.js +139 -134
- package/src/setup.js +214 -0
- package/templates/brainstorm.md +5 -0
- package/templates/execute.md +5 -2
- package/templates/plan.md +23 -2
- package/templates/verify.md +52 -0
package/README.md
CHANGED
|
@@ -86,7 +86,7 @@ claude --dangerously-skip-permissions
|
|
|
86
86
|
| `/sillyspec:brainstorm` | 需求探索+规范生成:直接产出 design.md + tasks.md |
|
|
87
87
|
| `/sillyspec:plan` | 实现计划:文件路径+任务描述+Wave 分组 |
|
|
88
88
|
| `/sillyspec:execute` | TDD 执行:子代理并行+用户自选确认频率 |
|
|
89
|
-
| `/sillyspec:verify` |
|
|
89
|
+
| `/sillyspec:verify` | 验证(可选):对照规范+测试套件+代码审查+E2E |
|
|
90
90
|
| `/sillyspec:archive` | 归档:规范沉淀到 knowledge/ |
|
|
91
91
|
|
|
92
92
|
### 辅助工具
|
|
@@ -97,11 +97,44 @@ claude --dangerously-skip-permissions
|
|
|
97
97
|
| `/sillyspec:continue` | 自动下一步 |
|
|
98
98
|
| `/sillyspec:explore` | 自由思考:画图、讨论、调研 |
|
|
99
99
|
| `/sillyspec:quick` | 快速模式:跳过完整流程 |
|
|
100
|
-
| `/sillyspec:
|
|
100
|
+
| `/sillyspec:commit` | 智能提交 |
|
|
101
|
+
| `/sillyspec:state` | 查看当前工作状态 |
|
|
101
102
|
| `/sillyspec:resume` | 恢复工作:支持大模块阶段进度 |
|
|
102
103
|
| `/sillyspec:workspace` | 工作区管理:多项目子项目 |
|
|
103
104
|
| `/sillyspec:export` | 导出成功方案为可复用模板 |
|
|
104
105
|
|
|
106
|
+
## CLI 命令
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
sillyspec status [--json] 显示当前项目状态
|
|
110
|
+
sillyspec next [--json] 显示下一步命令
|
|
111
|
+
sillyspec check [--json] 检查文档完整性
|
|
112
|
+
sillyspec setup 安装推荐 MCP 工具(交互式)
|
|
113
|
+
sillyspec setup --list 查看已安装 MCP 状态
|
|
114
|
+
sillyspec init 初始化(零交互,自动检测工具)
|
|
115
|
+
sillyspec init --tool <name> 指定工具安装
|
|
116
|
+
sillyspec init --workspace 工作区模式
|
|
117
|
+
sillyspec init --interactive 交互式引导
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## MCP 增强
|
|
121
|
+
|
|
122
|
+
通过 `sillyspec setup` 安装 MCP 工具增强 AI 能力:
|
|
123
|
+
|
|
124
|
+
- **Context7** — 查询最新库文档和 API 参考
|
|
125
|
+
- **grep.app** — 搜索开源代码实现
|
|
126
|
+
- **Chrome DevTools** — 浏览器自动化,支持 E2E 验证
|
|
127
|
+
|
|
128
|
+
## E2E 测试流程
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
plan: 识别 UI 功能 → 检测测试能力(E2E框架 > 通用测试 > 浏览器MCP)→ 添加 E2E 任务
|
|
132
|
+
execute: 编码完成后编写 E2E 测试(测试文件或 e2e-steps.md)
|
|
133
|
+
verify: 按优先级执行 → 用户确认修复策略 → 自动修复循环(quick)→ 结果记录
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
自动修复支持跨会话:测试结果持久化在 `.sillyspec/local.yaml`,按变更名隔离。
|
|
137
|
+
|
|
105
138
|
## 可靠性保障
|
|
106
139
|
|
|
107
140
|
SillySpec 不仅仅是 prompt,还有硬校验:
|
|
@@ -126,23 +159,13 @@ SillySpec 不仅仅是 prompt,还有硬校验:
|
|
|
126
159
|
|
|
127
160
|
```
|
|
128
161
|
sillyspec/
|
|
129
|
-
├──
|
|
130
|
-
|
|
131
|
-
│ ├──
|
|
132
|
-
│
|
|
133
|
-
│
|
|
134
|
-
|
|
135
|
-
├──
|
|
136
|
-
├── templates/ # 纯 prompt 模板(工具无关)
|
|
137
|
-
├── adapters/
|
|
138
|
-
│ └── adapters.sh # 多工具格式转换函数(bash 版,已废弃)
|
|
139
|
-
├── scripts/
|
|
140
|
-
│ ├── init.sh # init 入口(转发给 CLI)
|
|
141
|
-
│ ├── validate-proposal.sh # 校验 propose 输出
|
|
142
|
-
│ ├── validate-plan.sh # 校验 plan 输出
|
|
143
|
-
│ ├── validate-scan.sh # 校验 scan 输出
|
|
144
|
-
│ └── validate-all.sh # 综合校验
|
|
145
|
-
├── 操作文档.md # 详细操作指南
|
|
162
|
+
├── bin/sillyspec.js # CLI 入口
|
|
163
|
+
├── src/
|
|
164
|
+
│ ├── index.js # status/next/check 命令
|
|
165
|
+
│ ├── init.js # init 逻辑 + 工具适配器
|
|
166
|
+
│ └── setup.js # MCP 工具安装
|
|
167
|
+
├── templates/ # 命令模板(19 个)
|
|
168
|
+
├── SKILL.md # 技能描述
|
|
146
169
|
└── README.md
|
|
147
170
|
```
|
|
148
171
|
|
package/SKILL.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sillyspec
|
|
3
|
-
description: "
|
|
4
|
-
version: "
|
|
3
|
+
description: "规范驱动开发工具包 v3.6。绿地项目用 /sillyspec:init,棕地项目用 /sillyspec:scan。可用命令:init、scan、scan-quick、explore、brainstorm、plan、execute、verify、archive、commit、export、status、resume、continue、quick、state、workspace、workspace-sync。"
|
|
4
|
+
version: "3.6.1"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# SillySpec
|
|
7
|
+
# SillySpec v3.6
|
|
8
8
|
|
|
9
|
-
融合 Superpowers + OpenSpec + GSD
|
|
9
|
+
融合 Superpowers + OpenSpec + GSD,从"你说要啥"到"代码能跑"的完整流程。
|
|
10
|
+
Claude Code / Cursor / Codex / OpenCode / OpenClaw 都能用。
|
|
10
11
|
|
|
11
12
|
## 入口选择
|
|
12
13
|
|
|
@@ -25,21 +26,62 @@ version: "2.0.0"
|
|
|
25
26
|
工作区:workspace → (init/scan per project) → brainstorm → ...
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
##
|
|
29
|
+
## 19 个命令
|
|
30
|
+
|
|
31
|
+
### 核心流程
|
|
29
32
|
|
|
30
33
|
| 命令 | 用途 |
|
|
31
34
|
|---|---|
|
|
32
35
|
| `/sillyspec:init` | 绿地项目初始化 |
|
|
33
36
|
| `/sillyspec:scan` | 棕地项目扫描(7 份文档) |
|
|
37
|
+
| `/sillyspec:brainstorm` | 需求探索 + 生成设计文档 |
|
|
38
|
+
| `/sillyspec:plan` | 编写实现计划(Wave 分组) |
|
|
39
|
+
| `/sillyspec:execute` | TDD 执行 + 子代理并行 |
|
|
40
|
+
| `/sillyspec:verify` | 验证(测试 + 代码审查 + E2E) |
|
|
41
|
+
| `/sillyspec:archive` | 归档变更 |
|
|
42
|
+
|
|
43
|
+
### 辅助工具
|
|
44
|
+
|
|
45
|
+
| 命令 | 用途 |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `/sillyspec:status` | 查看项目进度和状态 |
|
|
48
|
+
| `/sillyspec:continue` | 自动判断并执行下一步 |
|
|
34
49
|
| `/sillyspec:explore` | 自由思考模式 |
|
|
35
|
-
| `/sillyspec:
|
|
36
|
-
| `/sillyspec:plan` | 实现计划 |
|
|
37
|
-
| `/sillyspec:execute` | TDD 执行 |
|
|
38
|
-
| `/sillyspec:verify` | 验证(可选) |
|
|
39
|
-
| `/sillyspec:archive` | 归档 |
|
|
40
|
-
| `/sillyspec:status` | 查看进度 |
|
|
41
|
-
| `/sillyspec:continue` | 自动下一步 |
|
|
42
|
-
| `/sillyspec:handoff` | 保存状态 |
|
|
50
|
+
| `/sillyspec:quick` | 快速任务,跳过完整流程 |
|
|
43
51
|
| `/sillyspec:resume` | 恢复工作 |
|
|
44
|
-
| `/sillyspec:
|
|
45
|
-
| `/sillyspec:
|
|
52
|
+
| `/sillyspec:state` | 查看当前工作状态 |
|
|
53
|
+
| `/sillyspec:commit` | 智能提交 |
|
|
54
|
+
| `/sillyspec:export` | 导出成功方案为可复用模板 |
|
|
55
|
+
| `/sillyspec:scan-quick` | 快速扫描(STACK + STRUCTURE) |
|
|
56
|
+
| `/sillyspec:workspace` | 多项目工作区管理 |
|
|
57
|
+
| `/sillyspec:workspace-sync` | 同步工作区子项目状态 |
|
|
58
|
+
|
|
59
|
+
## CLI 命令
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
sillyspec status [--json] 显示当前项目状态
|
|
63
|
+
sillyspec next [--json] 显示下一步命令
|
|
64
|
+
sillyspec check [--json] 检查文档完整性
|
|
65
|
+
sillyspec setup 安装推荐 MCP 工具
|
|
66
|
+
sillyspec setup --list 查看已安装 MCP 状态
|
|
67
|
+
sillyspec init 初始化(零交互,自动检测工具)
|
|
68
|
+
sillyspec init --tool <name> 指定工具安装
|
|
69
|
+
sillyspec init --workspace 工作区模式
|
|
70
|
+
sillyspec init --interactive 交互式引导
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## MCP 增强
|
|
74
|
+
|
|
75
|
+
通过 `sillyspec setup` 安装 MCP 工具增强 AI 能力:
|
|
76
|
+
|
|
77
|
+
- **Context7** — 查询最新库文档和 API 参考
|
|
78
|
+
- **grep.app** — 搜索开源代码实现
|
|
79
|
+
- **Chrome DevTools** — 浏览器自动化,支持 E2E 验证
|
|
80
|
+
|
|
81
|
+
## E2E 测试流程
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
plan: 识别 UI 功能 → 检测测试框架/浏览器 MCP → 添加 E2E 任务
|
|
85
|
+
execute: 编码完成后编写 E2E 测试(测试文件或 e2e-steps.md)
|
|
86
|
+
verify: 按优先级执行(E2E框架 > 通用测试 > 浏览器MCP)→ 自动修复循环
|
|
87
|
+
```
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -481,9 +481,11 @@ SillySpec CLI — 流程状态机
|
|
|
481
481
|
sillyspec status [--json] 显示当前项目状态
|
|
482
482
|
sillyspec next [--json] 显示下一步该执行的命令
|
|
483
483
|
sillyspec check [--json] 检查文档完整性和路径
|
|
484
|
-
sillyspec
|
|
484
|
+
sillyspec setup [--list] 安装推荐 MCP 工具(Context7、grep.app、浏览器)
|
|
485
|
+
sillyspec init 初始化 SillySpec(自动检测工具,零交互)
|
|
485
486
|
[--tool <name>] 只安装指定工具
|
|
486
487
|
[--workspace] 工作区模式
|
|
488
|
+
[--interactive] 交互式引导(选择工具、配置工作区)
|
|
487
489
|
[--dir <path>] 指定目录
|
|
488
490
|
|
|
489
491
|
选项:
|
|
@@ -495,6 +497,8 @@ SillySpec CLI — 流程状态机
|
|
|
495
497
|
sillyspec status --json
|
|
496
498
|
sillyspec next --json
|
|
497
499
|
sillyspec check
|
|
500
|
+
sillyspec setup
|
|
501
|
+
sillyspec setup --list
|
|
498
502
|
`);
|
|
499
503
|
}
|
|
500
504
|
|
|
@@ -516,6 +520,7 @@ async function main() {
|
|
|
516
520
|
let targetDir = process.cwd();
|
|
517
521
|
let tool = null;
|
|
518
522
|
let workspace = false;
|
|
523
|
+
let interactive = false;
|
|
519
524
|
const filteredArgs = [];
|
|
520
525
|
|
|
521
526
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -529,6 +534,10 @@ async function main() {
|
|
|
529
534
|
i++;
|
|
530
535
|
} else if (args[i] === '--workspace' || args[i] === '-w') {
|
|
531
536
|
workspace = true;
|
|
537
|
+
} else if (args[i] === '--interactive' || args[i] === '-i') {
|
|
538
|
+
interactive = true;
|
|
539
|
+
} else if (args[i] === '--list' || args[i] === '-l') {
|
|
540
|
+
filteredArgs.push('--list');
|
|
532
541
|
} else {
|
|
533
542
|
filteredArgs.push(args[i]);
|
|
534
543
|
}
|
|
@@ -553,7 +562,11 @@ async function main() {
|
|
|
553
562
|
cmdCheck(dir, { json });
|
|
554
563
|
break;
|
|
555
564
|
case 'init':
|
|
556
|
-
await cmdInit(dir, { tool, workspace });
|
|
565
|
+
await cmdInit(dir, { tool, workspace, interactive });
|
|
566
|
+
break;
|
|
567
|
+
case 'setup':
|
|
568
|
+
const setupList = filteredArgs.includes('--list') || filteredArgs.includes('-l');
|
|
569
|
+
await (await import('./setup.js')).cmdSetup(dir, { json, list: setupList });
|
|
557
570
|
break;
|
|
558
571
|
default:
|
|
559
572
|
console.error(`❌ 未知命令: ${command}`);
|
package/src/init.js
CHANGED
|
@@ -215,6 +215,18 @@ async function doInstall(projectDir, tools, isWorkspace, subprojects = []) {
|
|
|
215
215
|
mkdirSync(join(projectDir, '.sillyspec', 'workspace'), { recursive: true });
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
// 创建知识库骨架(所有模式)
|
|
219
|
+
const knowledgeDir = join(projectDir, '.sillyspec', 'knowledge');
|
|
220
|
+
mkdirSync(knowledgeDir, { recursive: true });
|
|
221
|
+
const indexPath = join(knowledgeDir, 'INDEX.md');
|
|
222
|
+
if (!existsSync(indexPath)) {
|
|
223
|
+
writeFileSync(indexPath, `# Knowledge Index\n\n> 子代理任务开始前查询此文件,按关键词匹配,只读命中的知识文件。\n> execute/quick 执行中发现的坑自动追加到 uncategorized.md,经用户确认后归类到对应文件。\n\n<!-- 格式:关键词1|关键词2|关键词3 → 文件路径 -->\n<!-- 示例:mybatis-plus|分页|Page → pagination.md -->\n<!-- 示例:跨域|CORS|preflight → cors.md -->\n`);
|
|
224
|
+
}
|
|
225
|
+
const uncatPath = join(knowledgeDir, 'uncategorized.md');
|
|
226
|
+
if (!existsSync(uncatPath)) {
|
|
227
|
+
writeFileSync(uncatPath, `# 未分类知识\n\n> execute/quick 执行中发现的坑暂存于此,用户审阅后归类到对应文件并更新 INDEX.md。\n`);
|
|
228
|
+
}
|
|
229
|
+
|
|
218
230
|
const gitignorePath = join(projectDir, '.gitignore');
|
|
219
231
|
const ignoreRules = ['.sillyspec/STATE.md', '.sillyspec/codebase/SCAN-RAW.md', '.sillyspec/local.yaml'];
|
|
220
232
|
if (existsSync(gitignorePath)) {
|
|
@@ -332,6 +344,9 @@ function showSummary(version, tools, isWorkspace, count) {
|
|
|
332
344
|
console.log('');
|
|
333
345
|
console.log(chalk.gray(' 重启你的 AI 工具以使 slash commands 生效。'));
|
|
334
346
|
console.log('');
|
|
347
|
+
console.log(chalk.dim(' 💡 推荐安装 MCP 工具增强 AI 能力:sillyspec setup'));
|
|
348
|
+
console.log(chalk.dim(' Context7 — 查最新文档 | grep.app — 搜开源实现 | Chrome DevTools — 浏览器自动化'));
|
|
349
|
+
console.log('');
|
|
335
350
|
}
|
|
336
351
|
|
|
337
352
|
// ── 读取版本号 ──
|
|
@@ -348,160 +363,150 @@ export function getVersion() {
|
|
|
348
363
|
// ── 主命令 ──
|
|
349
364
|
|
|
350
365
|
export async function cmdInit(projectDir, options = {}) {
|
|
351
|
-
const { tool, workspace } = options;
|
|
366
|
+
const { tool, workspace, interactive } = options;
|
|
352
367
|
const version = getVersion();
|
|
353
368
|
|
|
354
|
-
//
|
|
355
|
-
if (
|
|
356
|
-
|
|
357
|
-
console.error(`❌ 未知工具: ${tool}`);
|
|
358
|
-
console.error(`支持的工具: ${VALID_TOOLS.join(', ')}`);
|
|
359
|
-
process.exit(1);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
console.log(chalk.cyan(`🤪 SillySpec v${version}`));
|
|
363
|
-
console.log(chalk.cyan(`📦 安装工具: ${tool}`));
|
|
369
|
+
// ── 交互式模式(--interactive 或 -i)──
|
|
370
|
+
if (interactive && isTTY()) {
|
|
371
|
+
// 欢迎画面
|
|
364
372
|
console.log('');
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
showSummary(version, [tool], workspace, count);
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// 非交互模式
|
|
372
|
-
if (!isTTY()) {
|
|
373
|
-
const tools = detectTools(projectDir);
|
|
374
|
-
console.log(chalk.cyan(`🤪 SillySpec v${version} (非交互模式)`));
|
|
375
|
-
console.log(chalk.cyan(`📦 自动检测工具: ${tools.join(', ')}`));
|
|
373
|
+
console.log(chalk.cyan('🤪 SillySpec v' + version + ' — 规范驱动开发'));
|
|
374
|
+
console.log(chalk.cyan(' ===================================='));
|
|
376
375
|
console.log('');
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// ── 交互式引导 ──
|
|
383
|
-
|
|
384
|
-
// 欢迎画面
|
|
385
|
-
console.log('');
|
|
386
|
-
console.log(chalk.cyan('🤪 SillySpec v' + version + ' — 规范驱动开发'));
|
|
387
|
-
console.log(chalk.cyan(' ===================================='));
|
|
388
|
-
console.log('');
|
|
389
|
-
console.log(' 让 AI 像高级工程师一样工作:');
|
|
390
|
-
console.log(' 先思考、先规划、先验证,再写代码。');
|
|
391
|
-
console.log('');
|
|
392
|
-
console.log(chalk.gray(' 支持的 AI 工具:'));
|
|
393
|
-
console.log(chalk.gray(' Claude Code · Claude Skills · Cursor · Codex CLI · OpenCode · OpenClaw'));
|
|
394
|
-
console.log('');
|
|
395
|
-
|
|
396
|
-
await confirm({ message: '按回车开始设置...', default: true });
|
|
397
|
-
|
|
398
|
-
// 工具多选
|
|
399
|
-
const detected = detectTools(projectDir);
|
|
400
|
-
|
|
401
|
-
const toolChoices = VALID_TOOLS.map(v => ({
|
|
402
|
-
name: `${TOOL_LABELS[v]}${v === 'claude' ? ' (推荐)' : ''}`,
|
|
403
|
-
value: v,
|
|
404
|
-
checked: detected.includes(v),
|
|
405
|
-
}));
|
|
406
|
-
|
|
407
|
-
const selectedTools = await checkbox({
|
|
408
|
-
message: '选择要安装的 AI 工具',
|
|
409
|
-
choices: toolChoices,
|
|
410
|
-
validate: (answer) => answer.length > 0 || '至少选择一个工具',
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
// 工作区模式
|
|
414
|
-
const isWorkspace = await select({
|
|
415
|
-
message: '选择项目模式',
|
|
416
|
-
choices: [
|
|
417
|
-
{ name: '单项目模式', value: 'false' },
|
|
418
|
-
{ name: '多项目工作区', value: 'true' },
|
|
419
|
-
],
|
|
420
|
-
}) === 'true';
|
|
421
|
-
|
|
422
|
-
// 工作区子项目引导
|
|
423
|
-
if (isWorkspace) {
|
|
376
|
+
console.log(' 让 AI 像高级工程师一样工作:');
|
|
377
|
+
console.log(' 先思考、先规划、先验证,再写代码。');
|
|
424
378
|
console.log('');
|
|
425
|
-
console.log(chalk.
|
|
426
|
-
console.log(chalk.
|
|
379
|
+
console.log(chalk.gray(' 支持的 AI 工具:'));
|
|
380
|
+
console.log(chalk.gray(' Claude Code · Claude Skills · Cursor · Codex CLI · OpenCode · OpenClaw'));
|
|
427
381
|
console.log('');
|
|
428
382
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
383
|
+
// 工具多选
|
|
384
|
+
const detected = detectTools(projectDir);
|
|
385
|
+
|
|
386
|
+
const toolChoices = VALID_TOOLS.map(v => ({
|
|
387
|
+
name: `${TOOL_LABELS[v]}${v === 'claude' ? ' (推荐)' : ''}`,
|
|
388
|
+
value: v,
|
|
389
|
+
checked: detected.includes(v),
|
|
390
|
+
}));
|
|
391
|
+
|
|
392
|
+
const selectedTools = await checkbox({
|
|
393
|
+
message: '选择要安装的 AI 工具(空格选择,回车确认)',
|
|
394
|
+
choices: toolChoices,
|
|
395
|
+
validate: (answer) => answer.length > 0 || '至少选择一个工具',
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// 工作区模式
|
|
399
|
+
const isWorkspace = await select({
|
|
400
|
+
message: '选择项目模式',
|
|
401
|
+
choices: [
|
|
402
|
+
{ name: '单项目模式', value: 'false' },
|
|
403
|
+
{ name: '多项目工作区', value: 'true' },
|
|
404
|
+
],
|
|
405
|
+
}) === 'true';
|
|
406
|
+
|
|
407
|
+
// 工作区子项目引导
|
|
408
|
+
let subprojects = [];
|
|
409
|
+
if (isWorkspace) {
|
|
410
|
+
console.log('');
|
|
411
|
+
console.log(chalk.yellow('📋 工作区模式 — 添加子项目'));
|
|
412
|
+
console.log(chalk.dim(' 子项目是工作区中的独立项目目录(如 frontend/、backend/)'));
|
|
413
|
+
console.log('');
|
|
414
|
+
|
|
415
|
+
const addMore = await confirm({ message: '现在添加子项目?', default: true });
|
|
416
|
+
if (addMore) {
|
|
417
|
+
let suggestions = [];
|
|
418
|
+
try {
|
|
419
|
+
const entries = readdirSync(projectDir, { withFileTypes: true });
|
|
420
|
+
suggestions = entries
|
|
421
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules')
|
|
422
|
+
.map(e => e.name)
|
|
423
|
+
.sort();
|
|
424
|
+
} catch {}
|
|
446
425
|
|
|
447
|
-
|
|
448
|
-
|
|
426
|
+
if (suggestions.length > 0) {
|
|
427
|
+
console.log('');
|
|
428
|
+
console.log(chalk.dim(` 检测到以下目录:${suggestions.join(', ')}`));
|
|
429
|
+
console.log('');
|
|
430
|
+
}
|
|
449
431
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
432
|
+
let adding = true;
|
|
433
|
+
while (adding) {
|
|
434
|
+
const name = await input({
|
|
435
|
+
message: '子项目名称(如 frontend,留空结束)',
|
|
436
|
+
default: suggestions.find(s => !subprojects.find(p => p.name === s)) || '',
|
|
437
|
+
});
|
|
455
438
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
439
|
+
if (!name.trim()) {
|
|
440
|
+
adding = false;
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
460
443
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
444
|
+
const pathHint = suggestions.includes(name.trim()) ? `./${name.trim()}` : '';
|
|
445
|
+
const subPath = await input({
|
|
446
|
+
message: '子项目目录路径',
|
|
447
|
+
default: pathHint,
|
|
448
|
+
});
|
|
466
449
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
450
|
+
const role = await input({
|
|
451
|
+
message: '子项目描述(如 前端 - Vue3 + TypeScript)',
|
|
452
|
+
default: '',
|
|
453
|
+
});
|
|
471
454
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
} catch {}
|
|
455
|
+
let repo = '';
|
|
456
|
+
try {
|
|
457
|
+
const { execSync } = await import('child_process');
|
|
458
|
+
const absPath = resolve(projectDir, subPath.trim() || `./${name.trim()}`);
|
|
459
|
+
repo = execSync('git remote get-url origin', { cwd: absPath, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
460
|
+
} catch {}
|
|
479
461
|
|
|
480
|
-
|
|
481
|
-
name: name.trim(),
|
|
482
|
-
path: subPath.trim() || `./${name.trim()}`,
|
|
483
|
-
role: role.trim(),
|
|
484
|
-
repo,
|
|
485
|
-
});
|
|
462
|
+
subprojects.push({ name: name.trim(), path: subPath.trim() || `./${name.trim()}`, role: role.trim(), repo });
|
|
486
463
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
if (idx >= 0) suggestions.splice(idx, 1);
|
|
464
|
+
const idx = suggestions.indexOf(name.trim());
|
|
465
|
+
if (idx >= 0) suggestions.splice(idx, 1);
|
|
490
466
|
|
|
491
|
-
|
|
492
|
-
|
|
467
|
+
const again = await confirm({ message: '继续添加子项目?', default: subprojects.length < suggestions.length });
|
|
468
|
+
if (!again) adding = false;
|
|
469
|
+
}
|
|
493
470
|
}
|
|
471
|
+
}
|
|
494
472
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
473
|
+
console.log('');
|
|
474
|
+
const count = await doInstall(projectDir, selectedTools, isWorkspace, subprojects);
|
|
475
|
+
showSummary(version, selectedTools, isWorkspace, count);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ── 默认快速模式:检测 → 安装 → 结束 ──
|
|
480
|
+
|
|
481
|
+
let tools = [];
|
|
482
|
+
if (tool) {
|
|
483
|
+
if (!VALID_TOOLS.includes(tool)) {
|
|
484
|
+
console.error(`❌ 未知工具: ${tool}`);
|
|
485
|
+
console.error(`支持的工具: ${VALID_TOOLS.join(', ')}`);
|
|
486
|
+
process.exit(1);
|
|
498
487
|
}
|
|
488
|
+
tools = [tool];
|
|
489
|
+
} else {
|
|
490
|
+
tools = detectTools(projectDir);
|
|
499
491
|
}
|
|
500
492
|
|
|
501
|
-
|
|
502
|
-
console.log('');
|
|
503
|
-
const count = await doInstall(projectDir, selectedTools, isWorkspace, global.__sillyspec_subprojects || []);
|
|
493
|
+
const count = await doInstall(projectDir, tools, !!workspace);
|
|
504
494
|
|
|
505
|
-
|
|
506
|
-
|
|
495
|
+
console.log('');
|
|
496
|
+
console.log(chalk.green(` ✅ SillySpec v${version} 安装完成!`));
|
|
497
|
+
console.log('');
|
|
498
|
+
console.log(` 📄 ${count} 个命令已就绪`);
|
|
499
|
+
console.log(' 📁 .sillyspec/ — 项目规范目录');
|
|
500
|
+
console.log('');
|
|
501
|
+
console.log(' 下一步:');
|
|
502
|
+
console.log(` 全新项目 → ${chalk.bold('/sillyspec:init')}`);
|
|
503
|
+
console.log(` 已有代码 → ${chalk.bold('/sillyspec:scan')}`);
|
|
504
|
+
console.log(` 自由探索 → ${chalk.bold('/sillyspec:explore "你的想法"')}`);
|
|
505
|
+
if (workspace) {
|
|
506
|
+
console.log(` 管理子项目 → ${chalk.bold('/sillyspec:workspace add')}`);
|
|
507
|
+
}
|
|
508
|
+
console.log('');
|
|
509
|
+
console.log(chalk.dim(' 💡 增强能力:sillyspec setup(安装 MCP 工具)'));
|
|
510
|
+
console.log(chalk.dim(' 💡 完整配置:sillyspec init --interactive'));
|
|
511
|
+
console.log('');
|
|
507
512
|
}
|
package/src/setup.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { checkbox, confirm } from '@inquirer/prompts';
|
|
6
|
+
|
|
7
|
+
// ── MCP 工具定义 ──
|
|
8
|
+
|
|
9
|
+
const MCP_TOOLS = [
|
|
10
|
+
{
|
|
11
|
+
id: 'context7',
|
|
12
|
+
name: 'Context7',
|
|
13
|
+
description: '查询最新库文档和 API 参考',
|
|
14
|
+
command: 'npx',
|
|
15
|
+
args: ['-y', '@upstash/context7-mcp@latest'],
|
|
16
|
+
url: 'https://github.com/upstash/context7-mcp',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'grep-app',
|
|
20
|
+
name: 'grep.app',
|
|
21
|
+
description: '搜索开源代码实现和参考',
|
|
22
|
+
command: 'npx',
|
|
23
|
+
args: ['-y', '@nicobailon/grep-app-mcp'],
|
|
24
|
+
url: 'https://grep.app',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'chrome-devtools',
|
|
28
|
+
name: 'Chrome DevTools MCP',
|
|
29
|
+
description: '浏览器自动化,支持 E2E 测试(需 Chrome 已运行)',
|
|
30
|
+
command: 'npx',
|
|
31
|
+
args: ['-y', '@nicholasxjy/chrome-devtools-mcp'],
|
|
32
|
+
url: 'https://github.com/ChromeDevTools/chrome-devtools-mcp',
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// ── MCP 配置文件路径 ──
|
|
37
|
+
|
|
38
|
+
const MCP_CONFIG_PATHS = [
|
|
39
|
+
{ tool: 'Claude Code', path: '.claude/mcp.json', key: 'claude' },
|
|
40
|
+
{ tool: 'Cursor', path: '.cursor/mcp.json', key: 'cursor' },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// ── 工具函数 ──
|
|
44
|
+
|
|
45
|
+
function readMcpConfig(dir, configPath) {
|
|
46
|
+
const fullPath = join(dir, configPath);
|
|
47
|
+
if (!existsSync(fullPath)) return null;
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(readFileSync(fullPath, 'utf8'));
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function writeMcpConfig(dir, configPath, config) {
|
|
56
|
+
const fullPath = join(dir, configPath);
|
|
57
|
+
mkdirSync(join(dir, configPath).replace(/[^/]+$/, ''), { recursive: true });
|
|
58
|
+
writeFileSync(fullPath, JSON.stringify(config, null, 2) + '\n');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function installMcp(config, mcpTool) {
|
|
62
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
63
|
+
config.mcpServers[mcpTool.id] = {
|
|
64
|
+
command: mcpTool.command,
|
|
65
|
+
args: mcpTool.args,
|
|
66
|
+
};
|
|
67
|
+
return config;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function hasMcpInstalled(config, mcpId) {
|
|
71
|
+
return config?.mcpServers?.[mcpId] != null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── 命令实现 ──
|
|
75
|
+
|
|
76
|
+
export async function cmdSetup(dir, options = {}) {
|
|
77
|
+
const { json } = options;
|
|
78
|
+
|
|
79
|
+
// 检查哪些 AI 工具有配置文件
|
|
80
|
+
const availableTools = MCP_CONFIG_PATHS.filter(({ path }) => {
|
|
81
|
+
return existsSync(join(dir, path));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (availableTools.length === 0) {
|
|
85
|
+
if (json) {
|
|
86
|
+
console.log(JSON.stringify({ installed: [], message: '未检测到 AI 工具配置文件,请先运行 sillyspec init' }));
|
|
87
|
+
} else {
|
|
88
|
+
console.log(chalk.yellow('⚠️ 未检测到 AI 工具配置文件(.claude/mcp.json 或 .cursor/mcp.json)'));
|
|
89
|
+
console.log(chalk.dim(' 请先运行 sillyspec init 初始化项目'));
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// --list 模式:只查看状态
|
|
95
|
+
if (options.list) {
|
|
96
|
+
const results = {};
|
|
97
|
+
for (const { tool, path } of availableTools) {
|
|
98
|
+
const config = readMcpConfig(dir, path);
|
|
99
|
+
const installed = {};
|
|
100
|
+
for (const mcp of MCP_TOOLS) {
|
|
101
|
+
installed[mcp.id] = hasMcpInstalled(config, mcp.id);
|
|
102
|
+
}
|
|
103
|
+
results[tool] = { configPath: path, mcp: installed };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (json) {
|
|
107
|
+
console.log(JSON.stringify(results, null, 2));
|
|
108
|
+
} else {
|
|
109
|
+
console.log('');
|
|
110
|
+
for (const [tool, data] of Object.entries(results)) {
|
|
111
|
+
console.log(chalk.bold(`📋 ${tool} (${data.configPath})`));
|
|
112
|
+
for (const mcp of MCP_TOOLS) {
|
|
113
|
+
const status = data.mcp[mcp.id] ? chalk.green('✅') : chalk.gray('⬜');
|
|
114
|
+
console.log(` ${status} ${mcp.name} — ${mcp.description}`);
|
|
115
|
+
}
|
|
116
|
+
console.log('');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 非交互模式
|
|
123
|
+
if (!process.stdin.isTTY) {
|
|
124
|
+
if (json) {
|
|
125
|
+
console.log(JSON.stringify({ message: '交互模式需要 TTY,请运行 sillyspec setup' }));
|
|
126
|
+
} else {
|
|
127
|
+
console.log(chalk.yellow('⚠️ 请在交互式终端运行 sillyspec setup'));
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── 交互式安装 ──
|
|
133
|
+
|
|
134
|
+
console.log('');
|
|
135
|
+
console.log(chalk.cyan('🔧 SillySpec Setup — 安装推荐 MCP 工具'));
|
|
136
|
+
console.log('');
|
|
137
|
+
console.log(' MCP 工具让 AI 能力增强:查文档、搜代码、控制浏览器。');
|
|
138
|
+
console.log(' 选择要安装的工具(已安装的会跳过):');
|
|
139
|
+
console.log('');
|
|
140
|
+
|
|
141
|
+
// 检测已安装状态(从所有配置文件合并)
|
|
142
|
+
const installedSet = new Set();
|
|
143
|
+
for (const { path } of availableTools) {
|
|
144
|
+
const config = readMcpConfig(dir, path);
|
|
145
|
+
for (const mcp of MCP_TOOLS) {
|
|
146
|
+
if (hasMcpInstalled(config, mcp.id)) installedSet.add(mcp.id);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const choices = MCP_TOOLS.filter(m => !installedSet.has(m.id)).map(m => ({
|
|
151
|
+
name: `${m.name} — ${m.description}`,
|
|
152
|
+
value: m.id,
|
|
153
|
+
checked: false,
|
|
154
|
+
}));
|
|
155
|
+
|
|
156
|
+
if (choices.length === 0) {
|
|
157
|
+
console.log(chalk.green(' ✅ 所有推荐的 MCP 工具已安装!'));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const selected = await checkbox({
|
|
162
|
+
message: '选择要安装的 MCP 工具',
|
|
163
|
+
choices,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
if (selected.length === 0) {
|
|
167
|
+
console.log(chalk.dim(' 未选择任何工具,退出。'));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const selectedTools = MCP_TOOLS.filter(m => selected.includes(m.id));
|
|
172
|
+
|
|
173
|
+
// 选择安装到哪些 AI 工具
|
|
174
|
+
const toolChoices = availableTools.map(t => ({
|
|
175
|
+
name: t.tool,
|
|
176
|
+
value: t.key,
|
|
177
|
+
checked: true,
|
|
178
|
+
}));
|
|
179
|
+
|
|
180
|
+
const targetTools = await checkbox({
|
|
181
|
+
message: '安装到哪些 AI 工具?',
|
|
182
|
+
choices: toolChoices,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const targets = availableTools.filter(t => targetTools.includes(t.key));
|
|
186
|
+
|
|
187
|
+
// 安装
|
|
188
|
+
console.log('');
|
|
189
|
+
for (const { tool, path } of targets) {
|
|
190
|
+
const spinner = ora(`安装到 ${tool}...`).start();
|
|
191
|
+
let config = readMcpConfig(dir, path) || { mcpServers: {} };
|
|
192
|
+
|
|
193
|
+
for (const mcp of selectedTools) {
|
|
194
|
+
config = installMcp(config, mcp);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
writeMcpConfig(dir, path, config);
|
|
198
|
+
spinner.succeed(`${tool} 完成 (${selected.length} 个工具)`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 总结
|
|
202
|
+
console.log('');
|
|
203
|
+
console.log(chalk.green(' ═══════════════════════════════════════'));
|
|
204
|
+
console.log(chalk.green(' ✅ MCP 工具安装完成!'));
|
|
205
|
+
console.log(chalk.green(' ═══════════════════════════════════════'));
|
|
206
|
+
console.log('');
|
|
207
|
+
for (const mcp of selectedTools) {
|
|
208
|
+
console.log(` 🔌 ${chalk.cyan(mcp.name)} — ${mcp.description}`);
|
|
209
|
+
console.log(chalk.dim(` ${mcp.url}`));
|
|
210
|
+
}
|
|
211
|
+
console.log('');
|
|
212
|
+
console.log(chalk.dim(' 重启你的 AI 工具以使 MCP 配置生效。'));
|
|
213
|
+
console.log('');
|
|
214
|
+
}
|
package/templates/brainstorm.md
CHANGED
|
@@ -117,6 +117,8 @@ git add .sillyspec/changes/<变更名>/MASTER.md
|
|
|
117
117
|
3. 探索顺序按需:目的 → 约束 → 边界 → 成功标准
|
|
118
118
|
4. **大多数 brainstorm 2-3 轮就应进入方案讨论**
|
|
119
119
|
|
|
120
|
+
探索阶段可使用项目已配置的 MCP 工具或 web search 调研技术方案和 API 用法,不要凭记忆写方案。检测可用工具:`cat .claude/mcp.json .cursor/mcp.json 2>/dev/null`
|
|
121
|
+
|
|
120
122
|
### Step 4: 提出 2-3 种方案
|
|
121
123
|
|
|
122
124
|
每种方案列优劣,给出推荐和理由。
|
|
@@ -157,6 +159,9 @@ git add .sillyspec/changes/<变更名>/MASTER.md
|
|
|
157
159
|
- 范围控制:是否包含不必要功能(YAGNI)?
|
|
158
160
|
- 验收标准:是否具体、可测试?
|
|
159
161
|
- 变更冲突:是否与 Step 1.5 检测到的已有变更冲突?
|
|
162
|
+
- 缺口:模糊表述("适当的"/"必要时"等)是否已明确化
|
|
163
|
+
- 缺口:隐含假设(登录态、数据量、并发预期)是否已显式写出
|
|
164
|
+
- 缺口:边界场景(空数据、并发、服务不可用)是否已考虑
|
|
160
165
|
|
|
161
166
|
发现问题 → 修改文档,重新自审。不确定的标注「⚠️ 自审存疑」让用户判断。
|
|
162
167
|
|
package/templates/execute.md
CHANGED
|
@@ -52,6 +52,8 @@ cat .sillyspec/knowledge/INDEX.md 2>/dev/null
|
|
|
52
52
|
```
|
|
53
53
|
根据当前 task 描述中的关键词(技术名词、模块名、文件路径等)匹配 INDEX.md 条目。命中时读取对应 knowledge 文件,将内容注入子代理 prompt 的「相关知识」段。未命中则跳过,不注入空段。
|
|
54
54
|
|
|
55
|
+
子代理遇到不熟悉的库或 API 时,优先使用已配置的 MCP 工具(Context7 等)或 web search 查最新文档,不要凭记忆猜测用法。
|
|
56
|
+
|
|
55
57
|
如果 `$ARGUMENTS` 指定范围(如 `wave-1`、`task-3`),只执行对应部分。
|
|
56
58
|
|
|
57
59
|
---
|
|
@@ -108,8 +110,9 @@ cat .sillyspec/knowledge/INDEX.md 2>/dev/null
|
|
|
108
110
|
4. **不自行补全:** 发现缺失接口/方法,不自己写,报告 BLOCKED
|
|
109
111
|
5. **TDD 不跳步:** 按任务步骤逐步执行,每步必须运行测试命令并确认结果
|
|
110
112
|
6. **测试直接通过 = 测了已有行为,重写测试**
|
|
111
|
-
7.
|
|
112
|
-
8.
|
|
113
|
+
7. **E2E 任务:** 如果任务描述包含"E2E"或"端到端",先 cat 相关功能代码和页面组件,理解交互逻辑。有测试框架则编写测试文件,无框架则编写 `.sillyspec/changes/<变更名>/e2e-steps.md` 结构化测试步骤(每条包含操作和断言)
|
|
114
|
+
8. **暂存:** 完成后在工作目录执行 git add -A(不要 commit,由用户通过 /sillyspec:commit 统一提交)
|
|
115
|
+
9. **不修改计划外的文件**,如必须修改则在报告中说明
|
|
113
116
|
|
|
114
117
|
## 完成后报告(严格按此格式)
|
|
115
118
|
|
package/templates/plan.md
CHANGED
|
@@ -98,12 +98,33 @@ cat .sillyspec/REQUIREMENTS.md 2>/dev/null
|
|
|
98
98
|
|
|
99
99
|
**直接覆盖** `.sillyspec/changes/<变更名>/tasks.md`。不再生成单独的 plan.md 文件。
|
|
100
100
|
|
|
101
|
-
### 5.
|
|
101
|
+
### 5. E2E 测试规划
|
|
102
|
+
|
|
103
|
+
识别 design.md 中是否有 UI 交互功能(页面跳转、表单提交、按钮操作等),如有则:
|
|
104
|
+
|
|
105
|
+
**检测测试能力(按优先级):**
|
|
106
|
+
```bash
|
|
107
|
+
# 优先级 1:专业 E2E 框架
|
|
108
|
+
cat package.json 2>/dev/null | grep -E "playwright|cypress" ; ls node_modules/playwright node_modules/cypress 2>/dev/null
|
|
109
|
+
# 优先级 2:通用测试框架
|
|
110
|
+
cat package.json 2>/dev/null | grep -E "jest|vitest|mocha" ; ls node_modules/jest node_modules/vitest 2>/dev/null
|
|
111
|
+
# 优先级 3:浏览器 MCP
|
|
112
|
+
cat .claude/mcp.json .cursor/mcp.json 2>/dev/null | grep -i "browser\|chrome\|devtools"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- **有 E2E/测试框架** → tasks.md 中添加 E2E 测试任务(同波次,编码后编写)
|
|
116
|
+
- **无框架但有浏览器 MCP** → tasks.md 中添加"编写 e2e-steps.md 测试步骤"任务
|
|
117
|
+
- **什么都没有** → 提示用户运行 `sillyspec setup` 安装 MCP 工具,或手动安装测试框架
|
|
118
|
+
|
|
119
|
+
纯后端/无 UI 的变更跳过此步骤。
|
|
120
|
+
|
|
121
|
+
### 6. 自检门控
|
|
102
122
|
|
|
103
123
|
- [ ] 每个 task 有具体文件路径?
|
|
104
124
|
- [ ] 标注了 Wave 和依赖关系?
|
|
125
|
+
- [ ] 涉及 UI 的任务是否有对应的 E2E 测试任务?
|
|
105
126
|
|
|
106
|
-
###
|
|
127
|
+
### 7. 完成
|
|
107
128
|
|
|
108
129
|
```bash
|
|
109
130
|
sillyspec status --json && sillyspec next
|
package/templates/verify.md
CHANGED
|
@@ -65,12 +65,62 @@ cat "$CHANGE_DIR"/{design,tasks}.md 2>/dev/null
|
|
|
65
65
|
pnpm test 2>/dev/null || npm test 2>/dev/null || pytest 2>/dev/null || go test ./... 2>/dev/null
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
### 4b. E2E 测试
|
|
69
|
+
|
|
70
|
+
检测项目中是否有 E2E 测试或测试步骤文件:
|
|
71
|
+
```bash
|
|
72
|
+
ls tests/e2e/ e2e/ cypress/e2e/ 2>/dev/null | head -5
|
|
73
|
+
cat .sillyspec/changes/*/e2e-steps.md 2>/dev/null | head -5
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**无任何测试** → 跳过此步骤。
|
|
77
|
+
|
|
78
|
+
**有测试** → 确认修复策略(AskUserQuestion):
|
|
79
|
+
1. 自动修复,同一用例最多 5 次(超过停止,提示人工介入)
|
|
80
|
+
2. 一直修复直到全绿
|
|
81
|
+
3. 只报告,不自动修复
|
|
82
|
+
|
|
83
|
+
**按优先级执行:**
|
|
84
|
+
|
|
85
|
+
**优先级 1:专业 E2E 框架(Playwright/Cypress)**
|
|
86
|
+
```bash
|
|
87
|
+
npx playwright test 2>/dev/null || npx cypress run 2>/dev/null
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**优先级 2:通用测试框架(jest/vitest)**
|
|
91
|
+
```bash
|
|
92
|
+
npx vitest run 2>/dev/null || npx jest 2>/dev/null
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**优先级 3:浏览器 MCP + e2e-steps.md(兜底)**
|
|
96
|
+
读取 `.sillyspec/changes/<变更名>/e2e-steps.md`,按步骤逐条执行。每条标注 ✅/❌,断言失败记为 FAILED。
|
|
97
|
+
> ⚠️ 使用 MCP 执行时 AI 判断可能不如测试框架精确。追求可靠性建议安装 Playwright。
|
|
98
|
+
|
|
99
|
+
**自动修复循环(选了策略 1 或 2 时):**
|
|
100
|
+
读取 `.sillyspec/local.yaml` 中当前变更的 `fixAttempts`,对每个失败测试:
|
|
101
|
+
- fixAttempts 未达上限 → 调 `/sillyspec:quick "修复 E2E 失败:<失败描述>"` → 重跑该测试 → 更新 local.yaml
|
|
102
|
+
- fixAttempts 达到上限 → 停止,报告失败详情,提示人工介入
|
|
103
|
+
|
|
104
|
+
**更新测试结果到 `.sillyspec/local.yaml`(按变更名隔离,覆盖写入):**
|
|
105
|
+
```yaml
|
|
106
|
+
e2e:
|
|
107
|
+
{变更名}:
|
|
108
|
+
login.spec.ts:
|
|
109
|
+
status: passed
|
|
110
|
+
fixAttempts: 0
|
|
111
|
+
form-submit.spec.ts:
|
|
112
|
+
status: failed
|
|
113
|
+
fixAttempts: 3
|
|
114
|
+
```
|
|
115
|
+
|
|
68
116
|
### 5. 代码质量扫描
|
|
69
117
|
|
|
70
118
|
```bash
|
|
71
119
|
grep -r "TODO\|FIXME\|HACK\|XXX" src/ lib/ app/ --include="*.ts" --include="*.tsx" --include="*.py" --include="*.js" 2>/dev/null | head -20
|
|
72
120
|
```
|
|
73
121
|
|
|
122
|
+
审查 design.md「文件变更」中列出的文件:安全问题(输入校验、SQL拼接、硬编码敏感信息)、潜在 bug(空值、边界条件)、与 CONVENTIONS.md 一致性。每个问题标 🔴必须 / 🟡建议 / 🔵优化。
|
|
123
|
+
|
|
74
124
|
### 6. 输出验证报告
|
|
75
125
|
|
|
76
126
|
```markdown
|
|
@@ -79,6 +129,8 @@ grep -r "TODO\|FIXME\|HACK\|XXX" src/ lib/ app/ --include="*.ts" --include="*.ts
|
|
|
79
129
|
## 设计一致性
|
|
80
130
|
## 测试结果:passed N, failed N
|
|
81
131
|
## 技术债务标记
|
|
132
|
+
## 代码审查:🔴 N / 🟡 N / 🔵 N
|
|
133
|
+
## E2E 测试:passed N / failed N / fixAttempts 详情
|
|
82
134
|
## 结论:✅ PASS / ⚠️ PASS WITH NOTES / ❌ FAIL
|
|
83
135
|
```
|
|
84
136
|
|