sillyspec 3.17.15 → 3.18.1
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/docs/platform-scan-protocol.md +298 -0
- package/package.json +1 -1
- package/src/constants.js +70 -0
- package/src/db.js +4 -0
- package/src/hooks/worktree-guard.js +97 -4
- package/src/index.js +56 -1
- package/src/progress.js +41 -14
- package/src/run.js +315 -83
- package/src/scan-postcheck.js +17 -16
- package/src/stage-contract.js +244 -12
- package/src/stages/brainstorm.js +228 -8
- package/src/stages/index.js +0 -2
- package/src/stages/plan.js +237 -52
- package/src/stages/propose.js +30 -4
- package/src/stages/quick.js +13 -10
- package/src/stages/scan.js +12 -0
- package/src/stages/verify.js +31 -13
- package/src/workflow.js +1 -0
- package/test/platform-artifacts.test.mjs +190 -0
- package/test/platform-failure-samples.test.mjs +195 -0
- package/test/platform-recovery-chain.test.mjs +179 -0
- package/test/platform-recovery.test.mjs +14 -6
- package/test/platform-scan-p0.test.mjs +5 -2
- package/test/run-tests.mjs +31 -3
- package/test/scan-paths.test.mjs +1 -1
- package/test/scan-postcheck.test.mjs +4 -3
- package/test/spec-dir.test.mjs +3 -2
- package/test/stage-contract.test.mjs +120 -7
- package/test/stage-definitions.test.mjs +2 -6
- package/test/wait-gates.test.mjs +501 -0
- package/test/worktree-guard.test.mjs +58 -0
package/src/progress.js
CHANGED
|
@@ -12,16 +12,32 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from 'fs';
|
|
15
|
-
import { join, basename } from 'path';
|
|
15
|
+
import { join, basename, dirname, resolve } from 'path';
|
|
16
16
|
import { DB } from './db.js';
|
|
17
17
|
|
|
18
18
|
// 默认规范目录名(相对于 cwd)
|
|
19
19
|
const SPEC_DIR_NAME = '.sillyspec';
|
|
20
20
|
const RUNTIME_SUBDIR = '.runtime';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 向上查找含 .sillyspec 目录的祖先目录,类似 git 找 .git 的逻辑。
|
|
24
|
+
* 找到则返回 <祖先>/.sillyspec,否则 fallback 到 <cwd>/.sillyspec。
|
|
25
|
+
*/
|
|
26
|
+
export function resolveSpecDir(startDir) {
|
|
27
|
+
let dir = resolve(startDir);
|
|
28
|
+
while (true) {
|
|
29
|
+
const candidate = join(dir, SPEC_DIR_NAME);
|
|
30
|
+
if (existsSync(candidate)) return candidate;
|
|
31
|
+
const parent = dirname(dir);
|
|
32
|
+
if (parent === dir) break; // 到达根目录
|
|
33
|
+
dir = parent;
|
|
34
|
+
}
|
|
35
|
+
return join(resolve(startDir), SPEC_DIR_NAME);
|
|
36
|
+
}
|
|
21
37
|
const CHANGES_SUBDIR = 'changes';
|
|
22
38
|
const GLOBAL_FILE = 'global.json';
|
|
23
39
|
const CURRENT_VERSION = 3;
|
|
24
|
-
const VALID_STAGES = ['scan', 'brainstorm', '
|
|
40
|
+
const VALID_STAGES = ['scan', 'brainstorm', 'plan', 'execute', 'verify', 'archive', 'quick', 'explore'];
|
|
25
41
|
const VALID_STATUSES = ['pending', 'in-progress', 'completed', 'failed', 'blocked', 'waiting'];
|
|
26
42
|
|
|
27
43
|
const STAGE_LABELS = {
|
|
@@ -62,9 +78,10 @@ export class ProgressManager {
|
|
|
62
78
|
|
|
63
79
|
// ── 路径工具 ──
|
|
64
80
|
|
|
65
|
-
/** 获取 specDir
|
|
81
|
+
/** 获取 specDir(优先自定义,否则向上查找含 .sillyspec 的目录,fallback 到 cwd/.sillyspec) */
|
|
66
82
|
_getSpecDir(cwd) {
|
|
67
|
-
|
|
83
|
+
if (this._customSpecDir) return this._customSpecDir;
|
|
84
|
+
return resolveSpecDir(cwd);
|
|
68
85
|
}
|
|
69
86
|
|
|
70
87
|
_runtimePath(cwd, ...parts) {
|
|
@@ -207,21 +224,29 @@ export class ProgressManager {
|
|
|
207
224
|
if (stageIds.length > 0) {
|
|
208
225
|
const placeholders = stageIds.map(() => '?').join(',');
|
|
209
226
|
stepRows = sqlDb.exec(
|
|
210
|
-
`SELECT stage_id, name, status, output, completed_at, ordering, wait_reason, wait_options, wait_answer, waited_at FROM steps WHERE stage_id IN (${placeholders}) ORDER BY stage_id, ordering`,
|
|
227
|
+
`SELECT stage_id, name, status, output, completed_at, ordering, wait_reason, wait_options, wait_answer, waited_at, wait_answers, wait_round, max_wait_rounds FROM steps WHERE stage_id IN (${placeholders}) ORDER BY stage_id, ordering`,
|
|
211
228
|
stageIds
|
|
212
229
|
);
|
|
213
230
|
}
|
|
214
231
|
// 按阶段分组步骤
|
|
215
232
|
const stepsByStage = {};
|
|
216
233
|
if (stepRows && stepRows.length > 0) {
|
|
217
|
-
for (const
|
|
234
|
+
for (const row of stepRows[0].values) {
|
|
235
|
+
const [stageId, name, status, output, completedAt, ordering, waitReason, waitOptions, waitAnswer, waitedAt, waitAnswersJson, waitRound, maxWaitRounds] = row;
|
|
218
236
|
if (!stepsByStage[stageId]) stepsByStage[stageId] = [];
|
|
237
|
+
let waitAnswers = null;
|
|
238
|
+
if (waitAnswersJson) {
|
|
239
|
+
try { waitAnswers = JSON.parse(waitAnswersJson); } catch {}
|
|
240
|
+
}
|
|
219
241
|
stepsByStage[stageId].push({
|
|
220
242
|
name, status, output, completedAt,
|
|
221
243
|
...(waitReason ? { waitReason } : {}),
|
|
222
244
|
...(waitOptions ? { waitOptions } : {}),
|
|
223
245
|
...(waitAnswer ? { waitAnswer } : {}),
|
|
224
246
|
...(waitedAt ? { waitedAt } : {}),
|
|
247
|
+
...(waitAnswers ? { waitAnswers } : {}),
|
|
248
|
+
...(waitRound != null ? { waitRound } : {}),
|
|
249
|
+
...(maxWaitRounds != null ? { maxWaitRounds } : {}),
|
|
225
250
|
});
|
|
226
251
|
}
|
|
227
252
|
}
|
|
@@ -257,6 +282,9 @@ export class ProgressManager {
|
|
|
257
282
|
...(s.waitOptions ? { waitOptions: s.waitOptions } : {}),
|
|
258
283
|
...(s.waitAnswer ? { waitAnswer: s.waitAnswer } : {}),
|
|
259
284
|
...(s.waitedAt ? { waitedAt: s.waitedAt } : {}),
|
|
285
|
+
...(s.waitAnswers ? { waitAnswers: s.waitAnswers } : {}),
|
|
286
|
+
...(s.waitRound != null ? { waitRound: s.waitRound } : {}),
|
|
287
|
+
...(s.maxWaitRounds != null ? { maxWaitRounds: s.maxWaitRounds } : {}),
|
|
260
288
|
}));
|
|
261
289
|
stages[stage] = {
|
|
262
290
|
status: info.status,
|
|
@@ -340,9 +368,11 @@ export class ProgressManager {
|
|
|
340
368
|
// UPSERT 步骤(先删再插,steps 表无 UNIQUE 约束)
|
|
341
369
|
sqlDb.run('DELETE FROM steps WHERE stage_id = ? AND name = ?', [stageId, step.name]);
|
|
342
370
|
sqlDb.run(
|
|
343
|
-
'INSERT INTO steps (stage_id, name, status, output, completed_at, ordering, wait_reason, wait_options, wait_answer, waited_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
371
|
+
'INSERT INTO steps (stage_id, name, status, output, completed_at, ordering, wait_reason, wait_options, wait_answer, waited_at, wait_answers, wait_round, max_wait_rounds) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
344
372
|
[stageId, step.name, step.status || 'pending', step.output || null, step.completedAt || null, i,
|
|
345
|
-
step.waitReason
|
|
373
|
+
step.waitReason ?? null, step.waitOptions ?? null, step.waitAnswer ?? null, step.waitedAt ?? null,
|
|
374
|
+
Array.isArray(step.waitAnswers) ? JSON.stringify(step.waitAnswers) : null,
|
|
375
|
+
step.waitRound ?? null, step.maxWaitRounds ?? null]
|
|
346
376
|
);
|
|
347
377
|
}
|
|
348
378
|
}
|
|
@@ -411,11 +441,8 @@ export class ProgressManager {
|
|
|
411
441
|
VALUES (?, ?, ?)`,
|
|
412
442
|
[changeName, now, now]
|
|
413
443
|
);
|
|
414
|
-
//
|
|
415
|
-
|
|
416
|
-
`UPDATE changes SET status = 'active', last_active = ? WHERE name = ? AND status = 'archived'`,
|
|
417
|
-
[now, changeName]
|
|
418
|
-
);
|
|
444
|
+
// 注意:不复活已归档的变更——归档是不可逆操作
|
|
445
|
+
// 如果变更已存在且为 archived,保持 archived 状态不变
|
|
419
446
|
});
|
|
420
447
|
}
|
|
421
448
|
|
|
@@ -634,7 +661,7 @@ export class ProgressManager {
|
|
|
634
661
|
const changeId = changeRow[0].values[0][0];
|
|
635
662
|
|
|
636
663
|
// 批量插入 9 个阶段(INSERT OR IGNORE 跳过已存在的)
|
|
637
|
-
const allStages = ['scan', 'brainstorm', 'plan', 'execute', 'verify', 'archive', 'quick', 'explore'
|
|
664
|
+
const allStages = ['scan', 'brainstorm', 'plan', 'execute', 'verify', 'archive', 'quick', 'explore'];
|
|
638
665
|
for (const stage of allStages) {
|
|
639
666
|
sqlDb.run(
|
|
640
667
|
`INSERT OR IGNORE INTO stages (change_id, stage, status)
|