sillyspec 3.20.2 → 3.20.4
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-archive/SKILL.md +21 -21
- package/.claude/skills/sillyspec-auto/SKILL.md +83 -83
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -44
- package/.claude/skills/sillyspec-commit/SKILL.md +106 -106
- package/.claude/skills/sillyspec-continue/SKILL.md +45 -45
- package/.claude/skills/sillyspec-doctor/SKILL.md +31 -31
- package/.claude/skills/sillyspec-execute/SKILL.md +30 -30
- package/.claude/skills/sillyspec-explore/SKILL.md +109 -109
- package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
- package/.claude/skills/sillyspec-plan/SKILL.md +21 -21
- package/.claude/skills/sillyspec-propose/SKILL.md +21 -21
- package/.claude/skills/sillyspec-quick/SKILL.md +21 -21
- package/.claude/skills/sillyspec-resume/SKILL.md +68 -68
- package/.claude/skills/sillyspec-scan/SKILL.md +21 -21
- package/.claude/skills/sillyspec-state/SKILL.md +54 -54
- package/.claude/skills/sillyspec-status/SKILL.md +21 -21
- package/.claude/skills/sillyspec-verify/SKILL.md +21 -21
- package/.claude/skills/sillyspec-workspace/SKILL.md +157 -157
- package/.husky/pre-push +13 -13
- package/CLAUDE.md +18 -18
- package/README.md +198 -188
- package/SKILL.md +90 -91
- package/bin/sillyspec.js +2 -2
- package/docs/brainstorm-plan-contract.md +64 -64
- package/docs/plan-execute-contract.md +123 -123
- package/docs/platform-scan-protocol.md +298 -298
- package/docs/revision-mode.md +115 -115
- package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -99
- package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -218
- package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -167
- package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -148
- package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -193
- package/docs/sillyspec/file-lifecycle.md +125 -125
- package/docs/workflow-contract-regression.md +106 -106
- package/docs/worktree-isolation.md +252 -252
- package/package.json +40 -40
- package/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -7446
- package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -1
- package/packages/dashboard/dist/index.html +16 -16
- package/packages/dashboard/index.html +15 -15
- package/packages/dashboard/package-lock.json +2384 -2384
- package/packages/dashboard/package.json +25 -25
- package/packages/dashboard/server/executor.js +86 -86
- package/packages/dashboard/server/index.js +588 -588
- package/packages/dashboard/server/parser.js +526 -526
- package/packages/dashboard/server/watcher.js +344 -344
- package/packages/dashboard/src/App.vue +558 -558
- package/packages/dashboard/src/components/ActionBar.vue +93 -93
- package/packages/dashboard/src/components/CommandPalette.vue +96 -96
- package/packages/dashboard/src/components/DetailPanel.vue +137 -137
- package/packages/dashboard/src/components/LogStream.vue +65 -65
- package/packages/dashboard/src/components/PipelineStage.vue +95 -95
- package/packages/dashboard/src/components/PipelineView.vue +156 -156
- package/packages/dashboard/src/components/ProjectList.vue +210 -210
- package/packages/dashboard/src/components/StageBadge.vue +67 -67
- package/packages/dashboard/src/components/StepCard.vue +94 -94
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
- package/packages/dashboard/src/composables/useDashboard.js +170 -170
- package/packages/dashboard/src/composables/useKeyboard.js +119 -119
- package/packages/dashboard/src/composables/useWebSocket.js +129 -129
- package/packages/dashboard/src/main.js +8 -8
- package/packages/dashboard/src/style.css +132 -132
- package/packages/dashboard/vite.config.js +18 -18
- package/src/brainstorm-postcheck.js +158 -158
- package/src/change-list.js +52 -52
- package/src/change-risk-profile.js +352 -352
- package/src/classify-change.js +73 -73
- package/src/constants.js +70 -70
- package/src/contract-matrix.js +278 -278
- package/src/db.js +201 -201
- package/src/endpoint-extractor.js +315 -315
- package/src/hooks/claude-pre-tool-use.cjs +125 -125
- package/src/hooks/worktree-guard.js +653 -653
- package/src/index.js +922 -900
- package/src/init.js +431 -431
- package/src/knowledge-match.js +130 -130
- package/src/migrate.js +117 -117
- package/src/modules.js +482 -482
- package/src/progress.js +1734 -1734
- package/src/run.js +3465 -3358
- package/src/scan-postcheck.js +387 -383
- package/src/setup.js +398 -398
- package/src/stage-contract.js +700 -700
- package/src/stages/archive.js +160 -160
- package/src/stages/brainstorm-auto.js +229 -229
- package/src/stages/brainstorm.js +645 -645
- package/src/stages/doctor.js +365 -365
- package/src/stages/execute.js +625 -625
- package/src/stages/explore.js +34 -34
- package/src/stages/index.js +29 -29
- package/src/stages/knowledge.js +498 -498
- package/src/stages/plan-postcheck.js +511 -513
- package/src/stages/plan.js +582 -582
- package/src/stages/propose.js +174 -174
- package/src/stages/quick.js +82 -82
- package/src/stages/scan.js +558 -558
- package/src/stages/status.js +65 -65
- package/src/stages/verify.js +322 -322
- package/src/sync.js +497 -497
- package/src/task-review.js +346 -346
- package/src/workflow.js +785 -785
- package/src/worktree-apply.js +549 -549
- package/src/worktree-deps.js +185 -0
- package/src/worktree.js +982 -932
- package/templates/workflows/archive-impact.yaml +79 -79
- package/templates/workflows/scan-docs.yaml +132 -132
- package/test/brainstorm-plan-contract.test.mjs +273 -273
- package/test/check-syntax.mjs +26 -26
- package/test/contract-artifacts.test.mjs +323 -323
- package/test/decision-supersede.test.mjs +277 -277
- package/test/knowledge-match.test.mjs +231 -231
- package/test/plan-execute-contract.test.mjs +330 -330
- package/test/plan-optimization.test.mjs +572 -572
- package/test/platform-artifacts.test.mjs +166 -166
- package/test/platform-failure-samples.test.mjs +199 -199
- package/test/platform-recovery-chain.test.mjs +167 -167
- package/test/platform-recovery.test.mjs +136 -136
- package/test/platform-scan-p0.test.mjs +168 -168
- package/test/revision-v1.test.mjs +1145 -1145
- package/test/run-scan-project-parse.test.mjs +200 -200
- package/test/run-tests.mjs +48 -48
- package/test/scan-knowledge.test.mjs +175 -175
- package/test/scan-paths.test.mjs +68 -68
- package/test/scan-postcheck.test.mjs +197 -197
- package/test/spec-dir.test.mjs +206 -206
- package/test/stage-contract.test.mjs +299 -299
- package/test/stage-definitions.test.mjs +39 -39
- package/test/wait-gates.test.mjs +496 -496
- package/test/worktree-deps-provision.test.mjs +148 -0
- package/test/worktree-guard.test.mjs +71 -71
- package/test/worktree-native-overlay.test.mjs +188 -188
|
@@ -1,158 +1,158 @@
|
|
|
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
|
-
}
|
|
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
|
+
}
|
package/src/change-list.js
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
import { readFileSync, existsSync } from 'fs'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 从 design.md 解析文件变更清单
|
|
5
|
-
* @param {string} designMdPath - design.md 文件路径
|
|
6
|
-
* @returns {Set<string>} 文件路径集合(相对路径,如 "src/worktree.js")
|
|
7
|
-
*/
|
|
8
|
-
export function parseFileChangeList(designMdPath) {
|
|
9
|
-
const result = new Set()
|
|
10
|
-
|
|
11
|
-
if (!designMdPath || !existsSync(designMdPath)) return result
|
|
12
|
-
|
|
13
|
-
const content = readFileSync(designMdPath, 'utf8')
|
|
14
|
-
|
|
15
|
-
// 定位"文件变更清单"标题
|
|
16
|
-
const sectionRegex = /^#{2,3}\s*文件变更清单/m
|
|
17
|
-
const sectionMatch = content.match(sectionRegex)
|
|
18
|
-
if (!sectionMatch) return result
|
|
19
|
-
|
|
20
|
-
// 从标题后开始,截取到下一个 ## 标题或文件末尾
|
|
21
|
-
const afterSection = content.slice(sectionMatch.index + sectionMatch[0].length)
|
|
22
|
-
const nextSectionMatch = afterSection.match(/^##\s/m)
|
|
23
|
-
const relevantContent = nextSectionMatch
|
|
24
|
-
? afterSection.slice(0, nextSectionMatch.index)
|
|
25
|
-
: afterSection
|
|
26
|
-
|
|
27
|
-
// 解析表格行
|
|
28
|
-
const lines = relevantContent.split('\n')
|
|
29
|
-
let headerSkipped = false
|
|
30
|
-
for (const line of lines) {
|
|
31
|
-
// 跳过分隔行和非表格行
|
|
32
|
-
if (!line.startsWith('|') || /^\|[-:\s|]+\|$/.test(line)) continue
|
|
33
|
-
|
|
34
|
-
const cells = line.split('|').slice(1, -1) // 去掉首尾空元素
|
|
35
|
-
if (cells.length < 2) continue
|
|
36
|
-
|
|
37
|
-
// 跳过 header 行(包含「文件路径」的表头)
|
|
38
|
-
if (!headerSkipped) {
|
|
39
|
-
headerSkipped = true
|
|
40
|
-
continue
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const filePath = cells[1].trim().replace(/^`|`$/g, '')
|
|
44
|
-
|
|
45
|
-
// 忽略空路径、注释、.sillyspec/ 内的路径
|
|
46
|
-
if (!filePath || filePath === '—' || filePath === '-' || filePath.startsWith('.sillyspec/')) continue
|
|
47
|
-
|
|
48
|
-
result.add(filePath)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return result
|
|
52
|
-
}
|
|
1
|
+
import { readFileSync, existsSync } from 'fs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 从 design.md 解析文件变更清单
|
|
5
|
+
* @param {string} designMdPath - design.md 文件路径
|
|
6
|
+
* @returns {Set<string>} 文件路径集合(相对路径,如 "src/worktree.js")
|
|
7
|
+
*/
|
|
8
|
+
export function parseFileChangeList(designMdPath) {
|
|
9
|
+
const result = new Set()
|
|
10
|
+
|
|
11
|
+
if (!designMdPath || !existsSync(designMdPath)) return result
|
|
12
|
+
|
|
13
|
+
const content = readFileSync(designMdPath, 'utf8')
|
|
14
|
+
|
|
15
|
+
// 定位"文件变更清单"标题
|
|
16
|
+
const sectionRegex = /^#{2,3}\s*文件变更清单/m
|
|
17
|
+
const sectionMatch = content.match(sectionRegex)
|
|
18
|
+
if (!sectionMatch) return result
|
|
19
|
+
|
|
20
|
+
// 从标题后开始,截取到下一个 ## 标题或文件末尾
|
|
21
|
+
const afterSection = content.slice(sectionMatch.index + sectionMatch[0].length)
|
|
22
|
+
const nextSectionMatch = afterSection.match(/^##\s/m)
|
|
23
|
+
const relevantContent = nextSectionMatch
|
|
24
|
+
? afterSection.slice(0, nextSectionMatch.index)
|
|
25
|
+
: afterSection
|
|
26
|
+
|
|
27
|
+
// 解析表格行
|
|
28
|
+
const lines = relevantContent.split('\n')
|
|
29
|
+
let headerSkipped = false
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
// 跳过分隔行和非表格行
|
|
32
|
+
if (!line.startsWith('|') || /^\|[-:\s|]+\|$/.test(line)) continue
|
|
33
|
+
|
|
34
|
+
const cells = line.split('|').slice(1, -1) // 去掉首尾空元素
|
|
35
|
+
if (cells.length < 2) continue
|
|
36
|
+
|
|
37
|
+
// 跳过 header 行(包含「文件路径」的表头)
|
|
38
|
+
if (!headerSkipped) {
|
|
39
|
+
headerSkipped = true
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const filePath = cells[1].trim().replace(/^`|`$/g, '')
|
|
44
|
+
|
|
45
|
+
// 忽略空路径、注释、.sillyspec/ 内的路径
|
|
46
|
+
if (!filePath || filePath === '—' || filePath === '-' || filePath.startsWith('.sillyspec/')) continue
|
|
47
|
+
|
|
48
|
+
result.add(filePath)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result
|
|
52
|
+
}
|