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.
Files changed (71) hide show
  1. package/.claude/skills/sillyspec-auto/SKILL.md +8 -13
  2. package/.claude/skills/sillyspec-brainstorm/SKILL.md +8 -6
  3. package/.claude/skills/sillyspec-continue/SKILL.md +3 -3
  4. package/.claude/skills/sillyspec-doctor/SKILL.md +1 -1
  5. package/.claude/skills/sillyspec-execute/SKILL.md +10 -4
  6. package/.claude/skills/sillyspec-plan/SKILL.md +1 -1
  7. package/.claude/skills/sillyspec-verify/SKILL.md +1 -1
  8. package/README.md +9 -0
  9. package/package.json +2 -2
  10. package/src/change-list.js +11 -8
  11. package/src/change-risk-profile.js +22 -0
  12. package/src/db.js +160 -159
  13. package/src/dispatch/backends/local-agent.js +152 -0
  14. package/src/dispatch/backends/sillyhub-mcp.js +242 -0
  15. package/src/dispatch/probe.js +208 -0
  16. package/src/dispatch/strategy.js +121 -0
  17. package/src/doctor-diagnostics.js +19 -25
  18. package/src/fs-atomic.js +69 -69
  19. package/src/git-helper.js +66 -0
  20. package/src/hooks/worktree-guard.js +64 -55
  21. package/src/index.js +215 -34
  22. package/src/init.js +7 -14
  23. package/src/machine-interface.js +54 -19
  24. package/src/migrate.js +14 -4
  25. package/src/modules.js +16 -5
  26. package/src/progress/change-registry.js +72 -70
  27. package/src/progress/consistency-doctor.js +71 -14
  28. package/src/progress/shared.js +6 -3
  29. package/src/progress/stage-machine.js +61 -65
  30. package/src/progress/step-store.js +62 -67
  31. package/src/progress.js +155 -233
  32. package/src/quicklog.js +13 -5
  33. package/src/run/command.js +65 -37
  34. package/src/run/complete-handlers.js +1005 -889
  35. package/src/run/complete.js +115 -208
  36. package/src/run/concurrent-detect.js +132 -0
  37. package/src/run/gates.js +289 -17
  38. package/src/run/prompt.js +22 -68
  39. package/src/run/quick-audit.js +10 -9
  40. package/src/run/shared.js +648 -600
  41. package/src/run/stage.js +43 -24
  42. package/src/scan-postcheck.js +9 -31
  43. package/src/sillyhub-mcp/client.js +389 -0
  44. package/src/spec-dir-typo.js +71 -0
  45. package/src/stage-contract-engine.js +22 -1
  46. package/src/stage-contract-spec.js +11 -4
  47. package/src/stage-contract.js +26 -3
  48. package/src/stage-review.js +13 -9
  49. package/src/stages/archive.js +4 -6
  50. package/src/stages/brainstorm-auto.js +26 -20
  51. package/src/stages/brainstorm.js +29 -5
  52. package/src/stages/cmd-existence.js +145 -0
  53. package/src/stages/doctor.js +9 -6
  54. package/src/stages/execute.js +61 -20
  55. package/src/stages/plan-postcheck.js +152 -0
  56. package/src/stages/plan.js +16 -31
  57. package/src/stages/quick.js +1 -1
  58. package/src/stages/scan.js +9 -3
  59. package/src/stages/status.js +0 -1
  60. package/src/stages/verify.js +12 -7
  61. package/src/sync.js +4 -4
  62. package/src/task-review.js +30 -1
  63. package/src/verify-postcheck.js +933 -862
  64. package/src/version.js +19 -0
  65. package/src/worktree-apply.js +85 -68
  66. package/src/worktree-deps.js +89 -5
  67. package/src/worktree.js +213 -111
  68. package/templates/prompts/taskcard-rules.md +27 -0
  69. package/templates/prompts/verify-probes.md +3 -3
  70. package/.claude/skills/sillyspec-propose/SKILL.md +0 -87
  71. 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
- async _validateStageArtifacts(cwd, stage, changeName) {
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 = await this.pm.readGlobal(cwd);
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
- async completeStage(cwd, stage, changeName = null, opts = {}) {
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 = await this.pm._ensureDB(cwd);
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 = await this.pm.listChanges(cwd);
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 = await this._validateStageArtifacts(cwd, stage, cn);
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((sqlDb) => {
79
- const changeRow = sqlDb.exec('SELECT id FROM changes WHERE name = ?', [cn]);
80
- if (!changeRow || changeRow.length === 0 || changeRow[0].values.length === 0) return;
81
- const changeId = changeRow[0].values[0][0];
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.exec('SELECT id FROM stages WHERE change_id = ? AND stage = ?', [changeId, stage]);
97
- if (stageRow && stageRow.length > 0 && stageRow[0].values.length > 0) {
98
- const stageId = stageRow[0].values[0][0];
99
- sqlDb.run(
100
- 'UPDATE steps SET status = "completed", completed_at = ? WHERE stage_id = ? AND status IN ("pending", "stale")', // stale 一并回填(reopen --done 场景,坑 brainstorm-reopen-step-state-desync)
101
- [now, stageId]
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.run('UPDATE changes SET last_active = ? WHERE name = ?', [now, cn]);
100
+ sqlDb.prepare('UPDATE changes SET last_active = ? WHERE name = ?').run(now, cn);
107
101
  });
108
102
 
109
103
  // 写 history 文件(保持文件系统,不变)
110
- const data = await this.pm.read(cwd, cn);
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
- async show(cwd, changeName = null) {
119
+ show(cwd, changeName = null) {
126
120
  // 如果指定了变更名,只显示该变更
127
121
  if (changeName) {
128
- return await this._showChange(cwd, changeName);
122
+ return this._showChange(cwd, changeName);
129
123
  }
130
124
 
131
125
  // 否则显示所有变更
132
- const changes = await this.pm.listChanges(cwd);
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 await this._showChange(cwd, changes[0]);
133
+ return this._showChange(cwd, changes[0]);
140
134
  }
141
135
 
142
136
  // 多个变更:汇总显示
143
- const global = await this.pm.readGlobal(cwd);
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 = await this.pm.read(cwd, cn);
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
- async _showChange(cwd, changeName) {
171
- const data = await this.pm.read(cwd, changeName);
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
- async status(cwd, changeName = null) {
317
- await this.show(cwd, changeName);
310
+ status(cwd, changeName = null) {
311
+ this.show(cwd, changeName);
318
312
  }
319
313
 
320
- async validate(cwd, changeName = null) {
321
- const data = await this.pm.read(cwd, changeName);
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
- await this.pm._write(cwd, fixed);
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
- async reopenStage(cwd, stage, opts = {}) {
362
+ reopenStage(cwd, stage, opts = {}) {
369
363
  const { fromStep, changeName = null } = opts;
370
364
 
371
- const data = await this.pm.read(cwd, changeName);
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
- await this.pm._write(cwd, data, changeName);
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 = await this.pm.read(cwd, changeName); // 重新读取以获取最新状态
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
- await this.pm._write(cwd, data2, changeName);
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
- async reset(cwd, stage, changeName = null) {
464
+ reset(cwd, stage, changeName = null) {
471
465
  if (stage) {
472
- const data = await this.pm.read(cwd, changeName);
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
- await this.pm._write(cwd, data);
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 = await this.pm._ensureDB(cwd);
490
- db.transaction((sqlDb) => {
491
- const changeRow = sqlDb.exec('SELECT id FROM changes WHERE name = ?', [changeName]);
492
- if (changeRow && changeRow.length > 0 && changeRow[0].values.length > 0) {
493
- const changeId = changeRow[0].values[0][0];
494
- sqlDb.run('DELETE FROM steps WHERE stage_id IN (SELECT id FROM stages WHERE change_id = ?)', [changeId]);
495
- sqlDb.run('DELETE FROM stages WHERE change_id = ?', [changeId]);
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.run('INSERT OR IGNORE INTO stages (change_id, stage, status) VALUES (?, ?, "pending")', [changeId, s]);
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 = await this.pm.listChanges(cwd);
499
+ const changes = this.pm.listChanges(cwd);
505
500
  console.warn(`⚠️ 即将重置【所有 ${changes.length} 个变更】的进度(仅 stage 状态,产物文件不动)。此操作不可逆且范围最大。`);
506
- const db = await this.pm._ensureDB(cwd);
507
- db.transaction((sqlDb) => {
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.exec('SELECT id FROM changes WHERE name = ?', [cn]);
510
- if (changeRow && changeRow.length > 0 && changeRow[0].values.length > 0) {
511
- const changeId = changeRow[0].values[0][0];
512
- sqlDb.run('DELETE FROM steps WHERE stage_id IN (SELECT id FROM stages WHERE change_id = ?)', [changeId]);
513
- sqlDb.run('UPDATE stages SET status = "pending", started_at = NULL, completed_at = NULL WHERE change_id = ?', [changeId]);
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
- async setStage(cwd, stage, changeName = null) {
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 = await this.pm._ensureDB(cwd);
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 = await this.pm.listChanges(cwd);
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((sqlDb) => {
33
+ db.transaction(() => {
34
+ const sqlDb = db.getDb();
34
35
  // 确保 change 存在
35
- const changeRow = sqlDb.exec('SELECT id, current_stage FROM changes WHERE name = ?', [cn]);
36
- if (!changeRow || changeRow.length === 0 || changeRow[0].values.length === 0) return;
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[0].values[0][0];
40
+ const changeId = changeRow.id;
40
41
 
41
42
  // UPDATE changes.current_stage + last_active
42
- sqlDb.run('UPDATE changes SET current_stage = ?, last_active = ? WHERE name = ?', [stage, now, cn]);
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
- async addStep(cwd, stage, stepName, changeName = null) {
63
+ addStep(cwd, stage, stepName, changeName = null) {
69
64
  if (!stepName) { console.log('❌ 请指定步骤名称'); return; }
70
65
 
71
- const db = await this.pm._ensureDB(cwd);
66
+ const db = this.pm._ensureDB(cwd);
72
67
 
73
68
  // 获取变更名
74
69
  let cn = changeName;
75
70
  if (!cn) {
76
- const changes = await this.pm.listChanges(cwd);
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.exec(
84
- 'SELECT s.id FROM stages s JOIN changes c ON s.change_id = c.id WHERE c.name = ? AND s.stage = ?',
85
- [cn, stage]
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[0].values[0][0];
86
+ const stageId = stageRow.id;
93
87
 
94
88
  // 重复步骤名检查
95
- const dupRow = sqlDb.exec('SELECT id FROM steps WHERE stage_id = ? AND name = ?', [stageId, stepName]);
96
- if (dupRow && dupRow.length > 0 && dupRow[0].values.length > 0) {
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((tDb) => {
103
- tDb.run(
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
- [stageId, stepName, stageId]
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
- async updateStep(cwd, stage, stepName, options = {}, changeName = null) {
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 = await this.pm._ensureDB(cwd);
112
+ const db = this.pm._ensureDB(cwd);
119
113
 
120
114
  // 获取变更名
121
115
  let cn = changeName;
122
116
  if (!cn) {
123
- const changes = await this.pm.listChanges(cwd);
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.exec(
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
- [cn, stage, stepName]
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[0].values[0][0];
140
+ const stepId = stepRow.id;
148
141
 
149
142
  // UPDATE steps
150
143
  let stageCompletionCandidateId = null;
151
- db.transaction((tDb) => {
144
+ db.transaction(() => {
145
+ const tDb = db.getDb();
152
146
  const now = new Date().toISOString();
153
147
  if (status) {
154
- tDb.run('UPDATE steps SET status = ?, completed_at = ? WHERE id = ? AND name = ?', [status, now, stepId, stepName]);
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.run('UPDATE steps SET output = ? WHERE id = ? AND name = ?', [output, stepId, stepName]);
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.exec('SELECT stage_id FROM steps WHERE id = ?', [stepId]);
165
- if (stRow && stRow.length > 0 && stRow[0].values.length > 0) {
166
- const stId = stRow[0].values[0][0];
167
- const pendingRows = tDb.exec('SELECT COUNT(*) FROM steps WHERE stage_id = ? AND status != "completed"', [stId]);
168
- if (pendingRows && pendingRows.length > 0 && pendingRows[0].values[0][0] === 0) {
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.run('UPDATE changes SET last_active = ? WHERE name = ?', [now, cn]);
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 = await this.pm._validateStageArtifacts(cwd, stage, cn);
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((tDb) => {
198
- tDb.run('UPDATE stages SET status = "completed", completed_at = ? WHERE id = ?', [new Date().toISOString(), stageCompletionCandidateId]);
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
- async updateBatchProgress(cwd, batchData, changeName = null) {
202
+ updateBatchProgress(cwd, batchData, changeName = null) {
208
203
  const cn = changeName || null;
209
204
 
210
- const db = await this.pm._ensureDB(cwd);
211
- db.transaction((sqlDb) => {
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.exec('SELECT id FROM changes WHERE name = ?', [cn]);
216
- if (row && row.length > 0 && row[0].values.length > 0) changeId = row[0].values[0][0];
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.exec("SELECT id FROM changes WHERE status = 'active'");
221
- if (rows && rows.length > 0 && rows[0].values.length === 1) changeId = rows[0].values[0][0];
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.run(
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
- [changeId, batchData.total || 0, batchData.completed || 0, batchData.failed || 0, batchData.skipped || 0]
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
- async readBatchProgress(cwd, changeName = null) {
239
- const data = await this.pm.read(cwd, changeName);
233
+ readBatchProgress(cwd, changeName = null) {
234
+ const data = this.pm.read(cwd, changeName);
240
235
  return data?.batchProgress || null;
241
236
  }
242
237