sillyspec 2.4.4 → 2.6.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.
Files changed (34) hide show
  1. package/.claude/commands/sillyspec/archive.md +36 -43
  2. package/.claude/commands/sillyspec/brainstorm.md +86 -483
  3. package/.claude/commands/sillyspec/continue.md +24 -29
  4. package/.claude/commands/sillyspec/execute.md +63 -173
  5. package/.claude/commands/sillyspec/explore.md +20 -68
  6. package/.claude/commands/sillyspec/export.md +13 -37
  7. package/.claude/commands/sillyspec/init.md +25 -127
  8. package/.claude/commands/sillyspec/plan.md +40 -188
  9. package/.claude/commands/sillyspec/propose.md +36 -186
  10. package/.claude/commands/sillyspec/quick.md +17 -47
  11. package/.claude/commands/sillyspec/resume.md +22 -79
  12. package/.claude/commands/sillyspec/scan.md +109 -507
  13. package/.claude/commands/sillyspec/status.md +20 -95
  14. package/.claude/commands/sillyspec/verify.md +27 -86
  15. package/.claude/commands/sillyspec/workspace.md +27 -95
  16. package/.sillyspec/config.yaml +13 -0
  17. package/package.json +7 -2
  18. package/src/index.js +2 -2
  19. package/src/init.js +240 -75
  20. package/templates/archive.md +36 -43
  21. package/templates/brainstorm.md +81 -483
  22. package/templates/continue.md +20 -30
  23. package/templates/execute.md +63 -173
  24. package/templates/explore.md +15 -68
  25. package/templates/export.md +10 -39
  26. package/templates/init.md +20 -127
  27. package/templates/plan.md +40 -188
  28. package/templates/propose.md +36 -186
  29. package/templates/quick.md +13 -48
  30. package/templates/resume.md +22 -79
  31. package/templates/scan.md +109 -507
  32. package/templates/status.md +16 -96
  33. package/templates/verify.md +22 -86
  34. package/templates/workspace.md +22 -95
package/src/init.js CHANGED
@@ -2,6 +2,9 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from
2
2
  import { join, resolve, dirname } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { homedir } from 'os';
5
+ import { checkbox, select, confirm, input } from '@inquirer/prompts';
6
+ import chalk from 'chalk';
7
+ import ora from 'ora';
5
8
 
6
9
  const __filename = fileURLToPath(import.meta.url);
7
10
  const __dirname = dirname(__filename);
@@ -49,6 +52,15 @@ const ARG_HINTS = {
49
52
 
50
53
  const VALID_TOOLS = ['claude', 'claude_skills', 'cursor', 'codex', 'opencode', 'openclaw'];
51
54
 
55
+ const TOOL_LABELS = {
56
+ claude: 'Claude Code',
57
+ claude_skills: 'Claude Skills',
58
+ cursor: 'Cursor',
59
+ codex: 'Codex CLI',
60
+ opencode: 'OpenCode',
61
+ openclaw: 'OpenClaw',
62
+ };
63
+
52
64
  // ── 适配器 ──
53
65
 
54
66
  function generateClaude(projectDir, name, desc, body, argHint) {
@@ -92,7 +104,6 @@ ${body}`
92
104
  }
93
105
 
94
106
  function generateCodex(projectDir, name, desc, body, argHint) {
95
- // codex outputs to user home directory
96
107
  const outDir = join(homedir(), '.agents', 'skills', `sillyspec-${name}`);
97
108
  mkdirSync(outDir, { recursive: true });
98
109
  writeFileSync(join(outDir, 'SKILL.md'),
@@ -154,46 +165,26 @@ function detectTools(projectDir) {
154
165
  return found;
155
166
  }
156
167
 
157
- // ── 主命令 ──
158
-
159
- export function cmdInit(projectDir, options = {}) {
160
- const { tool, workspace } = options;
168
+ // ── TTY 工具函数 ──
161
169
 
162
- // 确定要安装的工具
163
- let tools;
164
- if (tool) {
165
- if (!VALID_TOOLS.includes(tool)) {
166
- console.error(`❌ 未知工具: ${tool}`);
167
- console.error(`支持的工具: ${VALID_TOOLS.join(', ')}`);
168
- process.exit(1);
169
- }
170
- tools = [tool];
171
- } else {
172
- tools = detectTools(projectDir);
173
- }
170
+ function isTTY() {
171
+ return process.stdin.isTTY && process.stdout.isTTY;
172
+ }
174
173
 
175
- console.log('🤪 SillySpec v2.2 — 规范驱动开发');
176
- console.log('====================================');
177
- console.log('');
178
- console.log(`📦 安装工具: ${tools.join(', ')}`);
179
- if (workspace) console.log('📦 工作区模式');
180
- console.log('');
174
+ // ── 核心安装逻辑 ──
181
175
 
176
+ async function doInstall(projectDir, tools, isWorkspace, subprojects = []) {
182
177
  // 创建基础目录
183
- const dirs = [
184
- '.sillyspec/codebase',
185
- '.sillyspec/changes/archive',
186
- '.sillyspec/plans',
187
- '.sillyspec/specs',
188
- '.sillyspec/phases',
189
- ];
190
- if (workspace) {
191
- dirs.push('.sillyspec/shared', '.sillyspec/workspace');
192
- }
193
- for (const d of dirs) {
194
- mkdirSync(join(projectDir, d), { recursive: true });
178
+ // 不预创建子目录,由各命令按需创建
179
+ // .sillyspec/codebase/ → scan
180
+ // .sillyspec/changes/ → brainstorm/propose
181
+ // .sillyspec/changes/archive/ → archive
182
+ // .sillyspec/plans/ → plan
183
+ // .sillyspec/specs/ → propose
184
+ if (isWorkspace) {
185
+ mkdirSync(join(projectDir, '.sillyspec', 'shared'), { recursive: true });
186
+ mkdirSync(join(projectDir, '.sillyspec', 'workspace'), { recursive: true });
195
187
  }
196
- mkdirSync(join(homedir(), '.sillyspec', 'templates'), { recursive: true });
197
188
 
198
189
  // .gitignore
199
190
  const gitignorePath = join(projectDir, '.gitignore');
@@ -201,69 +192,243 @@ export function cmdInit(projectDir, options = {}) {
201
192
  writeFileSync(gitignorePath, '.sillyspec/STATE.md\n');
202
193
  }
203
194
 
204
- // 为每个工具生成文件
195
+ // 生成文件
205
196
  const templateFiles = readdirSync(TEMPLATE_DIR).filter(f => f.endsWith('.md'));
206
197
  let count = 0;
207
198
 
208
- for (const toolName of tools) {
209
- console.log(`🔧 安装 ${toolName}...`);
210
- const gen = GENERATORS[toolName];
211
- for (const file of templateFiles) {
212
- const name = file.replace('.md', '');
213
- const desc = DESCRIPTIONS[name] || `SillySpec ${name}`;
214
- const argHint = ARG_HINTS[name] || '';
215
- const body = readFileSync(join(TEMPLATE_DIR, file), 'utf8');
216
- gen(projectDir, name, desc, body, argHint);
217
- count++;
199
+ for (let i = 0; i < tools.length; i++) {
200
+ const toolName = tools[i];
201
+ const label = TOOL_LABELS[toolName] || toolName;
202
+ const spinner = ora(`安装 ${label}... (${i + 1}/${tools.length})`).start();
203
+ try {
204
+ const gen = GENERATORS[toolName];
205
+ for (const file of templateFiles) {
206
+ const name = file.replace('.md', '');
207
+ const desc = DESCRIPTIONS[name] || `SillySpec ${name}`;
208
+ const argHint = ARG_HINTS[name] || '';
209
+ const body = readFileSync(join(TEMPLATE_DIR, file), 'utf8');
210
+ gen(projectDir, name, desc, body, argHint);
211
+ count++;
212
+ }
213
+ spinner.succeed(`${label} 完成`);
214
+ } catch (err) {
215
+ spinner.fail(`${label} 失败: ${err.message}`);
216
+ throw err;
218
217
  }
219
- console.log(` ✅ ${toolName} 完成`);
220
218
  }
221
219
 
222
- console.log('');
223
- console.log(`📄 ${count} 个命令已安装`);
224
-
225
220
  // 工作区配置
226
- if (workspace) {
221
+ if (isWorkspace) {
227
222
  const configPath = join(projectDir, '.sillyspec', 'config.yaml');
228
223
  if (!existsSync(configPath)) {
224
+ let projectsYaml = '';
225
+ if (subprojects.length > 0) {
226
+ projectsYaml = subprojects.map(p =>
227
+ ` ${p.name}:\n path: ${p.path}\n role: ${p.role || p.name}`
228
+ ).join('\n');
229
+ }
230
+
229
231
  writeFileSync(configPath,
230
232
  `# SillySpec 工作区配置
231
233
  # 修改此文件后运行 /sillyspec:workspace 更新
232
234
 
233
- projects: {}
234
- # 示例:
235
- # frontend:
236
- # path: ./frontend
237
- # role: 前端 - Vue3 + TypeScript
238
- # backend:
239
- # path: ./backend
240
- # role: 后端 - Node.js + PostgreSQL
235
+ projects:
236
+ ${projectsYaml || ' # 运行 /sillyspec:workspace add 添加子项目'}
241
237
 
242
238
  shared: []
243
239
  `
244
240
  );
245
- console.log('📄 .sillyspec/config.yaml → 工作区配置 ✓');
246
241
  }
247
242
  }
248
243
 
244
+ return count;
245
+ }
246
+
247
+ // ── 安装完成总结 ──
248
+
249
+ function showSummary(version, tools, isWorkspace, count) {
250
+ const toolLabels = tools.map(t => TOOL_LABELS[t] || t);
251
+ const mode = isWorkspace ? '多项目工作区' : '单项目';
252
+
253
+ console.log('');
254
+ console.log(chalk.green(' ═══════════════════════════════════════'));
255
+ console.log(chalk.green(` ✅ SillySpec v${version} 安装完成!`));
256
+ console.log(chalk.green(' ═══════════════════════════════════════'));
249
257
  console.log('');
250
- console.log('=====================================');
251
- if (workspace) {
252
- console.log('✅ SillySpec v2.2 安装完成!(工作区模式)');
258
+ console.log(` 已安装工具: ${chalk.cyan(toolLabels.join(', '))}`);
259
+ console.log(` 模式: ${chalk.yellow(mode)}`);
260
+ console.log('');
261
+ console.log(` 📄 ${count} 个命令已就绪`);
262
+ console.log(' 📁 .sillyspec/ — 项目规范目录');
263
+ console.log('');
264
+ console.log(' 入口选择:');
265
+ console.log(' 全新项目:' + chalk.bold('/sillyspec:init'));
266
+ console.log(' 已有代码:' + chalk.bold('/sillyspec:scan'));
267
+ console.log(' 自由思考:' + chalk.bold('/sillyspec:explore "你的想法"'));
268
+ console.log('');
269
+ console.log(chalk.gray(' 重启你的 AI 工具以使 slash commands 生效。'));
270
+ console.log('');
271
+ }
272
+
273
+ // ── 读取版本号 ──
274
+
275
+ function getVersion() {
276
+ try {
277
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf8'));
278
+ return pkg.version;
279
+ } catch {
280
+ return '?.?.?';
281
+ }
282
+ }
283
+
284
+ // ── 主命令 ──
285
+
286
+ export async function cmdInit(projectDir, options = {}) {
287
+ const { tool, workspace } = options;
288
+ const version = getVersion();
289
+
290
+ // CLI 参数模式(非交互)
291
+ if (tool) {
292
+ if (!VALID_TOOLS.includes(tool)) {
293
+ console.error(`❌ 未知工具: ${tool}`);
294
+ console.error(`支持的工具: ${VALID_TOOLS.join(', ')}`);
295
+ process.exit(1);
296
+ }
297
+
298
+ console.log(chalk.cyan(`🤪 SillySpec v${version}`));
299
+ console.log(chalk.cyan(`📦 安装工具: ${tool}`));
253
300
  console.log('');
254
- console.log(`已安装工具: ${tools.join(', ')}`);
301
+
302
+ const count = await doInstall(projectDir, [tool], workspace);
303
+ showSummary(version, [tool], workspace, count);
304
+ return;
305
+ }
306
+
307
+ // 非交互模式
308
+ if (!isTTY()) {
309
+ const tools = detectTools(projectDir);
310
+ console.log(chalk.cyan(`🤪 SillySpec v${version} (非交互模式)`));
311
+ console.log(chalk.cyan(`📦 自动检测工具: ${tools.join(', ')}`));
255
312
  console.log('');
256
- console.log('工作区命令:');
257
- console.log(' /sillyspec:workspace add — 添加子项目');
258
- console.log(' /sillyspec:workspace status — 查看工作区状态');
259
- } else {
260
- console.log('✅ SillySpec v2.2 安装完成!');
313
+ const count = await doInstall(projectDir, tools, workspace);
314
+ showSummary(version, tools, workspace, count);
315
+ return;
316
+ }
317
+
318
+ // ── 交互式引导 ──
319
+
320
+ // 欢迎画面
321
+ console.log('');
322
+ console.log(chalk.cyan('🤪 SillySpec v' + version + ' — 规范驱动开发'));
323
+ console.log(chalk.cyan(' ===================================='));
324
+ console.log('');
325
+ console.log(' 让 AI 像高级工程师一样工作:');
326
+ console.log(' 先思考、先规划、先验证,再写代码。');
327
+ console.log('');
328
+ console.log(chalk.gray(' 支持的 AI 工具:'));
329
+ console.log(chalk.gray(' Claude Code · Claude Skills · Cursor · Codex CLI · OpenCode · OpenClaw'));
330
+ console.log('');
331
+
332
+ await confirm({ message: '按回车开始设置...', default: true });
333
+
334
+ // 工具多选
335
+ const detected = detectTools(projectDir);
336
+
337
+ const toolChoices = VALID_TOOLS.map(v => ({
338
+ name: `${TOOL_LABELS[v]}${v === 'claude' ? ' (slash commands)' : v === 'claude_skills' ? ' (skills 目录)' : ''}`,
339
+ value: v,
340
+ checked: detected.includes(v),
341
+ }));
342
+
343
+ const selectedTools = await checkbox({
344
+ message: '选择要安装的 AI 工具',
345
+ choices: toolChoices,
346
+ validate: (answer) => answer.length > 0 || '至少选择一个工具',
347
+ });
348
+
349
+ // 工作区模式
350
+ const isWorkspace = await select({
351
+ message: '选择项目模式',
352
+ choices: [
353
+ { name: '单项目模式', value: 'false' },
354
+ { name: '多项目工作区', value: 'true' },
355
+ ],
356
+ }) === 'true';
357
+
358
+ // 工作区子项目引导
359
+ if (isWorkspace) {
261
360
  console.log('');
262
- console.log(`已安装工具: ${tools.join(', ')}`);
361
+ console.log(chalk.yellow('📋 工作区模式 — 添加子项目'));
362
+ console.log(chalk.dim(' 子项目是工作区中的独立项目目录(如 frontend/、backend/)'));
263
363
  console.log('');
264
- console.log('入口选择:');
265
- console.log(' 绿地项目:/sillyspec:init');
266
- console.log(' 棕地项目:/sillyspec:scan');
267
- console.log(' 自由思考:/sillyspec:explore "你的想法"');
364
+
365
+ const addMore = await confirm({ message: '现在添加子项目?', default: true });
366
+ if (addMore) {
367
+ // 读取当前目录下的子目录作为建议
368
+ let suggestions = [];
369
+ try {
370
+ const entries = readdirSync(projectDir, { withFileTypes: true });
371
+ suggestions = entries
372
+ .filter(e => e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules')
373
+ .map(e => e.name)
374
+ .sort();
375
+ } catch {}
376
+
377
+ if (suggestions.length > 0) {
378
+ console.log('');
379
+ console.log(chalk.dim(` 检测到以下目录:${suggestions.join(', ')}`));
380
+ console.log('');
381
+ }
382
+
383
+ const subprojects = [];
384
+ let adding = true;
385
+
386
+ while (adding) {
387
+ const name = await input({
388
+ message: '子项目名称(如 frontend)',
389
+ default: suggestions.find(s => !subprojects.find(p => p.name === s)) || '',
390
+ });
391
+
392
+ if (!name.trim()) {
393
+ adding = false;
394
+ break;
395
+ }
396
+
397
+ const pathHint = suggestions.includes(name.trim()) ? `./${name.trim()}` : '';
398
+ const subPath = await input({
399
+ message: `子项目目录路径`,
400
+ default: pathHint,
401
+ });
402
+
403
+ const role = await input({
404
+ message: '子项目描述(如 前端 - Vue3 + TypeScript)',
405
+ default: '',
406
+ });
407
+
408
+ subprojects.push({
409
+ name: name.trim(),
410
+ path: subPath.trim() || `./${name.trim()}`,
411
+ role: role.trim(),
412
+ });
413
+
414
+ // 从建议中移除已添加的
415
+ const idx = suggestions.indexOf(name.trim());
416
+ if (idx >= 0) suggestions.splice(idx, 1);
417
+
418
+ const again = await confirm({ message: '继续添加子项目?', default: subprojects.length < suggestions.length });
419
+ if (!again) adding = false;
420
+ }
421
+
422
+ // 保存子项目到临时变量,安装后写入 config.yaml
423
+ // 存到全局变量让 doInstall 后使用
424
+ global.__sillyspec_subprojects = subprojects;
425
+ }
268
426
  }
427
+
428
+ // 安装
429
+ console.log('');
430
+ const count = await doInstall(projectDir, selectedTools, isWorkspace, global.__sillyspec_subprojects || []);
431
+
432
+ // 总结
433
+ showSummary(version, selectedTools, isWorkspace, count);
269
434
  }
@@ -1,69 +1,62 @@
1
1
  ## 交互规范
2
-
3
2
  **当需要用户从多个选项中做出选择时,必须使用 Claude Code 内置的 AskUserQuestion 工具,将选项以参数传入。**
4
3
 
5
- 不要用编号列表让用户手动输入数字。
6
- 如果需要自由输入,在 AskUserQuestion 的选项中加入"Other(自定义输入)"。
7
-
8
- 你现在是 SillySpec 的归档器。
4
+ ## 核心约束(必须遵守)
5
+ - 未经验证就归档(必须先确认验证通过)
6
+ - ❌ 未勾选的 checkbox 未告知用户就归档
7
+ - 归档后留下活跃变更的残留状态
8
+ - ❌ 覆盖已存在的归档目录
9
9
 
10
10
  ## 变更名称
11
11
  $ARGUMENTS
12
12
 
13
+ ---
14
+
13
15
  ## 流程
14
16
 
15
- ### 1. 确认验证通过
17
+ ### 1. 前置检查(门禁)
18
+
19
+ 读取 `.sillyspec/changes/<change-name>/` 下所有必要文件,逐项检查:
16
20
 
17
- 检查是否执行过验证。如果没有 提示先运行 `/sillyspec:verify`。
21
+ - [ ] **verify 状态:** 检查是否有验证记录,无则提示先执行 `/sillyspec:verify`
22
+ - [ ] **文件完整性:** 检查 `proposal.md` 和 `design.md` 是否存在,缺失则警告
23
+ - [ ] **任务完成度:** 读取 `tasks.md`,统计已完成/未完成任务数。**有未完成的 → 用 AskUserQuestion 询问:**
24
+ - ① 继续归档(未完成任务将被标记完成)
25
+ - ② 取消,回去完成任务
18
26
 
19
- ### 2. 归档
27
+ > 任一门禁不通过且用户选择取消 → 终止流程。
20
28
 
21
- ### 3. 确认归档
29
+ ### 2. 展示归档清单
22
30
 
23
- 在移动文件之前,展示即将归档的内容:
31
+ 展示即将归档的内容摘要:
24
32
  - 变更目录名
25
33
  - 包含的文件列表
26
- - 生成总结
34
+ - 任务完成统计(✅ 已完成 / ⬜ 未完成)
35
+ - 一句话总结本次变更
27
36
 
28
- **归档前确认:**
37
+ ### 3. Spec 沉淀
29
38
 
30
- 请确认是否执行归档:
31
- 1. 确认归档
32
- 2. 取消
39
+ 将 `.sillyspec/changes/<change-name>/specs/` 下的设计文档**复制到 `.sillyspec/specs/` 主目录**,确保已完成的设计规范可被后续变更参考。如目标已存在同名文件则跳过并提示。
33
40
 
34
- `.sillyspec/changes/<change-name>/` 移动到 `.sillyspec/changes/archive/YYYY-MM-DD-<change-name>/`。
41
+ ### 4. 用户确认
35
42
 
36
- ### 3. 更新 tasks.md
43
+ AskUserQuestion 让用户确认:
44
+ - ① 确认归档
45
+ - ② 取消
37
46
 
38
- 确保所有 checkbox 都已勾选。如有遗漏 → 打勾。
47
+ ### 5. 执行归档
39
48
 
40
- ### 4. 更新路线图(如存在)
49
+ - 目标路径:`.sillyspec/changes/archive/YYYY-MM-DD-<change-name>/`
50
+ - **检查目标路径是否已存在**,存在则中止并报错,防止覆盖
51
+ - 移动变更目录到归档路径
41
52
 
42
- 如果 `.sillyspec/ROADMAP.md` 存在,标记对应 Phase 为已完成。
53
+ ### 6. 归档后更新
43
54
 
44
- ### 5. Git 提交
45
-
46
- ```bash
47
- git add .sillyspec/
48
- git commit -m "docs: archive sillyspec change <change-name>"
49
- ```
55
+ - **tasks.md:** 确保所有 checkbox 都已勾选 `[x]`
56
+ - **ROADMAP.md**(如存在):标记对应 Phase 已完成
57
+ - **STATE.md:** 清除当前变更信息,历史记录追加归档完成
58
+ - **Git 提交:** `git add .sillyspec/ && git commit -m "docs: archive sillyspec change <change-name>"`
50
59
 
51
60
  ### 最后说:
52
61
 
53
- > ✅ 变更 `<change-name>` 已归档。
54
- >
55
- > 累积规范:
56
- > - `.sillyspec/changes/archive/` — X 个已归档变更
57
- > - `.sillyspec/specs/` — X 份设计文档
58
- > - `.sillyspec/plans/` — X 份实现计划
59
- >
60
- > 继续下一个:`/sillyspec:brainstorm "新想法"`
61
-
62
- ### 更新 STATE.md
63
-
64
- archive 完成后,**必须自动更新** `.sillyspec/STATE.md`:
65
-
66
- - 清除当前变更信息(归档后不再活跃)
67
- - 如果是主变更(有 MASTER.md),标记所有阶段为 ✅,然后清除
68
- - 历史记录追加时间 + 归档完成
69
- - 下一步改为 `/sillyspec:brainstorm "新想法"` 或留空
62
+ > ✅ 变更 `<change-name>` 已归档到 `archive/YYYY-MM-DD-<change-name>/`。继续:`/sillyspec:brainstorm "新想法"`