sillyspec 3.10.5 → 3.10.6
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 +16 -13
- package/src/init.js +5 -8
- package/src/progress.js +337 -90
- package/src/run.js +92 -74
- package/src/stages/brainstorm.js +1 -1
- package/src/stages/doctor.js +12 -6
- package/src/stages/scan.js +1 -1
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -136,9 +136,13 @@ async function main() {
|
|
|
136
136
|
break;
|
|
137
137
|
case 'progress': {
|
|
138
138
|
const pm = new ProgressManager();
|
|
139
|
+
pm._migrateIfNeeded(dir);
|
|
139
140
|
const subCommand = filteredArgs[1];
|
|
140
141
|
const stageIdx = filteredArgs.indexOf('--stage');
|
|
141
142
|
const stage = stageIdx >= 0 && filteredArgs[stageIdx + 1] ? filteredArgs[stageIdx + 1] : null;
|
|
143
|
+
// 解析 --change 参数
|
|
144
|
+
const progChangeIdx = args.indexOf('--change');
|
|
145
|
+
const progChangeName = progChangeIdx >= 0 && args[progChangeIdx + 1] ? args[progChangeIdx + 1] : null;
|
|
142
146
|
|
|
143
147
|
switch (subCommand) {
|
|
144
148
|
case 'init':
|
|
@@ -146,49 +150,48 @@ async function main() {
|
|
|
146
150
|
break;
|
|
147
151
|
case 'status':
|
|
148
152
|
case 'show':
|
|
149
|
-
pm.show(dir);
|
|
153
|
+
pm.show(dir, progChangeName);
|
|
150
154
|
break;
|
|
151
155
|
case 'validate':
|
|
152
|
-
await pm.validate(dir);
|
|
156
|
+
await pm.validate(dir, progChangeName);
|
|
153
157
|
break;
|
|
154
158
|
case 'reset':
|
|
155
|
-
pm.reset(dir, stage);
|
|
159
|
+
pm.reset(dir, stage, progChangeName);
|
|
156
160
|
break;
|
|
157
161
|
case 'set-stage': {
|
|
158
162
|
const setStageName = filteredArgs[2];
|
|
159
|
-
if (!setStageName) { console.log('❌ 用法: sillyspec progress set-stage <stage>'); break; }
|
|
160
|
-
pm.setStage(dir, setStageName);
|
|
163
|
+
if (!setStageName) { console.log('❌ 用法: sillyspec progress set-stage <stage> [--change <name>]'); break; }
|
|
164
|
+
pm.setStage(dir, setStageName, progChangeName);
|
|
161
165
|
break;
|
|
162
166
|
}
|
|
163
167
|
case 'add-step': {
|
|
164
168
|
const addStepStage = filteredArgs[2];
|
|
165
169
|
const addStepName = filteredArgs[3];
|
|
166
|
-
if (!addStepStage || !addStepName) { console.log('❌ 用法: sillyspec progress add-step <stage> <step-name>'); break; }
|
|
167
|
-
pm.addStep(dir, addStepStage, addStepName);
|
|
170
|
+
if (!addStepStage || !addStepName) { console.log('❌ 用法: sillyspec progress add-step <stage> <step-name> [--change <name>]'); break; }
|
|
171
|
+
pm.addStep(dir, addStepStage, addStepName, progChangeName);
|
|
168
172
|
break;
|
|
169
173
|
}
|
|
170
174
|
case 'update-step': {
|
|
171
175
|
const updStepStage = filteredArgs[2];
|
|
172
176
|
const updStepName = filteredArgs[3];
|
|
173
|
-
if (!updStepStage || !updStepName) { console.log('❌ 用法: sillyspec progress update-step <stage> <step-name> --status <status> [--output <text>]'); break; }
|
|
174
|
-
// Parse --status and --output from args
|
|
177
|
+
if (!updStepStage || !updStepName) { console.log('❌ 用法: sillyspec progress update-step <stage> <step-name> --status <status> [--output <text>] [--change <name>]'); break; }
|
|
175
178
|
let updStatus = null, updOutput = undefined;
|
|
176
179
|
for (let ai = 0; ai < args.length; ai++) {
|
|
177
180
|
if (args[ai] === '--status' && args[ai + 1]) { updStatus = args[ai + 1]; ai++; }
|
|
178
181
|
if (args[ai] === '--output' && args[ai + 1]) { updOutput = args[ai + 1]; ai++; }
|
|
179
182
|
}
|
|
180
|
-
pm.updateStep(dir, updStepStage, updStepName, { status: updStatus, output: updOutput });
|
|
183
|
+
pm.updateStep(dir, updStepStage, updStepName, { status: updStatus, output: updOutput }, progChangeName);
|
|
181
184
|
break;
|
|
182
185
|
}
|
|
183
186
|
case 'complete-stage': {
|
|
184
187
|
const compStageName = filteredArgs[2];
|
|
185
188
|
if (!compStageName) { console.log('❌ 用法: sillyspec progress complete-stage <stage>'); break; }
|
|
186
|
-
pm.completeStage(dir, compStageName);
|
|
189
|
+
pm.completeStage(dir, compStageName, progChangeName);
|
|
187
190
|
break;
|
|
188
191
|
}
|
|
189
192
|
case 'batch': {
|
|
190
193
|
if (filteredArgs.includes('--status')) {
|
|
191
|
-
const bp = pm.readBatchProgress(dir);
|
|
194
|
+
const bp = pm.readBatchProgress(dir, progChangeName);
|
|
192
195
|
if (!bp) { console.log('📭 无批量进度数据'); break; }
|
|
193
196
|
const line = pm._renderBatchProgress(bp);
|
|
194
197
|
console.log(line || '📭 无批量进度数据');
|
|
@@ -207,7 +210,7 @@ async function main() {
|
|
|
207
210
|
console.log(' sillyspec progress batch --status');
|
|
208
211
|
break;
|
|
209
212
|
}
|
|
210
|
-
pm.updateBatchProgress(dir, batchData);
|
|
213
|
+
pm.updateBatchProgress(dir, batchData, progChangeName);
|
|
211
214
|
console.log('✅ 批量进度已更新');
|
|
212
215
|
}
|
|
213
216
|
break;
|
package/src/init.js
CHANGED
|
@@ -56,7 +56,7 @@ const INJECTION_CONTENT = `## SillySpec — 规范驱动开发
|
|
|
56
56
|
- 遵循 \`.sillyspec/docs/<project>/scan/CONVENTIONS.md\` 中的代码风格
|
|
57
57
|
|
|
58
58
|
### 工作流程
|
|
59
|
-
-
|
|
59
|
+
- 读取当前变更的 progress.json 确认当前阶段(使用 \`sillyspec progress show\`)
|
|
60
60
|
- 各阶段产出文件位于 \`.sillyspec/changes/<变更名>/\` 下
|
|
61
61
|
`;
|
|
62
62
|
|
|
@@ -141,18 +141,15 @@ async function doInstall(projectDir, tools, subprojects = []) {
|
|
|
141
141
|
writeFileSync(uncatPath, `# 未分类知识\n\n> execute/quick 执行中发现的坑暂存于此,用户审阅后归类到对应文件并更新 INDEX.md。\n`);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
// 创建 .sillyspec/.runtime/
|
|
144
|
+
// 创建 .sillyspec/.runtime/ 目录结构(全局状态)
|
|
145
145
|
const runtimeDir = join(projectDir, '.sillyspec', '.runtime');
|
|
146
146
|
for (const sub of ['artifacts', 'history', 'logs', 'templates']) {
|
|
147
147
|
mkdirSync(join(runtimeDir, sub), { recursive: true });
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
//
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
const pm = new ProgressManager();
|
|
154
|
-
pm.init(projectDir);
|
|
155
|
-
}
|
|
150
|
+
// 创建全局状态文件
|
|
151
|
+
const pm = new ProgressManager();
|
|
152
|
+
pm.init(projectDir);
|
|
156
153
|
|
|
157
154
|
// 创建初始 user-inputs.md
|
|
158
155
|
const inputsPath = join(runtimeDir, 'user-inputs.md');
|
package/src/progress.js
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SillySpec ProgressManager — 进度恢复管理
|
|
3
3
|
*
|
|
4
|
-
* 纯 Node.js
|
|
4
|
+
* 纯 Node.js,无外部依赖。支持多变更并行。
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* 存储结构(v3):
|
|
7
|
+
* .sillyspec/.runtime/global.json — 全局状态(项目名、活跃变更列表)
|
|
8
|
+
* .sillyspec/changes/<name>/progress.json — 每个变更独立的阶段/步骤状态
|
|
9
|
+
*
|
|
10
|
+
* 向后兼容:如果存在旧的 .sillyspec/.runtime/progress.json,自动迁移。
|
|
7
11
|
*/
|
|
8
12
|
|
|
9
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, copyFileSync, unlinkSync } from 'fs';
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, copyFileSync, unlinkSync, readdirSync } from 'fs';
|
|
10
14
|
import { join, basename } from 'path';
|
|
11
15
|
|
|
12
16
|
const RUNTIME_DIR = '.sillyspec/.runtime';
|
|
17
|
+
const CHANGES_DIR = '.sillyspec/changes';
|
|
18
|
+
const GLOBAL_FILE = 'global.json';
|
|
13
19
|
const PROGRESS_FILE = 'progress.json';
|
|
14
|
-
const
|
|
20
|
+
const BACKUP_SUFFIX = '.bak';
|
|
15
21
|
|
|
16
|
-
const CURRENT_VERSION =
|
|
22
|
+
const CURRENT_VERSION = 3;
|
|
17
23
|
const VALID_STAGES = ['scan', 'brainstorm', 'plan', 'execute', 'verify', 'archive', 'quick', 'explore'];
|
|
18
24
|
const VALID_STATUSES = ['pending', 'in-progress', 'completed', 'failed', 'blocked'];
|
|
19
25
|
|
|
@@ -38,18 +44,131 @@ function makeInitialProgress(project) {
|
|
|
38
44
|
return { _version: CURRENT_VERSION, project: project || '', currentStage: '', currentChange: null, stages, lastActive: null };
|
|
39
45
|
}
|
|
40
46
|
|
|
47
|
+
function makeInitialGlobal(project) {
|
|
48
|
+
return { _version: CURRENT_VERSION, project: project || '', activeChanges: [] };
|
|
49
|
+
}
|
|
50
|
+
|
|
41
51
|
// ── ProgressManager ──
|
|
42
52
|
|
|
43
53
|
export class ProgressManager {
|
|
44
|
-
// ──
|
|
54
|
+
// ── 路径工具 ──
|
|
45
55
|
|
|
46
|
-
|
|
56
|
+
_runtimePath(cwd, ...parts) {
|
|
47
57
|
return join(cwd, RUNTIME_DIR, ...parts);
|
|
48
58
|
}
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
_changePath(cwd, changeName, ...parts) {
|
|
61
|
+
return join(cwd, CHANGES_DIR, changeName, ...parts);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_ensureRuntimeDir(cwd) {
|
|
65
|
+
const runtimeDir = this._runtimePath(cwd);
|
|
66
|
+
if (!existsSync(runtimeDir)) {
|
|
67
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
68
|
+
for (const d of ['artifacts', 'history', 'logs', 'templates']) {
|
|
69
|
+
mkdirSync(join(runtimeDir, d), { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_ensureChangeDir(cwd, changeName) {
|
|
75
|
+
const dir = this._changePath(cwd, changeName);
|
|
76
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
77
|
+
return dir;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── 向后兼容:检测并迁移旧版 progress.json ──
|
|
81
|
+
|
|
82
|
+
_migrateIfNeeded(cwd) {
|
|
83
|
+
const oldPath = this._runtimePath(cwd, PROGRESS_FILE);
|
|
84
|
+
const globalPath = this._runtimePath(cwd, GLOBAL_FILE);
|
|
85
|
+
|
|
86
|
+
// 新版已存在,不迁移
|
|
87
|
+
if (existsSync(globalPath)) return;
|
|
88
|
+
|
|
89
|
+
// 旧版不存在,不迁移
|
|
90
|
+
if (!existsSync(oldPath)) return;
|
|
91
|
+
|
|
92
|
+
const oldData = this._parseWithRecovery(readFileSync(oldPath, 'utf8'));
|
|
93
|
+
if (!oldData) return;
|
|
94
|
+
|
|
95
|
+
console.log('🔄 检测到旧版 progress.json,正在迁移到按变更隔离存储...');
|
|
96
|
+
|
|
97
|
+
// 提取变更名
|
|
98
|
+
const changeName = oldData.currentChange || 'default';
|
|
99
|
+
|
|
100
|
+
// 迁移:将旧 progress.json 复制到变更目录
|
|
101
|
+
this._ensureChangeDir(cwd, changeName);
|
|
102
|
+
const newChangePath = this._changePath(cwd, changeName, PROGRESS_FILE);
|
|
103
|
+
if (!existsSync(newChangePath)) {
|
|
104
|
+
writeFileSync(newChangePath, JSON.stringify(oldData, null, 2) + '\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 创建全局文件
|
|
108
|
+
const globalData = makeInitialGlobal(oldData.project);
|
|
109
|
+
globalData.activeChanges = [changeName];
|
|
110
|
+
if (!existsSync(globalPath)) {
|
|
111
|
+
writeFileSync(globalPath, JSON.stringify(globalData, null, 2) + '\n');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 备份旧文件
|
|
115
|
+
const backupPath = oldPath + BACKUP_SUFFIX;
|
|
116
|
+
if (!existsSync(backupPath)) copyFileSync(oldPath, backupPath);
|
|
117
|
+
unlinkSync(oldPath);
|
|
118
|
+
|
|
119
|
+
console.log(` ✅ 已迁移到 .sillyspec/changes/${changeName}/progress.json`);
|
|
120
|
+
console.log(` 📦 旧文件已备份到 .sillyspec/.runtime/progress.json.bak`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── 全局状态 ──
|
|
124
|
+
|
|
125
|
+
readGlobal(cwd) {
|
|
126
|
+
this._migrateIfNeeded(cwd);
|
|
127
|
+
const globalPath = this._runtimePath(cwd, GLOBAL_FILE);
|
|
128
|
+
if (!existsSync(globalPath)) return null;
|
|
129
|
+
return this._parseWithRecovery(readFileSync(globalPath, 'utf8'));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
writeGlobal(cwd, data) {
|
|
133
|
+
this._ensureRuntimeDir(cwd);
|
|
134
|
+
const globalPath = this._runtimePath(cwd, GLOBAL_FILE);
|
|
135
|
+
const tmpPath = globalPath + '.tmp';
|
|
136
|
+
writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\n');
|
|
137
|
+
renameSync(tmpPath, globalPath);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── 变更级别状态 ──
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 读取指定变更的 progress
|
|
144
|
+
* @param {string} cwd
|
|
145
|
+
* @param {string|null} changeName - 变更名,null 时尝试自动检测
|
|
146
|
+
*/
|
|
147
|
+
read(cwd, changeName = null) {
|
|
148
|
+
// 向后兼容:如果没有 changeName,尝试读旧版路径
|
|
149
|
+
if (!changeName) {
|
|
150
|
+
// 先看新版全局文件
|
|
151
|
+
const global = this.readGlobal(cwd);
|
|
152
|
+
if (global && global.activeChanges && global.activeChanges.length === 1) {
|
|
153
|
+
changeName = global.activeChanges[0];
|
|
154
|
+
} else {
|
|
155
|
+
// fallback:扫描 changes 目录
|
|
156
|
+
const changes = this.listChanges(cwd);
|
|
157
|
+
if (changes.length === 1) {
|
|
158
|
+
changeName = changes[0];
|
|
159
|
+
} else {
|
|
160
|
+
// 最后尝试旧版路径
|
|
161
|
+
const oldPath = this._runtimePath(cwd, PROGRESS_FILE);
|
|
162
|
+
if (existsSync(oldPath)) {
|
|
163
|
+
return this._parseWithRecovery(readFileSync(oldPath, 'utf8'));
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const progressPath = this._changePath(cwd, changeName, PROGRESS_FILE);
|
|
171
|
+
const backupPath = progressPath + BACKUP_SUFFIX;
|
|
53
172
|
|
|
54
173
|
for (const p of [progressPath, backupPath]) {
|
|
55
174
|
if (!existsSync(p)) continue;
|
|
@@ -65,62 +184,125 @@ export class ProgressManager {
|
|
|
65
184
|
return null;
|
|
66
185
|
}
|
|
67
186
|
|
|
68
|
-
|
|
69
|
-
|
|
187
|
+
/**
|
|
188
|
+
* 写入指定变更的 progress
|
|
189
|
+
* @param {string} cwd
|
|
190
|
+
* @param {object} data
|
|
191
|
+
* @param {string|null} changeName - 从 data.currentChange 推导,或显式传入
|
|
192
|
+
*/
|
|
193
|
+
_write(cwd, data, changeName = null) {
|
|
194
|
+
const cn = changeName || data.currentChange;
|
|
195
|
+
if (!cn) {
|
|
196
|
+
// 无变更名时 fallback 到旧路径(不应该发生,但保底)
|
|
197
|
+
const progressPath = this._runtimePath(cwd, PROGRESS_FILE);
|
|
198
|
+
this._ensureRuntimeDir(cwd);
|
|
199
|
+
const tmpPath = progressPath + '.tmp';
|
|
200
|
+
writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\n');
|
|
201
|
+
renameSync(tmpPath, progressPath);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this._ensureChangeDir(cwd, cn);
|
|
206
|
+
const progressPath = this._changePath(cwd, cn, PROGRESS_FILE);
|
|
70
207
|
const tmpPath = progressPath + '.tmp';
|
|
71
|
-
this._ensureDir(cwd);
|
|
72
208
|
writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\n');
|
|
73
209
|
renameSync(tmpPath, progressPath);
|
|
74
210
|
}
|
|
75
211
|
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
if (!
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
212
|
+
_backup(cwd, data) {
|
|
213
|
+
const cn = data?.currentChange;
|
|
214
|
+
if (!cn) return;
|
|
215
|
+
const p = this._changePath(cwd, cn, PROGRESS_FILE);
|
|
216
|
+
if (existsSync(p)) copyFileSync(p, this._changePath(cwd, cn, PROGRESS_FILE + BACKUP_SUFFIX));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── 变更管理 ──
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 列出所有变更名(不含 archive 子目录)
|
|
223
|
+
*/
|
|
224
|
+
listChanges(cwd) {
|
|
225
|
+
const changesDir = join(cwd, CHANGES_DIR);
|
|
226
|
+
if (!existsSync(changesDir)) return [];
|
|
227
|
+
return readdirSync(changesDir, { withFileTypes: true })
|
|
228
|
+
.filter(e => e.isDirectory() && e.name !== 'archive')
|
|
229
|
+
.map(e => e.name);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 注册变更到全局活跃列表
|
|
234
|
+
*/
|
|
235
|
+
registerChange(cwd, changeName) {
|
|
236
|
+
let global = this.readGlobal(cwd);
|
|
237
|
+
if (!global) {
|
|
238
|
+
global = makeInitialGlobal(basename(cwd));
|
|
239
|
+
}
|
|
240
|
+
if (!global.activeChanges.includes(changeName)) {
|
|
241
|
+
global.activeChanges.push(changeName);
|
|
242
|
+
this.writeGlobal(cwd, global);
|
|
83
243
|
}
|
|
84
244
|
}
|
|
85
245
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
246
|
+
/**
|
|
247
|
+
* 从全局活跃列表移除变更(归档时调用)
|
|
248
|
+
*/
|
|
249
|
+
unregisterChange(cwd, changeName) {
|
|
250
|
+
const global = this.readGlobal(cwd);
|
|
251
|
+
if (!global) return;
|
|
252
|
+
global.activeChanges = global.activeChanges.filter(c => c !== changeName);
|
|
253
|
+
this.writeGlobal(cwd, global);
|
|
89
254
|
}
|
|
90
255
|
|
|
91
256
|
// ── CLI 命令 ──
|
|
92
257
|
|
|
93
258
|
init(cwd) {
|
|
94
|
-
this.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
259
|
+
this._migrateIfNeeded(cwd);
|
|
260
|
+
this._ensureRuntimeDir(cwd);
|
|
261
|
+
|
|
262
|
+
const globalPath = this._runtimePath(cwd, GLOBAL_FILE);
|
|
263
|
+
if (!existsSync(globalPath)) {
|
|
264
|
+
const data = makeInitialGlobal(basename(cwd));
|
|
265
|
+
this.writeGlobal(cwd, data);
|
|
266
|
+
console.log(`✅ 已创建全局状态文件`);
|
|
267
|
+
} else {
|
|
268
|
+
console.log(`ℹ️ 全局状态文件已存在,跳过`);
|
|
100
269
|
}
|
|
101
270
|
|
|
102
|
-
const project = basename(cwd);
|
|
103
|
-
const data = makeInitialProgress(project);
|
|
104
|
-
this._write(cwd, data);
|
|
105
|
-
console.log(`✅ 已创建 ${join(RUNTIME_DIR, PROGRESS_FILE)}`);
|
|
106
|
-
|
|
107
271
|
// 创建 user-inputs.md
|
|
108
|
-
const inputsPath = this.
|
|
272
|
+
const inputsPath = this._runtimePath(cwd, 'user-inputs.md');
|
|
109
273
|
if (!existsSync(inputsPath)) {
|
|
110
274
|
writeFileSync(inputsPath, '# 用户输入记录\n\n> 每步完成时由 AI 自动追加,记录用户所有原话。\n\n');
|
|
111
275
|
}
|
|
112
276
|
|
|
113
277
|
this._ensureGitignore(cwd);
|
|
114
|
-
return
|
|
278
|
+
return this.readGlobal(cwd);
|
|
115
279
|
}
|
|
116
280
|
|
|
117
|
-
|
|
281
|
+
/**
|
|
282
|
+
* 初始化指定变更的 progress
|
|
283
|
+
*/
|
|
284
|
+
initChange(cwd, changeName) {
|
|
285
|
+
this._ensureChangeDir(cwd, changeName);
|
|
286
|
+
this.registerChange(cwd, changeName);
|
|
287
|
+
|
|
288
|
+
const progressPath = this._changePath(cwd, changeName, PROGRESS_FILE);
|
|
289
|
+
if (!existsSync(progressPath)) {
|
|
290
|
+
const data = makeInitialProgress(basename(cwd));
|
|
291
|
+
data.currentChange = changeName;
|
|
292
|
+
this._write(cwd, data, changeName);
|
|
293
|
+
console.log(`✅ 已创建变更 ${changeName} 的 progress.json`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return this.read(cwd, changeName);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
setStage(cwd, stage, changeName = null) {
|
|
118
300
|
if (!VALID_STAGES.includes(stage)) {
|
|
119
301
|
console.log(`❌ 未知阶段: ${stage},可选: ${VALID_STAGES.join(', ')}`);
|
|
120
302
|
return;
|
|
121
303
|
}
|
|
122
304
|
|
|
123
|
-
const data = this._readOrInit(cwd);
|
|
305
|
+
const data = this._readOrInit(cwd, changeName);
|
|
124
306
|
if (!data) return;
|
|
125
307
|
|
|
126
308
|
if (!data.stages[stage]) data.stages[stage] = emptyStage();
|
|
@@ -133,14 +315,14 @@ export class ProgressManager {
|
|
|
133
315
|
}
|
|
134
316
|
data.lastActive = new Date().toLocaleString('zh-CN',{hour12:false});
|
|
135
317
|
|
|
136
|
-
this._backup(cwd);
|
|
318
|
+
this._backup(cwd, data);
|
|
137
319
|
this._write(cwd, data);
|
|
138
320
|
console.log(`✅ 当前阶段已设为: ${STAGE_LABELS[stage] || stage} (${stageData.status})`);
|
|
139
321
|
}
|
|
140
322
|
|
|
141
|
-
addStep(cwd, stage, stepName) {
|
|
323
|
+
addStep(cwd, stage, stepName, changeName = null) {
|
|
142
324
|
if (!stepName) { console.log('❌ 请指定步骤名称'); return; }
|
|
143
|
-
const data = this._requireStage(cwd, stage);
|
|
325
|
+
const data = this._requireStage(cwd, stage, changeName);
|
|
144
326
|
if (!data) return;
|
|
145
327
|
|
|
146
328
|
const stageData = data.stages[stage];
|
|
@@ -152,15 +334,15 @@ export class ProgressManager {
|
|
|
152
334
|
stageData.steps.push({ name: stepName, status: 'pending' });
|
|
153
335
|
data.lastActive = new Date().toLocaleString('zh-CN',{hour12:false});
|
|
154
336
|
|
|
155
|
-
this._backup(cwd);
|
|
337
|
+
this._backup(cwd, data);
|
|
156
338
|
this._write(cwd, data);
|
|
157
339
|
console.log(`✅ 已添加步骤: ${stage}/${stepName}`);
|
|
158
340
|
}
|
|
159
341
|
|
|
160
|
-
updateStep(cwd, stage, stepName, options = {}) {
|
|
342
|
+
updateStep(cwd, stage, stepName, options = {}, changeName = null) {
|
|
161
343
|
const { status, output } = options;
|
|
162
344
|
if (!stepName) { console.log('❌ 请指定步骤名称'); return; }
|
|
163
|
-
const data = this._requireStage(cwd, stage);
|
|
345
|
+
const data = this._requireStage(cwd, stage, changeName);
|
|
164
346
|
if (!data) return;
|
|
165
347
|
|
|
166
348
|
const stageData = data.stages[stage];
|
|
@@ -184,18 +366,18 @@ export class ProgressManager {
|
|
|
184
366
|
}
|
|
185
367
|
|
|
186
368
|
data.lastActive = new Date().toLocaleString('zh-CN',{hour12:false});
|
|
187
|
-
this._backup(cwd);
|
|
369
|
+
this._backup(cwd, data);
|
|
188
370
|
this._write(cwd, data);
|
|
189
371
|
console.log(`✅ 步骤已更新: ${stage}/${stepName} → ${status || step.status}`);
|
|
190
372
|
}
|
|
191
373
|
|
|
192
|
-
completeStage(cwd, stage) {
|
|
374
|
+
completeStage(cwd, stage, changeName = null) {
|
|
193
375
|
if (!VALID_STAGES.includes(stage)) {
|
|
194
376
|
console.log(`❌ 未知阶段: ${stage}`);
|
|
195
377
|
return;
|
|
196
378
|
}
|
|
197
379
|
|
|
198
|
-
const data = this._readOrInit(cwd);
|
|
380
|
+
const data = this._readOrInit(cwd, changeName);
|
|
199
381
|
if (!data) return;
|
|
200
382
|
|
|
201
383
|
if (!data.stages[stage]) data.stages[stage] = emptyStage();
|
|
@@ -210,27 +392,74 @@ export class ProgressManager {
|
|
|
210
392
|
|
|
211
393
|
data.lastActive = new Date().toLocaleString('zh-CN',{hour12:false});
|
|
212
394
|
|
|
213
|
-
// 归档到 history/(ISO
|
|
214
|
-
const historyDir = this.
|
|
395
|
+
// 归档到 history/(ISO 时间戳)
|
|
396
|
+
const historyDir = this._runtimePath(cwd, 'history');
|
|
215
397
|
mkdirSync(historyDir, { recursive: true });
|
|
216
398
|
const ts = new Date().toISOString().replace(/[:.TZ-]/g, '');
|
|
217
|
-
|
|
399
|
+
const cn = data.currentChange || 'unknown';
|
|
400
|
+
writeFileSync(join(historyDir, `${cn}-${stage}-${ts}.json`), JSON.stringify({ change: cn, stage, data: stageData, completedAt: stageData.completedAt }, null, 2) + '\n');
|
|
218
401
|
|
|
219
|
-
this._backup(cwd);
|
|
402
|
+
this._backup(cwd, data);
|
|
220
403
|
this._write(cwd, data);
|
|
221
404
|
|
|
222
405
|
console.log(`✅ 阶段 ${stage} 已标记为完成(不自动推进,下一步由你决定)`);
|
|
223
406
|
}
|
|
224
407
|
|
|
225
|
-
show(cwd) {
|
|
226
|
-
|
|
408
|
+
show(cwd, changeName = null) {
|
|
409
|
+
// 如果指定了变更名,只显示该变更
|
|
410
|
+
if (changeName) {
|
|
411
|
+
return this._showChange(cwd, changeName);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// 否则显示所有变更
|
|
415
|
+
const changes = this.listChanges(cwd);
|
|
416
|
+
if (changes.length === 0) {
|
|
417
|
+
console.log('ℹ️ 没有活跃的变更');
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (changes.length === 1) {
|
|
422
|
+
return this._showChange(cwd, changes[0]);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// 多个变更:汇总显示
|
|
426
|
+
const global = this.readGlobal(cwd);
|
|
427
|
+
console.log('');
|
|
428
|
+
console.log(' ═══════════════════════════════════════');
|
|
429
|
+
console.log(` 项目: ${(global?.project) || basename(cwd) || '(未命名)'}`);
|
|
430
|
+
console.log(` 活跃变更: ${changes.length} 个`);
|
|
431
|
+
console.log(' ═══════════════════════════════════════');
|
|
432
|
+
console.log('');
|
|
433
|
+
|
|
434
|
+
for (const cn of changes) {
|
|
435
|
+
const data = this.read(cwd, cn);
|
|
436
|
+
if (!data) {
|
|
437
|
+
console.log(` 📂 ${cn} — (无法读取)`);
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
const currentStage = data.currentStage || '(无)';
|
|
441
|
+
const stageLabel = STAGE_LABELS[data.currentStage] || currentStage;
|
|
442
|
+
const lastActive = data.lastActive ? this._timeAgo(data.lastActive) : '未知';
|
|
443
|
+
|
|
444
|
+
console.log(` 📂 ${cn}`);
|
|
445
|
+
console.log(` 当前阶段: ${stageLabel} 最近活跃: ${lastActive}`);
|
|
446
|
+
console.log('');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
console.log(` 💡 查看详情:sillyspec progress show --change <name>`);
|
|
450
|
+
console.log('');
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
_showChange(cwd, changeName) {
|
|
454
|
+
const data = this.read(cwd, changeName);
|
|
227
455
|
if (!data) {
|
|
228
|
-
console.log(
|
|
456
|
+
console.log(`❌ 未找到变更 ${changeName} 的 progress.json`);
|
|
229
457
|
return;
|
|
230
458
|
}
|
|
231
459
|
|
|
232
460
|
console.log('');
|
|
233
461
|
console.log(' ═══════════════════════════════════════');
|
|
462
|
+
console.log(` 变更: ${changeName}`);
|
|
234
463
|
console.log(` 项目: ${data.project || '(未命名)'}`);
|
|
235
464
|
console.log(` 当前阶段: ${STAGE_LABELS[data.currentStage] || data.currentStage || '(无)'}`);
|
|
236
465
|
console.log(` 最近活跃: ${data.lastActive ? this._timeAgo(data.lastActive) : '未知'}`);
|
|
@@ -275,12 +504,12 @@ export class ProgressManager {
|
|
|
275
504
|
console.log('');
|
|
276
505
|
}
|
|
277
506
|
|
|
278
|
-
status(cwd) {
|
|
279
|
-
this.show(cwd);
|
|
507
|
+
status(cwd, changeName = null) {
|
|
508
|
+
this.show(cwd, changeName);
|
|
280
509
|
}
|
|
281
510
|
|
|
282
|
-
async validate(cwd) {
|
|
283
|
-
const data = this.read(cwd);
|
|
511
|
+
async validate(cwd, changeName = null) {
|
|
512
|
+
const data = this.read(cwd, changeName);
|
|
284
513
|
if (!data) { console.log('❌ 无法读取 progress.json'); return false; }
|
|
285
514
|
|
|
286
515
|
const errors = [];
|
|
@@ -307,7 +536,7 @@ export class ProgressManager {
|
|
|
307
536
|
if (!fixed.stages[s]) { fixed.stages[s] = emptyStage(); changed = true; }
|
|
308
537
|
}
|
|
309
538
|
if (changed) {
|
|
310
|
-
this._backup(cwd);
|
|
539
|
+
this._backup(cwd, fixed);
|
|
311
540
|
this._write(cwd, fixed);
|
|
312
541
|
console.log('✅ 已修复并备份');
|
|
313
542
|
}
|
|
@@ -315,56 +544,76 @@ export class ProgressManager {
|
|
|
315
544
|
return true;
|
|
316
545
|
}
|
|
317
546
|
|
|
318
|
-
reset(cwd, stage) {
|
|
319
|
-
this._ensureDir(cwd);
|
|
320
|
-
|
|
547
|
+
reset(cwd, stage, changeName = null) {
|
|
321
548
|
if (stage) {
|
|
322
|
-
this.
|
|
323
|
-
const data = this.read(cwd);
|
|
549
|
+
const data = this.read(cwd, changeName);
|
|
324
550
|
if (!data) { console.log('❌ 无法读取 progress.json'); return; }
|
|
551
|
+
this._backup(cwd, data);
|
|
325
552
|
if (!data.stages[stage]) { console.log(`❌ 未知阶段: ${stage}`); return; }
|
|
326
553
|
data.stages[stage] = emptyStage();
|
|
327
554
|
data.lastActive = new Date().toLocaleString('zh-CN',{hour12:false});
|
|
328
555
|
this._write(cwd, data);
|
|
329
556
|
console.log(`✅ 已重置阶段: ${stage}`);
|
|
330
557
|
} else {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
558
|
+
// 重置所有变更或指定变更
|
|
559
|
+
if (changeName) {
|
|
560
|
+
const p = this._changePath(cwd, changeName, PROGRESS_FILE);
|
|
561
|
+
const backup = this._changePath(cwd, changeName, PROGRESS_FILE + BACKUP_SUFFIX);
|
|
562
|
+
let didReset = false;
|
|
563
|
+
if (existsSync(p)) { unlinkSync(p); didReset = true; }
|
|
564
|
+
if (existsSync(backup)) { unlinkSync(backup); didReset = true; }
|
|
565
|
+
if (didReset) console.log(`✅ 已重置变更 ${changeName} 的进度`);
|
|
566
|
+
else console.log('ℹ️ 无进度文件可重置');
|
|
338
567
|
} else {
|
|
339
|
-
|
|
568
|
+
const changes = this.listChanges(cwd);
|
|
569
|
+
for (const cn of changes) {
|
|
570
|
+
const p = this._changePath(cwd, cn, PROGRESS_FILE);
|
|
571
|
+
const backup = this._changePath(cwd, cn, PROGRESS_FILE + BACKUP_SUFFIX);
|
|
572
|
+
if (existsSync(p)) unlinkSync(p);
|
|
573
|
+
if (existsSync(backup)) unlinkSync(backup);
|
|
574
|
+
}
|
|
575
|
+
console.log('✅ 已重置所有变更的进度');
|
|
340
576
|
}
|
|
341
577
|
}
|
|
342
578
|
}
|
|
343
579
|
|
|
344
580
|
// ── 内部辅助 ──
|
|
345
581
|
|
|
346
|
-
_readOrInit(cwd) {
|
|
347
|
-
let data = this.read(cwd);
|
|
582
|
+
_readOrInit(cwd, changeName = null) {
|
|
583
|
+
let data = this.read(cwd, changeName);
|
|
348
584
|
if (!data) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
585
|
+
// 尝试自动检测变更名
|
|
586
|
+
if (!changeName) {
|
|
587
|
+
const changes = this.listChanges(cwd);
|
|
588
|
+
if (changes.length === 1) changeName = changes[0];
|
|
589
|
+
}
|
|
590
|
+
if (changeName) {
|
|
591
|
+
this._ensureChangeDir(cwd, changeName);
|
|
592
|
+
const progressPath = this._changePath(cwd, changeName, PROGRESS_FILE);
|
|
593
|
+
if (!existsSync(progressPath)) {
|
|
594
|
+
data = makeInitialProgress(basename(cwd));
|
|
595
|
+
data.currentChange = changeName;
|
|
596
|
+
this._write(cwd, data, changeName);
|
|
597
|
+
this.registerChange(cwd, changeName);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
if (!data) {
|
|
601
|
+
data = this.read(cwd, changeName);
|
|
602
|
+
}
|
|
603
|
+
if (!data) {
|
|
604
|
+
console.log('❌ 无法确定当前变更,请指定 --change <name>');
|
|
356
605
|
return null;
|
|
357
606
|
}
|
|
358
607
|
}
|
|
359
608
|
return data;
|
|
360
609
|
}
|
|
361
610
|
|
|
362
|
-
_requireStage(cwd, stage) {
|
|
611
|
+
_requireStage(cwd, stage, changeName = null) {
|
|
363
612
|
if (!VALID_STAGES.includes(stage)) {
|
|
364
613
|
console.log(`❌ 未知阶段: ${stage},可选: ${VALID_STAGES.join(', ')}`);
|
|
365
614
|
return null;
|
|
366
615
|
}
|
|
367
|
-
const data = this._readOrInit(cwd);
|
|
616
|
+
const data = this._readOrInit(cwd, changeName);
|
|
368
617
|
if (!data) return null;
|
|
369
618
|
if (!data.stages[stage]) data.stages[stage] = emptyStage();
|
|
370
619
|
return data;
|
|
@@ -393,7 +642,6 @@ export class ProgressManager {
|
|
|
393
642
|
_timeAgo(dateStr) {
|
|
394
643
|
if (!dateStr) return '未知';
|
|
395
644
|
let ts = Date.parse(dateStr);
|
|
396
|
-
// toLocaleString('zh-CN') 格式(如 2026/5/12 21:09:00)可能解析失败,尝试手动解析
|
|
397
645
|
if (isNaN(ts)) {
|
|
398
646
|
const m = dateStr.match(/(\d{4})[\/-](\d{1,2})[\/-](\d{1,2})[\s,]+(\d{1,2}):(\d{2})(?::(\d{2}))?/);
|
|
399
647
|
if (m) ts = new Date(+m[1], +m[2]-1, +m[3], +m[4], +m[5], +(m[6]||0)).getTime();
|
|
@@ -410,8 +658,8 @@ export class ProgressManager {
|
|
|
410
658
|
|
|
411
659
|
// ── 批量进度 ──
|
|
412
660
|
|
|
413
|
-
updateBatchProgress(cwd, batchData) {
|
|
414
|
-
const data = this._readOrInit(cwd);
|
|
661
|
+
updateBatchProgress(cwd, batchData, changeName = null) {
|
|
662
|
+
const data = this._readOrInit(cwd, changeName);
|
|
415
663
|
if (!data) return;
|
|
416
664
|
|
|
417
665
|
if (!data.batchProgress) {
|
|
@@ -423,19 +671,18 @@ export class ProgressManager {
|
|
|
423
671
|
if (batchData.skipped !== undefined) data.batchProgress.skipped = batchData.skipped;
|
|
424
672
|
|
|
425
673
|
data.lastActive = new Date().toLocaleString('zh-CN', { hour12: false });
|
|
426
|
-
this._backup(cwd);
|
|
674
|
+
this._backup(cwd, data);
|
|
427
675
|
this._write(cwd, data);
|
|
428
676
|
}
|
|
429
677
|
|
|
430
|
-
readBatchProgress(cwd) {
|
|
431
|
-
const data = this.read(cwd);
|
|
678
|
+
readBatchProgress(cwd, changeName = null) {
|
|
679
|
+
const data = this.read(cwd, changeName);
|
|
432
680
|
return data?.batchProgress || null;
|
|
433
681
|
}
|
|
434
682
|
|
|
435
683
|
_renderBatchProgress(batchProgress) {
|
|
436
684
|
if (!batchProgress || !batchProgress.total) return null;
|
|
437
685
|
const { total, completed = 0, failed = 0, skipped = 0 } = batchProgress;
|
|
438
|
-
const done = Math.min(completed + failed + skipped, total);
|
|
439
686
|
const barLen = 20;
|
|
440
687
|
const filled = Math.round((completed / total) * barLen);
|
|
441
688
|
const bar = '█'.repeat(filled) + '░'.repeat(barLen - filled);
|
package/src/run.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* sillyspec run 命令实现
|
|
3
3
|
*
|
|
4
4
|
* CLI 成为流程引擎,AI 变成步骤执行器。
|
|
5
|
+
* 支持多变更并行:每个变更独立 progress.json。
|
|
5
6
|
*/
|
|
6
7
|
import { basename, join } from 'path'
|
|
7
8
|
import { existsSync, readdirSync, mkdirSync, writeFileSync, appendFileSync, readFileSync, statSync } from 'fs'
|
|
@@ -11,7 +12,7 @@ import { buildExecuteSteps } from './stages/execute.js'
|
|
|
11
12
|
import { buildPlanSteps } from './stages/plan.js'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
|
-
*
|
|
15
|
+
* 统一查找变更目录(与 progress.js 的变更检测逻辑一致)
|
|
15
16
|
*/
|
|
16
17
|
function resolveChangeDir(cwd, progress) {
|
|
17
18
|
const changesDir = join(cwd, '.sillyspec', 'changes')
|
|
@@ -48,6 +49,19 @@ function autoDetectChange(progress, cwd) {
|
|
|
48
49
|
return false
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
/**
|
|
53
|
+
* 从 progress 或变更目录推导变更名
|
|
54
|
+
*/
|
|
55
|
+
function resolveChangeName(cwd, progress) {
|
|
56
|
+
if (progress.currentChange) return progress.currentChange
|
|
57
|
+
const changesDir = join(cwd, '.sillyspec', 'changes')
|
|
58
|
+
if (!existsSync(changesDir)) return null
|
|
59
|
+
const entries = readdirSync(changesDir, { withFileTypes: true })
|
|
60
|
+
.filter(e => e.isDirectory() && e.name !== 'archive')
|
|
61
|
+
if (entries.length === 1) return entries[0].name
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
51
65
|
/**
|
|
52
66
|
* 获取阶段的步骤定义(execute 需要动态构建)
|
|
53
67
|
*/
|
|
@@ -70,7 +84,7 @@ async function getStageSteps(stageName, cwd, progress) {
|
|
|
70
84
|
}
|
|
71
85
|
|
|
72
86
|
/**
|
|
73
|
-
* 确保阶段的 steps 已初始化到 progress
|
|
87
|
+
* 确保阶段的 steps 已初始化到 progress
|
|
74
88
|
*/
|
|
75
89
|
async function ensureStageSteps(progress, stageName, cwd) {
|
|
76
90
|
if (!progress.stages) progress.stages = {}
|
|
@@ -90,7 +104,6 @@ async function ensureStageSteps(progress, stageName, cwd) {
|
|
|
90
104
|
|
|
91
105
|
// 检查步骤数量是否匹配(execute 动态步骤可能变化)
|
|
92
106
|
if (progress.stages[stageName].steps.length !== steps.length) {
|
|
93
|
-
// 保留已完成的状态,重新构建步骤列表
|
|
94
107
|
const oldSteps = progress.stages[stageName].steps
|
|
95
108
|
progress.stages[stageName].steps = steps.map(s => {
|
|
96
109
|
const old = oldSteps.find(step => step.name === s.name)
|
|
@@ -106,7 +119,7 @@ async function ensureStageSteps(progress, stageName, cwd) {
|
|
|
106
119
|
/**
|
|
107
120
|
* 输出当前步骤的 prompt
|
|
108
121
|
*/
|
|
109
|
-
function outputStep(stageName, stepIndex, steps, cwd) {
|
|
122
|
+
function outputStep(stageName, stepIndex, steps, cwd, changeName) {
|
|
110
123
|
const step = steps[stepIndex]
|
|
111
124
|
const total = steps.length
|
|
112
125
|
const projectName = basename(cwd)
|
|
@@ -131,6 +144,7 @@ function outputStep(stageName, stepIndex, steps, cwd) {
|
|
|
131
144
|
console.log(`step: ${stepIndex + 1}/${total}`)
|
|
132
145
|
console.log(`stepName: ${step.name}`)
|
|
133
146
|
console.log(`project: ${projectName}`)
|
|
147
|
+
if (changeName) console.log(`change: ${changeName}`)
|
|
134
148
|
console.log(`---\n`)
|
|
135
149
|
if (personas[stageName]) {
|
|
136
150
|
console.log(personas[stageName])
|
|
@@ -146,8 +160,9 @@ function outputStep(stageName, stepIndex, steps, cwd) {
|
|
|
146
160
|
console.log('- 完成后立即执行 --done 命令,不得跳过')
|
|
147
161
|
console.log('- 文档类型文件(.md/.yaml/.json 等)头部必须包含 author(git 用户名)和 created_at(精确到秒)')
|
|
148
162
|
console.log('- 执行构建/测试前必须先读 local.yaml,优先使用其中配置的命令、路径和环境变量;未配置时才使用默认值')
|
|
163
|
+
const changeFlag = changeName ? ` --change ${changeName}` : ''
|
|
149
164
|
console.log(`\n### 完成后执行`)
|
|
150
|
-
console.log(`sillyspec run ${stageName} --done --input "用户原始需求/反馈" --output "你的摘要"`)
|
|
165
|
+
console.log(`sillyspec run ${stageName} --done${changeFlag} --input "用户原始需求/反馈" --output "你的摘要"`)
|
|
151
166
|
}
|
|
152
167
|
|
|
153
168
|
/**
|
|
@@ -200,41 +215,45 @@ export async function runCommand(args, cwd) {
|
|
|
200
215
|
const isAuxiliary = auxiliaryStages.includes(stageName)
|
|
201
216
|
|
|
202
217
|
const pm = new ProgressManager()
|
|
203
|
-
|
|
218
|
+
pm._migrateIfNeeded(cwd)
|
|
219
|
+
let progress = pm.read(cwd, changeName)
|
|
204
220
|
|
|
205
221
|
if (!progress) {
|
|
206
|
-
//
|
|
207
|
-
|
|
222
|
+
// 如果指定了变更名或有变更目录,自动初始化变更的 progress
|
|
223
|
+
const autoChange = changeName || resolveChangeNameAuto(cwd)
|
|
224
|
+
if (autoChange) {
|
|
225
|
+
progress = pm.initChange(cwd, autoChange)
|
|
226
|
+
} else if (!isAuxiliary) {
|
|
208
227
|
console.error('❌ 未找到 progress.json,请先运行 sillyspec init')
|
|
228
|
+
console.error(' 提示:使用 --change <name> 指定变更名')
|
|
209
229
|
process.exit(1)
|
|
210
230
|
}
|
|
211
|
-
progress = pm.init(cwd)
|
|
212
231
|
}
|
|
213
232
|
|
|
233
|
+
// 确保 progress 有 currentChange
|
|
234
|
+
const effectiveChange = changeName || progress.currentChange || resolveChangeName(cwd, progress)
|
|
235
|
+
|
|
214
236
|
// -- auto 模式:自动推进所有流程阶段
|
|
215
237
|
if (stageName === 'auto') {
|
|
216
|
-
return await runAutoMode(pm, progress, cwd, flags)
|
|
238
|
+
return await runAutoMode(pm, progress, cwd, flags, effectiveChange)
|
|
217
239
|
}
|
|
218
240
|
|
|
219
|
-
// --change
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
pm._write(cwd, progress)
|
|
224
|
-
console.log(`✅ 当前变更设置为:${changeName}`)
|
|
225
|
-
return
|
|
241
|
+
// --change 只作为变更名标识,不再拦截流程
|
|
242
|
+
// 注册变更到全局活跃列表(如果尚未注册)
|
|
243
|
+
if (effectiveChange) {
|
|
244
|
+
pm.registerChange(cwd, effectiveChange)
|
|
226
245
|
}
|
|
227
246
|
|
|
228
247
|
// --reset
|
|
229
248
|
if (isReset) {
|
|
230
|
-
return await resetStage(pm, progress, stageName, cwd)
|
|
249
|
+
return await resetStage(pm, progress, stageName, cwd, effectiveChange)
|
|
231
250
|
}
|
|
232
251
|
|
|
233
252
|
// 确保步骤已初始化
|
|
234
253
|
const changed = await ensureStageSteps(progress, stageName, cwd)
|
|
235
254
|
if (changed) {
|
|
236
|
-
pm._write(cwd, progress)
|
|
237
|
-
progress = pm.read(cwd)
|
|
255
|
+
pm._write(cwd, progress, effectiveChange)
|
|
256
|
+
progress = pm.read(cwd, effectiveChange)
|
|
238
257
|
}
|
|
239
258
|
|
|
240
259
|
// --status
|
|
@@ -244,23 +263,35 @@ export async function runCommand(args, cwd) {
|
|
|
244
263
|
|
|
245
264
|
// --skip
|
|
246
265
|
if (isSkip) {
|
|
247
|
-
return await skipStep(pm, progress, stageName, cwd)
|
|
266
|
+
return await skipStep(pm, progress, stageName, cwd, effectiveChange)
|
|
248
267
|
}
|
|
249
268
|
|
|
250
269
|
// --done
|
|
251
270
|
if (isDone) {
|
|
252
|
-
return await completeStep(pm, progress, stageName, cwd, outputText, inputText, { confirm: isConfirm })
|
|
271
|
+
return await completeStep(pm, progress, stageName, cwd, outputText, inputText, { confirm: isConfirm, changeName: effectiveChange })
|
|
253
272
|
}
|
|
254
273
|
|
|
255
274
|
// 默认:输出当前步骤
|
|
256
|
-
return await runStage(pm, progress, stageName, cwd)
|
|
275
|
+
return await runStage(pm, progress, stageName, cwd, effectiveChange)
|
|
257
276
|
}
|
|
258
277
|
|
|
259
|
-
|
|
278
|
+
/**
|
|
279
|
+
* 自动推导变更名(不依赖 progress)
|
|
280
|
+
*/
|
|
281
|
+
function resolveChangeNameAuto(cwd) {
|
|
282
|
+
const changesDir = join(cwd, '.sillyspec', 'changes')
|
|
283
|
+
if (!existsSync(changesDir)) return null
|
|
284
|
+
const entries = readdirSync(changesDir, { withFileTypes: true })
|
|
285
|
+
.filter(e => e.isDirectory() && e.name !== 'archive')
|
|
286
|
+
if (entries.length === 1) return entries[0].name
|
|
287
|
+
return null
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function runStage(pm, progress, stageName, cwd, changeName) {
|
|
260
291
|
// 自动探测 currentChange
|
|
261
292
|
if (autoDetectChange(progress, cwd)) {
|
|
262
293
|
progress.lastActive = new Date().toLocaleString('zh-CN', { hour12: false })
|
|
263
|
-
pm._write(cwd, progress)
|
|
294
|
+
pm._write(cwd, progress, changeName)
|
|
264
295
|
}
|
|
265
296
|
|
|
266
297
|
const stageData = progress.stages[stageName]
|
|
@@ -273,7 +304,7 @@ async function runStage(pm, progress, stageName, cwd) {
|
|
|
273
304
|
if (progress.currentStage !== stageName) {
|
|
274
305
|
progress.currentStage = stageName
|
|
275
306
|
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
276
|
-
pm._write(cwd, progress)
|
|
307
|
+
pm._write(cwd, progress, changeName)
|
|
277
308
|
}
|
|
278
309
|
|
|
279
310
|
const steps = stageData.steps
|
|
@@ -288,13 +319,12 @@ async function runStage(pm, progress, stageName, cwd) {
|
|
|
288
319
|
stageData.status = 'in-progress'
|
|
289
320
|
stageData.startedAt = new Date().toLocaleString('zh-CN', { hour12: false })
|
|
290
321
|
stageData.completedAt = null
|
|
291
|
-
pm._write(cwd, progress)
|
|
322
|
+
pm._write(cwd, progress, changeName)
|
|
292
323
|
currentIdx = 0
|
|
293
324
|
console.log(`🔄 ${stageName} 阶段已自动重置,重新开始。\n`)
|
|
294
325
|
}
|
|
295
326
|
|
|
296
327
|
if (currentIdx > 0) {
|
|
297
|
-
// 有进行中的步骤,提示用户
|
|
298
328
|
const completed = currentIdx
|
|
299
329
|
const total = steps.length
|
|
300
330
|
console.log(`⚠️ ${stageName} 已进行到第 ${currentIdx + 1}/${total} 步(前 ${completed} 步已完成)。`)
|
|
@@ -303,7 +333,7 @@ async function runStage(pm, progress, stageName, cwd) {
|
|
|
303
333
|
|
|
304
334
|
const defSteps = await getStageSteps(stageName, cwd, progress)
|
|
305
335
|
if (defSteps && defSteps[currentIdx]) {
|
|
306
|
-
outputStep(stageName, currentIdx, defSteps, cwd)
|
|
336
|
+
outputStep(stageName, currentIdx, defSteps, cwd, changeName)
|
|
307
337
|
}
|
|
308
338
|
}
|
|
309
339
|
|
|
@@ -311,7 +341,6 @@ function validateMetadata(cwd, stageName) {
|
|
|
311
341
|
const changesDir = join(cwd, '.sillyspec', 'changes')
|
|
312
342
|
if (!existsSync(changesDir)) return
|
|
313
343
|
|
|
314
|
-
// 找最近 10 分钟内修改的 md/yaml 文件
|
|
315
344
|
const cutoff = Date.now() - 10 * 60 * 1000
|
|
316
345
|
const missing = []
|
|
317
346
|
|
|
@@ -340,7 +369,7 @@ function validateMetadata(cwd, stageName) {
|
|
|
340
369
|
}
|
|
341
370
|
|
|
342
371
|
async function completeStep(pm, progress, stageName, cwd, outputText, inputText = null, options = {}) {
|
|
343
|
-
const { printNext = true, confirm = false } = options
|
|
372
|
+
const { printNext = true, confirm = false, changeName } = options
|
|
344
373
|
const stageData = progress.stages[stageName]
|
|
345
374
|
if (!stageData || !stageData.steps) {
|
|
346
375
|
console.error(`❌ 阶段 ${stageName} 未初始化`)
|
|
@@ -355,19 +384,16 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
355
384
|
process.exit(1)
|
|
356
385
|
}
|
|
357
386
|
|
|
358
|
-
// 标记完成(先标记,再处理动态步骤插入)
|
|
359
|
-
|
|
360
387
|
steps[currentIdx].status = 'completed'
|
|
361
388
|
steps[currentIdx].completedAt = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
362
389
|
if (outputText) {
|
|
363
390
|
const MAX_OUTPUT = 200
|
|
364
391
|
if (outputText.length > MAX_OUTPUT) {
|
|
365
392
|
steps[currentIdx].output = outputText.slice(0, MAX_OUTPUT) + '…'
|
|
366
|
-
// Save full output to artifacts/
|
|
367
393
|
const artifactsDir = join(cwd, '.sillyspec', '.runtime', 'artifacts')
|
|
368
394
|
mkdirSync(artifactsDir, { recursive: true })
|
|
369
395
|
const ts = new Date().toISOString().slice(0,19).replace(/[-T:]/g, '')
|
|
370
|
-
writeFileSync(join(artifactsDir, `${stageName}-step${currentIdx + 1}-${ts}.txt`), outputText)
|
|
396
|
+
writeFileSync(join(artifactsDir, `${changeName || 'unknown'}-${stageName}-step${currentIdx + 1}-${ts}.txt`), outputText)
|
|
371
397
|
} else {
|
|
372
398
|
steps[currentIdx].output = outputText
|
|
373
399
|
}
|
|
@@ -384,10 +410,8 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
384
410
|
const fullSteps = buildPlanSteps(changeDir, planContent)
|
|
385
411
|
const prefixLen = fixedPrefix.length
|
|
386
412
|
const suffixLen = fixedSuffix.length
|
|
387
|
-
// 提取协调器步骤(prefix 和 suffix 之间)
|
|
388
413
|
const coordinatorSteps = fullSteps.slice(prefixLen, suffixLen > 0 ? -suffixLen : undefined)
|
|
389
414
|
if (coordinatorSteps.length > 0) {
|
|
390
|
-
// 在当前步骤之后插入协调器步骤(含 prompt,否则 outputStep 无法打印)
|
|
391
415
|
for (let i = 0; i < coordinatorSteps.length; i++) {
|
|
392
416
|
steps.splice(currentIdx + 1 + i, 0, {
|
|
393
417
|
name: coordinatorSteps[i].name,
|
|
@@ -406,36 +430,34 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
406
430
|
const nextPendingIdx = steps.findIndex(s => s.status === 'pending')
|
|
407
431
|
|
|
408
432
|
if (nextPendingIdx === -1) {
|
|
409
|
-
// 全部完成 — 仅标记当前阶段,不自动推进 currentStage
|
|
410
433
|
stageData.status = 'completed'
|
|
411
434
|
stageData.completedAt = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
412
435
|
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
413
|
-
pm._write(cwd, progress)
|
|
436
|
+
pm._write(cwd, progress, changeName)
|
|
414
437
|
|
|
415
438
|
// Append to user-inputs.md
|
|
416
439
|
if (outputText) {
|
|
417
440
|
const inputsPath = join(cwd, '.sillyspec', '.runtime', 'user-inputs.md')
|
|
418
|
-
const entry = `\n## ${new Date().toLocaleString('zh-CN',{hour12:false})} | ${stageName}: ${steps[currentIdx].name}\n${inputText ? "- 输入:" + inputText + "\n" : ""}- 输出:${outputText}\n`
|
|
441
|
+
const entry = `\n## ${new Date().toLocaleString('zh-CN',{hour12:false})} | ${changeName || '?'} | ${stageName}: ${steps[currentIdx].name}\n${inputText ? "- 输入:" + inputText + "\n" : ""}- 输出:${outputText}\n`
|
|
419
442
|
appendFileSync(inputsPath, entry)
|
|
420
443
|
}
|
|
421
444
|
|
|
422
|
-
// 验证:检查生成的文件是否包含 author 和 created_at
|
|
423
445
|
validateMetadata(cwd, stageName)
|
|
424
446
|
|
|
425
|
-
// archive
|
|
447
|
+
// archive 阶段确认归档
|
|
426
448
|
if (stageName === 'archive' && steps[currentIdx]?.name === '确认归档') {
|
|
427
449
|
if (confirm) {
|
|
428
450
|
const { renameSync } = await import('fs')
|
|
429
|
-
const
|
|
430
|
-
if (!
|
|
451
|
+
const archiveChangeName = progress.currentChange
|
|
452
|
+
if (!archiveChangeName) {
|
|
431
453
|
console.error('❌ 归档失败:未找到当前变更名(currentChange)')
|
|
432
454
|
process.exit(1)
|
|
433
455
|
}
|
|
434
456
|
const changesDir = join(cwd, '.sillyspec', 'changes')
|
|
435
457
|
const archiveDir = join(changesDir, 'archive')
|
|
436
|
-
const srcDir = join(changesDir,
|
|
458
|
+
const srcDir = join(changesDir, archiveChangeName)
|
|
437
459
|
const date = new Date().toISOString().slice(0, 10)
|
|
438
|
-
const destDir = join(archiveDir, `${date}-${
|
|
460
|
+
const destDir = join(archiveDir, `${date}-${archiveChangeName}`)
|
|
439
461
|
|
|
440
462
|
if (!existsSync(srcDir)) {
|
|
441
463
|
console.error(`❌ 归档失败:源目录不存在 ${srcDir}`)
|
|
@@ -448,21 +470,20 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
448
470
|
mkdirSync(archiveDir, { recursive: true })
|
|
449
471
|
renameSync(srcDir, destDir)
|
|
450
472
|
|
|
451
|
-
// 校验
|
|
452
473
|
if (!existsSync(destDir) || existsSync(srcDir)) {
|
|
453
474
|
console.error('❌ 归档校验失败:移动操作异常')
|
|
454
475
|
process.exit(1)
|
|
455
476
|
}
|
|
456
477
|
|
|
457
|
-
//
|
|
458
|
-
|
|
459
|
-
console.log(`📦 已归档:${
|
|
478
|
+
// 从全局活跃列表移除
|
|
479
|
+
pm.unregisterChange(cwd, archiveChangeName)
|
|
480
|
+
console.log(`📦 已归档:${archiveChangeName} → archive/${date}-${archiveChangeName}/`)
|
|
460
481
|
} else {
|
|
461
482
|
console.log('⚠️ 请添加 --confirm 确认归档,例如:sillyspec run archive --done --confirm --output "确认归档"')
|
|
462
483
|
}
|
|
463
484
|
}
|
|
464
485
|
|
|
465
|
-
//
|
|
486
|
+
// 辅助阶段完成后重置步骤
|
|
466
487
|
const stageDef = stageRegistry[stageName]
|
|
467
488
|
if (stageDef?.auxiliary) {
|
|
468
489
|
const freshSteps = (stageDef.steps || []).map(s => ({
|
|
@@ -474,13 +495,12 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
474
495
|
stageData.steps = freshSteps
|
|
475
496
|
stageData.status = 'pending'
|
|
476
497
|
stageData.completedAt = null
|
|
477
|
-
pm._write(cwd, progress)
|
|
498
|
+
pm._write(cwd, progress, changeName)
|
|
478
499
|
}
|
|
479
500
|
|
|
480
501
|
const total = steps.length
|
|
481
502
|
console.log(`✅ ${stageName} 阶段已完成(${total}/${total} 步)`)
|
|
482
503
|
|
|
483
|
-
// 阶段完成后提示下一步
|
|
484
504
|
if (stageName === 'execute') {
|
|
485
505
|
console.log('\n👉 下一步:sillyspec run verify(验证通过后才能归档)')
|
|
486
506
|
} else if (stageName === 'verify') {
|
|
@@ -494,24 +514,24 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
494
514
|
}
|
|
495
515
|
|
|
496
516
|
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
497
|
-
pm._write(cwd, progress)
|
|
517
|
+
pm._write(cwd, progress, changeName)
|
|
498
518
|
|
|
499
519
|
// Append to user-inputs.md
|
|
500
520
|
if (outputText) {
|
|
501
521
|
const inputsPath = join(cwd, '.sillyspec', '.runtime', 'user-inputs.md')
|
|
502
|
-
const entry = `\n## ${new Date().toLocaleString('zh-CN',{hour12:false})} | ${stageName}: ${steps[currentIdx].name}\n${inputText ? "- 输入:" + inputText + "\n" : ""}- 输出:${outputText}\n`
|
|
522
|
+
const entry = `\n## ${new Date().toLocaleString('zh-CN',{hour12:false})} | ${changeName || '?'} | ${stageName}: ${steps[currentIdx].name}\n${inputText ? "- 输入:" + inputText + "\n" : ""}- 输出:${outputText}\n`
|
|
503
523
|
appendFileSync(inputsPath, entry)
|
|
504
524
|
}
|
|
505
525
|
|
|
506
526
|
const defSteps = await getStageSteps(stageName, cwd, progress)
|
|
507
527
|
console.log(`✅ Step ${currentIdx + 1}/${steps.length} 完成:${steps[currentIdx].name}\n`)
|
|
508
528
|
if (printNext) {
|
|
509
|
-
outputStep(stageName, nextPendingIdx, defSteps, cwd)
|
|
529
|
+
outputStep(stageName, nextPendingIdx, defSteps, cwd, changeName)
|
|
510
530
|
}
|
|
511
531
|
return { stageCompleted: false, currentIdx, nextPendingIdx }
|
|
512
532
|
}
|
|
513
533
|
|
|
514
|
-
async function skipStep(pm, progress, stageName, cwd) {
|
|
534
|
+
async function skipStep(pm, progress, stageName, cwd, changeName) {
|
|
515
535
|
const stageData = progress.stages[stageName]
|
|
516
536
|
if (!stageData || !stageData.steps) {
|
|
517
537
|
console.error(`❌ 阶段 ${stageName} 未初始化`)
|
|
@@ -536,15 +556,14 @@ async function skipStep(pm, progress, stageName, cwd) {
|
|
|
536
556
|
steps[currentIdx].status = 'skipped'
|
|
537
557
|
steps[currentIdx].skippedAt = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
538
558
|
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
539
|
-
pm._write(cwd, progress)
|
|
559
|
+
pm._write(cwd, progress, changeName)
|
|
540
560
|
|
|
541
561
|
console.log(`⏭️ Step ${currentIdx + 1}/${steps.length} 已跳过:${steps[currentIdx].name}`)
|
|
542
562
|
|
|
543
|
-
// 输出下一步
|
|
544
563
|
const nextPendingIdx = steps.findIndex(s => s.status === 'pending')
|
|
545
564
|
if (nextPendingIdx !== -1 && defSteps) {
|
|
546
565
|
console.log('')
|
|
547
|
-
outputStep(stageName, nextPendingIdx, defSteps, cwd)
|
|
566
|
+
outputStep(stageName, nextPendingIdx, defSteps, cwd, changeName)
|
|
548
567
|
}
|
|
549
568
|
}
|
|
550
569
|
|
|
@@ -567,7 +586,6 @@ function showStatus(progress, stageName) {
|
|
|
567
586
|
|
|
568
587
|
const firstPending = steps.findIndex(s => s.status === 'pending')
|
|
569
588
|
|
|
570
|
-
// 批量进度
|
|
571
589
|
if (progress.batchProgress) {
|
|
572
590
|
const bp = progress.batchProgress
|
|
573
591
|
const bpTotal = bp.total || 0
|
|
@@ -591,7 +609,7 @@ function showStatus(progress, stageName) {
|
|
|
591
609
|
})
|
|
592
610
|
}
|
|
593
611
|
|
|
594
|
-
async function resetStage(pm, progress, stageName, cwd) {
|
|
612
|
+
async function resetStage(pm, progress, stageName, cwd, changeName) {
|
|
595
613
|
const defSteps = await getStageSteps(stageName, cwd, progress)
|
|
596
614
|
progress.stages[stageName] = {
|
|
597
615
|
status: 'in-progress',
|
|
@@ -600,14 +618,14 @@ async function resetStage(pm, progress, stageName, cwd) {
|
|
|
600
618
|
steps: defSteps ? defSteps.map(s => ({ name: s.name, status: 'pending' })) : []
|
|
601
619
|
}
|
|
602
620
|
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
603
|
-
pm._write(cwd, progress)
|
|
621
|
+
pm._write(cwd, progress, changeName)
|
|
604
622
|
console.log(`🔄 ${stageName} 阶段已重置`)
|
|
605
623
|
}
|
|
606
624
|
|
|
607
625
|
/**
|
|
608
626
|
* auto 模式:自动推进 brainstorm → plan → execute → verify
|
|
609
627
|
*/
|
|
610
|
-
async function runAutoMode(pm, progress, cwd, flags) {
|
|
628
|
+
async function runAutoMode(pm, progress, cwd, flags, changeName) {
|
|
611
629
|
const flowStages = ['brainstorm', 'plan', 'execute', 'verify']
|
|
612
630
|
const isDone = flags.includes('--done')
|
|
613
631
|
const outputIdx = flags.indexOf('--output')
|
|
@@ -623,8 +641,8 @@ async function runAutoMode(pm, progress, cwd, flags) {
|
|
|
623
641
|
const stageChanged = progress.currentStage !== stage
|
|
624
642
|
progress.currentStage = stage
|
|
625
643
|
const changed = await ensureStageSteps(progress, stage, cwd)
|
|
626
|
-
if (stageChanged || changed) pm._write(cwd, progress)
|
|
627
|
-
progress = pm.read(cwd)
|
|
644
|
+
if (stageChanged || changed) pm._write(cwd, progress, changeName)
|
|
645
|
+
progress = pm.read(cwd, changeName)
|
|
628
646
|
return progress
|
|
629
647
|
}
|
|
630
648
|
|
|
@@ -637,7 +655,6 @@ async function runAutoMode(pm, progress, cwd, flags) {
|
|
|
637
655
|
return
|
|
638
656
|
}
|
|
639
657
|
if (!flowStages.includes(currentStage)) {
|
|
640
|
-
// 当前在辅助阶段(scan/quick/archive 等),自动跳到第一个未完成的流程阶段
|
|
641
658
|
const openStage = firstOpenStage()
|
|
642
659
|
if (!openStage) {
|
|
643
660
|
console.log('All auto flow stages are complete.')
|
|
@@ -651,6 +668,7 @@ async function runAutoMode(pm, progress, cwd, flags) {
|
|
|
651
668
|
if (!isDone) {
|
|
652
669
|
console.log('════════════════════════════════════════')
|
|
653
670
|
console.log(' SillySpec Auto Mode')
|
|
671
|
+
if (changeName) console.log(` Change: ${changeName}`)
|
|
654
672
|
console.log('════════════════════════════════════════')
|
|
655
673
|
console.log(` Flow: ${flowStages.join(' -> ')}`)
|
|
656
674
|
console.log(` Current: ${currentStage}`)
|
|
@@ -671,7 +689,7 @@ async function runAutoMode(pm, progress, cwd, flags) {
|
|
|
671
689
|
else console.log('All auto flow stages are complete.')
|
|
672
690
|
return
|
|
673
691
|
}
|
|
674
|
-
outputStep(currentStage, pendingIdx, defSteps, cwd)
|
|
692
|
+
outputStep(currentStage, pendingIdx, defSteps, cwd, changeName)
|
|
675
693
|
return
|
|
676
694
|
}
|
|
677
695
|
|
|
@@ -680,14 +698,14 @@ async function runAutoMode(pm, progress, cwd, flags) {
|
|
|
680
698
|
process.exit(1)
|
|
681
699
|
}
|
|
682
700
|
|
|
683
|
-
const result = await completeStep(pm, progress, currentStage, cwd, outputText, inputText, { printNext: false })
|
|
701
|
+
const result = await completeStep(pm, progress, currentStage, cwd, outputText, inputText, { printNext: false, changeName })
|
|
684
702
|
if (!result) return
|
|
685
|
-
progress = pm.read(cwd)
|
|
703
|
+
progress = pm.read(cwd, changeName)
|
|
686
704
|
|
|
687
705
|
const nextPendingIdx = progress.stages[currentStage]?.steps?.findIndex(step => step.status === 'pending') ?? -1
|
|
688
706
|
if (nextPendingIdx !== -1) {
|
|
689
707
|
const defSteps = await getStageSteps(currentStage, cwd, progress)
|
|
690
|
-
outputStep(currentStage, nextPendingIdx, defSteps, cwd)
|
|
708
|
+
outputStep(currentStage, nextPendingIdx, defSteps, cwd, changeName)
|
|
691
709
|
return
|
|
692
710
|
}
|
|
693
711
|
|
|
@@ -707,11 +725,11 @@ async function runAutoMode(pm, progress, cwd, flags) {
|
|
|
707
725
|
}
|
|
708
726
|
progress.lastActive = new Date().toLocaleString('zh-CN',{hour12:false})
|
|
709
727
|
await ensureStageSteps(progress, next, cwd)
|
|
710
|
-
pm._write(cwd, progress)
|
|
711
|
-
progress = pm.read(cwd)
|
|
728
|
+
pm._write(cwd, progress, changeName)
|
|
729
|
+
progress = pm.read(cwd, changeName)
|
|
712
730
|
|
|
713
731
|
console.log(`\n${currentStage} complete. Auto advanced to ${next}.`)
|
|
714
732
|
const nextSteps = await getStageSteps(next, cwd, progress)
|
|
715
733
|
const firstPending = progress.stages[next]?.steps?.findIndex(step => step.status === 'pending') ?? -1
|
|
716
|
-
if (firstPending !== -1) outputStep(next, firstPending, nextSteps, cwd)
|
|
734
|
+
if (firstPending !== -1) outputStep(next, firstPending, nextSteps, cwd, changeName)
|
|
717
735
|
}
|
package/src/stages/brainstorm.js
CHANGED
package/src/stages/doctor.js
CHANGED
|
@@ -18,9 +18,15 @@ export const definition = {
|
|
|
18
18
|
for d in .sillyspec .sillyspec/projects .sillyspec/docs .sillyspec/changes .sillyspec/.runtime; do
|
|
19
19
|
[ -d "$d" ] && echo "✅ $d" || echo "❌ $d"
|
|
20
20
|
done
|
|
21
|
-
# 检查 progress.json
|
|
22
|
-
[ -
|
|
23
|
-
|
|
21
|
+
# 检查 progress.json(支持新版按变更隔离和旧版全局)
|
|
22
|
+
if [ -d .sillyspec/changes ]; then
|
|
23
|
+
PROGRESS_FILE=$(find .sillyspec/changes -maxdepth 2 -name progress.json | head -1)
|
|
24
|
+
fi
|
|
25
|
+
if [ -z "$PROGRESS_FILE" ]; then
|
|
26
|
+
PROGRESS_FILE='.sillyspec/.runtime/progress.json'
|
|
27
|
+
fi
|
|
28
|
+
[ -f "$PROGRESS_FILE" ] && echo "✅ progress.json 存在 ($PROGRESS_FILE)" || echo "❌ progress.json 不存在"
|
|
29
|
+
node -e "JSON.parse(require('fs').readFileSync('$PROGRESS_FILE','utf8')); console.log('✅ progress.json 可解析')" 2>/dev/null || echo "⚠️ progress.json 不可解析"
|
|
24
30
|
\`\`\`
|
|
25
31
|
|
|
26
32
|
### 2. 项目配置检查
|
|
@@ -42,7 +48,7 @@ done
|
|
|
42
48
|
\`\`\`bash
|
|
43
49
|
# 读取 currentChange 并检查目录存在性
|
|
44
50
|
node -e "
|
|
45
|
-
const p = JSON.parse(require('fs').readFileSync('.sillyspec/.runtime/progress.json','utf8'));
|
|
51
|
+
const p = JSON.parse(require('fs').readFileSync(process.env.PROGRESS_FILE || '.sillyspec/.runtime/progress.json','utf8'));
|
|
46
52
|
const cc = p.currentChange;
|
|
47
53
|
if (!cc) { console.log('ℹ️ 无当前变更'); process.exit(0); }
|
|
48
54
|
const dir = '.sillyspec/changes/' + cc;
|
|
@@ -68,7 +74,7 @@ if (!fs.existsSync(dir)) { console.log('ℹ️ changes/ 目录不存在'); proce
|
|
|
68
74
|
const subs = fs.readdirSync(dir).filter(f => fs.statSync(dir+'/'+f).isDirectory());
|
|
69
75
|
if (subs.length === 0) { console.log('ℹ️ 无变更目录'); process.exit(0); }
|
|
70
76
|
let progress;
|
|
71
|
-
try { progress = JSON.parse(fs.readFileSync('.sillyspec/.runtime/progress.json','utf8')); } catch { console.log('⚠️ 无法读取 progress.json'); subs.forEach(s => console.log('❓ ' + s)); process.exit(0); }
|
|
77
|
+
try { progress = JSON.parse(fs.readFileSync(process.env.PROGRESS_FILE || '.sillyspec/.runtime/progress.json','utf8')); } catch { console.log('⚠️ 无法读取 progress.json'); subs.forEach(s => console.log('❓ ' + s)); process.exit(0); }
|
|
72
78
|
const known = new Set();
|
|
73
79
|
if (progress.currentChange) known.add(progress.currentChange);
|
|
74
80
|
for (const sd of Object.values(progress.stages || {})) {
|
|
@@ -121,7 +127,7 @@ done
|
|
|
121
127
|
# 确定项目路径(使用 progress.json 中的项目或当前目录)
|
|
122
128
|
PROJECT_DIR=$(node -e "
|
|
123
129
|
const fs=require('fs');
|
|
124
|
-
try{const
|
|
130
|
+
try{const fs=require('fs'),path=require('path');const changesDir='.sillyspec/changes';let pp=null;if(fs.existsSync(changesDir)){const entries=fs.readdirSync(changesDir,{withFileTypes:true}).filter(e=>e.isDirectory()&&e.name!=='archive');if(entries.length===1)pp=path.join(changesDir,entries[0].name,'progress.json');}if(!pp)pp='.sillyspec/.runtime/progress.json';const p=JSON.parse(fs.readFileSync(pp,'utf8'));if(p.project){console.log(p.project);process.exit(0)}}catch{}
|
|
125
131
|
const files=fs.readdirSync('.sillyspec/projects').filter(f=>f.endsWith('.yaml'));
|
|
126
132
|
if(files.length>0){const c=fs.readFileSync('.sillyspec/projects/'+files[0],'utf8');const m=c.match(/^path:\\s*(.+)/m);console.log(m?m[1].trim():'.')}else console.log('.')
|
|
127
133
|
" 2>/dev/null)
|
package/src/stages/scan.js
CHANGED
|
@@ -39,7 +39,7 @@ export const definition = {
|
|
|
39
39
|
prompt: `检测已有扫描文档,只生成缺失的。
|
|
40
40
|
|
|
41
41
|
### 操作
|
|
42
|
-
1. \`PROJECT=$(python3 -c "import sys,json;
|
|
42
|
+
1. \`PROJECT=$(python3 -c "import sys,json,glob; files=glob.glob('.sillyspec/changes/*/progress.json'); print(json.load(open(files[0])).get('project','')) if files else print('')" 2>/dev/null || basename "$(pwd)")\`
|
|
43
43
|
2. 检查 7 份文档是否存在:ARCHITECTURE、STRUCTURE、CONVENTIONS、INTEGRATIONS、TESTING、CONCERNS、PROJECT
|
|
44
44
|
3. 列出已有 ✅ 和缺失 ⬜
|
|
45
45
|
4. 只生成缺失的文档
|