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.
Files changed (133) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +21 -21
  2. package/.claude/skills/sillyspec-auto/SKILL.md +83 -83
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -44
  4. package/.claude/skills/sillyspec-commit/SKILL.md +106 -106
  5. package/.claude/skills/sillyspec-continue/SKILL.md +45 -45
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +31 -31
  7. package/.claude/skills/sillyspec-execute/SKILL.md +30 -30
  8. package/.claude/skills/sillyspec-explore/SKILL.md +109 -109
  9. package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
  10. package/.claude/skills/sillyspec-plan/SKILL.md +21 -21
  11. package/.claude/skills/sillyspec-propose/SKILL.md +21 -21
  12. package/.claude/skills/sillyspec-quick/SKILL.md +21 -21
  13. package/.claude/skills/sillyspec-resume/SKILL.md +68 -68
  14. package/.claude/skills/sillyspec-scan/SKILL.md +21 -21
  15. package/.claude/skills/sillyspec-state/SKILL.md +54 -54
  16. package/.claude/skills/sillyspec-status/SKILL.md +21 -21
  17. package/.claude/skills/sillyspec-verify/SKILL.md +21 -21
  18. package/.claude/skills/sillyspec-workspace/SKILL.md +157 -157
  19. package/.husky/pre-push +13 -13
  20. package/CLAUDE.md +18 -18
  21. package/README.md +198 -188
  22. package/SKILL.md +90 -91
  23. package/bin/sillyspec.js +2 -2
  24. package/docs/brainstorm-plan-contract.md +64 -64
  25. package/docs/plan-execute-contract.md +123 -123
  26. package/docs/platform-scan-protocol.md +298 -298
  27. package/docs/revision-mode.md +115 -115
  28. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -99
  29. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -218
  30. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -167
  31. package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -148
  32. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -193
  33. package/docs/sillyspec/file-lifecycle.md +125 -125
  34. package/docs/workflow-contract-regression.md +106 -106
  35. package/docs/worktree-isolation.md +252 -252
  36. package/package.json +40 -40
  37. package/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -7446
  38. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -1
  39. package/packages/dashboard/dist/index.html +16 -16
  40. package/packages/dashboard/index.html +15 -15
  41. package/packages/dashboard/package-lock.json +2384 -2384
  42. package/packages/dashboard/package.json +25 -25
  43. package/packages/dashboard/server/executor.js +86 -86
  44. package/packages/dashboard/server/index.js +588 -588
  45. package/packages/dashboard/server/parser.js +526 -526
  46. package/packages/dashboard/server/watcher.js +344 -344
  47. package/packages/dashboard/src/App.vue +558 -558
  48. package/packages/dashboard/src/components/ActionBar.vue +93 -93
  49. package/packages/dashboard/src/components/CommandPalette.vue +96 -96
  50. package/packages/dashboard/src/components/DetailPanel.vue +137 -137
  51. package/packages/dashboard/src/components/LogStream.vue +65 -65
  52. package/packages/dashboard/src/components/PipelineStage.vue +95 -95
  53. package/packages/dashboard/src/components/PipelineView.vue +156 -156
  54. package/packages/dashboard/src/components/ProjectList.vue +210 -210
  55. package/packages/dashboard/src/components/StageBadge.vue +67 -67
  56. package/packages/dashboard/src/components/StepCard.vue +94 -94
  57. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
  58. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
  59. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
  60. package/packages/dashboard/src/composables/useDashboard.js +170 -170
  61. package/packages/dashboard/src/composables/useKeyboard.js +119 -119
  62. package/packages/dashboard/src/composables/useWebSocket.js +129 -129
  63. package/packages/dashboard/src/main.js +8 -8
  64. package/packages/dashboard/src/style.css +132 -132
  65. package/packages/dashboard/vite.config.js +18 -18
  66. package/src/brainstorm-postcheck.js +158 -158
  67. package/src/change-list.js +52 -52
  68. package/src/change-risk-profile.js +352 -352
  69. package/src/classify-change.js +73 -73
  70. package/src/constants.js +70 -70
  71. package/src/contract-matrix.js +278 -278
  72. package/src/db.js +201 -201
  73. package/src/endpoint-extractor.js +315 -315
  74. package/src/hooks/claude-pre-tool-use.cjs +125 -125
  75. package/src/hooks/worktree-guard.js +653 -653
  76. package/src/index.js +922 -900
  77. package/src/init.js +431 -431
  78. package/src/knowledge-match.js +130 -130
  79. package/src/migrate.js +117 -117
  80. package/src/modules.js +482 -482
  81. package/src/progress.js +1734 -1734
  82. package/src/run.js +3465 -3358
  83. package/src/scan-postcheck.js +387 -383
  84. package/src/setup.js +398 -398
  85. package/src/stage-contract.js +700 -700
  86. package/src/stages/archive.js +160 -160
  87. package/src/stages/brainstorm-auto.js +229 -229
  88. package/src/stages/brainstorm.js +645 -645
  89. package/src/stages/doctor.js +365 -365
  90. package/src/stages/execute.js +625 -625
  91. package/src/stages/explore.js +34 -34
  92. package/src/stages/index.js +29 -29
  93. package/src/stages/knowledge.js +498 -498
  94. package/src/stages/plan-postcheck.js +511 -513
  95. package/src/stages/plan.js +582 -582
  96. package/src/stages/propose.js +174 -174
  97. package/src/stages/quick.js +82 -82
  98. package/src/stages/scan.js +558 -558
  99. package/src/stages/status.js +65 -65
  100. package/src/stages/verify.js +322 -322
  101. package/src/sync.js +497 -497
  102. package/src/task-review.js +346 -346
  103. package/src/workflow.js +785 -785
  104. package/src/worktree-apply.js +549 -549
  105. package/src/worktree-deps.js +185 -0
  106. package/src/worktree.js +982 -932
  107. package/templates/workflows/archive-impact.yaml +79 -79
  108. package/templates/workflows/scan-docs.yaml +132 -132
  109. package/test/brainstorm-plan-contract.test.mjs +273 -273
  110. package/test/check-syntax.mjs +26 -26
  111. package/test/contract-artifacts.test.mjs +323 -323
  112. package/test/decision-supersede.test.mjs +277 -277
  113. package/test/knowledge-match.test.mjs +231 -231
  114. package/test/plan-execute-contract.test.mjs +330 -330
  115. package/test/plan-optimization.test.mjs +572 -572
  116. package/test/platform-artifacts.test.mjs +166 -166
  117. package/test/platform-failure-samples.test.mjs +199 -199
  118. package/test/platform-recovery-chain.test.mjs +167 -167
  119. package/test/platform-recovery.test.mjs +136 -136
  120. package/test/platform-scan-p0.test.mjs +168 -168
  121. package/test/revision-v1.test.mjs +1145 -1145
  122. package/test/run-scan-project-parse.test.mjs +200 -200
  123. package/test/run-tests.mjs +48 -48
  124. package/test/scan-knowledge.test.mjs +175 -175
  125. package/test/scan-paths.test.mjs +68 -68
  126. package/test/scan-postcheck.test.mjs +197 -197
  127. package/test/spec-dir.test.mjs +206 -206
  128. package/test/stage-contract.test.mjs +299 -299
  129. package/test/stage-definitions.test.mjs +39 -39
  130. package/test/wait-gates.test.mjs +496 -496
  131. package/test/worktree-deps-provision.test.mjs +148 -0
  132. package/test/worktree-guard.test.mjs +71 -71
  133. 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
+ }
@@ -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
+ }