sillyspec 3.25.8 → 3.25.11
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-auto/SKILL.md +8 -13
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +8 -6
- package/.claude/skills/sillyspec-continue/SKILL.md +3 -3
- package/.claude/skills/sillyspec-doctor/SKILL.md +1 -1
- package/.claude/skills/sillyspec-execute/SKILL.md +10 -4
- package/.claude/skills/sillyspec-plan/SKILL.md +1 -1
- package/.claude/skills/sillyspec-verify/SKILL.md +1 -1
- package/README.md +9 -0
- package/package.json +2 -2
- package/src/change-list.js +11 -8
- package/src/change-risk-profile.js +22 -0
- package/src/db.js +160 -159
- package/src/dispatch/backends/local-agent.js +152 -0
- package/src/dispatch/backends/sillyhub-mcp.js +242 -0
- package/src/dispatch/probe.js +208 -0
- package/src/dispatch/strategy.js +121 -0
- package/src/doctor-diagnostics.js +19 -25
- package/src/fs-atomic.js +69 -69
- package/src/git-helper.js +66 -0
- package/src/hooks/worktree-guard.js +64 -55
- package/src/index.js +215 -34
- package/src/init.js +7 -14
- package/src/machine-interface.js +54 -19
- package/src/migrate.js +14 -4
- package/src/modules.js +16 -5
- package/src/progress/change-registry.js +72 -70
- package/src/progress/consistency-doctor.js +71 -14
- package/src/progress/shared.js +6 -3
- package/src/progress/stage-machine.js +61 -65
- package/src/progress/step-store.js +62 -67
- package/src/progress.js +155 -233
- package/src/quicklog.js +13 -5
- package/src/run/command.js +65 -37
- package/src/run/complete-handlers.js +1005 -889
- package/src/run/complete.js +115 -208
- package/src/run/concurrent-detect.js +132 -0
- package/src/run/gates.js +289 -17
- package/src/run/prompt.js +22 -68
- package/src/run/quick-audit.js +10 -9
- package/src/run/shared.js +648 -600
- package/src/run/stage.js +43 -24
- package/src/scan-postcheck.js +9 -31
- package/src/sillyhub-mcp/client.js +389 -0
- package/src/spec-dir-typo.js +71 -0
- package/src/stage-contract-engine.js +22 -1
- package/src/stage-contract-spec.js +11 -4
- package/src/stage-contract.js +26 -3
- package/src/stage-review.js +13 -9
- package/src/stages/archive.js +4 -6
- package/src/stages/brainstorm-auto.js +26 -20
- package/src/stages/brainstorm.js +29 -5
- package/src/stages/cmd-existence.js +145 -0
- package/src/stages/doctor.js +9 -6
- package/src/stages/execute.js +61 -20
- package/src/stages/plan-postcheck.js +152 -0
- package/src/stages/plan.js +16 -31
- package/src/stages/quick.js +1 -1
- package/src/stages/scan.js +9 -3
- package/src/stages/status.js +0 -1
- package/src/stages/verify.js +12 -7
- package/src/sync.js +4 -4
- package/src/task-review.js +30 -1
- package/src/verify-postcheck.js +933 -862
- package/src/version.js +19 -0
- package/src/worktree-apply.js +85 -68
- package/src/worktree-deps.js +89 -5
- package/src/worktree.js +213 -111
- package/templates/prompts/taskcard-rules.md +27 -0
- package/templates/prompts/verify-probes.md +3 -3
- package/.claude/skills/sillyspec-propose/SKILL.md +0 -87
- package/src/stages/propose.js +0 -181
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { mkdirSync } from 'fs';
|
|
7
7
|
import { join, resolve, basename } from 'path';
|
|
8
8
|
import { writeAtomicSync } from '../fs-atomic.js';
|
|
9
|
+
import { runValidators } from '../stage-contract.js';
|
|
9
10
|
import { VALID_STAGES, STAGE_LABELS, STAGE_ORDER, MAIN_FLOW_ORDER, SPEC_DIR_NAME, CURRENT_VERSION, emptyStage } from './shared.js';
|
|
10
11
|
|
|
11
12
|
export class StageMachine {
|
|
@@ -17,14 +18,13 @@ export class StageMachine {
|
|
|
17
18
|
* 阶段产物校验门:progress complete-stage / update-step 自动完成时复用
|
|
18
19
|
* stage-contract 的 validator,防止零产物阶段被直接标 completed。
|
|
19
20
|
*/
|
|
20
|
-
|
|
21
|
-
const { runValidators } = await import('../stage-contract.js');
|
|
21
|
+
_validateStageArtifacts(cwd, stage, changeName) {
|
|
22
22
|
const specDir = this.pm._getSpecDir(cwd);
|
|
23
23
|
const defaultSpec = join(resolve(cwd), SPEC_DIR_NAME);
|
|
24
24
|
const specRoot = resolve(specDir) === defaultSpec ? null : specDir;
|
|
25
25
|
let projectName = null;
|
|
26
26
|
try {
|
|
27
|
-
const g =
|
|
27
|
+
const g = this.pm.readGlobal(cwd);
|
|
28
28
|
projectName = g?.project || null;
|
|
29
29
|
} catch {}
|
|
30
30
|
return runValidators(stage, cwd, changeName, {
|
|
@@ -33,27 +33,27 @@ export class StageMachine {
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
completeStage(cwd, stage, changeName = null, opts = {}) {
|
|
37
37
|
const { force = false } = opts;
|
|
38
38
|
if (!VALID_STAGES.includes(stage)) {
|
|
39
39
|
console.log(`❌ 未知阶段: ${stage}`);
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const db =
|
|
43
|
+
const db = this.pm._ensureDB(cwd);
|
|
44
44
|
const now = new Date().toISOString();
|
|
45
45
|
|
|
46
46
|
// 获取变更名
|
|
47
47
|
let cn = changeName;
|
|
48
48
|
if (!cn) {
|
|
49
|
-
const changes =
|
|
49
|
+
const changes = this.pm.listChanges(cwd);
|
|
50
50
|
if (changes.length === 1) cn = changes[0];
|
|
51
51
|
if (!cn) { console.log('❌ 无法确定当前变更,请指定 --change <name>'); return; }
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
// ── 产物校验门:complete-stage 不再是零校验后门 ──
|
|
55
55
|
// 与 run.js completeStep 的 validator 同源;--force 为显式逃生口(留审计)。
|
|
56
|
-
const validation =
|
|
56
|
+
const validation = this._validateStageArtifacts(cwd, stage, cn);
|
|
57
57
|
if (!validation.ok) {
|
|
58
58
|
if (!force) {
|
|
59
59
|
console.error(`❌ complete-stage 被拒绝:阶段 ${stage} 产物校验未通过`);
|
|
@@ -75,39 +75,33 @@ export class StageMachine {
|
|
|
75
75
|
this.pm._appendAuditLog(cwd, { action: 'complete-stage --force', stage, change: cn, validationErrors: [] });
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
db.transaction((
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
db.transaction(() => {
|
|
79
|
+
const sqlDb = db.getDb();
|
|
80
|
+
const changeRow = sqlDb.prepare('SELECT id FROM changes WHERE name = ?').get(cn);
|
|
81
|
+
if (changeRow === undefined) return;
|
|
82
|
+
const changeId = changeRow.id;
|
|
82
83
|
|
|
83
84
|
// 确保 stages 行存在(阶段不存在时自动创建)
|
|
84
|
-
sqlDb.run(
|
|
85
|
-
'INSERT OR IGNORE INTO stages (change_id, stage, status) VALUES (?, ?, "pending")',
|
|
86
|
-
[changeId, stage]
|
|
87
|
-
);
|
|
85
|
+
sqlDb.prepare(`INSERT OR IGNORE INTO stages (change_id, stage, status) VALUES (?, ?, 'pending')`).run(changeId, stage);
|
|
88
86
|
|
|
89
87
|
// UPDATE stages.status=completed + completed_at
|
|
90
|
-
sqlDb.run(
|
|
91
|
-
'UPDATE stages SET status = "completed", completed_at = ? WHERE change_id = ? AND stage = ?',
|
|
92
|
-
[now, changeId, stage]
|
|
93
|
-
);
|
|
88
|
+
sqlDb.prepare(`UPDATE stages SET status = 'completed', completed_at = ? WHERE change_id = ? AND stage = ?`).run(now, changeId, stage);
|
|
94
89
|
|
|
95
90
|
// 将该阶段所有 pending 步骤标记为 completed
|
|
96
|
-
const stageRow = sqlDb.
|
|
97
|
-
if (stageRow
|
|
98
|
-
const stageId = stageRow
|
|
99
|
-
sqlDb.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
);
|
|
91
|
+
const stageRow = sqlDb.prepare('SELECT id FROM stages WHERE change_id = ? AND stage = ?').get(changeId, stage);
|
|
92
|
+
if (stageRow !== undefined) {
|
|
93
|
+
const stageId = stageRow.id;
|
|
94
|
+
sqlDb.prepare(
|
|
95
|
+
`UPDATE steps SET status = 'completed', completed_at = ? WHERE stage_id = ? AND status IN ('pending', 'stale')` // stale 一并回填(reopen --done 场景,坑 brainstorm-reopen-step-state-desync)
|
|
96
|
+
).run(now, stageId);
|
|
103
97
|
}
|
|
104
98
|
|
|
105
99
|
// UPDATE changes.last_active
|
|
106
|
-
sqlDb.
|
|
100
|
+
sqlDb.prepare('UPDATE changes SET last_active = ? WHERE name = ?').run(now, cn);
|
|
107
101
|
});
|
|
108
102
|
|
|
109
103
|
// 写 history 文件(保持文件系统,不变)
|
|
110
|
-
const data =
|
|
104
|
+
const data = this.pm.read(cwd, cn);
|
|
111
105
|
if (data && data.stages && data.stages[stage]) {
|
|
112
106
|
const historyDir = this.pm._runtimePath(cwd, 'history');
|
|
113
107
|
mkdirSync(historyDir, { recursive: true });
|
|
@@ -122,25 +116,25 @@ export class StageMachine {
|
|
|
122
116
|
console.log(`✅ 阶段 ${stage} 已标记为完成(不自动推进,下一步由你决定)`);
|
|
123
117
|
}
|
|
124
118
|
|
|
125
|
-
|
|
119
|
+
show(cwd, changeName = null) {
|
|
126
120
|
// 如果指定了变更名,只显示该变更
|
|
127
121
|
if (changeName) {
|
|
128
|
-
return
|
|
122
|
+
return this._showChange(cwd, changeName);
|
|
129
123
|
}
|
|
130
124
|
|
|
131
125
|
// 否则显示所有变更
|
|
132
|
-
const changes =
|
|
126
|
+
const changes = this.pm.listChanges(cwd);
|
|
133
127
|
if (changes.length === 0) {
|
|
134
128
|
console.log('ℹ️ 没有活跃的变更');
|
|
135
129
|
return;
|
|
136
130
|
}
|
|
137
131
|
|
|
138
132
|
if (changes.length === 1) {
|
|
139
|
-
return
|
|
133
|
+
return this._showChange(cwd, changes[0]);
|
|
140
134
|
}
|
|
141
135
|
|
|
142
136
|
// 多个变更:汇总显示
|
|
143
|
-
const global =
|
|
137
|
+
const global = this.pm.readGlobal(cwd);
|
|
144
138
|
console.log('');
|
|
145
139
|
console.log(' ═══════════════════════════════════════');
|
|
146
140
|
console.log(` 项目: ${(global?.project) || basename(cwd) || '(未命名)'}`);
|
|
@@ -149,7 +143,7 @@ export class StageMachine {
|
|
|
149
143
|
console.log('');
|
|
150
144
|
|
|
151
145
|
for (const cn of changes) {
|
|
152
|
-
const data =
|
|
146
|
+
const data = this.pm.read(cwd, cn);
|
|
153
147
|
if (!data) {
|
|
154
148
|
console.log(` 📂 ${cn} — (无法读取)`);
|
|
155
149
|
continue;
|
|
@@ -167,8 +161,8 @@ export class StageMachine {
|
|
|
167
161
|
console.log('');
|
|
168
162
|
}
|
|
169
163
|
|
|
170
|
-
|
|
171
|
-
const data =
|
|
164
|
+
_showChange(cwd, changeName) {
|
|
165
|
+
const data = this.pm.read(cwd, changeName);
|
|
172
166
|
if (!data) {
|
|
173
167
|
console.log(`❌ 未找到变更 ${changeName}`);
|
|
174
168
|
return;
|
|
@@ -313,12 +307,12 @@ export class StageMachine {
|
|
|
313
307
|
return null;
|
|
314
308
|
}
|
|
315
309
|
|
|
316
|
-
|
|
317
|
-
|
|
310
|
+
status(cwd, changeName = null) {
|
|
311
|
+
this.show(cwd, changeName);
|
|
318
312
|
}
|
|
319
313
|
|
|
320
|
-
|
|
321
|
-
const data =
|
|
314
|
+
validate(cwd, changeName = null) {
|
|
315
|
+
const data = this.pm.read(cwd, changeName);
|
|
322
316
|
if (!data) { console.log('❌ 无法读取进度数据'); return false; }
|
|
323
317
|
|
|
324
318
|
const errors = [];
|
|
@@ -345,7 +339,7 @@ export class StageMachine {
|
|
|
345
339
|
if (!fixed.stages[s]) { fixed.stages[s] = emptyStage(); changed = true; }
|
|
346
340
|
}
|
|
347
341
|
if (changed) {
|
|
348
|
-
|
|
342
|
+
this.pm._write(cwd, fixed);
|
|
349
343
|
console.log('✅ 已修复');
|
|
350
344
|
}
|
|
351
345
|
|
|
@@ -365,10 +359,10 @@ export class StageMachine {
|
|
|
365
359
|
* @param {string} [opts.changeName]
|
|
366
360
|
* @returns {{ ok: boolean, error?: string }}
|
|
367
361
|
*/
|
|
368
|
-
|
|
362
|
+
reopenStage(cwd, stage, opts = {}) {
|
|
369
363
|
const { fromStep, changeName = null } = opts;
|
|
370
364
|
|
|
371
|
-
const data =
|
|
365
|
+
const data = this.pm.read(cwd, changeName);
|
|
372
366
|
if (!data) return { ok: false, error: '无法读取进度数据' };
|
|
373
367
|
|
|
374
368
|
const stageData = data.stages[stage];
|
|
@@ -435,12 +429,12 @@ export class StageMachine {
|
|
|
435
429
|
data.lastActive = now;
|
|
436
430
|
data.currentStage = stage;
|
|
437
431
|
|
|
438
|
-
|
|
432
|
+
this.pm._write(cwd, data, changeName);
|
|
439
433
|
|
|
440
434
|
// 级联标记下游阶段为 stale
|
|
441
435
|
const downstreamStages = this._getDownstreamStages(stage);
|
|
442
436
|
if (downstreamStages.length > 0) {
|
|
443
|
-
const data2 =
|
|
437
|
+
const data2 = this.pm.read(cwd, changeName); // 重新读取以获取最新状态
|
|
444
438
|
if (data2) {
|
|
445
439
|
for (const ds of downstreamStages) {
|
|
446
440
|
if (data2.stages[ds] && data2.stages[ds].status === 'completed') {
|
|
@@ -449,7 +443,7 @@ export class StageMachine {
|
|
|
449
443
|
data2.stages[ds].completedAt = null;
|
|
450
444
|
}
|
|
451
445
|
}
|
|
452
|
-
|
|
446
|
+
this.pm._write(cwd, data2, changeName);
|
|
453
447
|
}
|
|
454
448
|
}
|
|
455
449
|
|
|
@@ -467,9 +461,9 @@ export class StageMachine {
|
|
|
467
461
|
return MAIN_FLOW_ORDER.slice(idx + 1);
|
|
468
462
|
}
|
|
469
463
|
|
|
470
|
-
|
|
464
|
+
reset(cwd, stage, changeName = null) {
|
|
471
465
|
if (stage) {
|
|
472
|
-
const data =
|
|
466
|
+
const data = this.pm.read(cwd, changeName);
|
|
473
467
|
if (!data) { console.log('❌ 无法读取进度数据'); return; }
|
|
474
468
|
if (!data.stages[stage]) { console.log(`❌ 未知阶段: ${stage}`); return; }
|
|
475
469
|
// 破坏性预览:reset 不可逆地清空该阶段所有步骤(含已完成),先告诉 agent 丢了什么。
|
|
@@ -479,38 +473,40 @@ export class StageMachine {
|
|
|
479
473
|
console.warn(`⚠️ 即将重置阶段「${stage}」:丢弃 ${steps.length} 个步骤(其中 ${doneCount} 个已完成,revision ${sd.revision || 0})。此操作不可逆。`);
|
|
480
474
|
data.stages[stage] = emptyStage();
|
|
481
475
|
data.lastActive = new Date().toLocaleString('zh-CN',{hour12:false});
|
|
482
|
-
|
|
476
|
+
this.pm._write(cwd, data);
|
|
483
477
|
console.log(`✅ 已重置阶段: ${stage}`);
|
|
484
478
|
} else {
|
|
485
479
|
// 重置所有变更或指定变更
|
|
486
480
|
if (changeName) {
|
|
487
481
|
console.warn(`⚠️ 即将重置变更「${changeName}」的全部进度(所有阶段的 steps + stage 状态,产物文件不动)。此操作不可逆。`);
|
|
488
482
|
// SQL: 删除该变更的所有 stages 和 steps 数据
|
|
489
|
-
const db =
|
|
490
|
-
db.transaction((
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
sqlDb.
|
|
483
|
+
const db = this.pm._ensureDB(cwd);
|
|
484
|
+
db.transaction(() => {
|
|
485
|
+
const sqlDb = db.getDb();
|
|
486
|
+
const changeRow = sqlDb.prepare('SELECT id FROM changes WHERE name = ?').get(changeName);
|
|
487
|
+
if (changeRow !== undefined) {
|
|
488
|
+
const changeId = changeRow.id;
|
|
489
|
+
sqlDb.prepare('DELETE FROM steps WHERE stage_id IN (SELECT id FROM stages WHERE change_id = ?)').run(changeId);
|
|
490
|
+
sqlDb.prepare('DELETE FROM stages WHERE change_id = ?').run(changeId);
|
|
496
491
|
// 重新插入所有阶段(注意:上方已 DELETE stages,无需再 UPDATE)
|
|
497
492
|
for (const s of VALID_STAGES) {
|
|
498
|
-
sqlDb.
|
|
493
|
+
sqlDb.prepare(`INSERT OR IGNORE INTO stages (change_id, stage, status) VALUES (?, ?, 'pending')`).run(changeId, s);
|
|
499
494
|
}
|
|
500
495
|
}
|
|
501
496
|
});
|
|
502
497
|
console.log(`✅ 已重置变更 ${changeName} 的进度`);
|
|
503
498
|
} else {
|
|
504
|
-
const changes =
|
|
499
|
+
const changes = this.pm.listChanges(cwd);
|
|
505
500
|
console.warn(`⚠️ 即将重置【所有 ${changes.length} 个变更】的进度(仅 stage 状态,产物文件不动)。此操作不可逆且范围最大。`);
|
|
506
|
-
const db =
|
|
507
|
-
db.transaction((
|
|
501
|
+
const db = this.pm._ensureDB(cwd);
|
|
502
|
+
db.transaction(() => {
|
|
503
|
+
const sqlDb = db.getDb();
|
|
508
504
|
for (const cn of changes) {
|
|
509
|
-
const changeRow = sqlDb.
|
|
510
|
-
if (changeRow
|
|
511
|
-
const changeId = changeRow
|
|
512
|
-
sqlDb.
|
|
513
|
-
sqlDb.
|
|
505
|
+
const changeRow = sqlDb.prepare('SELECT id FROM changes WHERE name = ?').get(cn);
|
|
506
|
+
if (changeRow !== undefined) {
|
|
507
|
+
const changeId = changeRow.id;
|
|
508
|
+
sqlDb.prepare('DELETE FROM steps WHERE stage_id IN (SELECT id FROM stages WHERE change_id = ?)').run(changeId);
|
|
509
|
+
sqlDb.prepare(`UPDATE stages SET status = 'pending', started_at = NULL, completed_at = NULL WHERE change_id = ?`).run(changeId);
|
|
514
510
|
}
|
|
515
511
|
}
|
|
516
512
|
});
|
|
@@ -12,46 +12,41 @@ export class StepStore {
|
|
|
12
12
|
this.pm = pm;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
setStage(cwd, stage, changeName = null) {
|
|
16
16
|
if (!VALID_STAGES.includes(stage)) {
|
|
17
17
|
console.log(`❌ 未知阶段: ${stage},可选: ${VALID_STAGES.join(', ')}`);
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const db =
|
|
21
|
+
const db = this.pm._ensureDB(cwd);
|
|
22
22
|
const now = new Date().toISOString();
|
|
23
23
|
|
|
24
24
|
// 获取变更名
|
|
25
25
|
let cn = changeName;
|
|
26
26
|
if (!cn) {
|
|
27
|
-
const changes =
|
|
27
|
+
const changes = this.pm.listChanges(cwd);
|
|
28
28
|
if (changes.length === 1) cn = changes[0];
|
|
29
29
|
if (!cn) { console.log('❌ 无法确定当前变更,请指定 --change <name>'); return; }
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
let changeFound = false;
|
|
33
|
-
db.transaction((
|
|
33
|
+
db.transaction(() => {
|
|
34
|
+
const sqlDb = db.getDb();
|
|
34
35
|
// 确保 change 存在
|
|
35
|
-
const changeRow = sqlDb.
|
|
36
|
-
if (
|
|
36
|
+
const changeRow = sqlDb.prepare('SELECT id, current_stage FROM changes WHERE name = ?').get(cn);
|
|
37
|
+
if (changeRow === undefined) return;
|
|
37
38
|
|
|
38
39
|
changeFound = true;
|
|
39
|
-
const changeId = changeRow
|
|
40
|
+
const changeId = changeRow.id;
|
|
40
41
|
|
|
41
42
|
// UPDATE changes.current_stage + last_active
|
|
42
|
-
sqlDb.
|
|
43
|
+
sqlDb.prepare('UPDATE changes SET current_stage = ?, last_active = ? WHERE name = ?').run(stage, now, cn);
|
|
43
44
|
|
|
44
45
|
// 确保 stages 行存在(INSERT OR IGNORE)
|
|
45
|
-
sqlDb.run(
|
|
46
|
-
'INSERT OR IGNORE INTO stages (change_id, stage, status) VALUES (?, ?, "pending")',
|
|
47
|
-
[changeId, stage]
|
|
48
|
-
);
|
|
46
|
+
sqlDb.prepare(`INSERT OR IGNORE INTO stages (change_id, stage, status) VALUES (?, ?, 'pending')`).run(changeId, stage);
|
|
49
47
|
|
|
50
48
|
// UPDATE stages.status 为 in-progress(仅当仍为 pending 时)
|
|
51
|
-
sqlDb.run(
|
|
52
|
-
"UPDATE stages SET status = 'in-progress', started_at = ? WHERE change_id = ? AND stage = ? AND status = 'pending'",
|
|
53
|
-
[now, changeId, stage]
|
|
54
|
-
);
|
|
49
|
+
sqlDb.prepare("UPDATE stages SET status = 'in-progress', started_at = ? WHERE change_id = ? AND stage = ? AND status = 'pending'").run(now, changeId, stage);
|
|
55
50
|
});
|
|
56
51
|
|
|
57
52
|
if (!changeFound) {
|
|
@@ -65,62 +60,61 @@ export class StepStore {
|
|
|
65
60
|
console.log(`✅ 当前阶段已设为: ${STAGE_LABELS[stage] || stage}(变更 ${cn})`);
|
|
66
61
|
}
|
|
67
62
|
|
|
68
|
-
|
|
63
|
+
addStep(cwd, stage, stepName, changeName = null) {
|
|
69
64
|
if (!stepName) { console.log('❌ 请指定步骤名称'); return; }
|
|
70
65
|
|
|
71
|
-
const db =
|
|
66
|
+
const db = this.pm._ensureDB(cwd);
|
|
72
67
|
|
|
73
68
|
// 获取变更名
|
|
74
69
|
let cn = changeName;
|
|
75
70
|
if (!cn) {
|
|
76
|
-
const changes =
|
|
71
|
+
const changes = this.pm.listChanges(cwd);
|
|
77
72
|
if (changes.length === 1) cn = changes[0];
|
|
78
73
|
if (!cn) { console.log('❌ 无法确定当前变更,请指定 --change <name>'); return; }
|
|
79
74
|
}
|
|
80
75
|
|
|
81
76
|
// 查找 stage_id
|
|
82
77
|
const sqlDb = db.getDb();
|
|
83
|
-
const stageRow = sqlDb.
|
|
84
|
-
'SELECT s.id FROM stages s JOIN changes c ON s.change_id = c.id WHERE c.name = ? AND s.stage = ?'
|
|
85
|
-
|
|
86
|
-
)
|
|
87
|
-
if (!stageRow || stageRow.length === 0 || stageRow[0].values.length === 0) {
|
|
78
|
+
const stageRow = sqlDb.prepare(
|
|
79
|
+
'SELECT s.id FROM stages s JOIN changes c ON s.change_id = c.id WHERE c.name = ? AND s.stage = ?'
|
|
80
|
+
).get(cn, stage);
|
|
81
|
+
if (stageRow === undefined) {
|
|
88
82
|
// stages 行不存在,静默跳过
|
|
89
83
|
console.log(`ℹ️ 阶段 ${stage} 不存在`);
|
|
90
84
|
return;
|
|
91
85
|
}
|
|
92
|
-
const stageId = stageRow
|
|
86
|
+
const stageId = stageRow.id;
|
|
93
87
|
|
|
94
88
|
// 重复步骤名检查
|
|
95
|
-
const dupRow = sqlDb.
|
|
96
|
-
if (dupRow
|
|
89
|
+
const dupRow = sqlDb.prepare('SELECT id FROM steps WHERE stage_id = ? AND name = ?').get(stageId, stepName);
|
|
90
|
+
if (dupRow !== undefined) {
|
|
97
91
|
console.log(`ℹ️ 步骤 "${stepName}" 已存在于 ${stage}`);
|
|
98
92
|
return;
|
|
99
93
|
}
|
|
100
94
|
|
|
101
95
|
// INSERT INTO steps(ordering 递增)
|
|
102
|
-
db.transaction((
|
|
103
|
-
tDb.
|
|
96
|
+
db.transaction(() => {
|
|
97
|
+
const tDb = db.getDb();
|
|
98
|
+
tDb.prepare(
|
|
104
99
|
`INSERT INTO steps (stage_id, name, ordering, status)
|
|
105
|
-
VALUES (?, ?, (SELECT COALESCE(MAX(ordering), 0) + 1 FROM steps WHERE stage_id = ?), 'pending')
|
|
106
|
-
|
|
107
|
-
);
|
|
108
|
-
tDb.run('UPDATE changes SET last_active = ? WHERE name = ?', [new Date().toISOString(), cn]);
|
|
100
|
+
VALUES (?, ?, (SELECT COALESCE(MAX(ordering), 0) + 1 FROM steps WHERE stage_id = ?), 'pending')`
|
|
101
|
+
).run(stageId, stepName, stageId);
|
|
102
|
+
tDb.prepare('UPDATE changes SET last_active = ? WHERE name = ?').run(new Date().toISOString(), cn);
|
|
109
103
|
});
|
|
110
104
|
|
|
111
105
|
console.log(`✅ 已添加步骤: ${stage}/${stepName}`);
|
|
112
106
|
}
|
|
113
107
|
|
|
114
|
-
|
|
108
|
+
updateStep(cwd, stage, stepName, options = {}, changeName = null) {
|
|
115
109
|
const { status, output } = options;
|
|
116
110
|
if (!stepName) { console.log('❌ 请指定步骤名称'); return; }
|
|
117
111
|
|
|
118
|
-
const db =
|
|
112
|
+
const db = this.pm._ensureDB(cwd);
|
|
119
113
|
|
|
120
114
|
// 获取变更名
|
|
121
115
|
let cn = changeName;
|
|
122
116
|
if (!cn) {
|
|
123
|
-
const changes =
|
|
117
|
+
const changes = this.pm.listChanges(cwd);
|
|
124
118
|
if (changes.length === 1) cn = changes[0];
|
|
125
119
|
if (!cn) { console.log('❌ 无法确定当前变更,请指定 --change <name>'); return; }
|
|
126
120
|
}
|
|
@@ -133,50 +127,50 @@ export class StepStore {
|
|
|
133
127
|
|
|
134
128
|
// 查找 step_id:通过 changes → stages → steps JOIN 查询
|
|
135
129
|
const sqlDb = db.getDb();
|
|
136
|
-
const stepRow = sqlDb.
|
|
130
|
+
const stepRow = sqlDb.prepare(
|
|
137
131
|
`SELECT st.id, st.status FROM steps st
|
|
138
132
|
JOIN stages sg ON st.stage_id = sg.id
|
|
139
133
|
JOIN changes c ON sg.change_id = c.id
|
|
140
|
-
WHERE c.name = ? AND sg.stage = ? AND st.name =
|
|
141
|
-
|
|
142
|
-
)
|
|
143
|
-
if (!stepRow || stepRow.length === 0 || stepRow[0].values.length === 0) {
|
|
134
|
+
WHERE c.name = ? AND sg.stage = ? AND st.name = ?`
|
|
135
|
+
).get(cn, stage, stepName);
|
|
136
|
+
if (stepRow === undefined) {
|
|
144
137
|
console.log(`❌ 步骤不存在: ${stage}/${stepName}`);
|
|
145
138
|
return;
|
|
146
139
|
}
|
|
147
|
-
const stepId = stepRow
|
|
140
|
+
const stepId = stepRow.id;
|
|
148
141
|
|
|
149
142
|
// UPDATE steps
|
|
150
143
|
let stageCompletionCandidateId = null;
|
|
151
|
-
db.transaction((
|
|
144
|
+
db.transaction(() => {
|
|
145
|
+
const tDb = db.getDb();
|
|
152
146
|
const now = new Date().toISOString();
|
|
153
147
|
if (status) {
|
|
154
|
-
tDb.
|
|
148
|
+
tDb.prepare('UPDATE steps SET status = ?, completed_at = ? WHERE id = ? AND name = ?').run(status, now, stepId, stepName);
|
|
155
149
|
}
|
|
156
150
|
if (output !== undefined) {
|
|
157
|
-
tDb.
|
|
151
|
+
tDb.prepare('UPDATE steps SET output = ? WHERE id = ? AND name = ?').run(output, stepId, stepName);
|
|
158
152
|
}
|
|
159
153
|
|
|
160
154
|
// 自动完成检测:同 stage_id 下所有 steps 都 completed 时,候选标记 stage completed
|
|
161
155
|
// (实际标记延后到事务外,先过产物校验门,防止 update-step 成为绕过 validator 的后门)
|
|
162
156
|
if (status === 'completed') {
|
|
163
157
|
// 获取 stage_id
|
|
164
|
-
const stRow = tDb.
|
|
165
|
-
if (stRow
|
|
166
|
-
const stId = stRow
|
|
167
|
-
const
|
|
168
|
-
if (
|
|
158
|
+
const stRow = tDb.prepare('SELECT stage_id FROM steps WHERE id = ?').get(stepId);
|
|
159
|
+
if (stRow !== undefined) {
|
|
160
|
+
const stId = stRow.stage_id;
|
|
161
|
+
const pendingRow = tDb.prepare("SELECT COUNT(*) AS cnt FROM steps WHERE stage_id = ? AND status NOT IN ('completed','skipped')").get(stId);
|
|
162
|
+
if (pendingRow.cnt === 0) {
|
|
169
163
|
stageCompletionCandidateId = stId;
|
|
170
164
|
}
|
|
171
165
|
}
|
|
172
166
|
}
|
|
173
167
|
|
|
174
|
-
tDb.
|
|
168
|
+
tDb.prepare('UPDATE changes SET last_active = ? WHERE name = ?').run(now, cn);
|
|
175
169
|
});
|
|
176
170
|
|
|
177
171
|
if (stageCompletionCandidateId !== null) {
|
|
178
172
|
const { force = false } = options;
|
|
179
|
-
const validation =
|
|
173
|
+
const validation = this.pm._validateStageArtifacts(cwd, stage, cn);
|
|
180
174
|
if (!validation.ok && !force) {
|
|
181
175
|
console.error(`⚠️ 阶段 ${stage} 所有步骤已完成,但产物校验未通过,阶段不标记为 completed:`);
|
|
182
176
|
for (const err of validation.errors) console.error(` - ${err}`);
|
|
@@ -194,8 +188,9 @@ export class StepStore {
|
|
|
194
188
|
validationErrors: validation.errors,
|
|
195
189
|
});
|
|
196
190
|
}
|
|
197
|
-
db.transaction((
|
|
198
|
-
tDb
|
|
191
|
+
db.transaction(() => {
|
|
192
|
+
const tDb = db.getDb();
|
|
193
|
+
tDb.prepare(`UPDATE stages SET status = 'completed', completed_at = ? WHERE id = ?`).run(new Date().toISOString(), stageCompletionCandidateId);
|
|
199
194
|
});
|
|
200
195
|
console.log(`✅ 阶段 ${stage} 所有步骤已完成,阶段已标记为 completed`);
|
|
201
196
|
}
|
|
@@ -204,39 +199,39 @@ export class StepStore {
|
|
|
204
199
|
console.log(`✅ 步骤已更新: ${stage}/${stepName} → ${status || '(仅更新 output)'}`);
|
|
205
200
|
}
|
|
206
201
|
|
|
207
|
-
|
|
202
|
+
updateBatchProgress(cwd, batchData, changeName = null) {
|
|
208
203
|
const cn = changeName || null;
|
|
209
204
|
|
|
210
|
-
const db =
|
|
211
|
-
db.transaction((
|
|
205
|
+
const db = this.pm._ensureDB(cwd);
|
|
206
|
+
db.transaction(() => {
|
|
207
|
+
const sqlDb = db.getDb();
|
|
212
208
|
// 获取 change_id
|
|
213
209
|
let changeId = null;
|
|
214
210
|
if (cn) {
|
|
215
|
-
const row = sqlDb.
|
|
216
|
-
if (row
|
|
211
|
+
const row = sqlDb.prepare('SELECT id FROM changes WHERE name = ?').get(cn);
|
|
212
|
+
if (row !== undefined) changeId = row.id;
|
|
217
213
|
}
|
|
218
214
|
if (!changeId) {
|
|
219
215
|
// 尝试从唯一活跃变更获取
|
|
220
|
-
const rows = sqlDb.
|
|
221
|
-
if (rows
|
|
216
|
+
const rows = sqlDb.prepare("SELECT id FROM changes WHERE status = 'active'").all();
|
|
217
|
+
if (rows.length === 1) changeId = rows[0].id;
|
|
222
218
|
}
|
|
223
219
|
if (!changeId) return;
|
|
224
220
|
|
|
225
|
-
sqlDb.
|
|
221
|
+
sqlDb.prepare(
|
|
226
222
|
`INSERT INTO batch_progress (change_id, total, completed, failed, skipped)
|
|
227
223
|
VALUES (?, ?, ?, ?, ?)
|
|
228
224
|
ON CONFLICT(change_id) DO UPDATE SET
|
|
229
225
|
total = excluded.total,
|
|
230
226
|
completed = excluded.completed,
|
|
231
227
|
failed = excluded.failed,
|
|
232
|
-
skipped = excluded.skipped
|
|
233
|
-
|
|
234
|
-
);
|
|
228
|
+
skipped = excluded.skipped`
|
|
229
|
+
).run(changeId, batchData.total || 0, batchData.completed || 0, batchData.failed || 0, batchData.skipped || 0);
|
|
235
230
|
});
|
|
236
231
|
}
|
|
237
232
|
|
|
238
|
-
|
|
239
|
-
const data =
|
|
233
|
+
readBatchProgress(cwd, changeName = null) {
|
|
234
|
+
const data = this.pm.read(cwd, changeName);
|
|
240
235
|
return data?.batchProgress || null;
|
|
241
236
|
}
|
|
242
237
|
|