sillyspec 3.7.13 → 3.7.15
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/.claude/skills/sillyspec-archive/SKILL.md +77 -0
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +591 -0
- package/.claude/skills/sillyspec-continue/SKILL.md +44 -0
- package/.claude/skills/sillyspec-execute/SKILL.md +233 -0
- package/.claude/skills/sillyspec-explore/SKILL.md +96 -0
- package/.claude/skills/sillyspec-export/SKILL.md +53 -0
- package/.claude/skills/sillyspec-init/SKILL.md +171 -0
- package/.claude/skills/sillyspec-plan/SKILL.md +263 -0
- package/.claude/skills/sillyspec-propose/SKILL.md +248 -0
- package/.claude/skills/sillyspec-quick/SKILL.md +102 -0
- package/.claude/skills/sillyspec-resume/SKILL.md +111 -0
- package/{templates/scan.md → .claude/skills/sillyspec-scan/SKILL.md} +22 -60
- package/.claude/skills/sillyspec-state/SKILL.md +54 -0
- package/.claude/skills/sillyspec-status/SKILL.md +131 -0
- package/.claude/skills/sillyspec-verify/SKILL.md +146 -0
- package/.claude/skills/sillyspec-workspace/SKILL.md +149 -0
- package/.sillyspec/changes/run-command-design/design.md +1230 -0
- package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
- package/.sillyspec/knowledge/INDEX.md +8 -0
- package/.sillyspec/knowledge/uncategorized.md +3 -0
- package/.sillyspec/projects/sillyspec.yaml +3 -0
- package/README.md +4 -0
- package/logo.jpg +0 -0
- package/package.json +7 -1
- package/packages/dashboard/dist/assets/index-Bx0cgoK_.js +7446 -0
- package/packages/dashboard/dist/assets/index-DbkUSsNO.css +1 -0
- package/packages/dashboard/dist/favicon.jpg +0 -0
- package/packages/dashboard/dist/index.html +2 -2
- package/packages/dashboard/dist/logo.jpg +0 -0
- package/packages/dashboard/package-lock.json +220 -0
- package/packages/dashboard/package.json +8 -5
- package/packages/dashboard/public/favicon.jpg +0 -0
- package/packages/dashboard/public/logo.jpg +0 -0
- package/packages/dashboard/server/index.js +92 -4
- package/packages/dashboard/server/parser.js +252 -28
- package/packages/dashboard/src/App.vue +139 -9
- package/packages/dashboard/src/components/ActionBar.vue +23 -39
- package/packages/dashboard/src/components/CommandPalette.vue +40 -65
- package/packages/dashboard/src/components/DetailPanel.vue +68 -53
- package/packages/dashboard/src/components/DocPreview.vue +137 -20
- package/packages/dashboard/src/components/DocTree.vue +48 -26
- package/packages/dashboard/src/components/LogStream.vue +12 -32
- package/packages/dashboard/src/components/PipelineStage.vue +8 -8
- package/packages/dashboard/src/components/PipelineView.vue +35 -43
- package/packages/dashboard/src/components/ProjectList.vue +52 -77
- package/packages/dashboard/src/components/ProjectOverview.vue +178 -0
- package/packages/dashboard/src/components/StageBadge.vue +13 -13
- package/packages/dashboard/src/components/StepCard.vue +11 -11
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -0
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -0
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -0
- package/packages/dashboard/src/main.js +4 -1
- package/packages/dashboard/src/style.css +13 -13
- package/src/index.js +55 -8
- package/src/init.js +66 -196
- package/src/migrate.js +1 -18
- package/src/progress.js +279 -281
- package/src/run.js +320 -0
- package/src/setup.js +1 -9
- package/src/stages/brainstorm.js +210 -0
- package/src/stages/execute.js +190 -0
- package/src/stages/index.js +22 -0
- package/src/stages/plan.js +118 -0
- package/src/stages/propose.js +115 -0
- package/src/stages/verify.js +98 -0
- package/packages/dashboard/dist/assets/index-5Rrvs0Rl.css +0 -1
- package/packages/dashboard/dist/assets/index-ZNToqi9V.js +0 -17
- package/templates/archive.md +0 -121
- package/templates/brainstorm.md +0 -246
- package/templates/commit.md +0 -123
- package/templates/continue.md +0 -32
- package/templates/execute.md +0 -314
- package/templates/explore.md +0 -60
- package/templates/export.md +0 -21
- package/templates/init.md +0 -61
- package/templates/plan.md +0 -157
- package/templates/progress-format.md +0 -90
- package/templates/propose.md +0 -73
- package/templates/quick.md +0 -135
- package/templates/resume-dialog.md +0 -55
- package/templates/resume.md +0 -53
- package/templates/scan-quick.md +0 -49
- package/templates/skills/playwright-e2e/SKILL.md +0 -340
- package/templates/status.md +0 -72
- package/templates/verify.md +0 -253
- package/templates/workspace-sync.md +0 -89
- package/templates/workspace.md +0 -67
- /package/.sillyspec/{docs/sillyspec/brainstorm → changes/brainstorm-archive}/2026-04-05-dashboard-design.md +0 -0
- /package/.sillyspec/{docs/sillyspec/brainstorm → changes/brainstorm-archive}/2026-04-05-unified-docs-design.md +0 -0
- /package/.sillyspec/{specs/2026-04-05-dashboard-design.md → changes/dashboard/design.md.braindraft} +0 -0
- /package/.sillyspec/{specs/2026-04-05-unified-docs-design.md → changes/unified-docs-design/design.md} +0 -0
package/src/progress.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
* SillySpec ProgressManager — 进度恢复管理
|
|
3
3
|
*
|
|
4
4
|
* 纯 Node.js,无外部依赖。管理 .sillyspec/.runtime/progress.json。
|
|
5
|
+
*
|
|
6
|
+
* Schema v2: { project, currentStage, stages: { [name]: { status, steps, startedAt, completedAt } }, lastActive }
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, readdirSync, unlinkSync
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, readdirSync, unlinkSync } from 'fs';
|
|
8
10
|
import { join, resolve, dirname } from 'path';
|
|
9
11
|
import { fileURLToPath } from 'url';
|
|
10
12
|
|
|
@@ -14,403 +16,399 @@ const RUNTIME_DIR = '.sillyspec/.runtime';
|
|
|
14
16
|
const PROGRESS_FILE = 'progress.json';
|
|
15
17
|
const BACKUP_FILE = 'progress.json.bak';
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
brainstorm: [
|
|
21
|
-
{ id: 1, name: '加载项目上下文' },
|
|
22
|
-
{ id: 2, name: '协作与复用检查' },
|
|
23
|
-
{ id: 3, name: '原型/设计图分析' },
|
|
24
|
-
{ id: 4, name: '评估需求范围' },
|
|
25
|
-
{ id: 5, name: '对话式探索' },
|
|
26
|
-
{ id: 6, name: '提出方案并推荐' },
|
|
27
|
-
{ id: 7, name: '分段展示设计' },
|
|
28
|
-
{ id: 8, name: '写设计文档' },
|
|
29
|
-
{ id: 9, name: 'AI 自审' },
|
|
30
|
-
{ id: 10, name: '用户确认设计方案' },
|
|
31
|
-
{ id: 11, name: '输出技术方案' },
|
|
32
|
-
{ id: 12, name: '更新 STATE.md' },
|
|
33
|
-
{ id: 13, name: '保存最终进度' },
|
|
34
|
-
],
|
|
35
|
-
propose: [
|
|
36
|
-
{ id: 1, name: '变更范围' },
|
|
37
|
-
{ id: 2, name: '方案设计' },
|
|
38
|
-
{ id: 3, name: '任务拆分' },
|
|
39
|
-
{ id: 4, name: '优先级排序' },
|
|
40
|
-
{ id: 5, name: '规范输出' },
|
|
41
|
-
],
|
|
42
|
-
plan: [
|
|
43
|
-
{ id: 1, name: '任务分析' },
|
|
44
|
-
{ id: 2, name: '依赖梳理' },
|
|
45
|
-
{ id: 3, name: '实现路径' },
|
|
46
|
-
{ id: 4, name: '风险评估' },
|
|
47
|
-
{ id: 5, name: '计划输出' },
|
|
48
|
-
],
|
|
49
|
-
execute: [
|
|
50
|
-
{ id: 1, name: '环境准备' },
|
|
51
|
-
{ id: 2, name: '编码实现' },
|
|
52
|
-
{ id: 3, name: '单元测试' },
|
|
53
|
-
{ id: 4, name: '代码审查' },
|
|
54
|
-
{ id: 5, name: '集成验证' },
|
|
55
|
-
],
|
|
56
|
-
verify: [
|
|
57
|
-
{ id: 1, name: '规范对照' },
|
|
58
|
-
{ id: 2, name: '功能测试' },
|
|
59
|
-
{ id: 3, name: '边界测试' },
|
|
60
|
-
{ id: 4, name: '回归测试' },
|
|
61
|
-
{ id: 5, name: '验收报告' },
|
|
62
|
-
],
|
|
63
|
-
};
|
|
19
|
+
const CURRENT_VERSION = 2;
|
|
20
|
+
const VALID_STAGES = ['brainstorm', 'propose', 'plan', 'execute', 'verify'];
|
|
21
|
+
const VALID_STATUSES = ['pending', 'in-progress', 'completed', 'failed', 'blocked'];
|
|
64
22
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
checkpoint: '',
|
|
72
|
-
stages: {},
|
|
73
|
-
artifacts: [],
|
|
23
|
+
const STAGE_LABELS = {
|
|
24
|
+
brainstorm: '🧠 需求探索',
|
|
25
|
+
propose: '📋 方案设计',
|
|
26
|
+
plan: '📐 实现计划',
|
|
27
|
+
execute: '⚡ 波次执行',
|
|
28
|
+
verify: '🔍 验证确认',
|
|
74
29
|
};
|
|
75
30
|
|
|
76
31
|
function emptyStage() {
|
|
77
|
-
return {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
};
|
|
32
|
+
return { status: 'pending', steps: [], startedAt: null, completedAt: null };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function makeInitialProgress(project) {
|
|
36
|
+
const stages = {};
|
|
37
|
+
for (const s of VALID_STAGES) stages[s] = emptyStage();
|
|
38
|
+
return { _version: CURRENT_VERSION, project: project || '', currentStage: '', stages, lastActive: null };
|
|
85
39
|
}
|
|
86
40
|
|
|
87
41
|
// ── ProgressManager ──
|
|
88
42
|
|
|
89
43
|
export class ProgressManager {
|
|
90
|
-
// ──
|
|
44
|
+
// ── 核心读写 ──
|
|
91
45
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
46
|
+
_path(cwd, ...parts) {
|
|
47
|
+
return join(cwd, RUNTIME_DIR, ...parts);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
read(cwd) {
|
|
51
|
+
const progressPath = this._path(cwd, PROGRESS_FILE);
|
|
52
|
+
const backupPath = this._path(cwd, BACKUP_FILE);
|
|
53
|
+
|
|
54
|
+
for (const p of [progressPath, backupPath]) {
|
|
55
|
+
if (!existsSync(p)) continue;
|
|
56
|
+
const parsed = this._parseWithRecovery(readFileSync(p, 'utf8'));
|
|
57
|
+
if (parsed) {
|
|
58
|
+
if (p === backupPath) {
|
|
59
|
+
console.log('⚠️ progress.json 损坏,已从备份恢复');
|
|
60
|
+
writeFileSync(progressPath, JSON.stringify(parsed, null, 2) + '\n');
|
|
61
|
+
}
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
98
64
|
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_write(cwd, data) {
|
|
69
|
+
const progressPath = this._path(cwd, PROGRESS_FILE);
|
|
70
|
+
const tmpPath = progressPath + '.tmp';
|
|
71
|
+
this._ensureDir(cwd);
|
|
72
|
+
writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\n');
|
|
73
|
+
renameSync(tmpPath, progressPath);
|
|
74
|
+
}
|
|
99
75
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
for (const
|
|
105
|
-
|
|
76
|
+
_ensureDir(cwd) {
|
|
77
|
+
const runtimeDir = this._path(cwd);
|
|
78
|
+
if (!existsSync(runtimeDir)) {
|
|
79
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
80
|
+
for (const d of ['artifacts', 'history', 'logs', 'templates']) {
|
|
81
|
+
mkdirSync(join(runtimeDir, d), { recursive: true });
|
|
106
82
|
}
|
|
107
|
-
writeFileSync(progressPath, JSON.stringify(data, null, 2) + '\n');
|
|
108
|
-
console.log(`✅ 已创建 ${join(RUNTIME_DIR, PROGRESS_FILE)}`);
|
|
109
|
-
} else {
|
|
110
|
-
console.log(`ℹ️ ${join(RUNTIME_DIR, PROGRESS_FILE)} 已存在,跳过`);
|
|
111
83
|
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_backup(cwd) {
|
|
87
|
+
const p = this._path(cwd, PROGRESS_FILE);
|
|
88
|
+
if (existsSync(p)) renameSync(p, this._path(cwd, BACKUP_FILE));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ── CLI 命令 ──
|
|
92
|
+
|
|
93
|
+
init(cwd) {
|
|
94
|
+
this._ensureDir(cwd);
|
|
95
|
+
const progressPath = this._path(cwd, PROGRESS_FILE);
|
|
112
96
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const resumeDest = join(runtimeDir, 'templates', 'resume-dialog.md');
|
|
117
|
-
if (existsSync(resumeSrc) && !existsSync(resumeDest)) {
|
|
118
|
-
copyFileSync(resumeSrc, resumeDest);
|
|
97
|
+
if (existsSync(progressPath)) {
|
|
98
|
+
console.log(`ℹ️ progress.json 已存在,跳过`);
|
|
99
|
+
return this.read(cwd);
|
|
119
100
|
}
|
|
120
101
|
|
|
102
|
+
const project = require('path').basename(cwd);
|
|
103
|
+
const data = makeInitialProgress(project);
|
|
104
|
+
this._write(cwd, data);
|
|
105
|
+
console.log(`✅ 已创建 ${join(RUNTIME_DIR, PROGRESS_FILE)}`);
|
|
106
|
+
|
|
121
107
|
// 创建 user-inputs.md
|
|
122
|
-
const inputsPath =
|
|
108
|
+
const inputsPath = this._path(cwd, 'user-inputs.md');
|
|
123
109
|
if (!existsSync(inputsPath)) {
|
|
124
110
|
writeFileSync(inputsPath, '# 用户输入记录\n\n> 每步完成时由 AI 自动追加,记录用户所有原话。\n\n');
|
|
125
111
|
}
|
|
126
112
|
|
|
127
|
-
// .gitignore
|
|
128
113
|
this._ensureGitignore(cwd);
|
|
114
|
+
return data;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
setStage(cwd, stage) {
|
|
118
|
+
if (!VALID_STAGES.includes(stage)) {
|
|
119
|
+
console.log(`❌ 未知阶段: ${stage},可选: ${VALID_STAGES.join(', ')}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const data = this._readOrInit(cwd);
|
|
124
|
+
if (!data) return;
|
|
125
|
+
|
|
126
|
+
if (!data.stages[stage]) data.stages[stage] = emptyStage();
|
|
127
|
+
const stageData = data.stages[stage];
|
|
129
128
|
|
|
130
|
-
|
|
129
|
+
data.currentStage = stage;
|
|
130
|
+
if (stageData.status === 'pending') {
|
|
131
|
+
stageData.status = 'in-progress';
|
|
132
|
+
stageData.startedAt = new Date().toISOString();
|
|
133
|
+
}
|
|
134
|
+
data.lastActive = new Date().toISOString();
|
|
135
|
+
|
|
136
|
+
this._backup(cwd);
|
|
137
|
+
this._write(cwd, data);
|
|
138
|
+
console.log(`✅ 当前阶段已设为: ${STAGE_LABELS[stage] || stage} (${stageData.status})`);
|
|
131
139
|
}
|
|
132
140
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
141
|
+
addStep(cwd, stage, stepName) {
|
|
142
|
+
if (!stepName) { console.log('❌ 请指定步骤名称'); return; }
|
|
143
|
+
const data = this._requireStage(cwd, stage);
|
|
144
|
+
if (!data) return;
|
|
136
145
|
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (parsed) return parsed;
|
|
146
|
+
const stageData = data.stages[stage];
|
|
147
|
+
if (stageData.steps.some(s => s.name === stepName)) {
|
|
148
|
+
console.log(`ℹ️ 步骤 "${stepName}" 已存在于 ${stage}`);
|
|
149
|
+
return;
|
|
142
150
|
}
|
|
143
151
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
stageData.steps.push({ name: stepName, status: 'pending' });
|
|
153
|
+
data.lastActive = new Date().toISOString();
|
|
154
|
+
|
|
155
|
+
this._backup(cwd);
|
|
156
|
+
this._write(cwd, data);
|
|
157
|
+
console.log(`✅ 已添加步骤: ${stage}/${stepName}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
updateStep(cwd, stage, stepName, options = {}) {
|
|
161
|
+
const { status, output } = options;
|
|
162
|
+
if (!stepName) { console.log('❌ 请指定步骤名称'); return; }
|
|
163
|
+
const data = this._requireStage(cwd, stage);
|
|
164
|
+
if (!data) return;
|
|
165
|
+
|
|
166
|
+
const stageData = data.stages[stage];
|
|
167
|
+
const step = stageData.steps.find(s => s.name === stepName);
|
|
168
|
+
if (!step) { console.log(`❌ 步骤不存在: ${stage}/${stepName}`); return; }
|
|
169
|
+
|
|
170
|
+
if (status) {
|
|
171
|
+
if (!VALID_STATUSES.includes(status)) {
|
|
172
|
+
console.log(`❌ 无效状态: ${status},可选: ${VALID_STATUSES.join(', ')}`);
|
|
173
|
+
return;
|
|
151
174
|
}
|
|
175
|
+
step.status = status;
|
|
152
176
|
}
|
|
177
|
+
if (output !== undefined) step.output = output;
|
|
153
178
|
|
|
154
|
-
|
|
179
|
+
// 检查是否所有步骤都 completed
|
|
180
|
+
if (stageData.steps.length > 0 && stageData.steps.every(s => s.status === 'completed')) {
|
|
181
|
+
stageData.status = 'completed';
|
|
182
|
+
stageData.completedAt = new Date().toISOString();
|
|
183
|
+
console.log(`✅ 阶段 ${stage} 所有步骤已完成,阶段已标记为 completed`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
data.lastActive = new Date().toISOString();
|
|
187
|
+
this._backup(cwd);
|
|
188
|
+
this._write(cwd, data);
|
|
189
|
+
console.log(`✅ 步骤已更新: ${stage}/${stepName} → ${status || step.status}`);
|
|
155
190
|
}
|
|
156
191
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return false;
|
|
192
|
+
completeStage(cwd, stage) {
|
|
193
|
+
if (!VALID_STAGES.includes(stage)) {
|
|
194
|
+
console.log(`❌ 未知阶段: ${stage}`);
|
|
195
|
+
return;
|
|
162
196
|
}
|
|
163
197
|
|
|
164
|
-
const
|
|
165
|
-
if (
|
|
166
|
-
console.log('✅ progress.json 格式正确');
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
198
|
+
const data = this._readOrInit(cwd);
|
|
199
|
+
if (!data) return;
|
|
169
200
|
|
|
170
|
-
|
|
201
|
+
if (!data.stages[stage]) data.stages[stage] = emptyStage();
|
|
202
|
+
const stageData = data.stages[stage];
|
|
203
|
+
stageData.status = 'completed';
|
|
204
|
+
stageData.completedAt = new Date().toISOString();
|
|
171
205
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
206
|
+
// 标记所有未完成步骤为 completed
|
|
207
|
+
for (const step of stageData.steps) {
|
|
208
|
+
if (step.status !== 'completed') step.status = 'completed';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 推进到下一个未完成阶段
|
|
212
|
+
const idx = VALID_STAGES.indexOf(stage);
|
|
213
|
+
let nextStage = null;
|
|
214
|
+
for (let i = idx + 1; i < VALID_STAGES.length; i++) {
|
|
215
|
+
const s = data.stages[VALID_STAGES[i]];
|
|
216
|
+
if (!s || s.status !== 'completed') {
|
|
217
|
+
nextStage = VALID_STAGES[i];
|
|
218
|
+
break;
|
|
179
219
|
}
|
|
180
220
|
}
|
|
181
|
-
if (!fixed._version) { fixed._version = 1; changed = true; }
|
|
182
|
-
if (!fixed.schemaVersion) { fixed.schemaVersion = '1.0.0'; changed = true; }
|
|
183
|
-
if (!fixed.artifacts) { fixed.artifacts = []; changed = true; }
|
|
184
|
-
if (!fixed.lastActiveAt) { fixed.lastActiveAt = new Date().toISOString(); changed = true; }
|
|
185
221
|
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
222
|
+
if (nextStage) {
|
|
223
|
+
data.currentStage = nextStage;
|
|
224
|
+
if (!data.stages[nextStage]) data.stages[nextStage] = emptyStage();
|
|
225
|
+
if (data.stages[nextStage].status === 'pending') {
|
|
226
|
+
data.stages[nextStage].status = 'in-progress';
|
|
227
|
+
data.stages[nextStage].startedAt = new Date().toISOString();
|
|
228
|
+
}
|
|
229
|
+
console.log(`✅ 阶段 ${stage} 已完成,推进到: ${STAGE_LABELS[nextStage] || nextStage}`);
|
|
191
230
|
} else {
|
|
192
|
-
|
|
193
|
-
|
|
231
|
+
data.currentStage = stage;
|
|
232
|
+
console.log(`✅ 阶段 ${stage} 已完成(已是最后阶段)`);
|
|
194
233
|
}
|
|
195
234
|
|
|
196
|
-
|
|
235
|
+
data.lastActive = new Date().toISOString();
|
|
236
|
+
|
|
237
|
+
// 归档到 history/
|
|
238
|
+
const historyDir = this._path(cwd, 'history');
|
|
239
|
+
mkdirSync(historyDir, { recursive: true });
|
|
240
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
241
|
+
writeFileSync(join(historyDir, `${stage}-${ts}.json`), JSON.stringify({ stage, data: stageData, completedAt: stageData.completedAt }, null, 2) + '\n');
|
|
242
|
+
|
|
243
|
+
this._backup(cwd);
|
|
244
|
+
this._write(cwd, data);
|
|
197
245
|
}
|
|
198
246
|
|
|
199
|
-
|
|
247
|
+
show(cwd) {
|
|
200
248
|
const data = this.read(cwd);
|
|
201
249
|
if (!data) {
|
|
202
250
|
console.log('❌ 未找到 progress.json,请先运行 sillyspec progress init');
|
|
203
251
|
return;
|
|
204
252
|
}
|
|
205
253
|
|
|
206
|
-
const stageLabels = {
|
|
207
|
-
brainstorm: '🧠 需求探索',
|
|
208
|
-
propose: '📋 方案设计',
|
|
209
|
-
plan: '📐 实现计划',
|
|
210
|
-
execute: '⚡ 波次执行',
|
|
211
|
-
verify: '🔍 验证确认',
|
|
212
|
-
};
|
|
213
|
-
|
|
214
254
|
console.log('');
|
|
215
255
|
console.log(' ═══════════════════════════════════════');
|
|
216
|
-
console.log(`
|
|
217
|
-
console.log(`
|
|
218
|
-
console.log(`
|
|
219
|
-
if (data.checkpoint) {
|
|
220
|
-
console.log(` 检查点: ${data.checkpoint}`);
|
|
221
|
-
}
|
|
256
|
+
console.log(` 项目: ${data.project || '(未命名)'}`);
|
|
257
|
+
console.log(` 当前阶段: ${STAGE_LABELS[data.currentStage] || data.currentStage || '(无)'}`);
|
|
258
|
+
console.log(` 最近活跃: ${data.lastActive ? this._timeAgo(data.lastActive) : '未知'}`);
|
|
222
259
|
console.log(' ═══════════════════════════════════════');
|
|
223
260
|
console.log('');
|
|
224
261
|
|
|
225
|
-
|
|
262
|
+
const statusIcons = { pending: '⬜', 'in-progress': '🔵', completed: '✅', failed: '❌', blocked: '🚫' };
|
|
263
|
+
|
|
264
|
+
for (const stage of VALID_STAGES) {
|
|
226
265
|
const stageData = data.stages[stage] || emptyStage();
|
|
227
|
-
const label =
|
|
228
|
-
const statusIcons = { not_started: '⬜', in_progress: '🔵', completed: '✅' };
|
|
266
|
+
const label = STAGE_LABELS[stage] || stage;
|
|
229
267
|
const icon = statusIcons[stageData.status] || '⬜';
|
|
268
|
+
const isCurrent = data.currentStage === stage ? ' ◀' : '';
|
|
230
269
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
steps += '○';
|
|
270
|
+
console.log(` ${icon} ${label}${isCurrent}`);
|
|
271
|
+
|
|
272
|
+
if (stageData.steps && stageData.steps.length > 0) {
|
|
273
|
+
for (const step of stageData.steps) {
|
|
274
|
+
const si = statusIcons[step.status] || '○';
|
|
275
|
+
const out = step.output ? ` — ${step.output.slice(0, 60)}` : '';
|
|
276
|
+
console.log(` ${si} ${step.name}${out}`);
|
|
239
277
|
}
|
|
240
278
|
}
|
|
241
279
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
280
|
+
if (stageData.startedAt) {
|
|
281
|
+
console.log(` 开始: ${new Date(stageData.startedAt).toLocaleString('zh-CN')}`);
|
|
282
|
+
}
|
|
283
|
+
if (stageData.completedAt) {
|
|
284
|
+
console.log(` 完成: ${new Date(stageData.completedAt).toLocaleString('zh-CN')}`);
|
|
285
|
+
}
|
|
245
286
|
}
|
|
246
287
|
|
|
247
288
|
console.log('');
|
|
289
|
+
}
|
|
248
290
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
291
|
+
status(cwd) {
|
|
292
|
+
this.show(cwd);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
validate(cwd) {
|
|
296
|
+
const data = this.read(cwd);
|
|
297
|
+
if (!data) { console.log('❌ 无法读取 progress.json'); return false; }
|
|
298
|
+
|
|
299
|
+
const errors = [];
|
|
300
|
+
if (!data._version || !Number.isInteger(data._version) || data._version < 1) {
|
|
301
|
+
errors.push(`_version 缺失或无效(期望正整数,实际为 ${JSON.stringify(data._version)})`);
|
|
302
|
+
}
|
|
303
|
+
if (!data.stages || typeof data.stages !== 'object') errors.push('缺少 stages');
|
|
304
|
+
if (!VALID_STAGES.every(s => data.stages[s])) errors.push('缺少阶段定义');
|
|
305
|
+
|
|
306
|
+
if (errors.length === 0) { console.log('✅ progress.json 格式正确'); return true; }
|
|
307
|
+
|
|
308
|
+
console.log(`⚠️ 发现问题,尝试修复...`);
|
|
309
|
+
let fixed = { ...data, stages: { ...data.stages } };
|
|
310
|
+
let changed = false;
|
|
311
|
+
if (!fixed._version || !Number.isInteger(fixed._version) || fixed._version < 1) {
|
|
312
|
+
fixed._version = CURRENT_VERSION;
|
|
313
|
+
changed = true;
|
|
314
|
+
}
|
|
315
|
+
for (const s of VALID_STAGES) {
|
|
316
|
+
if (!fixed.stages[s]) { fixed.stages[s] = emptyStage(); changed = true; }
|
|
256
317
|
}
|
|
318
|
+
if (changed) {
|
|
319
|
+
this._backup(cwd);
|
|
320
|
+
this._write(cwd, fixed);
|
|
321
|
+
console.log('✅ 已修复并备份');
|
|
322
|
+
}
|
|
323
|
+
return true;
|
|
257
324
|
}
|
|
258
325
|
|
|
259
326
|
reset(cwd, stage) {
|
|
260
327
|
this._ensureDir(cwd);
|
|
261
328
|
this._backup(cwd);
|
|
262
329
|
|
|
263
|
-
const progressPath = join(cwd, RUNTIME_DIR, PROGRESS_FILE);
|
|
264
|
-
|
|
265
330
|
if (stage) {
|
|
266
|
-
// 只重置指定阶段
|
|
267
331
|
const data = this.read(cwd);
|
|
268
332
|
if (!data) { console.log('❌ 无法读取 progress.json'); return; }
|
|
269
333
|
if (!data.stages[stage]) { console.log(`❌ 未知阶段: ${stage}`); return; }
|
|
270
|
-
|
|
271
334
|
data.stages[stage] = emptyStage();
|
|
272
|
-
data.
|
|
273
|
-
|
|
335
|
+
data.lastActive = new Date().toISOString();
|
|
336
|
+
this._write(cwd, data);
|
|
274
337
|
console.log(`✅ 已重置阶段: ${stage}`);
|
|
275
338
|
} else {
|
|
276
|
-
|
|
277
|
-
if (existsSync(
|
|
278
|
-
|
|
279
|
-
console.log('✅ 已重置所有进度(备份已保留)');
|
|
280
|
-
} else {
|
|
281
|
-
console.log('ℹ️ 无进度文件可重置');
|
|
282
|
-
}
|
|
339
|
+
const p = this._path(cwd, PROGRESS_FILE);
|
|
340
|
+
if (existsSync(p)) { unlinkSync(p); console.log('✅ 已重置所有进度(备份已保留)'); }
|
|
341
|
+
else console.log('ℹ️ 无进度文件可重置');
|
|
283
342
|
}
|
|
284
343
|
}
|
|
285
344
|
|
|
286
345
|
complete(cwd, stage) {
|
|
287
|
-
|
|
288
|
-
console.log('❌ 请指定阶段: --stage <stage>');
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const data = this.read(cwd);
|
|
293
|
-
if (!data) { console.log('❌ 无法读取 progress.json'); return; }
|
|
294
|
-
|
|
295
|
-
const stageData = data.stages[stage];
|
|
296
|
-
if (!stageData) { console.log(`❌ 未知阶段: ${stage}`); return; }
|
|
297
|
-
|
|
298
|
-
// 归档到 history/
|
|
299
|
-
const historyDir = join(cwd, RUNTIME_DIR, 'history');
|
|
300
|
-
mkdirSync(historyDir, { recursive: true });
|
|
301
|
-
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
302
|
-
const archiveName = `${stage}-${ts}.json`;
|
|
303
|
-
writeFileSync(join(historyDir, archiveName), JSON.stringify({ stage, data: stageData, completedAt: new Date().toISOString() }, null, 2) + '\n');
|
|
304
|
-
|
|
305
|
-
console.log(`✅ 已归档 ${stage} → ${join(RUNTIME_DIR, 'history', archiveName)}`);
|
|
346
|
+
this.completeStage(cwd, stage);
|
|
306
347
|
}
|
|
307
348
|
|
|
308
|
-
// ──
|
|
349
|
+
// ── 内部辅助 ──
|
|
309
350
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (!
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
351
|
+
_readOrInit(cwd) {
|
|
352
|
+
let data = this.read(cwd);
|
|
353
|
+
if (!data) {
|
|
354
|
+
this._ensureDir(cwd);
|
|
355
|
+
const progressPath = this._path(cwd, PROGRESS_FILE);
|
|
356
|
+
if (!existsSync(progressPath)) {
|
|
357
|
+
data = makeInitialProgress(require('path').basename(cwd));
|
|
358
|
+
this._write(cwd, data);
|
|
359
|
+
} else {
|
|
360
|
+
console.log('❌ progress.json 损坏,请运行 sillyspec progress validate');
|
|
361
|
+
return null;
|
|
316
362
|
}
|
|
317
363
|
}
|
|
364
|
+
return data;
|
|
318
365
|
}
|
|
319
366
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
renameSync(progressPath, backupPath);
|
|
367
|
+
_requireStage(cwd, stage) {
|
|
368
|
+
if (!VALID_STAGES.includes(stage)) {
|
|
369
|
+
console.log(`❌ 未知阶段: ${stage},可选: ${VALID_STAGES.join(', ')}`);
|
|
370
|
+
return null;
|
|
325
371
|
}
|
|
372
|
+
const data = this._readOrInit(cwd);
|
|
373
|
+
if (!data) return null;
|
|
374
|
+
if (!data.stages[stage]) data.stages[stage] = emptyStage();
|
|
375
|
+
return data;
|
|
326
376
|
}
|
|
327
377
|
|
|
328
378
|
_parseWithRecovery(jsonString) {
|
|
329
|
-
|
|
330
|
-
try {
|
|
331
|
-
return JSON.parse(jsonString);
|
|
332
|
-
} catch {}
|
|
333
|
-
|
|
334
|
-
// 第二层:修复常见问题
|
|
335
|
-
let fixed = jsonString;
|
|
336
|
-
|
|
337
|
-
// 去尾随逗号(对象和数组中的 ,} 和 ,])
|
|
338
|
-
fixed = fixed.replace(/,\s*([}\]])/g, '$1');
|
|
379
|
+
try { return JSON.parse(jsonString); } catch {}
|
|
339
380
|
|
|
340
|
-
|
|
341
|
-
// 只处理 key 和简单字符串值
|
|
381
|
+
let fixed = jsonString.replace(/,\s*([}\]])/g, '$1');
|
|
342
382
|
fixed = fixed.replace(/([{,]\s*)'([^']+)'(\s*:)/g, '$1"$2"$3');
|
|
343
383
|
fixed = fixed.replace(/:\s*'([^']*)'([,}\]])/g, ':"$1"$2');
|
|
384
|
+
try { return JSON.parse(fixed); } catch {}
|
|
344
385
|
|
|
345
|
-
try {
|
|
346
|
-
return JSON.parse(fixed);
|
|
347
|
-
} catch {}
|
|
348
|
-
|
|
349
|
-
// 第三层:尝试截断到最后一个完整对象
|
|
350
386
|
const lastBrace = fixed.lastIndexOf('}');
|
|
351
387
|
if (lastBrace > 0) {
|
|
352
|
-
const truncated = fixed.substring(0, lastBrace + 1);
|
|
353
|
-
// 补全外层括号
|
|
354
388
|
let open = 0;
|
|
355
|
-
for (const ch of
|
|
389
|
+
for (const ch of fixed.substring(0, lastBrace + 1)) {
|
|
356
390
|
if (ch === '{') open++;
|
|
357
391
|
if (ch === '}') open--;
|
|
358
392
|
}
|
|
359
|
-
|
|
360
|
-
try {
|
|
361
|
-
return JSON.parse(repaired);
|
|
362
|
-
} catch {}
|
|
393
|
+
try { return JSON.parse(fixed.substring(0, lastBrace + 1) + '}'.repeat(Math.max(0, open))); } catch {}
|
|
363
394
|
}
|
|
364
|
-
|
|
365
395
|
return null;
|
|
366
396
|
}
|
|
367
397
|
|
|
368
|
-
_validate(data) {
|
|
369
|
-
const errors = [];
|
|
370
|
-
if (!data || typeof data !== 'object') { errors.push('数据不是有效对象'); return errors; }
|
|
371
|
-
if (!data.stages || typeof data.stages !== 'object') { errors.push('缺少 stages 字段'); }
|
|
372
|
-
if (!data.currentStage || typeof data.currentStage !== 'string') { errors.push('缺少 currentStage 字段'); }
|
|
373
|
-
if (!data.schemaVersion) { errors.push('缺少 schemaVersion 字段'); }
|
|
374
|
-
if (typeof data._version !== 'number' || data._version < 1) { errors.push('_version 应为正整数'); }
|
|
375
|
-
|
|
376
|
-
// 校验阶段数据
|
|
377
|
-
if (data.stages) {
|
|
378
|
-
for (const [name, stage] of Object.entries(data.stages)) {
|
|
379
|
-
if (!stage.status) errors.push(`阶段 ${name} 缺少 status`);
|
|
380
|
-
if (stage.completedSteps && !Array.isArray(stage.completedSteps)) {
|
|
381
|
-
errors.push(`阶段 ${name} 的 completedSteps 不是数组`);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return errors;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
398
|
_timeAgo(dateStr) {
|
|
390
399
|
if (!dateStr) return '未知';
|
|
391
|
-
const
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
const days = Math.floor(hours / 24);
|
|
399
|
-
|
|
400
|
-
if (days > 0) return `${days} 天前`;
|
|
401
|
-
if (hours > 0) return `${hours} 小时前`;
|
|
402
|
-
if (minutes > 0) return `${minutes} 分钟前`;
|
|
403
|
-
return '刚刚';
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
_getStepDefinitions(stage) {
|
|
407
|
-
return STEP_DEFINITIONS[stage] || [];
|
|
400
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
401
|
+
const m = Math.floor(diff / 60000);
|
|
402
|
+
if (m < 1) return '刚刚';
|
|
403
|
+
if (m < 60) return `${m} 分钟前`;
|
|
404
|
+
const h = Math.floor(m / 60);
|
|
405
|
+
if (h < 24) return `${h} 小时前`;
|
|
406
|
+
return `${Math.floor(h / 24)} 天前`;
|
|
408
407
|
}
|
|
409
408
|
|
|
410
409
|
_ensureGitignore(cwd) {
|
|
411
410
|
const gitignorePath = join(cwd, '.gitignore');
|
|
412
411
|
const rule = '.sillyspec/.runtime/';
|
|
413
|
-
|
|
414
412
|
if (existsSync(gitignorePath)) {
|
|
415
413
|
const content = readFileSync(gitignorePath, 'utf8');
|
|
416
414
|
if (content.includes(rule)) return;
|