sillyspec 3.8.5 → 3.8.7

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.
Files changed (163) hide show
  1. package/README.md +0 -6
  2. package/docs/.vitepress/config.mts +45 -0
  3. package/docs/.vitepress/dist/404.html +25 -0
  4. package/docs/.vitepress/dist/assets/app.YytxICdd.js +1 -0
  5. package/docs/.vitepress/dist/assets/chunks/framework.Czhw_PXq.js +19 -0
  6. package/docs/.vitepress/dist/assets/chunks/theme.DusTRZQk.js +1 -0
  7. package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.js +1 -0
  8. package/docs/.vitepress/dist/assets/index.md.C3VCvtQA.lean.js +1 -0
  9. package/docs/.vitepress/dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  10. package/docs/.vitepress/dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  11. package/docs/.vitepress/dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  12. package/docs/.vitepress/dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  13. package/docs/.vitepress/dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  14. package/docs/.vitepress/dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  15. package/docs/.vitepress/dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  16. package/docs/.vitepress/dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  17. package/docs/.vitepress/dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  18. package/docs/.vitepress/dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  19. package/docs/.vitepress/dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  20. package/docs/.vitepress/dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  21. package/docs/.vitepress/dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  22. package/docs/.vitepress/dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  23. package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.js +15 -0
  24. package/docs/.vitepress/dist/assets/sillyspec_commands.md.CXFFsj08.lean.js +1 -0
  25. package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.js +4 -0
  26. package/docs/.vitepress/dist/assets/sillyspec_dashboard.md.BuPXHqjX.lean.js +1 -0
  27. package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.js +1 -0
  28. package/docs/.vitepress/dist/assets/sillyspec_file-io.md.Cz3x7llx.lean.js +1 -0
  29. package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.js +4 -0
  30. package/docs/.vitepress/dist/assets/sillyspec_getting-started.md.ClcvV8k3.lean.js +1 -0
  31. package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.js +5 -0
  32. package/docs/.vitepress/dist/assets/sillyspec_install.md.CKuR2tiT.lean.js +1 -0
  33. package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.js +28 -0
  34. package/docs/.vitepress/dist/assets/sillyspec_lifecycle.md.DY293cR1.lean.js +1 -0
  35. package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.js +30 -0
  36. package/docs/.vitepress/dist/assets/sillyspec_structure.md.sVYS4zPs.lean.js +1 -0
  37. package/docs/.vitepress/dist/assets/style.DFTx90Kk.css +1 -0
  38. package/docs/.vitepress/dist/hashmap.json +1 -0
  39. package/docs/.vitepress/dist/index.html +28 -0
  40. package/docs/.vitepress/dist/sillyspec/commands.html +42 -0
  41. package/docs/.vitepress/dist/sillyspec/dashboard.html +31 -0
  42. package/docs/.vitepress/dist/sillyspec/file-io.html +28 -0
  43. package/docs/.vitepress/dist/sillyspec/getting-started.html +31 -0
  44. package/docs/.vitepress/dist/sillyspec/install.html +32 -0
  45. package/docs/.vitepress/dist/sillyspec/lifecycle.html +55 -0
  46. package/docs/.vitepress/dist/sillyspec/structure.html +57 -0
  47. package/docs/.vitepress/dist/vp-icons.css +1 -0
  48. package/docs/index.md +34 -0
  49. package/docs/sillyspec/commands.md +218 -0
  50. package/docs/sillyspec/dashboard.md +51 -0
  51. package/docs/sillyspec/file-io.md +34 -0
  52. package/docs/sillyspec/getting-started.md +61 -0
  53. package/docs/sillyspec/install.md +51 -0
  54. package/docs/sillyspec/lifecycle.md +146 -0
  55. package/docs/sillyspec/structure.md +62 -0
  56. package/package.json +11 -9
  57. package/packages/dashboard/dist/assets/index-Bh-GPjKY.css +1 -0
  58. package/packages/dashboard/dist/assets/index-CrCn5Gg6.js +17 -0
  59. package/packages/dashboard/dist/index.html +2 -2
  60. package/packages/dashboard/package-lock.json +0 -220
  61. package/packages/dashboard/package.json +5 -8
  62. package/packages/dashboard/server/index.js +106 -255
  63. package/packages/dashboard/server/parser.js +29 -333
  64. package/packages/dashboard/server/watcher.js +131 -203
  65. package/packages/dashboard/src/App.vue +10 -181
  66. package/packages/dashboard/src/components/ActionBar.vue +42 -26
  67. package/packages/dashboard/src/components/CommandPalette.vue +65 -40
  68. package/packages/dashboard/src/components/DetailPanel.vue +53 -68
  69. package/packages/dashboard/src/components/LogStream.vue +33 -13
  70. package/packages/dashboard/src/components/PipelineStage.vue +8 -8
  71. package/packages/dashboard/src/components/PipelineView.vue +45 -80
  72. package/packages/dashboard/src/components/ProjectList.vue +45 -103
  73. package/packages/dashboard/src/components/StageBadge.vue +13 -13
  74. package/packages/dashboard/src/components/StepCard.vue +15 -15
  75. package/packages/dashboard/src/composables/useDashboard.js +6 -20
  76. package/packages/dashboard/src/composables/useKeyboard.js +4 -6
  77. package/packages/dashboard/src/main.js +1 -4
  78. package/packages/dashboard/src/style.css +17 -17
  79. package/src/index.js +12 -123
  80. package/src/init.js +227 -86
  81. package/src/setup.js +9 -1
  82. package/templates/archive.md +120 -0
  83. package/templates/brainstorm.md +170 -0
  84. package/{.claude/skills/sillyspec-commit/SKILL.md → templates/commit.md} +45 -29
  85. package/templates/continue.md +32 -0
  86. package/templates/execute.md +304 -0
  87. package/templates/explore.md +59 -0
  88. package/templates/export.md +21 -0
  89. package/templates/init.md +61 -0
  90. package/templates/plan.md +146 -0
  91. package/templates/quick.md +135 -0
  92. package/templates/scan-quick.md +49 -0
  93. package/templates/scan.md +156 -0
  94. package/templates/skills/playwright-e2e/SKILL.md +340 -0
  95. package/templates/status.md +75 -0
  96. package/templates/verify.md +236 -0
  97. package/templates/workspace-sync.md +99 -0
  98. package/templates/workspace.md +70 -0
  99. package/.claude/skills/sillyspec-archive/SKILL.md +0 -17
  100. package/.claude/skills/sillyspec-auto/SKILL.md +0 -77
  101. package/.claude/skills/sillyspec-brainstorm/SKILL.md +0 -17
  102. package/.claude/skills/sillyspec-continue/SKILL.md +0 -44
  103. package/.claude/skills/sillyspec-doctor/SKILL.md +0 -22
  104. package/.claude/skills/sillyspec-execute/SKILL.md +0 -17
  105. package/.claude/skills/sillyspec-explore/SKILL.md +0 -96
  106. package/.claude/skills/sillyspec-export/SKILL.md +0 -53
  107. package/.claude/skills/sillyspec-init/SKILL.md +0 -170
  108. package/.claude/skills/sillyspec-plan/SKILL.md +0 -52
  109. package/.claude/skills/sillyspec-propose/SKILL.md +0 -17
  110. package/.claude/skills/sillyspec-quick/SKILL.md +0 -17
  111. package/.claude/skills/sillyspec-resume/SKILL.md +0 -111
  112. package/.claude/skills/sillyspec-scan/SKILL.md +0 -17
  113. package/.claude/skills/sillyspec-state/SKILL.md +0 -54
  114. package/.claude/skills/sillyspec-status/SKILL.md +0 -17
  115. package/.claude/skills/sillyspec-verify/SKILL.md +0 -17
  116. package/.claude/skills/sillyspec-workspace/SKILL.md +0 -149
  117. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
  118. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
  119. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
  120. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
  121. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
  122. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
  123. package/.sillyspec/changes/auto-mode/design.md +0 -50
  124. package/.sillyspec/changes/auto-mode/proposal.md +0 -19
  125. package/.sillyspec/changes/auto-mode/requirements.md +0 -21
  126. package/.sillyspec/changes/auto-mode/tasks.md +0 -7
  127. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
  128. package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
  129. package/.sillyspec/changes/run-command-design/design.md +0 -1230
  130. package/.sillyspec/changes/unified-docs-design/design.md +0 -199
  131. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  132. package/.sillyspec/knowledge/INDEX.md +0 -8
  133. package/.sillyspec/knowledge/uncategorized.md +0 -3
  134. package/.sillyspec/projects/sillyspec.yaml +0 -3
  135. package/packages/dashboard/dist/assets/index-D1EVTLmc.js +0 -7446
  136. package/packages/dashboard/dist/assets/index-DGe8CqeP.css +0 -1
  137. package/packages/dashboard/public/logo.jpg +0 -0
  138. package/packages/dashboard/src/components/DocPreview.vue +0 -160
  139. package/packages/dashboard/src/components/DocTree.vue +0 -58
  140. package/packages/dashboard/src/components/ProjectOverview.vue +0 -178
  141. package/packages/dashboard/src/components/detail/DocsDetail.vue +0 -48
  142. package/packages/dashboard/src/components/detail/GitDetail.vue +0 -61
  143. package/packages/dashboard/src/components/detail/TechDetail.vue +0 -43
  144. package/src/derive.js +0 -147
  145. package/src/migrate.js +0 -117
  146. package/src/progress.js +0 -495
  147. package/src/run.js +0 -640
  148. package/src/stages/archive.js +0 -54
  149. package/src/stages/brainstorm.js +0 -239
  150. package/src/stages/doctor.js +0 -312
  151. package/src/stages/execute.js +0 -259
  152. package/src/stages/index.js +0 -35
  153. package/src/stages/plan.js +0 -259
  154. package/src/stages/propose.js +0 -115
  155. package/src/stages/quick.js +0 -64
  156. package/src/stages/scan.js +0 -141
  157. package/src/stages/status.js +0 -65
  158. package/src/stages/verify.js +0 -135
  159. /package/.sillyspec/{changes/brainstorm-archive → specs}/2026-04-05-dashboard-design.md +0 -0
  160. /package/{packages/dashboard → docs/.vitepress}/dist/favicon.jpg +0 -0
  161. /package/{logo.jpg → docs/.vitepress/dist/logo.jpg} +0 -0
  162. /package/{packages/dashboard → docs}/public/favicon.jpg +0 -0
  163. /package/{packages/dashboard/dist → docs/public}/logo.jpg +0 -0
@@ -0,0 +1,340 @@
1
+ ---
2
+ name: playwright-e2e
3
+ description: Playwright E2E 测试编写参考。当任务涉及编写端到端测试、浏览器自动化测试、Playwright 测试用例时自动读取,不要凭记忆编写 Playwright API。
4
+ ---
5
+
6
+ # Playwright E2E 测试编写参考
7
+
8
+ > ⚠️ 执行 E2E/测试任务时必须先读取此文件,不要凭训练数据记忆编写 Playwright API。
9
+
10
+ ## 测试基本结构
11
+
12
+ ```typescript
13
+ import { test, expect } from '@playwright/test';
14
+
15
+ test.describe('功能名称', () => {
16
+ test.beforeEach(async ({ page }) => {
17
+ await page.goto('/');
18
+ });
19
+
20
+ test('具体场景', async ({ page }) => {
21
+ // Arrange
22
+ // Act
23
+ // Assert
24
+ });
25
+ });
26
+ ```
27
+
28
+ ## 选择器优先级
29
+
30
+ ```typescript
31
+ // ✅ 最推荐:data-testid(最稳定)
32
+ await page.locator('[data-testid="submit-btn"]').click();
33
+
34
+ // ✅ 推荐:role-based(语义化)
35
+ await page.getByRole('button', { name: 'Submit' }).click();
36
+ await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
37
+
38
+ // ✅ 推荐:文本匹配
39
+ await page.getByText('Sign in').click();
40
+ await page.getByText(/welcome back/i).click();
41
+
42
+ // ✅ 可用:语义 HTML
43
+ await page.locator('button[type="submit"]').click();
44
+ await page.locator('input[name="email"]').fill('test@test.com');
45
+
46
+ // ❌ 避免:CSS 类名和 ID(重构易变)
47
+ // await page.locator('.btn-primary').click();
48
+ // await page.locator('#submit').click();
49
+ ```
50
+
51
+ ## 断言
52
+
53
+ ```typescript
54
+ // URL
55
+ await expect(page).toHaveURL('/dashboard');
56
+ await expect(page).toHaveURL(/.*dashboard/);
57
+
58
+ // 文本
59
+ await expect(page.locator('h1')).toHaveText('Welcome');
60
+ await expect(page.locator('.message')).toContainText('success');
61
+ await expect(page.locator('.items')).toHaveText(['Item 1', 'Item 2']);
62
+
63
+ // 可见性
64
+ await expect(page.locator('.modal')).toBeVisible();
65
+ await expect(page.locator('.spinner')).toBeHidden();
66
+ await expect(page.locator('button')).toBeEnabled();
67
+ await expect(page.locator('input')).toBeDisabled();
68
+
69
+ // 数量
70
+ await expect(page.locator('.item')).toHaveCount(3);
71
+
72
+ // 输入值
73
+ await expect(page.locator('input')).toHaveValue('test@example.com');
74
+ ```
75
+
76
+ ## 表单操作
77
+
78
+ ```typescript
79
+ // 文本输入
80
+ await page.getByLabel('Email').fill('user@example.com');
81
+ await page.getByPlaceholder('Enter your name').fill('John Doe');
82
+
83
+ // 清空再输入
84
+ await page.locator('#username').clear();
85
+ await page.locator('#username').type('newuser', { delay: 100 });
86
+
87
+ // 复选框
88
+ await page.getByLabel('I agree').check();
89
+ await page.getByLabel('I agree').uncheck();
90
+
91
+ // 单选按钮
92
+ await page.getByLabel('Option 2').check();
93
+
94
+ // 下拉选择
95
+ await page.selectOption('select#country', 'usa');
96
+ await page.selectOption('select#country', { label: 'United States' });
97
+
98
+ // 多选下拉
99
+ await page.selectOption('select#colors', ['red', 'blue']);
100
+
101
+ // 文件上传
102
+ await page.setInputFiles('input[type="file"]', 'path/to/file.pdf');
103
+ await page.setInputFiles('input[type="file"]', ['file1.pdf', 'file2.pdf']);
104
+ ```
105
+
106
+ ## 鼠标与键盘
107
+
108
+ ```typescript
109
+ // 点击
110
+ await page.click('button');
111
+ await page.click('button', { button: 'right' }); // 右键
112
+ await page.dblclick('button'); // 双击
113
+
114
+ // 悬停
115
+ await page.hover('.menu-item');
116
+
117
+ // 拖拽
118
+ await page.dragAndDrop('#source', '#target');
119
+
120
+ // 键盘
121
+ await page.keyboard.type('Hello', { delay: 100 });
122
+ await page.keyboard.press('Control+A');
123
+ await page.keyboard.press('Enter');
124
+ await page.keyboard.press('Tab');
125
+ ```
126
+
127
+ ## 测试数据准备
128
+
129
+ ```typescript
130
+ // ⚠️ E2E 测试不能因为"没有数据"就跳过!必须确保测试数据存在。
131
+
132
+ // 方案 1:通过 API 创建前置数据(推荐)
133
+ test.beforeEach(async ({ request }) => {
134
+ // 调用项目 API 创建测试所需数据
135
+ const resp = await request.post('/api/test/setup', {
136
+ data: { createUser: true, createOrder: true }
137
+ });
138
+ expect(resp.ok()).toBeTruthy();
139
+ });
140
+
141
+ // 方案 2:使用 storageState 复用认证状态
142
+ // 先登录一次保存状态,后续测试直接复用
143
+ test.use({ storageState: 'tests/auth/user.json' });
144
+
145
+ // 生成认证状态的脚本:
146
+ // npx playwright codegen --save-storage=tests/auth/user.json http://localhost:3000/login
147
+
148
+ // 方案 3:通过数据库 MCP 直接插入数据(有数据库 MCP 时)
149
+ // 在 beforeEach 中调用数据库 MCP 执行 INSERT 语句准备数据
150
+
151
+ // 方案 4:API Mock(不需要真实数据)
152
+ test('显示用户列表', async ({ page }) => {
153
+ await page.route('**/api/users', route => {
154
+ route.fulfill({
155
+ status: 200,
156
+ contentType: 'application/json',
157
+ body: JSON.stringify([{ id: 1, name: 'Test User' }])
158
+ });
159
+ });
160
+ await page.goto('/users');
161
+ await expect(page.locator('.user-name')).toHaveText('Test User');
162
+ });
163
+
164
+ // 清理:测试后清理数据,避免影响其他测试
165
+ test.afterEach(async ({ request }) => {
166
+ await request.post('/api/test/cleanup');
167
+ });
168
+ ```
169
+
170
+ **铁律:E2E 测试禁止因"没有数据"跳过。** 必须通过以上任一方案准备数据,确保测试可独立运行。
171
+
172
+ ## 弹窗与 iframe
173
+
174
+ ```typescript
175
+ // 新窗口/弹窗
176
+ const [popup] = await Promise.all([
177
+ page.waitForEvent('popup'),
178
+ page.click('button.open-popup'),
179
+ ]);
180
+ await popup.waitForLoadState();
181
+
182
+ // iframe
183
+ const frame = page.frameLocator('#my-iframe');
184
+ await frame.locator('button').click();
185
+ ```
186
+
187
+ ## 截图
188
+
189
+ ```typescript
190
+ // 全页截图
191
+ await page.screenshot({ path: 'screenshot.png', fullPage: true });
192
+
193
+ // 元素截图
194
+ await page.locator('.chart').screenshot({ path: 'chart.png' });
195
+
196
+ // 视觉回归对比(首次生成基线,后续对比)
197
+ await expect(page).toHaveScreenshot('homepage.png');
198
+
199
+ // ⚠️ playwright.config.ts 中配置失败自动截图:
200
+ // screenshot: 'only-on-failure'(已包含在上方 config 模板中)
201
+ ```
202
+
203
+ ## 等待策略
204
+
205
+ ```typescript
206
+ // ✅ 推荐:自动等待(Playwright 内置)
207
+ await expect(page.locator('.loaded')).toBeVisible();
208
+
209
+ // ✅ 推荐:等待 URL 变化
210
+ await page.waitForURL('**/dashboard');
211
+
212
+ // ✅ 推荐:等待网络响应
213
+ const responsePromise = page.waitForResponse('**/api/users');
214
+ await page.click('button#load-users');
215
+ const response = await responsePromise;
216
+
217
+ // ❌ 避免:硬编码 sleep
218
+ // await page.waitForTimeout(3000);
219
+ ```
220
+
221
+ ## Page Object Model
222
+
223
+ ```typescript
224
+ // pages/LoginPage.ts
225
+ export class LoginPage {
226
+ constructor(private page: Page) {}
227
+
228
+ async goto() {
229
+ await this.page.goto('/login');
230
+ }
231
+
232
+ async login(email: string, password: string) {
233
+ await this.page.getByLabel('Email').fill(email);
234
+ await this.page.getByLabel('Password').fill(password);
235
+ await this.page.getByRole('button', { name: 'Submit' }).click();
236
+ }
237
+ }
238
+
239
+ // tests/login.spec.ts
240
+ import { test, expect } from '@playwright/test';
241
+ import { LoginPage } from '../pages/LoginPage';
242
+
243
+ test('登录成功', async ({ page }) => {
244
+ const loginPage = new LoginPage(page);
245
+ await loginPage.goto();
246
+ await loginPage.login('test@example.com', 'password123');
247
+ await expect(page).toHaveURL('/dashboard');
248
+ });
249
+ ```
250
+
251
+ ## API Mock(隔离后端)
252
+
253
+ ```typescript
254
+ test('显示用户列表', async ({ page }) => {
255
+ await page.route('**/api/users', route => {
256
+ route.fulfill({
257
+ status: 200,
258
+ contentType: 'application/json',
259
+ body: JSON.stringify([{ id: 1, name: 'Test User' }])
260
+ });
261
+ });
262
+
263
+ await page.goto('/users');
264
+ await expect(page.locator('.user-name')).toHaveText('Test User');
265
+ });
266
+ ```
267
+
268
+ ## 常见错误
269
+
270
+ ### ❌ 用 CSS 类名选择元素
271
+ ```typescript
272
+ await page.click('.btn-primary'); // 脆弱!重构就坏
273
+ ```
274
+
275
+ ### ✅ 用 data-testid
276
+ ```typescript
277
+ await page.click('[data-testid="submit-btn"]'); // 稳定
278
+ ```
279
+
280
+ ### ❌ 测试之间有依赖
281
+ ```typescript
282
+ test('步骤1:登录', async ({ page }) => { /* ... */ });
283
+ test('步骤2:需要先登录', async ({ page }) => { /* 依赖步骤1,脆弱 */ });
284
+ ```
285
+
286
+ ### ✅ 每个测试独立
287
+ ```typescript
288
+ test.beforeEach(async ({ page }) => {
289
+ await page.goto('/login');
290
+ await page.getByLabel('Email').fill('test@example.com');
291
+ await page.getByLabel('Password').fill('password');
292
+ await page.getByRole('button', { name: 'Submit' }).click();
293
+ await expect(page).toHaveURL('/dashboard');
294
+ });
295
+ ```
296
+
297
+ ### ❌ 不处理异步状态
298
+ ```typescript
299
+ await page.click('[data-testid="submit"]');
300
+ await expect(page.locator('.result')).toBeVisible(); // 可能还没加载完
301
+ ```
302
+
303
+ ### ✅ 等待操作完成
304
+ ```typescript
305
+ await page.click('[data-testid="submit"]');
306
+ await expect(page.locator('.result')).toBeVisible({ timeout: 10000 });
307
+ ```
308
+
309
+ ## playwright.config.ts 模板
310
+
311
+ ```typescript
312
+ import { defineConfig } from '@playwright/test';
313
+
314
+ export default defineConfig({
315
+ testDir: './tests/e2e',
316
+ fullyParallel: true,
317
+ retries: process.env.CI ? 2 : 0,
318
+ timeout: 30000,
319
+ use: {
320
+ baseURL: 'http://localhost:3000', // ⚠️ 根据项目实际端口调整
321
+ trace: 'on-first-retry',
322
+ screenshot: 'only-on-failure',
323
+ },
324
+ webServer: {
325
+ command: 'npm run dev', // ⚠️ 根据项目实际命令调整
326
+ port: 3000,
327
+ reuseExistingServer: !process.env.CI,
328
+ },
329
+ });
330
+ ```
331
+
332
+ ## 调试命令
333
+
334
+ ```bash
335
+ npx playwright test --headed # 有头模式
336
+ npx playwright test --debug # 调试模式(带 inspector)
337
+ npx playwright test --slowmo=1000 # 慢放
338
+ npx playwright show-report # 查看报告
339
+ npx playwright codegen URL # 录制生成代码
340
+ ```
@@ -0,0 +1,75 @@
1
+ ## 交互规范
2
+ **当需要用户从多个选项中做出选择时,必须使用 Claude Code 内置的 AskUserQuestion 工具,将选项以参数传入。**
3
+
4
+ ## 核心约束(必须遵守)
5
+ - ❌ 修改任何文件(只读)
6
+
7
+ ---
8
+
9
+ ## 流程
10
+
11
+ ### Step 1: 检查工作区模式
12
+
13
+ ```bash
14
+ ls .sillyspec/projects/*.yaml 2>/dev/null | grep -q .
15
+ ```
16
+
17
+ **工作区模式:** 读取每个 `projects/*.yaml` 子项目列表,对每个子项目检查 PROJECT.md、docs/<project>/scan/ 文档数、进行中变更、归档数。**同时检查工作区根目录 `.sillyspec/changes/` 下的未归档变更。** 检查共享规范和工作区概览。输出汇总后结束,不执行单项目流程。
18
+
19
+ 工作区变更检查命令:
20
+ ```bash
21
+ # 工作区根目录的变更
22
+ ls .sillyspec/changes/ 2>/dev/null | grep -v archive
23
+ ls .sillyspec/changes/archive/ 2>/dev/null | wc -l
24
+
25
+ # 每个子项目的变更
26
+ for f in .sillyspec/projects/*.yaml; do
27
+ [ -f "$f" ] || continue
28
+ proj_name=$(basename "$f" .yaml)
29
+ proj_path=$(grep '^path:' "$f" | head -1 | sed 's/^path:[[:space:]]*//')
30
+ echo "=== $proj_name changes ==="
31
+ ls "$proj_path"/.sillyspec/changes/ 2>/dev/null | grep -v archive
32
+ ls "$proj_path"/.sillyspec/changes/archive/ 2>/dev/null | wc -l
33
+ done
34
+ ```
35
+
36
+ **汇总输出格式:**
37
+ ```
38
+ 📊 SillySpec 状态
39
+ 📋 工作区模式 — N 个子项目
40
+
41
+ 工作区变更:
42
+ 🔄 进行中:sec-bonus-penalty(tasks: 3/5)
43
+ ✅ 已归档:1 个
44
+
45
+ 主项目(工作区根目录):
46
+ 📂 代码库文档:— (主项目通常无独立代码)
47
+ 🔄 进行中:N 个变更
48
+ ✅ 已归档:N 个变更
49
+
50
+ ┌───────────────────┬────────────┬────────────┬────────┬────────┐
51
+ │ 子项目 │ PROJECT.md │ 代码库文档 │ 进行中 │ 已归档 │
52
+ ├───────────────────┼────────────┼────────────┼────────┼────────┤
53
+ │ back-service │ ✗ │ 7 份 │ 1 │ 0 │
54
+ └───────────────────┴────────────┴────────────┴────────┴────────┘
55
+ ```
56
+
57
+ **单项目模式:** 继续 Step 2。
58
+
59
+ ### Step 2-5: 单项目检查
60
+
61
+ 1. **项目基础:** PROJECT.md、docs/<project>/scan/ 文档、REQUIREMENTS.md、ROADMAP.md
62
+ 2. **进行中变更:** 对每个变更检查 design/tasks 完成度
63
+ 3. **归档历史:** `ls .sillyspec/changes/archive/ | wc -l`
64
+ 4. **代码库文档:** `ls .sillyspec/docs/<project>/scan/`
65
+
66
+ ### Step 6: 输出
67
+
68
+ ```
69
+ 📊 SillySpec 状态
70
+ 📋 项目:xxx(已初始化/未初始化)
71
+ 📂 代码库:已扫描(7 份文档)/ 未扫描
72
+ 🔄 进行中:N 个变更
73
+ ✅ 已归档:N 个变更
74
+ 💡 下一步:/sillyspec:continue
75
+ ```
@@ -0,0 +1,236 @@
1
+ ## 交互规范
2
+ **当需要用户从多个选项中做出选择时,必须使用 Claude Code 内置的 AskUserQuestion 工具,将选项以参数传入。**
3
+
4
+ > **可选阶段。** execute 完成后会询问是否需要 verify,也可以手动调用。
5
+
6
+ ## 核心约束(必须遵守)
7
+ - ❌ 修改任何代码(只做检查和报告)
8
+ - ❌ 自行推进到下一阶段
9
+
10
+
11
+
12
+ ## 工作区模式处理
13
+
14
+ 如果 `.sillyspec/projects/` 目录下有 yaml 文件:
15
+
16
+ 1. 检查工作区根目录 `.sillyspec/changes/` 下的未归档变更
17
+ 2. 检查每个子项目 `<子项目路径>/.sillyspec/changes/` 下的未归档变更
18
+ 3. 列出所有未归档变更,让用户选择要验证哪个
19
+ 4. 根据 $ARGUMENTS 或用户选择,cd 到对应目录执行验证
20
+
21
+ ---
22
+
23
+ ## 流程
24
+
25
+ ### 1. 加载规范
26
+
27
+ ```bash
28
+ # 确定变更目录
29
+ if [ -n "$ARGUMENTS" ]; then
30
+ CHANGE_DIR=".sillyspec/changes/$ARGUMENTS"
31
+ else
32
+ CHANGE_DIR=$(ls -d .sillyspec/changes/*/ 2>/dev/null | grep -v archive | tail -1)
33
+ fi
34
+ cat "$CHANGE_DIR"/{design,tasks}.md 2>/dev/null
35
+ cat .sillyspec/local.yaml 2>/dev/null
36
+ ```
37
+
38
+ 锚定确认实际存在的文件。
39
+
40
+ ### 2. 逐项检查 tasks.md
41
+
42
+ 对每个 checkbox 报告:✅ 已完成 / ❌ 未完成 / ⚠️ 部分完成
43
+
44
+ ### 3. 对照 design.md
45
+
46
+ 架构决策?文件变更一致性?数据模型?API 设计?
47
+
48
+ ### 4. 运行测试套件
49
+
50
+ ```bash
51
+ pnpm test 2>/dev/null || npm test 2>/dev/null || pytest 2>/dev/null || go test ./... 2>/dev/null
52
+ ```
53
+
54
+ ### 4b. E2E 测试
55
+
56
+ 检测项目中是否有 E2E 测试或测试步骤文件:
57
+ ```bash
58
+ ls tests/e2e/ e2e/ cypress/e2e/ 2>/dev/null | head -5
59
+ cat .sillyspec/changes/*/e2e-steps.md 2>/dev/null | head -5
60
+ ```
61
+
62
+ **无任何测试** → 跳过此步骤。
63
+
64
+ **有测试** → 确认修复策略(AskUserQuestion):
65
+ 1. 自动修复,同一用例最多 5 次(超过停止,提示人工介入)
66
+ 2. 一直修复直到全绿
67
+ 3. 只报告,不自动修复
68
+
69
+ **按优先级执行:**
70
+
71
+ **优先级 1:专业 E2E 框架(Playwright/Cypress)**
72
+ ```bash
73
+ npx playwright test 2>/dev/null || npx cypress run 2>/dev/null
74
+ ```
75
+
76
+ **优先级 2:通用测试框架(jest/vitest)**
77
+ ```bash
78
+ npx vitest run 2>/dev/null || npx jest 2>/dev/null
79
+ ```
80
+
81
+ **优先级 3:浏览器 MCP + e2e-steps.md(兜底)**
82
+ 读取 `.sillyspec/changes/<变更名>/e2e-steps.md`,按步骤逐条执行。每条标注 ✅/❌,断言失败记为 FAILED。
83
+ > ⚠️ 使用 MCP 执行时 AI 判断可能不如测试框架精确。追求可靠性建议安装 Playwright。
84
+
85
+ **自动修复循环(选了策略 1 或 2 时):**
86
+
87
+ 必须按以下流程严格执行,不可跳过:
88
+
89
+ ```
90
+ ROUND = 1
91
+ MAX_ROUNDS = 策略1时为5,策略2时为50
92
+
93
+ while ROUND <= MAX_ROUNDS:
94
+ 1. 运行失败测试,捕获完整输出(错误信息、堆栈、期望值 vs 实际值)
95
+ 2. 如果全部通过 → 跳出循环,标记 ✅
96
+ 3. 读取 .sillyspec/local.yaml 中当前变更的 fixAttempts
97
+ 4. 对每个失败测试:
98
+ a. 如果 fixAttempts >= MAX_ROUNDS → 跳过,标记 ❌ MAX_REACHED
99
+ b. 否则 → 调 /sillyspec:quick 修复,prompt 必须包含:
100
+ - 失败的测试文件路径和测试名
101
+ - 完整错误信息(含期望值 vs 实际值)
102
+ - 相关源文件路径
103
+ - "只修复这个测试失败,不要改其他代码"
104
+ c. 修复后重跑该测试确认是否通过
105
+ d. 通过 → fixAttempts 保持不变;仍失败 → fixAttempts + 1
106
+ 5. 写入 .sillyspec/local.yaml 6. ROUND++
107
+ 7. 如果本轮无任何修复(所有失败都已 MAX_REACHED)→ 跳出循环
108
+ ```
109
+
110
+ **quick 修复 prompt 模板:**
111
+ ```
112
+ /sillyspec:quick "修复测试失败:<测试文件路径>:<测试名>
113
+
114
+ 错误信息:
115
+ <完整错误输出,包含期望值和实际值>
116
+
117
+ 相关文件:
118
+ <被测源文件路径>
119
+
120
+ 只修复这个测试失败,不要改其他代码。修完后运行该测试确认通过。"
121
+ ```
122
+
123
+ **禁止行为:**
124
+ - ❌ 只看错误摘要就修复(必须看完整输出)
125
+ - ❌ 跳过 fixAttempts 计数
126
+ - ❌ 一次 quick 修复多个不相关的失败(逐个修复,每次修复后重跑确认)
127
+ - ❌ 主代理直接修改代码(verify 阶段禁止改代码,必须通过 /sillyspec:quick)
128
+
129
+ **更新测试结果到 `.sillyspec/local.yaml`(按变更名隔离,覆盖写入):**
130
+ ```yaml
131
+ e2e:
132
+ {变更名}:
133
+ login.spec.ts:
134
+ status: passed
135
+ fixAttempts: 0
136
+ form-submit.spec.ts:
137
+ status: failed
138
+ fixAttempts: 3
139
+ ```
140
+
141
+ ### 5. 代码质量扫描
142
+
143
+ ```bash
144
+ grep -r "TODO\|FIXME\|HACK\|XXX" src/ lib/ app/ --include="*.ts" --include="*.tsx" --include="*.py" --include="*.js" 2>/dev/null | head -20
145
+ ```
146
+
147
+ 审查 design.md「文件变更」中列出的文件:安全问题(输入校验、SQL拼接、硬编码敏感信息)、潜在 bug(空值、边界条件)、与 CONVENTIONS.md 一致性。每个问题标 🔴必须 / 🟡建议 / 🔵优化。
148
+
149
+ ### 5.5 MCP 基础设施验证
150
+
151
+ 检测已配置的 MCP 服务,利用它们做实际验证(不只查文档):
152
+
153
+ **MCP 能力检测:**
154
+
155
+ 不要只检查配置文件路径(不同客户端配置位置不同),直接检查当前可用工具列表中是否存在以下工具:
156
+
157
+ - 数据库相关工具(包含 postgres/sqlite/mysql/redis 关键词)
158
+ - 浏览器相关工具(包含 browser/chrome/puppeteer/playwright/devtools 关键词)
159
+ - 搜索相关工具(包含 search/web_search 关键词)
160
+
161
+ **判断方式:** 尝试调用或列出当前可用的 MCP 工具,有就用来验证,没有就跳过。
162
+
163
+ **按检测结果执行对应验证:**
164
+
165
+ **数据库 MCP(postgres/sqlite/mysql/redis):**
166
+ - 对照 design.md 中的数据模型,验证表/集合是否存在
167
+ - 验证字段类型、约束(主键、外键、唯一索引)是否与设计一致
168
+ - 验证新增的 API 是否能正确读写对应数据
169
+ - ⚠️ 只执行 SELECT 查询,禁止任何写操作
170
+
171
+ **浏览器 MCP(chrome-devtools/puppeteer/playwright):**
172
+ - 验证页面能否正常加载(无 404/500 错误)
173
+ - 验证关键 UI 元素是否存在(导航、表单、按钮等)
174
+ - 验证基础交互(点击、提交、跳转)
175
+
176
+ **API MCP:**
177
+ - 验证新增接口是否可达
178
+ - 验证请求/响应格式是否与设计一致
179
+
180
+ **无 MCP → 跳过此步骤,不影响验证结论。**
181
+
182
+ 将验证结果纳入验证报告。
183
+
184
+ ### 6. Lint / Format 检查
185
+
186
+ 自动检测并运行项目配置的 lint/format 工具,验证代码是否符合规范:
187
+
188
+ ```bash
189
+ # ESLint
190
+ if [ -f .eslintrc -o -f .eslintrc.js -o -f .eslintrc.cjs -o -f .eslintrc.json -o -f .eslintrc.yml ] || grep -q '"eslint"' package.json 2>/dev/null; then
191
+ echo "=== ESLint ==="
192
+ npx eslint . --max-warnings 0 2>&1 | tail -50
193
+ fi
194
+
195
+ # Prettier(检查而非修复)
196
+ if [ -f .prettierrc -o -f .prettierrc.js -o -f .prettierrc.json -o -f .prettierrc.yml ] || grep -q '"prettier"' package.json 2>/dev/null; then
197
+ echo "=== Prettier ==="
198
+ npx prettier --check . 2>&1 | tail -30
199
+ fi
200
+
201
+ # TypeScript 类型检查
202
+ if [ -f tsconfig.json ]; then
203
+ echo "=== TypeScript ==="
204
+ npx tsc --noEmit 2>&1 | tail -30
205
+ fi
206
+
207
+ # Stylelint
208
+ if [ -f .stylelintrc -o -f .stylelintrc.js -o -f .stylelintrc.json ] || grep -q '"stylelint"' package.json 2>/dev/null; then
209
+ echo "=== Stylelint ==="
210
+ npx stylelint "**/*.{css,scss,less}" 2>&1 | tail -30
211
+ fi
212
+ ```
213
+
214
+ **处理策略(AskUserQuestion):**
215
+ 1. **自动修复** — 对支持 `--fix` 的工具(ESLint、Prettier、Stylelint)自动修复后重跑检查,同一问题最多修复 3 次
216
+ 2. **只报告** — 仅列出所有 lint 错误,不修改代码
217
+
218
+ 将 lint 结果纳入验证报告(步骤 7)。
219
+
220
+ ### 7. 输出验证报告
221
+
222
+ ```markdown
223
+ # SillySpec 验证报告
224
+ ## 任务完成度:X/Y
225
+ ## 设计一致性
226
+ ## 测试结果:passed N, failed N
227
+ ## 技术债务标记
228
+ ## 代码审查:🔴 N / 🟡 N / 🔵 N
229
+ ## Lint 检查:ESLint ✅/❌ | Prettier ✅/❌ | TypeScript ✅/❌ | Stylelint ✅/❌
230
+ ## MCP 基础设施验证:数据库 ✅/❌/跳过 | 页面 ✅/❌/跳过 | API ✅/❌/跳过
231
+ ## E2E 测试:passed N / failed N / fixAttempts 详情
232
+ ## 结论:✅ PASS / ⚠️ PASS WITH NOTES / ❌ FAIL
233
+ ```
234
+
235
+ ### 7. 完成
236
+