sillyspec 3.7.0 → 3.7.2
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/package.json +1 -1
- package/src/index.js +11 -483
- package/src/setup.js +234 -66
- package/templates/brainstorm.md +2 -6
- package/templates/execute.md +19 -0
- package/templates/plan.md +6 -6
- package/templates/propose.md +3 -7
- package/templates/quick.md +2 -0
- package/templates/scan.md +17 -5
- package/.claude/commands/sillyspec/archive.md +0 -63
- package/.claude/commands/sillyspec/brainstorm.md +0 -182
- package/.claude/commands/sillyspec/continue.md +0 -32
- package/.claude/commands/sillyspec/execute.md +0 -133
- package/.claude/commands/sillyspec/explore.md +0 -43
- package/.claude/commands/sillyspec/export.md +0 -21
- package/.claude/commands/sillyspec/init.md +0 -64
- package/.claude/commands/sillyspec/plan.md +0 -112
- package/.claude/commands/sillyspec/propose.md +0 -77
- package/.claude/commands/sillyspec/quick.md +0 -76
- package/.claude/commands/sillyspec/resume.md +0 -53
- package/.claude/commands/sillyspec/scan-quick.md +0 -47
- package/.claude/commands/sillyspec/scan.md +0 -135
- package/.claude/commands/sillyspec/status.md +0 -72
- package/.claude/commands/sillyspec/verify.md +0 -87
- package/.claude/commands/sillyspec/workspace-sync.md +0 -89
- package/.claude/commands/sillyspec/workspace.md +0 -67
- package/.sillyspec/config.yaml +0 -13
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,502 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* SillySpec CLI —
|
|
4
|
+
* SillySpec CLI — 安装工具
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* 只负责两件事:init(安装命令模板)和 setup(安装 MCP 工具)。
|
|
7
|
+
* 状态管理由 AI 直接读文件(STATE.md)完成,不需要 CLI。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
11
11
|
import { join, resolve } from 'path';
|
|
12
12
|
import { cmdInit, getVersion } from './init.js';
|
|
13
13
|
|
|
14
|
-
const SILLYSPEC_DIR = '.sillyspec';
|
|
15
|
-
const CODEBASE_DIR = `${SILLYSPEC_DIR}/codebase`;
|
|
16
|
-
const CHANGES_DIR = `${SILLYSPEC_DIR}/changes`;
|
|
17
|
-
|
|
18
|
-
// ── SillySpec 生命周期定义 ──
|
|
19
|
-
// 每个阶段需要的文件,只有全部齐全才算该阶段完成
|
|
20
|
-
|
|
21
|
-
const LIFECYCLE = {
|
|
22
|
-
scan: {
|
|
23
|
-
label: '扫描',
|
|
24
|
-
files: [
|
|
25
|
-
{ path: `${CODEBASE_DIR}/STACK.md`, label: '技术栈' },
|
|
26
|
-
{ path: `${CODEBASE_DIR}/STRUCTURE.md`, label: '目录结构' },
|
|
27
|
-
],
|
|
28
|
-
},
|
|
29
|
-
'scan:deep': {
|
|
30
|
-
label: '深度扫描',
|
|
31
|
-
files: [
|
|
32
|
-
{ path: `${CODEBASE_DIR}/STACK.md`, label: '技术栈' },
|
|
33
|
-
{ path: `${CODEBASE_DIR}/ARCHITECTURE.md`, label: '架构' },
|
|
34
|
-
{ path: `${CODEBASE_DIR}/STRUCTURE.md`, label: '目录结构' },
|
|
35
|
-
{ path: `${CODEBASE_DIR}/CONVENTIONS.md`, label: '代码约定' },
|
|
36
|
-
{ path: `${CODEBASE_DIR}/INTEGRATIONS.md`, label: '集成' },
|
|
37
|
-
{ path: `${CODEBASE_DIR}/TESTING.md`, label: '测试' },
|
|
38
|
-
{ path: `${CODEBASE_DIR}/CONCERNS.md`, label: '技术债务' },
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
brainstorm: {
|
|
42
|
-
label: '需求探索',
|
|
43
|
-
// brainstorm 生成的设计文档在 specs/ 下,文件名不固定
|
|
44
|
-
detect: (dir) => {
|
|
45
|
-
const specsDir = `${dir}/${SILLYSPEC_DIR}/specs`;
|
|
46
|
-
if (!existsSync(specsDir)) return false;
|
|
47
|
-
const files = readdirSync(specsDir).filter(f => f.endsWith('.md'));
|
|
48
|
-
return files.length > 0;
|
|
49
|
-
},
|
|
50
|
-
detectLabel: 'specs/ 下的设计文档',
|
|
51
|
-
},
|
|
52
|
-
propose: {
|
|
53
|
-
label: '生成规范',
|
|
54
|
-
// 需要 changes/<name>/proposal.md
|
|
55
|
-
detect: (dir) => {
|
|
56
|
-
const changesDir = `${dir}/${CHANGES_DIR}`;
|
|
57
|
-
if (!existsSync(changesDir)) return false;
|
|
58
|
-
const changes = readdirSync(changesDir).filter(d => {
|
|
59
|
-
const p = join(changesDir, d);
|
|
60
|
-
return statSync(p).isDirectory() && d !== 'archive';
|
|
61
|
-
});
|
|
62
|
-
return changes.some(c => existsSync(join(changesDir, c, 'proposal.md')));
|
|
63
|
-
},
|
|
64
|
-
detectLabel: 'changes/<name>/proposal.md',
|
|
65
|
-
},
|
|
66
|
-
plan: {
|
|
67
|
-
label: '编写计划',
|
|
68
|
-
detect: (dir) => {
|
|
69
|
-
const changesDir = `${dir}/${CHANGES_DIR}`;
|
|
70
|
-
if (!existsSync(changesDir)) return false;
|
|
71
|
-
const changes = readdirSync(changesDir).filter(d => {
|
|
72
|
-
const p = join(changesDir, d);
|
|
73
|
-
return statSync(p).isDirectory() && d !== 'archive';
|
|
74
|
-
});
|
|
75
|
-
return changes.some(c => existsSync(join(changesDir, c, 'tasks.md')));
|
|
76
|
-
},
|
|
77
|
-
detectLabel: 'changes/<name>/tasks.md',
|
|
78
|
-
},
|
|
79
|
-
execute: {
|
|
80
|
-
label: '执行实现',
|
|
81
|
-
// 有 tasks.md 且 checkbox 未全部完成
|
|
82
|
-
detect: (dir) => {
|
|
83
|
-
const changesDir = `${dir}/${CHANGES_DIR}`;
|
|
84
|
-
if (!existsSync(changesDir)) return false;
|
|
85
|
-
const changes = readdirSync(changesDir).filter(d => {
|
|
86
|
-
const p = join(changesDir, d);
|
|
87
|
-
return statSync(p).isDirectory() && d !== 'archive';
|
|
88
|
-
});
|
|
89
|
-
return changes.some(c => {
|
|
90
|
-
const tasksPath = join(changesDir, c, 'tasks.md');
|
|
91
|
-
if (!existsSync(tasksPath)) return false;
|
|
92
|
-
const content = readFileSync(tasksPath, 'utf8');
|
|
93
|
-
// 有未完成的 checkbox (- [ ])
|
|
94
|
-
const unchecked = (content.match(/- \[ \]/g) || []).length;
|
|
95
|
-
return unchecked > 0;
|
|
96
|
-
});
|
|
97
|
-
},
|
|
98
|
-
detectLabel: 'tasks.md 有未完成项',
|
|
99
|
-
},
|
|
100
|
-
verify: {
|
|
101
|
-
label: '验证实现',
|
|
102
|
-
// tasks.md 全部完成但没有归档
|
|
103
|
-
detect: (dir) => {
|
|
104
|
-
const changesDir = `${dir}/${CHANGES_DIR}`;
|
|
105
|
-
if (!existsSync(changesDir)) return false;
|
|
106
|
-
const changes = readdirSync(changesDir).filter(d => {
|
|
107
|
-
const p = join(changesDir, d);
|
|
108
|
-
return statSync(p).isDirectory() && d !== 'archive';
|
|
109
|
-
});
|
|
110
|
-
return changes.some(c => {
|
|
111
|
-
const tasksPath = join(changesDir, c, 'tasks.md');
|
|
112
|
-
if (!existsSync(tasksPath)) return false;
|
|
113
|
-
const content = readFileSync(tasksPath, 'utf8');
|
|
114
|
-
const unchecked = (content.match(/- \[ \]/g) || []).length;
|
|
115
|
-
const checked = (content.match(/- \[x\]/gi) || []).length;
|
|
116
|
-
return unchecked === 0 && checked > 0;
|
|
117
|
-
});
|
|
118
|
-
},
|
|
119
|
-
detectLabel: 'tasks.md 全部完成',
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
// ── 工具函数 ──
|
|
124
|
-
|
|
125
|
-
function getLatestChange(dir) {
|
|
126
|
-
const changesDir = join(dir, CHANGES_DIR);
|
|
127
|
-
if (!existsSync(changesDir)) return null;
|
|
128
|
-
|
|
129
|
-
const changes = readdirSync(changesDir)
|
|
130
|
-
.filter(d => {
|
|
131
|
-
const p = join(changesDir, d);
|
|
132
|
-
return statSync(p).isDirectory() && d !== 'archive';
|
|
133
|
-
})
|
|
134
|
-
.sort((a, b) => {
|
|
135
|
-
const sa = statSync(join(changesDir, a));
|
|
136
|
-
const sb = statSync(join(changesDir, b));
|
|
137
|
-
return sb.mtimeMs - sa.mtimeMs;
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
return changes[0] || null;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function checkChangeFiles(dir, changeName) {
|
|
144
|
-
const changeDir = join(dir, CHANGES_DIR, changeName);
|
|
145
|
-
if (!existsSync(changeDir)) return {};
|
|
146
|
-
|
|
147
|
-
const checks = {
|
|
148
|
-
proposal: existsSync(join(changeDir, 'proposal.md')),
|
|
149
|
-
design: existsSync(join(changeDir, 'design.md')),
|
|
150
|
-
tasks: existsSync(join(changeDir, 'tasks.md')),
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
// 统计 tasks 完成情况
|
|
154
|
-
const tasksPath = join(changeDir, 'tasks.md');
|
|
155
|
-
if (checks.tasks) {
|
|
156
|
-
const content = readFileSync(tasksPath, 'utf8');
|
|
157
|
-
checks.totalTasks = (content.match(/- \[[ x]\]/gi) || []).length;
|
|
158
|
-
checks.completedTasks = (content.match(/- \[x\]/gi) || []).length;
|
|
159
|
-
checks.pendingTasks = (content.match(/- \[ \]/g) || []).length;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return checks;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function checkCodebaseFiles(dir) {
|
|
166
|
-
const files = {};
|
|
167
|
-
const allFiles = [
|
|
168
|
-
'STACK.md', 'ARCHITECTURE.md', 'STRUCTURE.md',
|
|
169
|
-
'CONVENTIONS.md', 'INTEGRATIONS.md', 'TESTING.md',
|
|
170
|
-
'CONCERNS.md', 'PROJECT.md', 'SCAN-RAW.md',
|
|
171
|
-
];
|
|
172
|
-
for (const f of allFiles) {
|
|
173
|
-
const p = join(dir, CODEBASE_DIR, f);
|
|
174
|
-
files[f] = existsSync(p);
|
|
175
|
-
}
|
|
176
|
-
return files;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// ── 确定当前阶段 ──
|
|
180
|
-
|
|
181
|
-
function determinePhase(dir) {
|
|
182
|
-
const codebase = checkCodebaseFiles(dir);
|
|
183
|
-
const latestChange = getLatestChange(dir);
|
|
184
|
-
const changeFiles = latestChange ? checkChangeFiles(dir, latestChange) : {};
|
|
185
|
-
|
|
186
|
-
// 检查大模块模式(MASTER.md)
|
|
187
|
-
let masterMode = false;
|
|
188
|
-
let masterChange = null;
|
|
189
|
-
let stages = {};
|
|
190
|
-
if (latestChange) {
|
|
191
|
-
const masterPath = join(dir, CHANGES_DIR, latestChange, 'MASTER.md');
|
|
192
|
-
if (existsSync(masterPath)) {
|
|
193
|
-
masterMode = true;
|
|
194
|
-
masterChange = latestChange;
|
|
195
|
-
const stagesDir = join(dir, CHANGES_DIR, latestChange, 'stages');
|
|
196
|
-
if (existsSync(stagesDir)) {
|
|
197
|
-
for (const stage of readdirSync(stagesDir)) {
|
|
198
|
-
const stageDir = join(stagesDir, stage);
|
|
199
|
-
if (statSync(stageDir).isDirectory()) {
|
|
200
|
-
const stageFiles = checkChangeFiles(dir, join(latestChange, 'stages', stage));
|
|
201
|
-
stages[stage] = stageFiles;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// 检查深度扫描是否做过
|
|
209
|
-
const hasDeepScan = codebase['ARCHITECTURE.md'] && codebase['CONVENTIONS.md'];
|
|
210
|
-
|
|
211
|
-
// 检查快速扫描
|
|
212
|
-
const hasQuickScan = codebase['STACK.md'] && codebase['STRUCTURE.md'];
|
|
213
|
-
|
|
214
|
-
// 检查扫描中断
|
|
215
|
-
if (existsSync(join(dir, CODEBASE_DIR, 'SCAN-RAW.md')) && !hasDeepScan) {
|
|
216
|
-
// 有预处理数据但文档不全,说明深度扫描中断
|
|
217
|
-
const missingDeep = Object.entries(codebase)
|
|
218
|
-
.filter(([f, exists]) => ['ARCHITECTURE.md','CONVENTIONS.md','INTEGRATIONS.md','TESTING.md','CONCERNS.md'].includes(f) && !exists)
|
|
219
|
-
.map(([f]) => f);
|
|
220
|
-
return {
|
|
221
|
-
phase: 'scan:resume',
|
|
222
|
-
label: '深度扫描(中断恢复)',
|
|
223
|
-
command: '/sillyspec:scan --deep',
|
|
224
|
-
details: { missingDocs: missingDeep },
|
|
225
|
-
masterMode: false,
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (hasQuickScan && !hasDeepScan) {
|
|
230
|
-
return {
|
|
231
|
-
phase: 'scan:quick_done',
|
|
232
|
-
label: '快速扫描完成',
|
|
233
|
-
command: '/sillyspec:scan --deep',
|
|
234
|
-
suggestion: '快速扫描已完成,可以用深度扫描获取更详细的分析',
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (!hasQuickScan) {
|
|
239
|
-
return {
|
|
240
|
-
phase: 'init',
|
|
241
|
-
label: '未开始',
|
|
242
|
-
command: '/sillyspec:scan',
|
|
243
|
-
suggestion: '棕地项目从这里开始',
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// 已扫描,检查是否做过 brainstorm
|
|
248
|
-
const specsDir = join(dir, SILLYSPEC_DIR, 'specs');
|
|
249
|
-
let hasBrainstorm = false;
|
|
250
|
-
let designDocs = [];
|
|
251
|
-
if (existsSync(specsDir)) {
|
|
252
|
-
designDocs = readdirSync(specsDir).filter(f => f.endsWith('.md'));
|
|
253
|
-
hasBrainstorm = designDocs.length > 0;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (!hasBrainstorm) {
|
|
257
|
-
return {
|
|
258
|
-
phase: 'brainstorm',
|
|
259
|
-
label: '需求探索',
|
|
260
|
-
command: '/sillyspec:brainstorm',
|
|
261
|
-
suggestion: '扫描已完成,开始头脑风暴',
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// 有 brainstorm,检查变更
|
|
266
|
-
if (!latestChange) {
|
|
267
|
-
return {
|
|
268
|
-
phase: 'propose',
|
|
269
|
-
label: '生成规范',
|
|
270
|
-
command: '/sillyspec:propose',
|
|
271
|
-
suggestion: '头脑风暴完成,开始生成结构化规范',
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// 大模块模式
|
|
276
|
-
if (masterMode) {
|
|
277
|
-
return {
|
|
278
|
-
phase: 'master',
|
|
279
|
-
label: '大模块执行中',
|
|
280
|
-
masterChange,
|
|
281
|
-
stages,
|
|
282
|
-
command: '/sillyspec:resume',
|
|
283
|
-
suggestion: '大模块模式,各阶段独立生命周期',
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// 普通变更
|
|
288
|
-
if (!changeFiles.proposal) {
|
|
289
|
-
return {
|
|
290
|
-
phase: 'propose',
|
|
291
|
-
label: '生成规范',
|
|
292
|
-
latestChange,
|
|
293
|
-
command: `/sillyspec:propose ${latestChange}`,
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (!changeFiles.design) {
|
|
298
|
-
return {
|
|
299
|
-
phase: 'plan',
|
|
300
|
-
label: '编写计划',
|
|
301
|
-
latestChange,
|
|
302
|
-
command: `/sillyspec:plan ${latestChange}`,
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (!changeFiles.tasks) {
|
|
307
|
-
return {
|
|
308
|
-
phase: 'plan',
|
|
309
|
-
label: '编写计划(生成任务)',
|
|
310
|
-
latestChange,
|
|
311
|
-
command: `/sillyspec:plan ${latestChange}`,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// 有 tasks,检查完成情况
|
|
316
|
-
if (changeFiles.pendingTasks > 0) {
|
|
317
|
-
return {
|
|
318
|
-
phase: 'execute',
|
|
319
|
-
label: '执行实现',
|
|
320
|
-
latestChange,
|
|
321
|
-
progress: {
|
|
322
|
-
completed: changeFiles.completedTasks,
|
|
323
|
-
total: changeFiles.totalTasks,
|
|
324
|
-
pending: changeFiles.pendingTasks,
|
|
325
|
-
},
|
|
326
|
-
command: `/sillyspec:execute ${latestChange}`,
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// tasks 全部完成
|
|
331
|
-
return {
|
|
332
|
-
phase: 'verify',
|
|
333
|
-
label: '验证实现',
|
|
334
|
-
latestChange,
|
|
335
|
-
command: `/sillyspec:verify ${latestChange}`,
|
|
336
|
-
suggestion: '所有任务已完成,验证后可归档',
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// ── 命令实现 ──
|
|
341
|
-
|
|
342
|
-
function cmdStatus(dir, options = {}) {
|
|
343
|
-
const phase = determinePhase(dir);
|
|
344
|
-
const codebase = checkCodebaseFiles(dir);
|
|
345
|
-
const latestChange = getLatestChange(dir);
|
|
346
|
-
|
|
347
|
-
if (options.json) {
|
|
348
|
-
const output = {
|
|
349
|
-
phase: phase.phase,
|
|
350
|
-
label: phase.label,
|
|
351
|
-
command: phase.command,
|
|
352
|
-
suggestion: phase.suggestion || null,
|
|
353
|
-
currentChange: phase.latestChange || null,
|
|
354
|
-
codebase: codebase,
|
|
355
|
-
masterMode: phase.masterMode || false,
|
|
356
|
-
...(phase.masterMode ? { masterChange: phase.masterChange, stages: phase.stages } : {}),
|
|
357
|
-
...(phase.progress ? { progress: phase.progress } : {}),
|
|
358
|
-
...(phase.details ? { details: phase.details } : {}),
|
|
359
|
-
};
|
|
360
|
-
console.log(JSON.stringify(output, null, 2));
|
|
361
|
-
} else {
|
|
362
|
-
// 人类可读
|
|
363
|
-
console.log('');
|
|
364
|
-
console.log(`📋 当前阶段: ${phase.label}`);
|
|
365
|
-
console.log(`📦 当前变更: ${phase.latestChange || '无'}`);
|
|
366
|
-
|
|
367
|
-
if (phase.progress) {
|
|
368
|
-
const pct = Math.round(phase.progress.completed / phase.progress.total * 100);
|
|
369
|
-
console.log(`📊 进度: ${phase.progress.completed}/${phase.progress.total} (${pct}%)`);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
console.log(`➡️ 下一步: ${phase.command}`);
|
|
373
|
-
|
|
374
|
-
if (phase.suggestion) {
|
|
375
|
-
console.log(`💡 ${phase.suggestion}`);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// codebase 文件概览
|
|
379
|
-
const codebaseCount = Object.values(codebase).filter(Boolean).length;
|
|
380
|
-
console.log(``);
|
|
381
|
-
console.log(`🗂️ codebase 文档: ${codebaseCount}/9`);
|
|
382
|
-
|
|
383
|
-
// 大模块信息
|
|
384
|
-
if (phase.masterMode && phase.stages) {
|
|
385
|
-
console.log('');
|
|
386
|
-
console.log('🏗️ 大模块阶段:');
|
|
387
|
-
for (const [name, files] of Object.entries(phase.stages)) {
|
|
388
|
-
const hasTasks = files.tasks;
|
|
389
|
-
const pending = files.pendingTasks || 0;
|
|
390
|
-
const total = files.totalTasks || 0;
|
|
391
|
-
const status = !hasTasks ? '⬜' : pending > 0 ? '🔄' : '✅';
|
|
392
|
-
console.log(` ${status} ${name}: ${hasTasks ? `${total - pending}/${total}` : '未计划'}`);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function cmdNext(dir, options = {}) {
|
|
399
|
-
const phase = determinePhase(dir);
|
|
400
|
-
|
|
401
|
-
if (options.json) {
|
|
402
|
-
const output = {
|
|
403
|
-
phase: phase.phase,
|
|
404
|
-
command: phase.command,
|
|
405
|
-
reason: phase.suggestion || `当前处于 ${phase.label} 阶段`,
|
|
406
|
-
...(phase.details ? { missingDocs: phase.details.missingDocs } : {}),
|
|
407
|
-
};
|
|
408
|
-
console.log(JSON.stringify(output, null, 2));
|
|
409
|
-
} else {
|
|
410
|
-
console.log(phase.command);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function cmdCheck(dir, options = {}) {
|
|
415
|
-
// 只检查文件存在性,用于断点续扫
|
|
416
|
-
const codebase = checkCodebaseFiles(dir);
|
|
417
|
-
const deepFiles = ['ARCHITECTURE.md','CONVENTIONS.md','INTEGRATIONS.md','TESTING.md','CONCERNS.md'];
|
|
418
|
-
|
|
419
|
-
const result = {
|
|
420
|
-
quickScan: {
|
|
421
|
-
STACK: codebase['STACK.md'],
|
|
422
|
-
STRUCTURE: codebase['STRUCTURE.md'],
|
|
423
|
-
done: codebase['STACK.md'] && codebase['STRUCTURE.md'],
|
|
424
|
-
},
|
|
425
|
-
deepScan: {},
|
|
426
|
-
allDone: true,
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
for (const f of deepFiles) {
|
|
430
|
-
const name = f.replace('.md', '');
|
|
431
|
-
result.deepScan[name] = codebase[f];
|
|
432
|
-
if (!codebase[f]) result.allDone = false;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
if (!result.quickScan.done) result.allDone = false;
|
|
436
|
-
|
|
437
|
-
// 检查误放文件
|
|
438
|
-
const misplaced = [];
|
|
439
|
-
const docNames = ['ARCHITECTURE.md','STACK.md','STRUCTURE.md','CONVENTIONS.md','INTEGRATIONS.md','TESTING.md','CONCERNS.md','PROJECT.md','SCAN-RAW.md'];
|
|
440
|
-
for (const name of docNames) {
|
|
441
|
-
// 检查项目根目录
|
|
442
|
-
if (existsSync(join(dir, name))) {
|
|
443
|
-
misplaced.push({ file: name, location: './', shouldBe: `${CODEBASE_DIR}/` });
|
|
444
|
-
}
|
|
445
|
-
// 检查 .sillyspec/ 但不在 codebase/ 下
|
|
446
|
-
if (existsSync(join(dir, SILLYSPEC_DIR, name))) {
|
|
447
|
-
misplaced.push({ file: name, location: `${SILLYSPEC_DIR}/`, shouldBe: `${CODEBASE_DIR}/` });
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
result.misplaced = misplaced;
|
|
451
|
-
|
|
452
|
-
if (options.json) {
|
|
453
|
-
console.log(JSON.stringify(result, null, 2));
|
|
454
|
-
} else {
|
|
455
|
-
console.log('');
|
|
456
|
-
console.log('📊 快速扫描:');
|
|
457
|
-
console.log(` ${result.quickScan.STACK ? '✅' : '⬜'} STACK.md`);
|
|
458
|
-
console.log(` ${result.quickScan.STRUCTURE ? '✅' : '⬜'} STRUCTURE.md`);
|
|
459
|
-
console.log('');
|
|
460
|
-
console.log('📊 深度扫描:');
|
|
461
|
-
for (const [name, exists] of Object.entries(result.deepScan)) {
|
|
462
|
-
console.log(` ${exists ? '✅' : '⬜'} ${name}.md`);
|
|
463
|
-
}
|
|
464
|
-
if (result.misplaced.length > 0) {
|
|
465
|
-
console.log('');
|
|
466
|
-
console.log('❌ 误放文件:');
|
|
467
|
-
for (const m of result.misplaced) {
|
|
468
|
-
console.log(` ${m.file} 在 ${m.location},应在 ${m.shouldBe}`);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
14
|
// ── CLI 入口 ──
|
|
475
15
|
|
|
476
16
|
function printUsage() {
|
|
477
17
|
console.log(`
|
|
478
|
-
SillySpec CLI —
|
|
18
|
+
SillySpec CLI — 规范驱动开发工具包
|
|
479
19
|
|
|
480
20
|
用法:
|
|
481
|
-
sillyspec
|
|
482
|
-
sillyspec next [--json] 显示下一步该执行的命令
|
|
483
|
-
sillyspec check [--json] 检查文档完整性和路径
|
|
484
|
-
sillyspec setup [--list] 安装推荐 MCP 工具(Context7、grep.app、浏览器)
|
|
485
|
-
sillyspec init 初始化 SillySpec(自动检测工具,零交互)
|
|
21
|
+
sillyspec init 初始化(零交互,自动检测工具)
|
|
486
22
|
[--tool <name>] 只安装指定工具
|
|
487
23
|
[--workspace] 工作区模式
|
|
488
|
-
[--interactive]
|
|
24
|
+
[--interactive] 交互式引导
|
|
489
25
|
[--dir <path>] 指定目录
|
|
26
|
+
sillyspec setup [--list] 安装推荐 MCP 工具
|
|
27
|
+
[--list] 查看已安装状态
|
|
490
28
|
|
|
491
29
|
选项:
|
|
492
30
|
--json 输出 JSON(给 AI 程序化读取)
|
|
493
31
|
--dir <path> 指定项目目录(默认当前目录)
|
|
494
32
|
|
|
495
33
|
示例:
|
|
496
|
-
sillyspec
|
|
497
|
-
sillyspec
|
|
498
|
-
sillyspec
|
|
499
|
-
sillyspec check
|
|
34
|
+
sillyspec init
|
|
35
|
+
sillyspec init --tool claude
|
|
36
|
+
sillyspec init --workspace
|
|
500
37
|
sillyspec setup
|
|
501
38
|
sillyspec setup --list
|
|
502
39
|
`);
|
|
@@ -552,15 +89,6 @@ async function main() {
|
|
|
552
89
|
}
|
|
553
90
|
|
|
554
91
|
switch (command) {
|
|
555
|
-
case 'status':
|
|
556
|
-
cmdStatus(dir, { json });
|
|
557
|
-
break;
|
|
558
|
-
case 'next':
|
|
559
|
-
cmdNext(dir, { json });
|
|
560
|
-
break;
|
|
561
|
-
case 'check':
|
|
562
|
-
cmdCheck(dir, { json });
|
|
563
|
-
break;
|
|
564
92
|
case 'init':
|
|
565
93
|
await cmdInit(dir, { tool, workspace, interactive });
|
|
566
94
|
break;
|