sillyspec 3.20.2 → 3.20.4

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 (133) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +21 -21
  2. package/.claude/skills/sillyspec-auto/SKILL.md +83 -83
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -44
  4. package/.claude/skills/sillyspec-commit/SKILL.md +106 -106
  5. package/.claude/skills/sillyspec-continue/SKILL.md +45 -45
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +31 -31
  7. package/.claude/skills/sillyspec-execute/SKILL.md +30 -30
  8. package/.claude/skills/sillyspec-explore/SKILL.md +109 -109
  9. package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
  10. package/.claude/skills/sillyspec-plan/SKILL.md +21 -21
  11. package/.claude/skills/sillyspec-propose/SKILL.md +21 -21
  12. package/.claude/skills/sillyspec-quick/SKILL.md +21 -21
  13. package/.claude/skills/sillyspec-resume/SKILL.md +68 -68
  14. package/.claude/skills/sillyspec-scan/SKILL.md +21 -21
  15. package/.claude/skills/sillyspec-state/SKILL.md +54 -54
  16. package/.claude/skills/sillyspec-status/SKILL.md +21 -21
  17. package/.claude/skills/sillyspec-verify/SKILL.md +21 -21
  18. package/.claude/skills/sillyspec-workspace/SKILL.md +157 -157
  19. package/.husky/pre-push +13 -13
  20. package/CLAUDE.md +18 -18
  21. package/README.md +198 -188
  22. package/SKILL.md +90 -91
  23. package/bin/sillyspec.js +2 -2
  24. package/docs/brainstorm-plan-contract.md +64 -64
  25. package/docs/plan-execute-contract.md +123 -123
  26. package/docs/platform-scan-protocol.md +298 -298
  27. package/docs/revision-mode.md +115 -115
  28. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -99
  29. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -218
  30. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -167
  31. package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -148
  32. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -193
  33. package/docs/sillyspec/file-lifecycle.md +125 -125
  34. package/docs/workflow-contract-regression.md +106 -106
  35. package/docs/worktree-isolation.md +252 -252
  36. package/package.json +40 -40
  37. package/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -7446
  38. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -1
  39. package/packages/dashboard/dist/index.html +16 -16
  40. package/packages/dashboard/index.html +15 -15
  41. package/packages/dashboard/package-lock.json +2384 -2384
  42. package/packages/dashboard/package.json +25 -25
  43. package/packages/dashboard/server/executor.js +86 -86
  44. package/packages/dashboard/server/index.js +588 -588
  45. package/packages/dashboard/server/parser.js +526 -526
  46. package/packages/dashboard/server/watcher.js +344 -344
  47. package/packages/dashboard/src/App.vue +558 -558
  48. package/packages/dashboard/src/components/ActionBar.vue +93 -93
  49. package/packages/dashboard/src/components/CommandPalette.vue +96 -96
  50. package/packages/dashboard/src/components/DetailPanel.vue +137 -137
  51. package/packages/dashboard/src/components/LogStream.vue +65 -65
  52. package/packages/dashboard/src/components/PipelineStage.vue +95 -95
  53. package/packages/dashboard/src/components/PipelineView.vue +156 -156
  54. package/packages/dashboard/src/components/ProjectList.vue +210 -210
  55. package/packages/dashboard/src/components/StageBadge.vue +67 -67
  56. package/packages/dashboard/src/components/StepCard.vue +94 -94
  57. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
  58. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
  59. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
  60. package/packages/dashboard/src/composables/useDashboard.js +170 -170
  61. package/packages/dashboard/src/composables/useKeyboard.js +119 -119
  62. package/packages/dashboard/src/composables/useWebSocket.js +129 -129
  63. package/packages/dashboard/src/main.js +8 -8
  64. package/packages/dashboard/src/style.css +132 -132
  65. package/packages/dashboard/vite.config.js +18 -18
  66. package/src/brainstorm-postcheck.js +158 -158
  67. package/src/change-list.js +52 -52
  68. package/src/change-risk-profile.js +352 -352
  69. package/src/classify-change.js +73 -73
  70. package/src/constants.js +70 -70
  71. package/src/contract-matrix.js +278 -278
  72. package/src/db.js +201 -201
  73. package/src/endpoint-extractor.js +315 -315
  74. package/src/hooks/claude-pre-tool-use.cjs +125 -125
  75. package/src/hooks/worktree-guard.js +653 -653
  76. package/src/index.js +922 -900
  77. package/src/init.js +431 -431
  78. package/src/knowledge-match.js +130 -130
  79. package/src/migrate.js +117 -117
  80. package/src/modules.js +482 -482
  81. package/src/progress.js +1734 -1734
  82. package/src/run.js +3465 -3358
  83. package/src/scan-postcheck.js +387 -383
  84. package/src/setup.js +398 -398
  85. package/src/stage-contract.js +700 -700
  86. package/src/stages/archive.js +160 -160
  87. package/src/stages/brainstorm-auto.js +229 -229
  88. package/src/stages/brainstorm.js +645 -645
  89. package/src/stages/doctor.js +365 -365
  90. package/src/stages/execute.js +625 -625
  91. package/src/stages/explore.js +34 -34
  92. package/src/stages/index.js +29 -29
  93. package/src/stages/knowledge.js +498 -498
  94. package/src/stages/plan-postcheck.js +511 -513
  95. package/src/stages/plan.js +582 -582
  96. package/src/stages/propose.js +174 -174
  97. package/src/stages/quick.js +82 -82
  98. package/src/stages/scan.js +558 -558
  99. package/src/stages/status.js +65 -65
  100. package/src/stages/verify.js +322 -322
  101. package/src/sync.js +497 -497
  102. package/src/task-review.js +346 -346
  103. package/src/workflow.js +785 -785
  104. package/src/worktree-apply.js +549 -549
  105. package/src/worktree-deps.js +185 -0
  106. package/src/worktree.js +982 -932
  107. package/templates/workflows/archive-impact.yaml +79 -79
  108. package/templates/workflows/scan-docs.yaml +132 -132
  109. package/test/brainstorm-plan-contract.test.mjs +273 -273
  110. package/test/check-syntax.mjs +26 -26
  111. package/test/contract-artifacts.test.mjs +323 -323
  112. package/test/decision-supersede.test.mjs +277 -277
  113. package/test/knowledge-match.test.mjs +231 -231
  114. package/test/plan-execute-contract.test.mjs +330 -330
  115. package/test/plan-optimization.test.mjs +572 -572
  116. package/test/platform-artifacts.test.mjs +166 -166
  117. package/test/platform-failure-samples.test.mjs +199 -199
  118. package/test/platform-recovery-chain.test.mjs +167 -167
  119. package/test/platform-recovery.test.mjs +136 -136
  120. package/test/platform-scan-p0.test.mjs +168 -168
  121. package/test/revision-v1.test.mjs +1145 -1145
  122. package/test/run-scan-project-parse.test.mjs +200 -200
  123. package/test/run-tests.mjs +48 -48
  124. package/test/scan-knowledge.test.mjs +175 -175
  125. package/test/scan-paths.test.mjs +68 -68
  126. package/test/scan-postcheck.test.mjs +197 -197
  127. package/test/spec-dir.test.mjs +206 -206
  128. package/test/stage-contract.test.mjs +299 -299
  129. package/test/stage-definitions.test.mjs +39 -39
  130. package/test/wait-gates.test.mjs +496 -496
  131. package/test/worktree-deps-provision.test.mjs +148 -0
  132. package/test/worktree-guard.test.mjs +71 -71
  133. package/test/worktree-native-overlay.test.mjs +188 -188
package/src/setup.js CHANGED
@@ -1,398 +1,398 @@
1
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
2
- import { join, dirname } from 'path';
3
- import { execSync } from 'child_process';
4
- import chalk from 'chalk';
5
- import ora from 'ora';
6
- import { checkbox, input } from '@inquirer/prompts';
7
-
8
- // ── MCP 工具定义 ──
9
-
10
- const MCP_TOOLS = [
11
- {
12
- id: 'context7',
13
- name: 'Context7',
14
- description: '查询最新库文档和 API 参考',
15
- command: 'npx',
16
- args: ['-y', '@upstash/context7-mcp@latest'],
17
- url: 'https://github.com/upstash/context7-mcp',
18
- },
19
- {
20
- id: 'chrome-devtools',
21
- name: 'Chrome DevTools MCP',
22
- description: '浏览器自动化,支持 E2E 测试(需 Chrome 已运行)',
23
- command: 'npx',
24
- args: ['chrome-devtools-mcp@latest'],
25
- url: 'https://github.com/ChromeDevTools/chrome-devtools-mcp',
26
- },
27
- {
28
- id: 'agent-browser',
29
- name: 'Agent Browser (Vercel)',
30
- description: 'Rust 原生浏览器 CLI,token 消耗极低,50+ 命令覆盖导航/表单/截图/网络',
31
- command: 'npx',
32
- args: ['@anthropic-ai/agent-browser@latest'],
33
- url: 'https://github.com/vercel-labs/agent-browser',
34
- },
35
- {
36
- id: 'pinchtab',
37
- name: 'PinchTab',
38
- description: '12MB Go 二进制,零依赖,accessibility tree 极省 token,有 MCP 支持',
39
- command: 'npx',
40
- args: ['pinchtab-mcp@latest'],
41
- url: 'https://github.com/pinchtab/pinchtab',
42
- },
43
- ];
44
-
45
- // ── 数据库 MCP 定义(需要连接信息)──
46
-
47
- const DB_MCP_TOOLS = [
48
- {
49
- id: 'postgres',
50
- name: 'PostgreSQL',
51
- description: '只读访问 PostgreSQL 数据库',
52
- command: 'npx',
53
- args: ['-y', '@modelcontextprotocol/server-postgres'],
54
- envTemplate: { POSTGRES_CONNECTION_STRING: 'postgresql://user:password@localhost:5432/dbname' },
55
- url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/postgres',
56
- },
57
- {
58
- id: 'sqlite',
59
- name: 'SQLite',
60
- description: '访问 SQLite 数据库文件',
61
- command: 'npx',
62
- args: ['-y', '@modelcontextprotocol/server-sqlite'],
63
- envTemplate: { SQLITE_DB_PATH: './data.db' },
64
- url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite',
65
- },
66
- {
67
- id: 'mysql',
68
- name: 'MySQL / MariaDB',
69
- description: '访问 MySQL / MariaDB 数据库',
70
- command: 'npx',
71
- args: ['-y', '@nicobailon/mysql-mcp-server'],
72
- envTemplate: { MYSQL_HOST: 'localhost', MYSQL_PORT: '3306', MYSQL_USER: 'root', MYSQL_PASSWORD: '', MYSQL_DATABASE: '' },
73
- url: 'https://github.com/modelcontextprotocol/servers',
74
- },
75
- {
76
- id: 'redis',
77
- name: 'Redis',
78
- description: '访问 Redis 数据库,查看缓存和键值数据',
79
- command: 'npx',
80
- args: ['-y', '@modelcontextprotocol/server-redis'],
81
- envTemplate: { REDIS_URL: 'redis://localhost:6379' },
82
- url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/redis',
83
- },
84
- ];
85
-
86
- // ── 全局工具定义 ──
87
-
88
- const GLOBAL_TOOLS = [
89
- {
90
- id: 'playwright',
91
- name: 'Playwright',
92
- description: 'E2E 测试框架,支持浏览器自动化测试',
93
- checkCommand: 'npx playwright --version',
94
- installCommand: 'npm install -g @playwright/test && npx playwright install chromium',
95
- url: 'https://playwright.dev',
96
- },
97
- ];
98
-
99
- // ── MCP 配置文件路径 ──
100
-
101
- const MCP_CONFIG_PATHS = [
102
- { tool: 'Claude Code', path: '.claude/mcp.json', key: 'claude' },
103
- { tool: 'Cursor', path: '.cursor/mcp.json', key: 'cursor' },
104
- ];
105
-
106
- // ── 工具函数 ──
107
-
108
- function readMcpConfig(dir, configPath) {
109
- const fullPath = join(dir, configPath);
110
- if (!existsSync(fullPath)) return null;
111
- try {
112
- return JSON.parse(readFileSync(fullPath, 'utf8'));
113
- } catch {
114
- return null;
115
- }
116
- }
117
-
118
- function writeMcpConfig(dir, configPath, config) {
119
- const fullPath = join(dir, configPath);
120
- mkdirSync(dirname(fullPath), { recursive: true });
121
- writeFileSync(fullPath, JSON.stringify(config, null, 2) + '\n');
122
- }
123
-
124
- function installMcp(config, mcpTool) {
125
- if (!config.mcpServers) config.mcpServers = {};
126
- const entry = {
127
- command: mcpTool.command,
128
- args: mcpTool.args,
129
- };
130
- if (mcpTool.env) entry.env = mcpTool.env;
131
- config.mcpServers[mcpTool.id] = entry;
132
- return config;
133
- }
134
-
135
- function hasMcpInstalled(config, mcpId) {
136
- return config?.mcpServers?.[mcpId] != null;
137
- }
138
-
139
- // ── 命令实现 ──
140
-
141
- export async function cmdSetup(dir, options = {}) {
142
- const { json } = options;
143
-
144
- // 检查哪些 AI 工具有配置文件
145
- const availableTools = MCP_CONFIG_PATHS.filter(({ path }) => {
146
- return existsSync(join(dir, path));
147
- });
148
-
149
- // --list 模式:只查看状态(不依赖 AI 工具配置)
150
- if (options.list) {
151
- const results = {};
152
-
153
- // MCP 状态
154
- for (const { tool, path } of availableTools) {
155
- const config = readMcpConfig(dir, path);
156
- const mcpStatus = {};
157
- for (const mcp of MCP_TOOLS) {
158
- mcpStatus[mcp.id] = hasMcpInstalled(config, mcp.id);
159
- }
160
- for (const db of DB_MCP_TOOLS) {
161
- mcpStatus[db.id] = hasMcpInstalled(config, db.id);
162
- }
163
- results[tool] = { configPath: path, mcp: mcpStatus };
164
- }
165
-
166
- // 全局工具状态
167
- results['全局工具'] = {};
168
- for (const g of GLOBAL_TOOLS) {
169
- try {
170
- execSync(g.checkCommand, { stdio: 'pipe', encoding: 'utf8' });
171
- results['全局工具'][g.id] = true;
172
- } catch {
173
- results['全局工具'][g.id] = false;
174
- }
175
- }
176
-
177
- if (json) {
178
- console.log(JSON.stringify(results, null, 2));
179
- } else {
180
- console.log('');
181
- for (const [tool, data] of Object.entries(results)) {
182
- if (data.mcp) {
183
- console.log(chalk.bold(`📋 ${tool} (${data.configPath})`));
184
- for (const mcp of MCP_TOOLS) {
185
- const status = data.mcp[mcp.id] ? chalk.green('✅') : chalk.gray('⬜');
186
- console.log(` ${status} ${mcp.name} — ${mcp.description}`);
187
- }
188
- for (const db of DB_MCP_TOOLS) {
189
- const status = data.mcp[db.id] ? chalk.green('✅') : chalk.gray('⬜');
190
- console.log(` ${status} ${db.name} — ${db.description}`);
191
- }
192
- } else {
193
- console.log(chalk.bold(`🛠️ ${tool}`));
194
- for (const g of GLOBAL_TOOLS) {
195
- const status = data[g.id] ? chalk.green('✅') : chalk.gray('⬜');
196
- console.log(` ${status} ${g.name} — ${g.description}`);
197
- }
198
- }
199
- console.log('');
200
- }
201
- }
202
- return;
203
- }
204
-
205
- // 非交互模式(或没有 AI 工具但有全局工具要装)
206
- if (!process.stdin.isTTY) {
207
- if (json) {
208
- console.log(JSON.stringify({ message: '交互模式需要 TTY,请运行 sillyspec setup' }));
209
- } else {
210
- console.log(chalk.yellow('⚠️ 请在交互式终端运行 sillyspec setup'));
211
- }
212
- return;
213
- }
214
-
215
- // ── 交互式安装 ──
216
-
217
- console.log('');
218
- console.log(chalk.cyan('🔧 SillySpec Setup — 增强工具安装'));
219
- console.log('');
220
-
221
- if (availableTools.length === 0) {
222
- console.log(chalk.dim(' 未检测到 AI 工具配置文件,MCP 工具将跳过。'));
223
- console.log(chalk.dim(' 请先运行 sillyspec init 初始化项目。'));
224
- console.log('');
225
- }
226
-
227
- console.log(' 选择要安装的工具(已安装的会跳过):');
228
- console.log('');
229
-
230
- // ── MCP 工具选择 ──
231
-
232
- const installedSet = new Set();
233
- for (const { path } of availableTools) {
234
- const config = readMcpConfig(dir, path);
235
- for (const mcp of MCP_TOOLS) {
236
- if (hasMcpInstalled(config, mcp.id)) installedSet.add(mcp.id);
237
- }
238
- }
239
-
240
- const mcpChoices = MCP_TOOLS.filter(m => !installedSet.has(m.id)).map(m => ({
241
- name: `${m.name} — ${m.description}`,
242
- value: `mcp:${m.id}`,
243
- checked: false,
244
- }));
245
-
246
- // ── 全局工具选择 ──
247
-
248
- const globalInstalled = new Set();
249
- for (const tool of GLOBAL_TOOLS) {
250
- try {
251
- execSync(tool.checkCommand, { stdio: 'pipe', encoding: 'utf8' });
252
- globalInstalled.add(tool.id);
253
- } catch {}
254
- }
255
-
256
- const globalChoices = GLOBAL_TOOLS.filter(t => !globalInstalled.has(t.id)).map(t => ({
257
- name: `${t.name} — ${t.description}(全局安装)`,
258
- value: `global:${t.id}`,
259
- checked: false,
260
- }));
261
-
262
- // ── 数据库 MCP 选择 ──
263
-
264
- const dbInstalled = new Set();
265
- for (const { path } of availableTools) {
266
- const config = readMcpConfig(dir, path);
267
- for (const db of DB_MCP_TOOLS) {
268
- if (hasMcpInstalled(config, db.id)) dbInstalled.add(db.id);
269
- }
270
- }
271
-
272
- const dbChoices = DB_MCP_TOOLS.filter(d => !dbInstalled.has(d.id)).map(d => ({
273
- name: `${d.name} — ${d.description}(需要连接信息)`,
274
- value: `db:${d.id}`,
275
- checked: false,
276
- }));
277
-
278
- const allChoices = [
279
- ...mcpChoices.length > 0 ? [{ name: chalk.bold('── MCP 工具(AI 能力增强)──'), value: '_mcp_header', disabled: true }] : [],
280
- ...mcpChoices,
281
- ...dbChoices.length > 0 ? [{ name: chalk.bold('── 数据库 MCP ──'), value: '_db_header', disabled: true }] : [],
282
- ...dbChoices,
283
- ...globalChoices.length > 0 ? [{ name: chalk.bold('── 全局工具 ──'), value: '_global_header', disabled: true }] : [],
284
- ...globalChoices,
285
- ];
286
-
287
- if (allChoices.length === 0) {
288
- console.log(chalk.green(' ✅ 所有推荐工具已安装!'));
289
- return;
290
- }
291
-
292
- const selected = await checkbox({
293
- message: '选择要安装的工具',
294
- choices: allChoices,
295
- });
296
-
297
- if (selected.length === 0) {
298
- console.log(chalk.dim(' 未选择任何工具,退出。'));
299
- return;
300
- }
301
-
302
- const selectedMcp = MCP_TOOLS.filter(m => selected.includes(`mcp:${m.id}`));
303
- const selectedDb = DB_MCP_TOOLS.filter(d => selected.includes(`db:${d.id}`));
304
- const selectedGlobal = GLOBAL_TOOLS.filter(t => selected.includes(`global:${t.id}`));
305
-
306
- // ── 收集数据库连接信息 ──
307
-
308
- if (selectedDb.length > 0) {
309
- console.log('');
310
- console.log(chalk.yellow('📡 数据库连接配置'));
311
- console.log('');
312
-
313
- for (const db of selectedDb) {
314
- console.log(chalk.cyan(` ${db.name}:`));
315
- const dbWithEnv = { ...db, env: {} };
316
- for (const [key, placeholder] of Object.entries(db.envTemplate)) {
317
- const value = await input({
318
- message: ` ${key}`,
319
- default: placeholder,
320
- });
321
- dbWithEnv.env[key] = value;
322
- }
323
- // 用带 env 的版本替换
324
- const idx = selectedDb.indexOf(db);
325
- selectedDb[idx] = dbWithEnv;
326
- }
327
- }
328
-
329
- // ── 安装 MCP 工具 ──
330
-
331
- const allMcp = [...selectedMcp, ...selectedDb];
332
-
333
- if (allMcp.length > 0) {
334
- const toolChoices = availableTools.map(t => ({
335
- name: t.tool,
336
- value: t.key,
337
- checked: true,
338
- }));
339
-
340
- const targetTools = await checkbox({
341
- message: 'MCP 工具安装到哪些 AI 工具?',
342
- choices: toolChoices,
343
- });
344
-
345
- const targets = availableTools.filter(t => targetTools.includes(t.key));
346
-
347
- console.log('');
348
- for (const { tool, path } of targets) {
349
- const spinner = ora(`安装 MCP 到 ${tool}...`).start();
350
- let config = readMcpConfig(dir, path) || { mcpServers: {} };
351
- for (const mcp of allMcp) {
352
- config = installMcp(config, mcp);
353
- }
354
- writeMcpConfig(dir, path, config);
355
- spinner.succeed(`${tool} MCP 完成 (${allMcp.length} 个工具)`);
356
- }
357
- }
358
-
359
- // ── 安装全局工具 ──
360
-
361
- if (selectedGlobal.length > 0) {
362
- console.log('');
363
- for (const tool of selectedGlobal) {
364
- const spinner = ora(`安装 ${tool.name}...`).start();
365
- try {
366
- execSync(tool.installCommand, { stdio: 'pipe', encoding: 'utf8', timeout: 120000 });
367
- spinner.succeed(`${tool.name} 安装完成`);
368
- } catch (err) {
369
- spinner.fail(`${tool.name} 安装失败: ${err.message}`);
370
- }
371
- }
372
- }
373
-
374
- // ── 总结 ──
375
-
376
- console.log('');
377
- console.log(chalk.green(' ═══════════════════════════════════════'));
378
- console.log(chalk.green(' ✅ 工具安装完成!'));
379
- console.log(chalk.green(' ═══════════════════════════════════════'));
380
- console.log('');
381
- for (const mcp of selectedMcp) {
382
- console.log(` 🔌 ${chalk.cyan(mcp.name)} — ${mcp.description}`);
383
- console.log(chalk.dim(` ${mcp.url}`));
384
- }
385
- for (const db of selectedDb) {
386
- console.log(` 🗄️ ${chalk.cyan(db.name)} — ${db.description}`);
387
- console.log(chalk.dim(` ${db.url}`));
388
- }
389
- for (const g of selectedGlobal) {
390
- console.log(` 🛠️ ${chalk.cyan(g.name)} — ${g.description}`);
391
- console.log(chalk.dim(` ${g.url}`));
392
- }
393
- console.log('');
394
- if (selectedMcp.length > 0) {
395
- console.log(chalk.dim(' 重启你的 AI 工具以使 MCP 配置生效。'));
396
- }
397
- console.log('');
398
- }
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { execSync } from 'child_process';
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ import { checkbox, input } from '@inquirer/prompts';
7
+
8
+ // ── MCP 工具定义 ──
9
+
10
+ const MCP_TOOLS = [
11
+ {
12
+ id: 'context7',
13
+ name: 'Context7',
14
+ description: '查询最新库文档和 API 参考',
15
+ command: 'npx',
16
+ args: ['-y', '@upstash/context7-mcp@latest'],
17
+ url: 'https://github.com/upstash/context7-mcp',
18
+ },
19
+ {
20
+ id: 'chrome-devtools',
21
+ name: 'Chrome DevTools MCP',
22
+ description: '浏览器自动化,支持 E2E 测试(需 Chrome 已运行)',
23
+ command: 'npx',
24
+ args: ['chrome-devtools-mcp@latest'],
25
+ url: 'https://github.com/ChromeDevTools/chrome-devtools-mcp',
26
+ },
27
+ {
28
+ id: 'agent-browser',
29
+ name: 'Agent Browser (Vercel)',
30
+ description: 'Rust 原生浏览器 CLI,token 消耗极低,50+ 命令覆盖导航/表单/截图/网络',
31
+ command: 'npx',
32
+ args: ['@anthropic-ai/agent-browser@latest'],
33
+ url: 'https://github.com/vercel-labs/agent-browser',
34
+ },
35
+ {
36
+ id: 'pinchtab',
37
+ name: 'PinchTab',
38
+ description: '12MB Go 二进制,零依赖,accessibility tree 极省 token,有 MCP 支持',
39
+ command: 'npx',
40
+ args: ['pinchtab-mcp@latest'],
41
+ url: 'https://github.com/pinchtab/pinchtab',
42
+ },
43
+ ];
44
+
45
+ // ── 数据库 MCP 定义(需要连接信息)──
46
+
47
+ const DB_MCP_TOOLS = [
48
+ {
49
+ id: 'postgres',
50
+ name: 'PostgreSQL',
51
+ description: '只读访问 PostgreSQL 数据库',
52
+ command: 'npx',
53
+ args: ['-y', '@modelcontextprotocol/server-postgres'],
54
+ envTemplate: { POSTGRES_CONNECTION_STRING: 'postgresql://user:password@localhost:5432/dbname' },
55
+ url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/postgres',
56
+ },
57
+ {
58
+ id: 'sqlite',
59
+ name: 'SQLite',
60
+ description: '访问 SQLite 数据库文件',
61
+ command: 'npx',
62
+ args: ['-y', '@modelcontextprotocol/server-sqlite'],
63
+ envTemplate: { SQLITE_DB_PATH: './data.db' },
64
+ url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite',
65
+ },
66
+ {
67
+ id: 'mysql',
68
+ name: 'MySQL / MariaDB',
69
+ description: '访问 MySQL / MariaDB 数据库',
70
+ command: 'npx',
71
+ args: ['-y', '@nicobailon/mysql-mcp-server'],
72
+ envTemplate: { MYSQL_HOST: 'localhost', MYSQL_PORT: '3306', MYSQL_USER: 'root', MYSQL_PASSWORD: '', MYSQL_DATABASE: '' },
73
+ url: 'https://github.com/modelcontextprotocol/servers',
74
+ },
75
+ {
76
+ id: 'redis',
77
+ name: 'Redis',
78
+ description: '访问 Redis 数据库,查看缓存和键值数据',
79
+ command: 'npx',
80
+ args: ['-y', '@modelcontextprotocol/server-redis'],
81
+ envTemplate: { REDIS_URL: 'redis://localhost:6379' },
82
+ url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/redis',
83
+ },
84
+ ];
85
+
86
+ // ── 全局工具定义 ──
87
+
88
+ const GLOBAL_TOOLS = [
89
+ {
90
+ id: 'playwright',
91
+ name: 'Playwright',
92
+ description: 'E2E 测试框架,支持浏览器自动化测试',
93
+ checkCommand: 'npx playwright --version',
94
+ installCommand: 'npm install -g @playwright/test && npx playwright install chromium',
95
+ url: 'https://playwright.dev',
96
+ },
97
+ ];
98
+
99
+ // ── MCP 配置文件路径 ──
100
+
101
+ const MCP_CONFIG_PATHS = [
102
+ { tool: 'Claude Code', path: '.claude/mcp.json', key: 'claude' },
103
+ { tool: 'Cursor', path: '.cursor/mcp.json', key: 'cursor' },
104
+ ];
105
+
106
+ // ── 工具函数 ──
107
+
108
+ function readMcpConfig(dir, configPath) {
109
+ const fullPath = join(dir, configPath);
110
+ if (!existsSync(fullPath)) return null;
111
+ try {
112
+ return JSON.parse(readFileSync(fullPath, 'utf8'));
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+
118
+ function writeMcpConfig(dir, configPath, config) {
119
+ const fullPath = join(dir, configPath);
120
+ mkdirSync(dirname(fullPath), { recursive: true });
121
+ writeFileSync(fullPath, JSON.stringify(config, null, 2) + '\n');
122
+ }
123
+
124
+ function installMcp(config, mcpTool) {
125
+ if (!config.mcpServers) config.mcpServers = {};
126
+ const entry = {
127
+ command: mcpTool.command,
128
+ args: mcpTool.args,
129
+ };
130
+ if (mcpTool.env) entry.env = mcpTool.env;
131
+ config.mcpServers[mcpTool.id] = entry;
132
+ return config;
133
+ }
134
+
135
+ function hasMcpInstalled(config, mcpId) {
136
+ return config?.mcpServers?.[mcpId] != null;
137
+ }
138
+
139
+ // ── 命令实现 ──
140
+
141
+ export async function cmdSetup(dir, options = {}) {
142
+ const { json } = options;
143
+
144
+ // 检查哪些 AI 工具有配置文件
145
+ const availableTools = MCP_CONFIG_PATHS.filter(({ path }) => {
146
+ return existsSync(join(dir, path));
147
+ });
148
+
149
+ // --list 模式:只查看状态(不依赖 AI 工具配置)
150
+ if (options.list) {
151
+ const results = {};
152
+
153
+ // MCP 状态
154
+ for (const { tool, path } of availableTools) {
155
+ const config = readMcpConfig(dir, path);
156
+ const mcpStatus = {};
157
+ for (const mcp of MCP_TOOLS) {
158
+ mcpStatus[mcp.id] = hasMcpInstalled(config, mcp.id);
159
+ }
160
+ for (const db of DB_MCP_TOOLS) {
161
+ mcpStatus[db.id] = hasMcpInstalled(config, db.id);
162
+ }
163
+ results[tool] = { configPath: path, mcp: mcpStatus };
164
+ }
165
+
166
+ // 全局工具状态
167
+ results['全局工具'] = {};
168
+ for (const g of GLOBAL_TOOLS) {
169
+ try {
170
+ execSync(g.checkCommand, { stdio: 'pipe', encoding: 'utf8' });
171
+ results['全局工具'][g.id] = true;
172
+ } catch {
173
+ results['全局工具'][g.id] = false;
174
+ }
175
+ }
176
+
177
+ if (json) {
178
+ console.log(JSON.stringify(results, null, 2));
179
+ } else {
180
+ console.log('');
181
+ for (const [tool, data] of Object.entries(results)) {
182
+ if (data.mcp) {
183
+ console.log(chalk.bold(`📋 ${tool} (${data.configPath})`));
184
+ for (const mcp of MCP_TOOLS) {
185
+ const status = data.mcp[mcp.id] ? chalk.green('✅') : chalk.gray('⬜');
186
+ console.log(` ${status} ${mcp.name} — ${mcp.description}`);
187
+ }
188
+ for (const db of DB_MCP_TOOLS) {
189
+ const status = data.mcp[db.id] ? chalk.green('✅') : chalk.gray('⬜');
190
+ console.log(` ${status} ${db.name} — ${db.description}`);
191
+ }
192
+ } else {
193
+ console.log(chalk.bold(`🛠️ ${tool}`));
194
+ for (const g of GLOBAL_TOOLS) {
195
+ const status = data[g.id] ? chalk.green('✅') : chalk.gray('⬜');
196
+ console.log(` ${status} ${g.name} — ${g.description}`);
197
+ }
198
+ }
199
+ console.log('');
200
+ }
201
+ }
202
+ return;
203
+ }
204
+
205
+ // 非交互模式(或没有 AI 工具但有全局工具要装)
206
+ if (!process.stdin.isTTY) {
207
+ if (json) {
208
+ console.log(JSON.stringify({ message: '交互模式需要 TTY,请运行 sillyspec setup' }));
209
+ } else {
210
+ console.log(chalk.yellow('⚠️ 请在交互式终端运行 sillyspec setup'));
211
+ }
212
+ return;
213
+ }
214
+
215
+ // ── 交互式安装 ──
216
+
217
+ console.log('');
218
+ console.log(chalk.cyan('🔧 SillySpec Setup — 增强工具安装'));
219
+ console.log('');
220
+
221
+ if (availableTools.length === 0) {
222
+ console.log(chalk.dim(' 未检测到 AI 工具配置文件,MCP 工具将跳过。'));
223
+ console.log(chalk.dim(' 请先运行 sillyspec init 初始化项目。'));
224
+ console.log('');
225
+ }
226
+
227
+ console.log(' 选择要安装的工具(已安装的会跳过):');
228
+ console.log('');
229
+
230
+ // ── MCP 工具选择 ──
231
+
232
+ const installedSet = new Set();
233
+ for (const { path } of availableTools) {
234
+ const config = readMcpConfig(dir, path);
235
+ for (const mcp of MCP_TOOLS) {
236
+ if (hasMcpInstalled(config, mcp.id)) installedSet.add(mcp.id);
237
+ }
238
+ }
239
+
240
+ const mcpChoices = MCP_TOOLS.filter(m => !installedSet.has(m.id)).map(m => ({
241
+ name: `${m.name} — ${m.description}`,
242
+ value: `mcp:${m.id}`,
243
+ checked: false,
244
+ }));
245
+
246
+ // ── 全局工具选择 ──
247
+
248
+ const globalInstalled = new Set();
249
+ for (const tool of GLOBAL_TOOLS) {
250
+ try {
251
+ execSync(tool.checkCommand, { stdio: 'pipe', encoding: 'utf8' });
252
+ globalInstalled.add(tool.id);
253
+ } catch {}
254
+ }
255
+
256
+ const globalChoices = GLOBAL_TOOLS.filter(t => !globalInstalled.has(t.id)).map(t => ({
257
+ name: `${t.name} — ${t.description}(全局安装)`,
258
+ value: `global:${t.id}`,
259
+ checked: false,
260
+ }));
261
+
262
+ // ── 数据库 MCP 选择 ──
263
+
264
+ const dbInstalled = new Set();
265
+ for (const { path } of availableTools) {
266
+ const config = readMcpConfig(dir, path);
267
+ for (const db of DB_MCP_TOOLS) {
268
+ if (hasMcpInstalled(config, db.id)) dbInstalled.add(db.id);
269
+ }
270
+ }
271
+
272
+ const dbChoices = DB_MCP_TOOLS.filter(d => !dbInstalled.has(d.id)).map(d => ({
273
+ name: `${d.name} — ${d.description}(需要连接信息)`,
274
+ value: `db:${d.id}`,
275
+ checked: false,
276
+ }));
277
+
278
+ const allChoices = [
279
+ ...mcpChoices.length > 0 ? [{ name: chalk.bold('── MCP 工具(AI 能力增强)──'), value: '_mcp_header', disabled: true }] : [],
280
+ ...mcpChoices,
281
+ ...dbChoices.length > 0 ? [{ name: chalk.bold('── 数据库 MCP ──'), value: '_db_header', disabled: true }] : [],
282
+ ...dbChoices,
283
+ ...globalChoices.length > 0 ? [{ name: chalk.bold('── 全局工具 ──'), value: '_global_header', disabled: true }] : [],
284
+ ...globalChoices,
285
+ ];
286
+
287
+ if (allChoices.length === 0) {
288
+ console.log(chalk.green(' ✅ 所有推荐工具已安装!'));
289
+ return;
290
+ }
291
+
292
+ const selected = await checkbox({
293
+ message: '选择要安装的工具',
294
+ choices: allChoices,
295
+ });
296
+
297
+ if (selected.length === 0) {
298
+ console.log(chalk.dim(' 未选择任何工具,退出。'));
299
+ return;
300
+ }
301
+
302
+ const selectedMcp = MCP_TOOLS.filter(m => selected.includes(`mcp:${m.id}`));
303
+ const selectedDb = DB_MCP_TOOLS.filter(d => selected.includes(`db:${d.id}`));
304
+ const selectedGlobal = GLOBAL_TOOLS.filter(t => selected.includes(`global:${t.id}`));
305
+
306
+ // ── 收集数据库连接信息 ──
307
+
308
+ if (selectedDb.length > 0) {
309
+ console.log('');
310
+ console.log(chalk.yellow('📡 数据库连接配置'));
311
+ console.log('');
312
+
313
+ for (const db of selectedDb) {
314
+ console.log(chalk.cyan(` ${db.name}:`));
315
+ const dbWithEnv = { ...db, env: {} };
316
+ for (const [key, placeholder] of Object.entries(db.envTemplate)) {
317
+ const value = await input({
318
+ message: ` ${key}`,
319
+ default: placeholder,
320
+ });
321
+ dbWithEnv.env[key] = value;
322
+ }
323
+ // 用带 env 的版本替换
324
+ const idx = selectedDb.indexOf(db);
325
+ selectedDb[idx] = dbWithEnv;
326
+ }
327
+ }
328
+
329
+ // ── 安装 MCP 工具 ──
330
+
331
+ const allMcp = [...selectedMcp, ...selectedDb];
332
+
333
+ if (allMcp.length > 0) {
334
+ const toolChoices = availableTools.map(t => ({
335
+ name: t.tool,
336
+ value: t.key,
337
+ checked: true,
338
+ }));
339
+
340
+ const targetTools = await checkbox({
341
+ message: 'MCP 工具安装到哪些 AI 工具?',
342
+ choices: toolChoices,
343
+ });
344
+
345
+ const targets = availableTools.filter(t => targetTools.includes(t.key));
346
+
347
+ console.log('');
348
+ for (const { tool, path } of targets) {
349
+ const spinner = ora(`安装 MCP 到 ${tool}...`).start();
350
+ let config = readMcpConfig(dir, path) || { mcpServers: {} };
351
+ for (const mcp of allMcp) {
352
+ config = installMcp(config, mcp);
353
+ }
354
+ writeMcpConfig(dir, path, config);
355
+ spinner.succeed(`${tool} MCP 完成 (${allMcp.length} 个工具)`);
356
+ }
357
+ }
358
+
359
+ // ── 安装全局工具 ──
360
+
361
+ if (selectedGlobal.length > 0) {
362
+ console.log('');
363
+ for (const tool of selectedGlobal) {
364
+ const spinner = ora(`安装 ${tool.name}...`).start();
365
+ try {
366
+ execSync(tool.installCommand, { stdio: 'pipe', encoding: 'utf8', timeout: 120000 });
367
+ spinner.succeed(`${tool.name} 安装完成`);
368
+ } catch (err) {
369
+ spinner.fail(`${tool.name} 安装失败: ${err.message}`);
370
+ }
371
+ }
372
+ }
373
+
374
+ // ── 总结 ──
375
+
376
+ console.log('');
377
+ console.log(chalk.green(' ═══════════════════════════════════════'));
378
+ console.log(chalk.green(' ✅ 工具安装完成!'));
379
+ console.log(chalk.green(' ═══════════════════════════════════════'));
380
+ console.log('');
381
+ for (const mcp of selectedMcp) {
382
+ console.log(` 🔌 ${chalk.cyan(mcp.name)} — ${mcp.description}`);
383
+ console.log(chalk.dim(` ${mcp.url}`));
384
+ }
385
+ for (const db of selectedDb) {
386
+ console.log(` 🗄️ ${chalk.cyan(db.name)} — ${db.description}`);
387
+ console.log(chalk.dim(` ${db.url}`));
388
+ }
389
+ for (const g of selectedGlobal) {
390
+ console.log(` 🛠️ ${chalk.cyan(g.name)} — ${g.description}`);
391
+ console.log(chalk.dim(` ${g.url}`));
392
+ }
393
+ console.log('');
394
+ if (selectedMcp.length > 0) {
395
+ console.log(chalk.dim(' 重启你的 AI 工具以使 MCP 配置生效。'));
396
+ }
397
+ console.log('');
398
+ }