sillyspec 3.25.1 → 3.25.2

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 (39) hide show
  1. package/.claude/skills/sillyspec-doctor/SKILL.md +9 -0
  2. package/.playwright-mcp/console-2026-07-27T15-45-50-621Z.log +1 -0
  3. package/.playwright-mcp/page-2026-07-27T15-45-51-459Z.yml +144 -0
  4. package/.playwright-mcp/prompt-final.png +0 -0
  5. package/.playwright-mcp/prompt-view.png +0 -0
  6. package/CLAUDE.md +18 -0
  7. package/package.json +1 -1
  8. package/src/change-risk-profile.js +60 -12
  9. package/src/check-primitives.js +65 -0
  10. package/src/index.js +135 -6
  11. package/src/progress/change-registry.js +217 -0
  12. package/src/progress/consistency-doctor.js +259 -0
  13. package/src/progress/shared.js +33 -0
  14. package/src/progress/stage-machine.js +535 -0
  15. package/src/progress/step-store.js +256 -0
  16. package/src/progress.js +59 -1191
  17. package/src/quicklog.js +3 -1
  18. package/src/run/command.js +127 -50
  19. package/src/run/complete-handlers.js +2 -1
  20. package/src/run/complete.js +725 -695
  21. package/src/run/gates.js +326 -321
  22. package/src/run/prompt.js +619 -604
  23. package/src/run/quick-audit.js +107 -102
  24. package/src/run/shared.js +43 -0
  25. package/src/stage-contract-engine.js +150 -0
  26. package/src/stage-contract-spec.js +517 -0
  27. package/src/stage-contract.js +83 -148
  28. package/src/stage-review.js +63 -0
  29. package/src/stages/brainstorm.js +3 -1
  30. package/src/stages/execute.js +5 -2
  31. package/src/stages/plan-postcheck.js +38 -27
  32. package/src/stages/plan.js +12 -49
  33. package/src/stages/propose.js +2 -1
  34. package/src/stages/scan.js +3 -1
  35. package/src/workflow.js +9 -17
  36. package/src/worktree-apply.js +5 -2
  37. package/src/worktree-deps.js +114 -32
  38. package/src/worktree.js +6 -3
  39. package/src/brainstorm-postcheck.js +0 -158
@@ -1,158 +0,0 @@
1
- /**
2
- * brainstorm-postcheck.js — plan 阶段对 brainstorm 产物的校验
3
- *
4
- * 在 brainstorm → plan 之间执行,校验 brainstorm 产物完整性。
5
- * PASS → 继续 plan
6
- * WARN → 继续 plan,记录警告并分配补齐任务
7
- * FAIL → 回退到 brainstorm 补齐(最多 2 次重试)
8
- */
9
-
10
- import { existsSync, readFileSync } from 'fs'
11
- import { join } from 'path'
12
-
13
- /**
14
- * 校验 brainstorm 产物完整性
15
- * @param {string} changeDir - 变更目录路径(.sillyspec/changes/<change>)
16
- * @returns {{ ok: boolean, level: 'PASS'|'WARN'|'FAIL', errors: string[], warnings: string[] }}
17
- */
18
- export function checkBrainstormArtifacts(changeDir) {
19
- const errors = []
20
- const warnings = []
21
- const brainstormDir = join(changeDir, 'brainstorm')
22
-
23
- // ── 1. 产物完整性检查 ──
24
- const requiredFiles = ['design.md', 'decisions.md', 'gaps.md', 'assumptions.md', 'next-action.json']
25
- for (const file of requiredFiles) {
26
- if (!existsSync(join(brainstormDir, file))) {
27
- errors.push(`brainstorm/${file} 不存在`)
28
- }
29
- }
30
-
31
- // ── 2. next-action.json 有效性检查 ──
32
- const nextActionFile = join(brainstormDir, 'next-action.json')
33
- if (existsSync(nextActionFile)) {
34
- try {
35
- const nextAction = JSON.parse(readFileSync(nextActionFile, 'utf8'))
36
-
37
- // 必填字段
38
- if (!['ready_for_plan', 'waiting_for_user'].includes(nextAction.status)) {
39
- errors.push(`next-action.json.status 无效值: ${nextAction.status}`)
40
- }
41
- if (typeof nextAction.has_blocking_questions !== 'boolean') {
42
- errors.push(`next-action.json.has_blocking_questions 不是布尔值`)
43
- }
44
- if (!['high', 'medium', 'low'].includes(nextAction.decision_level)) {
45
- warnings.push(`next-action.json.decision_level 无效值: ${nextAction.decision_level}`)
46
- }
47
-
48
- // 状态一致性
49
- if (nextAction.status === 'waiting_for_user' && nextAction.has_blocking_questions !== true) {
50
- errors.push(`next-action.json status=waiting_for_user 但 has_blocking_questions !== true`)
51
- }
52
- if (nextAction.status === 'ready_for_plan' && nextAction.has_blocking_questions === true) {
53
- errors.push(`next-action.json status=ready_for_plan 但 has_blocking_questions === true`)
54
- }
55
- } catch (e) {
56
- errors.push(`next-action.json 解析失败: ${e.message}`)
57
- }
58
- }
59
-
60
- // ── 3. design.md 覆盖度检查 ──
61
- const designFile = join(brainstormDir, 'design.md')
62
- if (existsSync(designFile)) {
63
- const designContent = readFileSync(designFile, 'utf8')
64
-
65
- // 必须覆盖的维度
66
- if (!/(?:目标|goal|objective|背景|background|问题|problem)/i.test(designContent)) {
67
- warnings.push('design.md 缺少设计目标/背景描述')
68
- }
69
- if (!/(?:范围|scope|总体方案|方案|approach)/i.test(designContent)) {
70
- warnings.push('design.md 缺少总体方案描述')
71
- }
72
-
73
- // 弱覆盖检测
74
- if (/影响模块.*TBD|文件变更.*TBD|待定/i.test(designContent)) {
75
- warnings.push('design.md 包含 TBD/待定内容')
76
- }
77
- if (!/(?:验收|acceptance|完成标准)/i.test(designContent)) {
78
- warnings.push('design.md 缺少验收标准')
79
- }
80
- }
81
-
82
- // ── 4. gaps.md 覆盖度检查 ──
83
- const gapsFile = join(brainstormDir, 'gaps.md')
84
- if (existsSync(gapsFile)) {
85
- const gapsContent = readFileSync(gapsFile, 'utf8')
86
- const hasBlocker = /BLOCKER/i.test(gapsContent)
87
- if (hasBlocker) {
88
- errors.push('gaps.md 包含 BLOCKER 级缺口,需要先解决')
89
- }
90
- // 检查是否为空
91
- const nonEmpty = gapsContent.split('\n').filter(l => l.trim() && !l.startsWith('#')).length
92
- if (nonEmpty === 0) {
93
- warnings.push('gaps.md 为空(brainstorm 可能遗漏了缺口分析)')
94
- }
95
- }
96
-
97
- // ── 5. assumptions.md 风险检查 ──
98
- const assumptionsFile = join(brainstormDir, 'assumptions.md')
99
- if (existsSync(assumptionsFile)) {
100
- const assumptionsContent = readFileSync(assumptionsFile, 'utf8')
101
- if (/假设现有数据格式不变|假设.*API.*不变|假设.*性能.*可接受/i.test(assumptionsContent)) {
102
- warnings.push('assumptions.md 包含高风险假设,需在 verify 阶段验证')
103
- }
104
- }
105
-
106
- // ── 6. decisions.md 一致性检查 ──
107
- const decisionsFile = join(brainstormDir, 'decisions.md')
108
- if (existsSync(decisionsFile)) {
109
- const decisionsContent = readFileSync(decisionsFile, 'utf8')
110
- const autoDecided = (decisionsContent.match(/AUTO_DECIDED/g) || []).length
111
- const autoDecidedWithReason = (decisionsContent.match(/AUTO_DECIDED[\s\S]*?checklist/i) || []).length
112
- if (autoDecided > 0 && autoDecidedWithReason < autoDecided) {
113
- errors.push(`${autoDecided - autoDecidedWithReason} 个 AUTO_DECIDED 决策缺少 checklist 依据`)
114
- }
115
- }
116
-
117
- // ── 判定结果 ──
118
- let level
119
- if (errors.length > 0) {
120
- level = 'FAIL'
121
- } else if (warnings.length > 0) {
122
- level = 'WARN'
123
- } else {
124
- level = 'PASS'
125
- }
126
-
127
- return { ok: level !== 'FAIL', level, errors, warnings }
128
- }
129
-
130
- /**
131
- * 执行 brainstorm postcheck(供 run.js 调用)
132
- */
133
- export async function executeBrainstormPostcheck(cwd, platformOpts, changeName) {
134
- const specBase = platformOpts?.specRoot || join(cwd, '.sillyspec')
135
- const changeDir = join(specBase, 'changes', changeName)
136
- if (!existsSync(changeDir)) {
137
- console.log('⚠️ brainstorm postcheck: 变更目录不存在,跳过')
138
- return
139
- }
140
-
141
- const result = checkBrainstormArtifacts(changeDir)
142
-
143
- if (result.level === 'PASS') {
144
- console.log('✅ brainstorm postcheck: PASS')
145
- } else if (result.level === 'WARN') {
146
- console.log('⚠️ brainstorm postcheck: WARN')
147
- for (const w of result.warnings) {
148
- console.log(` - ${w}`)
149
- }
150
- } else {
151
- console.error('❌ brainstorm postcheck: FAIL')
152
- for (const e of result.errors) {
153
- console.error(` - ${e}`)
154
- }
155
- console.error('\\n 请修复以上问题后重试,或使用 --skip-approval 跳过。')
156
- process.exit(1)
157
- }
158
- }