sillyspec 3.25.1 → 3.25.3

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/src/worktree.js CHANGED
@@ -167,9 +167,17 @@ function sleepMs(ms) {
167
167
  while (Date.now() < end) { /* spin */ }
168
168
  }
169
169
 
170
- function computeBaselineHash(cwd) {
171
- // 排除 .sillyspec/ 元数据目录,避免 brainstorm/plan 阶段修改的蓝图文件污染 baseline
172
- const exclude = '-- . ":(exclude).sillyspec/"';
170
+ export function computeBaselineHash(cwd) {
171
+ // 排除非交付物的元数据/文档 churn,避免多操作者仓库里别人改这些 → 整树 hash 变 → apply 误阻断
172
+ // (execute 自身也会改 .sillyspec/docs.sillyspec,否则每个 execute 收尾都因自身改动 BLOCKED)。
173
+ // 安全:coarse hash 放过这些不丢真冲突——applyWorktree step5a(未提交∩changedFiles)+step5b(HEAD blob)
174
+ // 对交付文件做精确 per-file 校验,不读这里的 exclude。
175
+ // - .sillyspec/:brainstorm/plan 蓝图 + runtime 产物
176
+ // - .claude/:agent 配置/skills/CLAUDE.md(多操作者 agent 指引 churn)
177
+ // - docs/:文档(非代码交付物;deliverable 文档冲突由 step5a/5b 兜底)
178
+ // - CLAUDE.md:根 agent 指引(多操作者常改)
179
+ // 必须和 applyWorktree step 4.5 (worktree-apply.js) 使用相同的排除规则。
180
+ const exclude = '-- . ":(exclude).sillyspec/" ":(exclude).claude/" ":(exclude)docs/" ":(exclude)CLAUDE.md"';
173
181
  const staged = gitQuiet(cwd, `diff --cached ${exclude}`) || '';
174
182
  const unstaged = gitQuiet(cwd, `diff ${exclude}`) || '';
175
183
  const untracked = gitQuiet(cwd, `ls-files --others --exclude-standard ${exclude}`) || '';
@@ -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
- }