sillyspec 3.27.2 → 3.27.5

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 (221) hide show
  1. package/.claude/skills/sillyspec-execute/SKILL.md +8 -0
  2. package/.claude/skills/sillyspec-quick/SKILL.md +2 -1
  3. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-archive/SKILL.md +87 -0
  4. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-auto/SKILL.md +93 -0
  5. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-brainstorm/SKILL.md +114 -0
  6. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-commit/SKILL.md +67 -0
  7. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-continue/SKILL.md +47 -0
  8. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-doctor/SKILL.md +76 -0
  9. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-execute/SKILL.md +214 -0
  10. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-explore/SKILL.md +102 -0
  11. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-export/SKILL.md +57 -0
  12. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-init/SKILL.md +177 -0
  13. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  14. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-plan/SKILL.md +156 -0
  15. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-quick/SKILL.md +116 -0
  16. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-resume/SKILL.md +70 -0
  17. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-scan/SKILL.md +79 -0
  18. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-state/SKILL.md +64 -0
  19. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-status/SKILL.md +54 -0
  20. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-verify/SKILL.md +87 -0
  21. package/.claude/worktrees/sillyspec-data-model-research/.claude/skills/sillyspec-workspace/SKILL.md +175 -0
  22. package/.claude/worktrees/sillyspec-data-model-research/.gitattributes +2 -0
  23. package/.claude/worktrees/sillyspec-data-model-research/.husky/pre-push +20 -0
  24. package/.claude/worktrees/sillyspec-data-model-research/README.md +203 -0
  25. package/.claude/worktrees/sillyspec-data-model-research/SKILL.md +91 -0
  26. package/.claude/worktrees/sillyspec-data-model-research/bin/sillyspec.js +2 -0
  27. package/.claude/worktrees/sillyspec-data-model-research/logo.jpg +0 -0
  28. package/.claude/worktrees/sillyspec-data-model-research/package-lock.json +977 -0
  29. package/.claude/worktrees/sillyspec-data-model-research/package.json +39 -0
  30. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -0
  31. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  32. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/favicon.jpg +0 -0
  33. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/index.html +16 -0
  34. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/logo.jpg +0 -0
  35. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-dashboard.html +836 -0
  36. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/dist/prototype-overview.html +256 -0
  37. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/package.json +25 -0
  38. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/executor.js +95 -0
  39. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/index.js +679 -0
  40. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/parser.js +526 -0
  41. package/.claude/worktrees/sillyspec-data-model-research/packages/dashboard/server/watcher.js +357 -0
  42. package/.claude/worktrees/sillyspec-data-model-research/src/agent-session-log.js +803 -0
  43. package/.claude/worktrees/sillyspec-data-model-research/src/change-list.js +290 -0
  44. package/.claude/worktrees/sillyspec-data-model-research/src/change-risk-profile.js +270 -0
  45. package/.claude/worktrees/sillyspec-data-model-research/src/check-primitives.js +65 -0
  46. package/.claude/worktrees/sillyspec-data-model-research/src/classify-change.js +109 -0
  47. package/.claude/worktrees/sillyspec-data-model-research/src/commit-suggest.js +202 -0
  48. package/.claude/worktrees/sillyspec-data-model-research/src/config-cat.js +88 -0
  49. package/.claude/worktrees/sillyspec-data-model-research/src/config-schema.js +328 -0
  50. package/.claude/worktrees/sillyspec-data-model-research/src/constants.js +97 -0
  51. package/.claude/worktrees/sillyspec-data-model-research/src/contract-matrix.js +480 -0
  52. package/.claude/worktrees/sillyspec-data-model-research/src/datetime.js +20 -0
  53. package/.claude/worktrees/sillyspec-data-model-research/src/db-engine.js +55 -0
  54. package/.claude/worktrees/sillyspec-data-model-research/src/db.js +381 -0
  55. package/.claude/worktrees/sillyspec-data-model-research/src/decision-distill.js +517 -0
  56. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/local-agent.js +155 -0
  57. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/backends/sillyhub-mcp.js +245 -0
  58. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/probe.js +246 -0
  59. package/.claude/worktrees/sillyspec-data-model-research/src/dispatch/strategy.js +121 -0
  60. package/.claude/worktrees/sillyspec-data-model-research/src/docs-check.js +825 -0
  61. package/.claude/worktrees/sillyspec-data-model-research/src/docs-debt.js +393 -0
  62. package/.claude/worktrees/sillyspec-data-model-research/src/docs-gate.js +107 -0
  63. package/.claude/worktrees/sillyspec-data-model-research/src/doctor-diagnostics.js +1099 -0
  64. package/.claude/worktrees/sillyspec-data-model-research/src/endpoint-extractor.js +420 -0
  65. package/.claude/worktrees/sillyspec-data-model-research/src/foreign-declared.js +78 -0
  66. package/.claude/worktrees/sillyspec-data-model-research/src/fs-atomic.js +73 -0
  67. package/.claude/worktrees/sillyspec-data-model-research/src/git-helper.js +150 -0
  68. package/.claude/worktrees/sillyspec-data-model-research/src/hooks/claude-pre-tool-use.cjs +130 -0
  69. package/.claude/worktrees/sillyspec-data-model-research/src/hooks/worktree-guard.js +892 -0
  70. package/.claude/worktrees/sillyspec-data-model-research/src/index.js +2798 -0
  71. package/.claude/worktrees/sillyspec-data-model-research/src/init.js +626 -0
  72. package/.claude/worktrees/sillyspec-data-model-research/src/knowledge-match.js +231 -0
  73. package/.claude/worktrees/sillyspec-data-model-research/src/local-detect.js +128 -0
  74. package/.claude/worktrees/sillyspec-data-model-research/src/local-register.js +104 -0
  75. package/.claude/worktrees/sillyspec-data-model-research/src/machine-interface.js +543 -0
  76. package/.claude/worktrees/sillyspec-data-model-research/src/migrate.js +127 -0
  77. package/.claude/worktrees/sillyspec-data-model-research/src/module-impact.js +166 -0
  78. package/.claude/worktrees/sillyspec-data-model-research/src/module-resolve.js +222 -0
  79. package/.claude/worktrees/sillyspec-data-model-research/src/modules.js +580 -0
  80. package/.claude/worktrees/sillyspec-data-model-research/src/progress/change-registry.js +336 -0
  81. package/.claude/worktrees/sillyspec-data-model-research/src/progress/consistency-doctor.js +355 -0
  82. package/.claude/worktrees/sillyspec-data-model-research/src/progress/shared.js +36 -0
  83. package/.claude/worktrees/sillyspec-data-model-research/src/progress/stage-machine.js +594 -0
  84. package/.claude/worktrees/sillyspec-data-model-research/src/progress/step-store.js +265 -0
  85. package/.claude/worktrees/sillyspec-data-model-research/src/progress.js +1304 -0
  86. package/.claude/worktrees/sillyspec-data-model-research/src/quick-recommend.js +115 -0
  87. package/.claude/worktrees/sillyspec-data-model-research/src/quicklog.js +852 -0
  88. package/.claude/worktrees/sillyspec-data-model-research/src/review-tier.js +77 -0
  89. package/.claude/worktrees/sillyspec-data-model-research/src/run/command.js +1595 -0
  90. package/.claude/worktrees/sillyspec-data-model-research/src/run/complete-handlers.js +1552 -0
  91. package/.claude/worktrees/sillyspec-data-model-research/src/run/complete.js +1259 -0
  92. package/.claude/worktrees/sillyspec-data-model-research/src/run/concurrent-detect.js +156 -0
  93. package/.claude/worktrees/sillyspec-data-model-research/src/run/gates.js +1084 -0
  94. package/.claude/worktrees/sillyspec-data-model-research/src/run/multi-repo-context.js +208 -0
  95. package/.claude/worktrees/sillyspec-data-model-research/src/run/next.js +128 -0
  96. package/.claude/worktrees/sillyspec-data-model-research/src/run/prompt.js +843 -0
  97. package/.claude/worktrees/sillyspec-data-model-research/src/run/quick-audit.js +186 -0
  98. package/.claude/worktrees/sillyspec-data-model-research/src/run/scan-profile.js +269 -0
  99. package/.claude/worktrees/sillyspec-data-model-research/src/run/shared.js +1562 -0
  100. package/.claude/worktrees/sillyspec-data-model-research/src/run/stage.js +584 -0
  101. package/.claude/worktrees/sillyspec-data-model-research/src/run.js +23 -0
  102. package/.claude/worktrees/sillyspec-data-model-research/src/scan-diff.js +388 -0
  103. package/.claude/worktrees/sillyspec-data-model-research/src/scan-postcheck.js +512 -0
  104. package/.claude/worktrees/sillyspec-data-model-research/src/scan-staleness.js +95 -0
  105. package/.claude/worktrees/sillyspec-data-model-research/src/setup.js +414 -0
  106. package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/client.js +597 -0
  107. package/.claude/worktrees/sillyspec-data-model-research/src/sillyhub-mcp/config.js +62 -0
  108. package/.claude/worktrees/sillyspec-data-model-research/src/spec-dir-typo.js +50 -0
  109. package/.claude/worktrees/sillyspec-data-model-research/src/spec-sync.js +358 -0
  110. package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-engine.js +171 -0
  111. package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract-spec.js +550 -0
  112. package/.claude/worktrees/sillyspec-data-model-research/src/stage-contract.js +889 -0
  113. package/.claude/worktrees/sillyspec-data-model-research/src/stage-review.js +597 -0
  114. package/.claude/worktrees/sillyspec-data-model-research/src/stages/archive.js +199 -0
  115. package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm-auto.js +236 -0
  116. package/.claude/worktrees/sillyspec-data-model-research/src/stages/brainstorm.js +617 -0
  117. package/.claude/worktrees/sillyspec-data-model-research/src/stages/cmd-existence.js +145 -0
  118. package/.claude/worktrees/sillyspec-data-model-research/src/stages/doctor.js +540 -0
  119. package/.claude/worktrees/sillyspec-data-model-research/src/stages/execute.js +1275 -0
  120. package/.claude/worktrees/sillyspec-data-model-research/src/stages/explore.js +43 -0
  121. package/.claude/worktrees/sillyspec-data-model-research/src/stages/index.js +29 -0
  122. package/.claude/worktrees/sillyspec-data-model-research/src/stages/knowledge.js +514 -0
  123. package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan-postcheck.js +1344 -0
  124. package/.claude/worktrees/sillyspec-data-model-research/src/stages/plan.js +650 -0
  125. package/.claude/worktrees/sillyspec-data-model-research/src/stages/quick.js +167 -0
  126. package/.claude/worktrees/sillyspec-data-model-research/src/stages/scan.js +608 -0
  127. package/.claude/worktrees/sillyspec-data-model-research/src/stages/status.js +64 -0
  128. package/.claude/worktrees/sillyspec-data-model-research/src/stages/verify.js +246 -0
  129. package/.claude/worktrees/sillyspec-data-model-research/src/sync.js +1470 -0
  130. package/.claude/worktrees/sillyspec-data-model-research/src/task-review.js +1498 -0
  131. package/.claude/worktrees/sillyspec-data-model-research/src/taskcard.js +210 -0
  132. package/.claude/worktrees/sillyspec-data-model-research/src/verify-postcheck.js +1718 -0
  133. package/.claude/worktrees/sillyspec-data-model-research/src/verify-probes.js +277 -0
  134. package/.claude/worktrees/sillyspec-data-model-research/src/version.js +19 -0
  135. package/.claude/worktrees/sillyspec-data-model-research/src/workflow.js +600 -0
  136. package/.claude/worktrees/sillyspec-data-model-research/src/workspace.js +125 -0
  137. package/.claude/worktrees/sillyspec-data-model-research/src/worktree-apply.js +1446 -0
  138. package/.claude/worktrees/sillyspec-data-model-research/src/worktree-deps.js +576 -0
  139. package/.claude/worktrees/sillyspec-data-model-research/src/worktree.js +1715 -0
  140. package/.claude/worktrees/sillyspec-data-model-research/templates/claude-instruction.md +23 -0
  141. package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/taskcard-rules.md +28 -0
  142. package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/testcase-design.md +12 -0
  143. package/.claude/worktrees/sillyspec-data-model-research/templates/prompts/verify-probes.md +32 -0
  144. package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/archive-impact.yaml +79 -0
  145. package/.claude/worktrees/sillyspec-data-model-research/templates/workflows/scan-docs.yaml +132 -0
  146. package/eval/README.md +86 -0
  147. package/eval/eval.config.json +28 -0
  148. package/eval/lib/adapters/cli.mjs +63 -0
  149. package/eval/lib/adapters/dryrun.mjs +27 -0
  150. package/eval/lib/config.mjs +82 -0
  151. package/eval/lib/db.mjs +76 -0
  152. package/eval/lib/report.mjs +120 -0
  153. package/eval/lib/runner.mjs +157 -0
  154. package/eval/lib/tasks.mjs +73 -0
  155. package/eval/lib/util.mjs +107 -0
  156. package/eval/lib/workdir.mjs +30 -0
  157. package/eval/run.mjs +83 -0
  158. package/eval/tasks/001-slugify/files/package.json +6 -0
  159. package/eval/tasks/001-slugify/solution/src/slugify.mjs +26 -0
  160. package/eval/tasks/001-slugify/task.json +6 -0
  161. package/eval/tasks/001-slugify/verify.mjs +28 -0
  162. package/eval/tasks/002-each-day/files/package.json +6 -0
  163. package/eval/tasks/002-each-day/files/src/dates.mjs +4 -0
  164. package/eval/tasks/002-each-day/solution/src/dates.mjs +30 -0
  165. package/eval/tasks/002-each-day/task.json +6 -0
  166. package/eval/tasks/002-each-day/verify.mjs +31 -0
  167. package/eval/tasks/003-word-report/files/data/notes.txt +5 -0
  168. package/eval/tasks/003-word-report/files/package.json +6 -0
  169. package/eval/tasks/003-word-report/solution/bin/report.mjs +36 -0
  170. package/eval/tasks/003-word-report/task.json +6 -0
  171. package/eval/tasks/003-word-report/verify.mjs +38 -0
  172. package/package.json +4 -2
  173. package/src/agent-session-log.js +45 -5
  174. package/src/change-risk-profile.js +83 -14
  175. package/src/config-schema.js +18 -3
  176. package/src/contract-matrix.js +90 -29
  177. package/src/datetime.js +20 -0
  178. package/src/decision-distill.js +517 -0
  179. package/src/dispatch/backends/local-agent.js +4 -4
  180. package/src/docs-check.js +227 -0
  181. package/src/docs-debt.js +187 -24
  182. package/src/doctor-diagnostics.js +102 -2
  183. package/src/foreign-declared.js +78 -0
  184. package/src/index.js +89 -9
  185. package/src/init.js +9 -7
  186. package/src/knowledge-match.js +107 -6
  187. package/src/local-register.js +7 -1
  188. package/src/module-resolve.js +12 -1
  189. package/src/modules.js +2 -1
  190. package/src/plan-adopt-waves.js +126 -0
  191. package/src/quicklog.js +50 -4
  192. package/src/run/command.js +122 -43
  193. package/src/run/complete-handlers.js +48 -19
  194. package/src/run/complete.js +5 -2
  195. package/src/run/gates.js +87 -22
  196. package/src/run/prompt.js +84 -42
  197. package/src/run/quick-audit.js +11 -0
  198. package/src/run/shared.js +152 -10
  199. package/src/run.js +2 -2
  200. package/src/scan-postcheck.js +30 -17
  201. package/src/stage-contract-spec.js +2 -1
  202. package/src/stage-contract.js +68 -4
  203. package/src/stages/archive.js +53 -12
  204. package/src/stages/brainstorm-auto.js +7 -0
  205. package/src/stages/brainstorm.js +11 -0
  206. package/src/stages/doctor.js +157 -1
  207. package/src/stages/execute.js +32 -6
  208. package/src/stages/plan-postcheck.js +76 -17
  209. package/src/stages/plan.js +1 -0
  210. package/src/stages/quick.js +6 -1
  211. package/src/stages/verify.js +31 -14
  212. package/src/sync.js +41 -3
  213. package/src/task-review.js +2 -2
  214. package/src/taskcard-placeholders.js +21 -0
  215. package/src/taskcard.js +10 -1
  216. package/src/verify-postcheck.js +315 -13
  217. package/src/verify-probes.js +46 -4
  218. package/src/worktree-apply.js +184 -7
  219. package/src/worktree-deps.js +57 -0
  220. package/src/worktree.js +201 -58
  221. package/templates/prompts/taskcard-rules.md +1 -0
@@ -0,0 +1,265 @@
1
+ // W6 Step9c: 阶段/步骤/批量进度管理(stages + steps + batch_progress 表)。
2
+ // 从 src/progress.js 单体 ProgressManager 抽出。持有 pm 引用(构造注入),调 pm._ensureDB /
3
+ // pm.listChanges / pm._validateStageArtifacts / pm._appendAuditLog / pm.read(persistence-core
4
+ // + 其他组 delegate 留 facade)。纯 SQL + 常量,无 fs/path 依赖。
5
+ import { VALID_STAGES, STAGE_LABELS } from './shared.js';
6
+
7
+ // step 状态白名单(仅 updateStep 用,本地化)
8
+ const VALID_STATUSES = ['pending', 'in-progress', 'completed', 'failed', 'blocked', 'waiting', 'stale'];
9
+
10
+ export class StepStore {
11
+ constructor(pm) {
12
+ this.pm = pm;
13
+ }
14
+
15
+ setStage(cwd, stage, changeName = null) {
16
+ if (!VALID_STAGES.includes(stage)) {
17
+ console.log(`❌ 未知阶段: ${stage},可选: ${VALID_STAGES.join(', ')}`);
18
+ return;
19
+ }
20
+
21
+ const db = this.pm._ensureDB(cwd);
22
+ const now = new Date().toISOString();
23
+
24
+ // 获取变更名
25
+ let cn = changeName;
26
+ if (!cn) {
27
+ const changes = this.pm.listChanges(cwd);
28
+ if (changes.length === 1) cn = changes[0];
29
+ if (!cn) { console.log('❌ 无法确定当前变更,请指定 --change <name>'); return; }
30
+ }
31
+
32
+ let changeFound = false;
33
+ db.transaction(() => {
34
+ const sqlDb = db.getDb();
35
+ // 确保 change 存在
36
+ const changeRow = sqlDb.prepare('SELECT id, current_stage FROM changes WHERE name = ?').get(cn);
37
+ if (changeRow === undefined) return;
38
+
39
+ changeFound = true;
40
+ const changeId = changeRow.id;
41
+
42
+ // UPDATE changes.current_stage + last_active
43
+ sqlDb.prepare('UPDATE changes SET current_stage = ?, last_active = ? WHERE name = ?').run(stage, now, cn);
44
+
45
+ // 确保 stages 行存在(INSERT OR IGNORE)
46
+ sqlDb.prepare(`INSERT OR IGNORE INTO stages (change_id, stage, status) VALUES (?, ?, 'pending')`).run(changeId, stage);
47
+
48
+ // UPDATE stages.status 为 in-progress(仅当仍为 pending 时)
49
+ sqlDb.prepare("UPDATE stages SET status = 'in-progress', started_at = ? WHERE change_id = ? AND stage = ? AND status = 'pending'").run(now, changeId, stage);
50
+
51
+ // 本地脏度(D-013 / task-04):切换阶段是本地推进
52
+ this.pm._touchLocalModified(cwd, cn, now);
53
+ });
54
+
55
+ if (!changeFound) {
56
+ // 历史教训:变更名打错/cwd 漂移命中错误 spec 时,事务内静默 return,
57
+ // 但旧代码此处仍无条件打「✅ 已设为」→ 假成功(agent 以为生效,实际什么都没做)。
58
+ console.log(`❌ 变更「${cn}」不存在,未设置阶段。检查变更名拼写,或 sillyspec status 查看活跃变更;cwd 漂移时回项目根或加 --spec-dir。`);
59
+ return;
60
+ }
61
+
62
+ // read() 已改为 SQL,直接通过 SQL 查询即可,无需 _write
63
+ console.log(`✅ 当前阶段已设为: ${STAGE_LABELS[stage] || stage}(变更 ${cn})`);
64
+ }
65
+
66
+ addStep(cwd, stage, stepName, changeName = null) {
67
+ if (!stepName) { console.log('❌ 请指定步骤名称'); return; }
68
+
69
+ const db = this.pm._ensureDB(cwd);
70
+
71
+ // 获取变更名
72
+ let cn = changeName;
73
+ if (!cn) {
74
+ const changes = this.pm.listChanges(cwd);
75
+ if (changes.length === 1) cn = changes[0];
76
+ if (!cn) { console.log('❌ 无法确定当前变更,请指定 --change <name>'); return; }
77
+ }
78
+
79
+ // 查找 stage_id
80
+ const sqlDb = db.getDb();
81
+ const stageRow = sqlDb.prepare(
82
+ 'SELECT s.id FROM stages s JOIN changes c ON s.change_id = c.id WHERE c.name = ? AND s.stage = ?'
83
+ ).get(cn, stage);
84
+ if (stageRow === undefined) {
85
+ // stages 行不存在,静默跳过
86
+ console.log(`ℹ️ 阶段 ${stage} 不存在`);
87
+ return;
88
+ }
89
+ const stageId = stageRow.id;
90
+
91
+ // 重复步骤名检查 + INSERT 同事务(2026-08-21 审查 BUG-8):查重在事务外时,
92
+ // 并发 add-step 同名可双双通过检查双插(steps 无 UNIQUE 约束),另一份 status 后续被 _write 覆盖丢失
93
+ let dupFound = false;
94
+ db.transaction(() => {
95
+ const tDb = db.getDb();
96
+ const dupRow = tDb.prepare('SELECT id FROM steps WHERE stage_id = ? AND name = ?').get(stageId, stepName);
97
+ if (dupRow !== undefined) {
98
+ dupFound = true;
99
+ return;
100
+ }
101
+ tDb.prepare(
102
+ `INSERT INTO steps (stage_id, name, ordering, status)
103
+ VALUES (?, ?, (SELECT COALESCE(MAX(ordering), 0) + 1 FROM steps WHERE stage_id = ?), 'pending')`
104
+ ).run(stageId, stepName, stageId);
105
+ tDb.prepare('UPDATE changes SET last_active = ? WHERE name = ?').run(new Date().toISOString(), cn);
106
+ // 本地脏度(D-013 / task-04):添加步骤是本地推进
107
+ this.pm._touchLocalModified(cwd, cn);
108
+ });
109
+ if (dupFound) {
110
+ console.log(`ℹ️ 步骤 "${stepName}" 已存在于 ${stage}`);
111
+ return;
112
+ }
113
+
114
+ console.log(`✅ 已添加步骤: ${stage}/${stepName}`);
115
+ }
116
+
117
+ updateStep(cwd, stage, stepName, options = {}, changeName = null) {
118
+ const { status, output } = options;
119
+ if (!stepName) { console.log('❌ 请指定步骤名称'); return; }
120
+
121
+ const db = this.pm._ensureDB(cwd);
122
+
123
+ // 获取变更名
124
+ let cn = changeName;
125
+ if (!cn) {
126
+ const changes = this.pm.listChanges(cwd);
127
+ if (changes.length === 1) cn = changes[0];
128
+ if (!cn) { console.log('❌ 无法确定当前变更,请指定 --change <name>'); return; }
129
+ }
130
+
131
+ // 状态校验
132
+ if (status && !VALID_STATUSES.includes(status)) {
133
+ console.log(`❌ 无效状态: ${status},可选: ${VALID_STATUSES.join(', ')}`);
134
+ return;
135
+ }
136
+
137
+ // 查找 step_id:通过 changes → stages → steps JOIN 查询
138
+ const sqlDb = db.getDb();
139
+ const stepRow = sqlDb.prepare(
140
+ `SELECT st.id, st.status FROM steps st
141
+ JOIN stages sg ON st.stage_id = sg.id
142
+ JOIN changes c ON sg.change_id = c.id
143
+ WHERE c.name = ? AND sg.stage = ? AND st.name = ?`
144
+ ).get(cn, stage, stepName);
145
+ if (stepRow === undefined) {
146
+ console.log(`❌ 步骤不存在: ${stage}/${stepName}`);
147
+ return;
148
+ }
149
+ const stepId = stepRow.id;
150
+
151
+ // UPDATE steps
152
+ let stageCompletionCandidateId = null;
153
+ db.transaction(() => {
154
+ const tDb = db.getDb();
155
+ const now = new Date().toISOString();
156
+ if (status) {
157
+ tDb.prepare('UPDATE steps SET status = ?, completed_at = ? WHERE id = ? AND name = ?').run(status, status === 'completed' ? now : null, stepId, stepName);
158
+ }
159
+ if (output !== undefined) {
160
+ tDb.prepare('UPDATE steps SET output = ? WHERE id = ? AND name = ?').run(output, stepId, stepName);
161
+ }
162
+
163
+ // 自动完成检测:同 stage_id 下所有 steps 都 completed 时,候选标记 stage completed
164
+ // (实际标记延后到事务外,先过产物校验门,防止 update-step 成为绕过 validator 的后门)
165
+ if (status === 'completed') {
166
+ // 获取 stage_id
167
+ const stRow = tDb.prepare('SELECT stage_id FROM steps WHERE id = ?').get(stepId);
168
+ if (stRow !== undefined) {
169
+ const stId = stRow.stage_id;
170
+ const pendingRow = tDb.prepare("SELECT COUNT(*) AS cnt FROM steps WHERE stage_id = ? AND status NOT IN ('completed','skipped')").get(stId);
171
+ if (pendingRow.cnt === 0) {
172
+ stageCompletionCandidateId = stId;
173
+ }
174
+ }
175
+ }
176
+
177
+ tDb.prepare('UPDATE changes SET last_active = ? WHERE name = ?').run(now, cn);
178
+ // 本地脏度(D-013 / task-04):更新步骤是本地推进
179
+ this.pm._touchLocalModified(cwd, cn, now);
180
+ });
181
+
182
+ if (stageCompletionCandidateId !== null) {
183
+ const { force = false } = options;
184
+ const validation = this.pm._validateStageArtifacts(cwd, stage, cn);
185
+ if (!validation.ok && !force) {
186
+ console.error(`⚠️ 阶段 ${stage} 所有步骤已完成,但产物校验未通过,阶段不标记为 completed:`);
187
+ for (const err of validation.errors) console.error(` - ${err}`);
188
+ // 出路提示常放在 warnings(如「写明不改理由即可豁免」),阻断时必须一并打出,
189
+ // 否则 agent 只看到症状看不到怎么过(驾驭问题根因:hint 在 warning、出口只打 error)。
190
+ for (const warn of (validation.warnings || [])) console.error(` · ${warn}`);
191
+ console.error(` 请修复产物后走正常流程 sillyspec run ${stage} --done,或使用 --force(将记录审计日志)。`);
192
+ } else {
193
+ if (!validation.ok && force) {
194
+ console.warn(`⚠️ --force 强制标记阶段 ${stage} completed(校验未通过,已记录审计日志)`);
195
+ this.pm._appendAuditLog(cwd, {
196
+ action: 'update-step --force (stage auto-complete)',
197
+ stage,
198
+ change: cn,
199
+ validationErrors: validation.errors,
200
+ });
201
+ }
202
+ db.transaction(() => {
203
+ const tDb = db.getDb();
204
+ tDb.prepare(`UPDATE stages SET status = 'completed', completed_at = ? WHERE id = ?`).run(new Date().toISOString(), stageCompletionCandidateId);
205
+ });
206
+ console.log(`✅ 阶段 ${stage} 所有步骤已完成,阶段已标记为 completed`);
207
+ }
208
+ }
209
+
210
+ console.log(`✅ 步骤已更新: ${stage}/${stepName} → ${status || '(仅更新 output)'}`);
211
+ }
212
+
213
+ updateBatchProgress(cwd, batchData, changeName = null) {
214
+ const cn = changeName || null;
215
+
216
+ const db = this.pm._ensureDB(cwd);
217
+ db.transaction(() => {
218
+ const sqlDb = db.getDb();
219
+ // 获取 change_id
220
+ let changeId = null;
221
+ if (cn) {
222
+ const row = sqlDb.prepare('SELECT id FROM changes WHERE name = ?').get(cn);
223
+ if (row !== undefined) changeId = row.id;
224
+ }
225
+ if (!changeId) {
226
+ // 尝试从唯一活跃变更获取
227
+ const rows = sqlDb.prepare("SELECT id FROM changes WHERE status = 'active'").all();
228
+ if (rows.length === 1) changeId = rows[0].id;
229
+ }
230
+ if (!changeId) return;
231
+
232
+ sqlDb.prepare(
233
+ `INSERT INTO batch_progress (change_id, total, completed, failed, skipped)
234
+ VALUES (?, ?, ?, ?, ?)
235
+ ON CONFLICT(change_id) DO UPDATE SET
236
+ total = excluded.total,
237
+ completed = excluded.completed,
238
+ failed = excluded.failed,
239
+ skipped = excluded.skipped`
240
+ ).run(changeId, batchData.total || 0, batchData.completed || 0, batchData.failed || 0, batchData.skipped || 0);
241
+ // 本地脏度(D-013 / task-04):批量进度更新是本地推进
242
+ const nameRow = sqlDb.prepare('SELECT name FROM changes WHERE id = ?').get(changeId);
243
+ if (nameRow) this.pm._touchLocalModified(cwd, nameRow.name);
244
+ });
245
+ }
246
+
247
+ readBatchProgress(cwd, changeName = null) {
248
+ const data = this.pm.read(cwd, changeName);
249
+ return data?.batchProgress || null;
250
+ }
251
+
252
+ _renderBatchProgress(batchProgress) {
253
+ if (!batchProgress || !batchProgress.total) return null;
254
+ const { total, completed = 0, failed = 0, skipped = 0 } = batchProgress;
255
+ const barLen = 20;
256
+ const filled = Math.round((completed / total) * barLen);
257
+ const bar = '█'.repeat(filled) + '░'.repeat(barLen - filled);
258
+ const parts = [];
259
+ if (failed > 0) parts.push(`${failed} 失败`);
260
+ if (skipped > 0) parts.push(`${skipped} 跳过`);
261
+ const suffix = parts.length ? ` (${parts.join(', ')})` : '';
262
+ return `📊 批量进度: ${bar} ${completed}/${total}${suffix}`;
263
+ }
264
+ }
265
+