sillyspec 3.20.2 → 3.20.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.
Files changed (208) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +1 -5
  2. package/.claude/skills/sillyspec-auto/SKILL.md +2 -8
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -28
  4. package/.claude/skills/sillyspec-commit/SKILL.md +3 -4
  5. package/.claude/skills/sillyspec-continue/SKILL.md +4 -5
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +3 -12
  7. package/.claude/skills/sillyspec-execute/SKILL.md +2 -15
  8. package/.claude/skills/sillyspec-explore/SKILL.md +1 -14
  9. package/.claude/skills/sillyspec-plan/SKILL.md +36 -5
  10. package/.claude/skills/sillyspec-propose/SKILL.md +0 -4
  11. package/.claude/skills/sillyspec-quick/SKILL.md +1 -5
  12. package/.claude/skills/sillyspec-resume/SKILL.md +66 -23
  13. package/.claude/skills/sillyspec-scan/SKILL.md +1 -5
  14. package/.claude/skills/sillyspec-state/SKILL.md +8 -8
  15. package/.claude/skills/sillyspec-status/SKILL.md +1 -5
  16. package/.claude/skills/sillyspec-verify/SKILL.md +1 -5
  17. package/.claude/skills/sillyspec-workspace/SKILL.md +3 -11
  18. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +97 -0
  19. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +51 -0
  20. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +29 -0
  21. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +34 -0
  22. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +13 -0
  23. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +43 -0
  24. package/.sillyspec/changes/auto-mode/design.md +50 -0
  25. package/.sillyspec/changes/auto-mode/proposal.md +19 -0
  26. package/.sillyspec/changes/auto-mode/requirements.md +21 -0
  27. package/.sillyspec/changes/auto-mode/tasks.md +7 -0
  28. package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +206 -0
  29. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +199 -0
  30. package/.sillyspec/changes/dashboard/design.md +219 -0
  31. package/.sillyspec/changes/dashboard/design.md.braindraft +206 -0
  32. package/.sillyspec/changes/run-command-design/design.md +1230 -0
  33. package/.sillyspec/changes/unified-docs-design/design.md +199 -0
  34. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  35. package/.sillyspec/knowledge/INDEX.md +8 -0
  36. package/.sillyspec/knowledge/uncategorized.md +3 -0
  37. package/.sillyspec/plans/2026-04-05-dashboard.md +737 -0
  38. package/.sillyspec/projects/sillyspec.yaml +3 -0
  39. package/README.md +11 -13
  40. package/SKILL.md +40 -44
  41. package/dist/steps/brainstorm/01-load-context.md +30 -0
  42. package/dist/steps/brainstorm/02-reuse-check.md +6 -0
  43. package/dist/steps/brainstorm/03-prototype-analysis.md +11 -0
  44. package/dist/steps/brainstorm/04-module-split.md +23 -0
  45. package/dist/steps/brainstorm/05-dialog-explore.md +8 -0
  46. package/dist/steps/brainstorm/06-propose-approaches.md +3 -0
  47. package/dist/steps/brainstorm/07-present-design.md +3 -0
  48. package/dist/steps/brainstorm/08-write-design.md +21 -0
  49. package/dist/steps/brainstorm/09-self-review.md +15 -0
  50. package/dist/steps/brainstorm/10-user-confirm.md +3 -0
  51. package/dist/steps/brainstorm/11-output-spec.md +7 -0
  52. package/dist/steps/brainstorm/manifest.yaml +26 -0
  53. package/dist/steps/execute/01-load-context.md +41 -0
  54. package/dist/steps/execute/02-scan-conventions.md +47 -0
  55. package/dist/steps/execute/03-skill-mcp.md +19 -0
  56. package/dist/steps/execute/04-assign-task.md +22 -0
  57. package/dist/steps/execute/04b-prompt-template.md +54 -0
  58. package/dist/steps/execute/05-write-test.md +7 -0
  59. package/dist/steps/execute/06-write-code.md +8 -0
  60. package/dist/steps/execute/07-run-test.md +26 -0
  61. package/dist/steps/execute/08-fix-issues.md +28 -0
  62. package/dist/steps/execute/09-next-task.md +33 -0
  63. package/dist/steps/execute/manifest.yaml +28 -0
  64. package/dist/steps/plan/01-load-context.md +22 -0
  65. package/dist/steps/plan/02-anchor-confirm.md +1 -0
  66. package/dist/steps/plan/03-expand-tasks.md +33 -0
  67. package/dist/steps/plan/04-mark-order.md +15 -0
  68. package/dist/steps/plan/05-e2e-planning.md +17 -0
  69. package/dist/steps/plan/06-self-check.md +16 -0
  70. package/dist/steps/plan/07-save.md +1 -0
  71. package/dist/steps/plan/manifest.yaml +18 -0
  72. package/dist/steps/scan/01-env-detect.md +51 -0
  73. package/dist/steps/scan/02-tech-stack.md +16 -0
  74. package/dist/steps/scan/03-conventions.md +16 -0
  75. package/dist/steps/scan/04-structure.md +19 -0
  76. package/dist/steps/scan/05-quality.md +18 -0
  77. package/dist/steps/scan/06-complete.md +49 -0
  78. package/dist/steps/scan/manifest.yaml +16 -0
  79. package/dist/steps/verify/01-load-specs.md +28 -0
  80. package/dist/steps/verify/02-check-tasks.md +1 -0
  81. package/dist/steps/verify/03-check-design.md +6 -0
  82. package/dist/steps/verify/04-run-tests.md +7 -0
  83. package/dist/steps/verify/05-e2e-tests.md +27 -0
  84. package/dist/steps/verify/05b-e2e-fix.md +33 -0
  85. package/dist/steps/verify/06-code-quality.md +25 -0
  86. package/dist/steps/verify/07-lint-check.md +27 -0
  87. package/dist/steps/verify/08-output-report.md +14 -0
  88. package/dist/steps/verify/manifest.yaml +22 -0
  89. package/package.json +1 -7
  90. package/packages/dashboard/dist/assets/{index-Bq_Z2hne.js → index-D1EVTLmc.js} +1264 -1264
  91. package/packages/dashboard/dist/assets/index-DGe8CqeP.css +1 -0
  92. package/packages/dashboard/dist/index.html +2 -2
  93. package/packages/dashboard/package-lock.json +6 -6
  94. package/packages/dashboard/server/executor.js +1 -1
  95. package/packages/dashboard/server/index.js +18 -98
  96. package/packages/dashboard/server/parser.js +71 -139
  97. package/packages/dashboard/server/watcher.js +6 -14
  98. package/packages/dashboard/src/App.vue +185 -418
  99. package/packages/dashboard/src/components/ActionBar.vue +1 -10
  100. package/packages/dashboard/src/components/CommandPalette.vue +1 -5
  101. package/packages/dashboard/src/components/DocPreview.vue +8 -105
  102. package/packages/dashboard/src/components/DocTree.vue +19 -75
  103. package/packages/dashboard/src/components/PipelineStage.vue +2 -22
  104. package/packages/dashboard/src/components/PipelineView.vue +5 -32
  105. package/packages/dashboard/src/components/ProjectOverview.vue +139 -113
  106. package/packages/dashboard/src/components/StageBadge.vue +3 -17
  107. package/packages/dashboard/src/components/StepCard.vue +2 -7
  108. package/packages/dashboard/src/composables/useDashboard.js +0 -28
  109. package/src/derive.js +147 -0
  110. package/src/index.js +40 -688
  111. package/src/init.js +63 -119
  112. package/src/migrate.js +7 -7
  113. package/src/progress.js +248 -1474
  114. package/src/run.js +302 -3008
  115. package/src/setup.js +64 -2
  116. package/src/stages/archive.js +17 -123
  117. package/src/stages/brainstorm.js +48 -454
  118. package/src/stages/doctor.js +46 -99
  119. package/src/stages/execute.js +59 -420
  120. package/src/stages/index.js +18 -12
  121. package/src/stages/plan.js +189 -492
  122. package/src/stages/propose.js +11 -70
  123. package/src/stages/quick.js +13 -52
  124. package/src/stages/scan.js +68 -485
  125. package/src/stages/status.js +1 -1
  126. package/src/stages/verify.js +16 -203
  127. package/src/step.js +543 -0
  128. package/.claude/skills/sillyspec-knowledge/SKILL.md +0 -270
  129. package/.husky/pre-push +0 -13
  130. package/CLAUDE.md +0 -18
  131. package/docs/brainstorm-plan-contract.md +0 -64
  132. package/docs/plan-execute-contract.md +0 -123
  133. package/docs/platform-scan-protocol.md +0 -298
  134. package/docs/revision-mode.md +0 -115
  135. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +0 -99
  136. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +0 -218
  137. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +0 -167
  138. package/docs/sillyspec/file-lifecycle/storage-and-state.md +0 -148
  139. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +0 -193
  140. package/docs/sillyspec/file-lifecycle.md +0 -125
  141. package/docs/workflow-contract-regression.md +0 -106
  142. package/docs/worktree-isolation.md +0 -252
  143. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +0 -1
  144. package/packages/dashboard/dist/prototype-dashboard.html +0 -836
  145. package/packages/dashboard/dist/prototype-overview.html +0 -256
  146. package/packages/dashboard/public/prototype-dashboard.html +0 -836
  147. package/packages/dashboard/public/prototype-overview.html +0 -256
  148. package/packages/dashboard/src/components/HResizeHandle.vue +0 -48
  149. package/packages/dashboard/src/components/ProjectCard.vue +0 -187
  150. package/packages/dashboard/src/components/VResizeHandle.vue +0 -61
  151. package/packages/dashboard/src/composables/useLayout.js +0 -131
  152. package/src/brainstorm-postcheck.js +0 -158
  153. package/src/change-list.js +0 -52
  154. package/src/change-risk-profile.js +0 -352
  155. package/src/classify-change.js +0 -73
  156. package/src/constants.js +0 -70
  157. package/src/contract-matrix.js +0 -278
  158. package/src/db.js +0 -201
  159. package/src/endpoint-extractor.js +0 -315
  160. package/src/hooks/claude-pre-tool-use.cjs +0 -125
  161. package/src/hooks/worktree-guard.js +0 -761
  162. package/src/knowledge-match.js +0 -130
  163. package/src/modules.js +0 -482
  164. package/src/scan-postcheck.js +0 -383
  165. package/src/stage-contract.js +0 -700
  166. package/src/stages/brainstorm-auto.js +0 -229
  167. package/src/stages/explore.js +0 -34
  168. package/src/stages/knowledge.js +0 -498
  169. package/src/stages/plan-postcheck.js +0 -513
  170. package/src/sync.js +0 -497
  171. package/src/task-review.js +0 -346
  172. package/src/workflow.js +0 -785
  173. package/src/worktree-apply.js +0 -549
  174. package/src/worktree.js +0 -932
  175. package/templates/workflows/archive-impact.yaml +0 -79
  176. package/templates/workflows/scan-docs.yaml +0 -132
  177. package/test/brainstorm-plan-contract.test.mjs +0 -273
  178. package/test/check-syntax.mjs +0 -26
  179. package/test/cli-top-level-aliases.test.mjs +0 -174
  180. package/test/contract-artifacts.test.mjs +0 -323
  181. package/test/decision-supersede.test.mjs +0 -277
  182. package/test/knowledge-match.test.mjs +0 -231
  183. package/test/plan-execute-contract.test.mjs +0 -330
  184. package/test/plan-optimization.test.mjs +0 -572
  185. package/test/platform-artifacts.test.mjs +0 -190
  186. package/test/platform-failure-samples.test.mjs +0 -199
  187. package/test/platform-recovery-chain.test.mjs +0 -179
  188. package/test/platform-recovery.test.mjs +0 -167
  189. package/test/platform-scan-p0.test.mjs +0 -175
  190. package/test/revision-v1.test.mjs +0 -1145
  191. package/test/run-sanitize-project-name.test.mjs +0 -51
  192. package/test/run-scan-postcheck-fail.test.mjs +0 -64
  193. package/test/run-scan-project-parse.test.mjs +0 -200
  194. package/test/run-tests.mjs +0 -48
  195. package/test/scan-docs-yaml-placeholders.test.mjs +0 -84
  196. package/test/scan-knowledge.test.mjs +0 -175
  197. package/test/scan-paths.test.mjs +0 -68
  198. package/test/scan-postcheck-project-priority.test.mjs +0 -85
  199. package/test/scan-postcheck.test.mjs +0 -197
  200. package/test/scan-workflow-anyfailed-block.test.mjs +0 -52
  201. package/test/spec-dir.test.mjs +0 -206
  202. package/test/stage-contract-failed-post-check.test.mjs +0 -102
  203. package/test/stage-contract.test.mjs +0 -299
  204. package/test/stage-definitions.test.mjs +0 -39
  205. package/test/wait-gates.test.mjs +0 -501
  206. package/test/workflow-spec-base.test.mjs +0 -142
  207. package/test/worktree-guard.test.mjs +0 -136
  208. package/test/worktree-native-overlay.test.mjs +0 -188
@@ -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
- }
@@ -1,52 +0,0 @@
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,352 +0,0 @@
1
- /**
2
- * change-risk-profile.js — 变更风险分级检测
3
- *
4
- * 根据变更涉及的文件类型、关键词、git diff、brainstorm 产物,
5
- * 自动判定 P0/P1/P2 风险等级,产出结构化 risk-profile.json。
6
- *
7
- * P0 = 阻塞确认(必须用户确认)
8
- * P1 = 自动推进但记录
9
- * P2 = 自动通过
10
- */
11
-
12
- // ============ P0 触发规则 ============
13
-
14
- const P0_FILE_PATTERNS = [
15
- { id: 'R-002', pattern: /(?:^|\/)(?:migrations?|migration)\/(?:.+)/i, desc: '数据库 migration' },
16
- { id: 'R-002', pattern: /(?:^|\/)schema\.(?:sql|prisma|ts|js)$/i, desc: '数据库 schema' },
17
- { id: 'R-003', pattern: /(?:^|\/)(?:auth|permission|role|login|token)\b/i, desc: '鉴权/权限' },
18
- { id: 'R-004', pattern: /(?:^|\/)(?:payment|billing|stripe|paypal)\b/i, desc: '支付/资金' },
19
- { id: 'R-005', pattern: /\.(?:env|env\.\w+)$/, desc: '环境配置' },
20
- { id: 'R-005', pattern: /(?:^|\/)(?:Dockerfile|docker-compose\.\w+)$/i, desc: '部署配置' },
21
- { id: 'R-005', pattern: /(?:^|\/)config\.(?:yaml|yml|json|toml)$/i, desc: '生产配置' },
22
- ]
23
-
24
- const P0_CONTENT_PATTERNS = [
25
- { id: 'R-001', pattern: /\b(?:DROP\s+TABLE|TRUNCATE\s+TABLE|DELETE\s+FROM\s+\w+\s+(?!WHERE\s+.+\s+LIMIT))/i, desc: '删除数据' },
26
- { id: 'R-002', pattern: /\b(?:CREATE\s+TABLE|ALTER\s+TABLE|ADD\s+COLUMN|DROP\s+COLUMN|CREATE\s+INDEX)/i, desc: '数据库 migration' },
27
- ]
28
-
29
- // ============ P1 触发规则 ============
30
-
31
- const P1_FILE_PATTERNS = [
32
- { id: 'R-101', pattern: /(?:^|\/)(?:routes|controllers?|handlers?)\//i, desc: 'API 层' },
33
- { id: 'R-102', pattern: /(?:^|\/)(?:models|entities)\//i, desc: '数据模型层' },
34
- { id: 'R-104', pattern: /(?:^|\/)(?:services)\//i, desc: '业务逻辑层(跨模块)' },
35
- { id: 'R-106', pattern: /(?:^|\/)index\.(?:ts|js)$/i, desc: 're-export 入口' },
36
- ]
37
-
38
- const P1_CONTENT_PATTERNS = [
39
- { id: 'R-101', pattern: /\b(?:daemon|backend|grpc|websocket|cross.?process|ipc|message.?queue)\b/i, desc: '跨进程通信' },
40
- { id: 'R-101', pattern: /\b(?:session|lease|agent.?run|lifecycle|state.?transition|claim|heartbeat)\b/i, desc: '状态机/生命周期' },
41
- { id: 'R-102', pattern: /\b(?:api|client|contract|dto)\b/i, desc: 'API contract' },
42
- { id: 'R-105', pattern: /(?:^|\/)(?:workflow|daemon|session|lifecycle|state-machine)\b/i, desc: '核心模块' },
43
- ]
44
-
45
- // ============ P0/P1 命中文件名关键词(用于 git diff 检测) ============
46
-
47
- const PROTECTED_FILE_NAMES = [
48
- '.env', 'package.json', 'tsconfig.json', 'vite.config.ts', 'vite.config.js',
49
- 'prisma/schema.prisma', 'docker-compose.yml', 'docker-compose.yaml',
50
- ]
51
-
52
- // ============ 向后兼容:旧的 INTEGRATION_CRITICAL_PATTERNS ============
53
-
54
- export const INTEGRATION_CRITICAL_PATTERNS = [
55
- /\bdaemon\b/i,
56
- /\bbackend\b/i,
57
- /\bclient.*api\b/i,
58
- /\bgrpc\b/i,
59
- /\bwebsocket\b/i,
60
- /\bhttp.*client\b/i,
61
- /\bsession\b/i,
62
- /\blease\b/i,
63
- /\bagent.?run\b/i,
64
- /\blifecycle\b/i,
65
- /\bstate.?transition\b/i,
66
- /\bclaim\b/i,
67
- /\bheartbeat\b/i,
68
- /\bcross.?process\b/i,
69
- /\bipc\b/i,
70
- /\bmessage.?queue\b/i,
71
- /\bpub.?sub\b/i,
72
- /\bcli\.ts\b/i,
73
- /\bmain\.ts\b/i,
74
- /\bentrypoint\b/i,
75
- /\bserver\.(js|ts)\b/i,
76
- /\bbootstrap\b/i,
77
- /\bdockerfile\b/i,
78
- /\bdocker.?compose\b/i,
79
- ]
80
-
81
- export const INTEGRATION_FILE_PATTERNS = [
82
- /daemon/i,
83
- /session.?manager/i,
84
- /agent.?run/i,
85
- /lifecycle/i,
86
- /state.?machine/i,
87
- /lease/i,
88
- /cli\.(js|ts)$/,
89
- /main\.(js|ts)$/,
90
- /server\.(js|ts)$/,
91
- /bootstrap/i,
92
- /startup/i,
93
- ]
94
-
95
- // ============ 核心检测函数 ============
96
-
97
- /**
98
- * 三级风险检测:P0 / P1 / P2
99
- * @param {object} opts
100
- * @param {string} [opts.designContent] - design.md 内容
101
- * @param {string} [opts.planContent] - plan.md 内容
102
- * @param {string[]} [opts.changedFiles] - 变更文件列表
103
- * @param {string} [opts.diffContent] - git diff 内容(可选,用于更精确检测)
104
- * @param {object} [opts.nextAction] - next-action.json 解析后的对象(可选)
105
- * @param {string[]} [opts.protectedFiles] - 项目自定义 protected files 列表
106
- * @returns {RiskProfile}
107
- *
108
- * @typedef {object} RiskProfile
109
- * @property {string} level - 'P0' | 'P1' | 'P2'
110
- * @property {string[]} triggers - 触发的规则 ID 列表
111
- * @property {object[]} assessedFrom - 评估依据
112
- * @property {string[]} applyBlockers - apply 阻塞原因
113
- * @property {boolean} canAutoApply - 是否可自动 apply
114
- * @property {string} applyReason - apply 决策原因
115
- */
116
- export function detectRiskProfile({ designContent = '', planContent = '', changedFiles = [], diffContent = '', nextAction = null, protectedFiles = [] } = {}) {
117
- const triggers = []
118
- const assessedFrom = []
119
-
120
- const combined = [designContent, planContent].join('\n')
121
-
122
- // ── P0 检测(最高优先级)──
123
-
124
- // 基于文件路径
125
- for (const rule of P0_FILE_PATTERNS) {
126
- for (const file of changedFiles) {
127
- if (rule.pattern.test(file)) {
128
- rule.pattern.lastIndex = 0
129
- if (!triggers.includes(rule.id)) {
130
- triggers.push(rule.id)
131
- assessedFrom.push({ source: 'file_path', pattern: rule.desc, matchedFiles: [file] })
132
- }
133
- }
134
- }
135
- }
136
-
137
- // 基于 diff 内容(P0 关键词)
138
- const diffText = diffContent || combined
139
- for (const rule of P0_CONTENT_PATTERNS) {
140
- if (rule.pattern.test(diffText)) {
141
- rule.pattern.lastIndex = 0
142
- if (!triggers.includes(rule.id)) {
143
- triggers.push(rule.id)
144
- assessedFrom.push({ source: 'content', pattern: rule.desc })
145
- }
146
- }
147
- }
148
-
149
- // protected files
150
- for (const file of changedFiles) {
151
- if (PROTECTED_FILE_NAMES.some(p => file === p || file.endsWith('/' + p))) {
152
- const id = 'R-005'
153
- if (!triggers.includes(id)) {
154
- triggers.push(id)
155
- assessedFrom.push({ source: 'protected_file', pattern: file })
156
- }
157
- }
158
- // 用户自定义 protected files
159
- if (protectedFiles.includes(file)) {
160
- const id = 'R-005'
161
- if (!triggers.includes(id)) {
162
- triggers.push(id)
163
- assessedFrom.push({ source: 'protected_file', pattern: file })
164
- }
165
- }
166
- }
167
-
168
- // next-action.json 有 blocking questions
169
- if (nextAction && nextAction.has_blocking_questions === true) {
170
- const id = 'R-009'
171
- if (!triggers.includes(id)) {
172
- triggers.push(id)
173
- assessedFrom.push({ source: 'brainstorm', pattern: 'has_blocking_questions === true' })
174
- }
175
- }
176
-
177
- // ── P1 检测(只在无 P0 时检测)──
178
- if (!triggers.some(t => t.startsWith('R-00'))) {
179
- // 基于文件路径
180
- for (const rule of P1_FILE_PATTERNS) {
181
- for (const file of changedFiles) {
182
- if (rule.pattern.test(file)) {
183
- rule.pattern.lastIndex = 0
184
- if (!triggers.includes(rule.id)) {
185
- triggers.push(rule.id)
186
- assessedFrom.push({ source: 'file_path', pattern: rule.desc, matchedFiles: [file] })
187
- }
188
- }
189
- }
190
- }
191
-
192
- // 基于内容关键词
193
- for (const rule of P1_CONTENT_PATTERNS) {
194
- if (rule.pattern.test(combined)) {
195
- rule.pattern.lastIndex = 0
196
- if (!triggers.includes(rule.id)) {
197
- triggers.push(rule.id)
198
- assessedFrom.push({ source: 'content', pattern: rule.desc })
199
- }
200
- }
201
- }
202
-
203
- // 跨模块检测(> 3 个文件的变更)
204
- if (changedFiles.length > 10) {
205
- const id = 'R-101'
206
- if (!triggers.includes(id)) {
207
- triggers.push(id)
208
- assessedFrom.push({ source: 'file_count', pattern: `${changedFiles.length} files changed` })
209
- }
210
- }
211
-
212
- // next-action.json 有 NEEDS_REVIEW 决策
213
- if (nextAction && Array.isArray(nextAction.auto_decisions)) {
214
- for (const d of nextAction.auto_decisions) {
215
- if (d.status === 'NEEDS_REVIEW') {
216
- const id = 'R-102'
217
- if (!triggers.includes(id)) {
218
- triggers.push(id)
219
- assessedFrom.push({ source: 'brainstorm', pattern: `NEEDS_REVIEW: ${d.decision}` })
220
- }
221
- }
222
- }
223
- }
224
- }
225
-
226
- // ── 判定等级 ──
227
- let level = 'P2'
228
- if (triggers.some(t => t.match(/^R-0\d+$/))) {
229
- level = 'P0'
230
- } else if (triggers.some(t => t.match(/^R-1\d+$/))) {
231
- level = 'P1'
232
- }
233
-
234
- // ── apply 决策 ──
235
- const applyBlockers = []
236
- if (level === 'P0') {
237
- applyBlockers.push('风险等级 P0:需要用户确认后才能 apply')
238
- }
239
-
240
- const canAutoApply = level !== 'P0' && applyBlockers.length === 0
241
- const applyReason = canAutoApply
242
- ? `风险等级 ${level},无 P0 触发,无 protected files 修改`
243
- : applyBlockers.join('; ')
244
-
245
- return { level, triggers, assessedFrom, applyBlockers, canAutoApply, applyReason }
246
- }
247
-
248
- // ============ 向后兼容 ============
249
-
250
- /**
251
- * 旧的 detectChangeRisk 接口保持向后兼容
252
- * @returns {{ level: string, triggers: string[], requiredVerification: string[] }}
253
- */
254
- export function detectChangeRisk({ designContent = '', planContent = '', changedFiles = [] } = {}) {
255
- const triggers = []
256
- const combined = [designContent, planContent].join('\n')
257
-
258
- for (const pattern of INTEGRATION_CRITICAL_PATTERNS) {
259
- if (pattern.test(combined)) {
260
- pattern.lastIndex = 0
261
- const match = combined.match(pattern)
262
- if (match && !triggers.includes(match[0])) triggers.push(match[0])
263
- }
264
- }
265
-
266
- for (const file of changedFiles) {
267
- for (const pattern of INTEGRATION_FILE_PATTERNS) {
268
- if (pattern.test(file)) {
269
- pattern.lastIndex = 0
270
- const match = file.match(pattern)
271
- if (match && !triggers.includes(match[0])) triggers.push(match[0])
272
- }
273
- }
274
- }
275
-
276
- if (triggers.length === 0) {
277
- return { level: 'doc-only', triggers: [], requiredVerification: ['static_check'] }
278
- }
279
-
280
- const deploymentTrigger = triggers.some(t => /cli\.ts|main\.ts|server\.(js|ts)|bootstrap|entrypoint/i.test(t))
281
- const lifecycleTrigger = triggers.some(t => /session|lease|agent.?run|lifecycle|state.?transition|claim|heartbeat/i.test(t))
282
- const crossProcessTrigger = triggers.some(t => /daemon|backend|client.*api|grpc|websocket|cross.?process|ipc|message.?queue/i.test(t))
283
-
284
- let level
285
- const requiredVerification = ['unit_tests']
286
-
287
- if (deploymentTrigger) {
288
- level = 'deployment-critical'
289
- requiredVerification.push('contract_tests', 'real_daemon_backend_integration', 'runtime_log_evidence', 'real_startup_once')
290
- } else if (lifecycleTrigger || crossProcessTrigger) {
291
- level = 'integration-critical'
292
- requiredVerification.push('contract_tests', 'real_daemon_backend_integration', 'runtime_log_evidence', 'terminal_state_assertion')
293
- } else if (triggers.some(t => /api|client|contract|dto/i.test(t))) {
294
- level = 'contract-required'
295
- requiredVerification.push('contract_tests')
296
- } else {
297
- level = 'unit-sufficient'
298
- }
299
-
300
- return { level, triggers, requiredVerification }
301
- }
302
-
303
- /**
304
- * 检查 verify-result.md 是否包含集成验证证据
305
- */
306
- export function checkIntegrationEvidence(verifyContent, requiredVerification) {
307
- const errors = []
308
- const warnings = []
309
- const lower = verifyContent.toLowerCase()
310
-
311
- const needsIntegration = requiredVerification.includes('real_daemon_backend_integration')
312
- const needsLogEvidence = requiredVerification.includes('runtime_log_evidence')
313
- const needsTerminalState = requiredVerification.includes('terminal_state_assertion')
314
- const needsRealStartup = requiredVerification.includes('real_startup_once')
315
-
316
- if (needsIntegration) {
317
- const hasMockOnly = /mock.*test.*passed|unit.*test.*passed/i.test(lower)
318
- const hasIntegrationEvidence =
319
- /集成测试|integration.*test|e2e.*test|端到端/i.test(lower) ||
320
- /daemon.*backend|backend.*daemon|真实.*集成|real.*integration/i.test(lower) ||
321
- /runtime.*evidence|运行时.*证据/i.test(lower)
322
-
323
- if (!hasIntegrationEvidence && hasMockOnly) {
324
- errors.push('integration-critical 变更只提供了 mock 单测证据,缺少真实 daemon↔backend 集成验证')
325
- } else if (!hasIntegrationEvidence) {
326
- errors.push('integration-critical 变更缺少集成验证证据 — 需要真实 daemon↔backend 测试结果或运行时日志')
327
- }
328
- }
329
-
330
- if (needsLogEvidence) {
331
- const hasRuntimeSection = /runtime.*evidence|运行时.*证据|daemon.*log|日志.*片段/i.test(lower)
332
- if (!hasRuntimeSection) {
333
- errors.push('integration-critical 变更的 verify-result.md 缺少 Runtime Evidence section')
334
- }
335
- }
336
-
337
- if (needsTerminalState) {
338
- const hasTerminalState = /terminal.*state|终态|running.*completed|completed.*failed|session.*end|lease.*end/i.test(lower)
339
- if (!hasTerminalState) {
340
- warnings.push('建议检查终态断言:AgentRun running→completed/failed、session end 状态同步')
341
- }
342
- }
343
-
344
- if (needsRealStartup) {
345
- const hasStartupEvidence = /启动.*一次|real.*startup|实际.*启动|docker.*up|npm.*start|node.*server/i.test(lower)
346
- if (!hasStartupEvidence) {
347
- errors.push('deployment-critical 变更需要真实启动验证证据')
348
- }
349
- }
350
-
351
- return { ok: errors.length === 0, errors, warnings }
352
- }
@@ -1,73 +0,0 @@
1
- /**
2
- * classify-change.js — 变更规模分类器
3
- *
4
- * 将用户需求描述分类为 quick / auto / full,
5
- * 供 auto 模式决定内部流程深度。
6
- */
7
-
8
- /**
9
- * 用户显式指定的关键词 → 强制模式
10
- */
11
- const FORCE_FULL_PATTERNS = [
12
- /数据库|database|schema/i,
13
- /迁移|migration|migrate/i,
14
- /鉴权|权限|auth|permission|rbac/i,
15
- /支付|payment|billing/i,
16
- /重构|refactor.*architectur/i,
17
- /微服务|microserv/i,
18
- ]
19
-
20
- const FORCE_QUICK_PATTERNS = [
21
- /fix typo/i,
22
- /更新文案|改文案|文案修改/i,
23
- /样式调整|style.*tweak/i,
24
- /修复.*\s*\bbug\b.*\bfix\b/i,
25
- ]
26
-
27
- /**
28
- * 分类变更规模
29
- * @param {object} opts
30
- * @param {string} opts.description - 用户需求描述
31
- * @param {string} [opts.explicitMode] - 用户显式指定的模式(auto/quick/full)
32
- * @param {object} [opts.localConfig] - local.yaml 中的 auto_mode 配置
33
- * @returns {{ mode: 'quick'|'auto'|'full', reason: string }}
34
- */
35
- export function classifyChange({ description = '', explicitMode, localConfig } = {}) {
36
- // 1. 用户显式指定优先级最高
37
- if (explicitMode && ['quick', 'auto', 'full'].includes(explicitMode)) {
38
- return { mode: explicitMode, reason: '用户显式指定' }
39
- }
40
-
41
- // 2. local.yaml force patterns
42
- if (localConfig) {
43
- const forceFullPatterns = localConfig.force_full_patterns || []
44
- const forceQuickPatterns = localConfig.force_quick_patterns || []
45
-
46
- for (const pattern of forceFullPatterns) {
47
- if (new RegExp(pattern, 'i').test(description)) {
48
- return { mode: 'full', reason: `local.yaml force_full_pattern 匹配: ${pattern}` }
49
- }
50
- }
51
- for (const pattern of forceQuickPatterns) {
52
- if (new RegExp(pattern, 'i').test(description)) {
53
- return { mode: 'quick', reason: `local.yaml force_quick_pattern 匹配: ${pattern}` }
54
- }
55
- }
56
- }
57
-
58
- // 3. 默认关键词匹配
59
- for (const pattern of FORCE_QUICK_PATTERNS) {
60
- if (pattern.test(description)) {
61
- return { mode: 'quick', reason: `命中 quick 关键词: ${pattern.source}` }
62
- }
63
- }
64
-
65
- for (const pattern of FORCE_FULL_PATTERNS) {
66
- if (pattern.test(description)) {
67
- return { mode: 'full', reason: `命中 full 关键词: ${pattern.source}` }
68
- }
69
- }
70
-
71
- // 4. 默认 auto
72
- return { mode: 'auto', reason: '默认模式' }
73
- }