sillyspec 3.20.1 → 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.
- package/.claude/skills/sillyspec-archive/SKILL.md +1 -5
- package/.claude/skills/sillyspec-auto/SKILL.md +2 -8
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -28
- package/.claude/skills/sillyspec-commit/SKILL.md +3 -4
- package/.claude/skills/sillyspec-continue/SKILL.md +4 -5
- package/.claude/skills/sillyspec-doctor/SKILL.md +3 -12
- package/.claude/skills/sillyspec-execute/SKILL.md +2 -15
- package/.claude/skills/sillyspec-explore/SKILL.md +1 -14
- package/.claude/skills/sillyspec-plan/SKILL.md +36 -5
- package/.claude/skills/sillyspec-propose/SKILL.md +0 -4
- package/.claude/skills/sillyspec-quick/SKILL.md +1 -5
- package/.claude/skills/sillyspec-resume/SKILL.md +66 -23
- package/.claude/skills/sillyspec-scan/SKILL.md +1 -5
- package/.claude/skills/sillyspec-state/SKILL.md +8 -8
- package/.claude/skills/sillyspec-status/SKILL.md +1 -5
- package/.claude/skills/sillyspec-verify/SKILL.md +1 -5
- package/.claude/skills/sillyspec-workspace/SKILL.md +3 -11
- package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +97 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +51 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +29 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +34 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +13 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +43 -0
- package/.sillyspec/changes/auto-mode/design.md +50 -0
- package/.sillyspec/changes/auto-mode/proposal.md +19 -0
- package/.sillyspec/changes/auto-mode/requirements.md +21 -0
- package/.sillyspec/changes/auto-mode/tasks.md +7 -0
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +206 -0
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +199 -0
- package/.sillyspec/changes/dashboard/design.md +219 -0
- package/.sillyspec/changes/dashboard/design.md.braindraft +206 -0
- package/.sillyspec/changes/run-command-design/design.md +1230 -0
- package/.sillyspec/changes/unified-docs-design/design.md +199 -0
- package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
- package/.sillyspec/knowledge/INDEX.md +8 -0
- package/.sillyspec/knowledge/uncategorized.md +3 -0
- package/.sillyspec/plans/2026-04-05-dashboard.md +737 -0
- package/.sillyspec/projects/sillyspec.yaml +3 -0
- package/README.md +11 -13
- package/SKILL.md +40 -44
- package/dist/steps/brainstorm/01-load-context.md +30 -0
- package/dist/steps/brainstorm/02-reuse-check.md +6 -0
- package/dist/steps/brainstorm/03-prototype-analysis.md +11 -0
- package/dist/steps/brainstorm/04-module-split.md +23 -0
- package/dist/steps/brainstorm/05-dialog-explore.md +8 -0
- package/dist/steps/brainstorm/06-propose-approaches.md +3 -0
- package/dist/steps/brainstorm/07-present-design.md +3 -0
- package/dist/steps/brainstorm/08-write-design.md +21 -0
- package/dist/steps/brainstorm/09-self-review.md +15 -0
- package/dist/steps/brainstorm/10-user-confirm.md +3 -0
- package/dist/steps/brainstorm/11-output-spec.md +7 -0
- package/dist/steps/brainstorm/manifest.yaml +26 -0
- package/dist/steps/execute/01-load-context.md +41 -0
- package/dist/steps/execute/02-scan-conventions.md +47 -0
- package/dist/steps/execute/03-skill-mcp.md +19 -0
- package/dist/steps/execute/04-assign-task.md +22 -0
- package/dist/steps/execute/04b-prompt-template.md +54 -0
- package/dist/steps/execute/05-write-test.md +7 -0
- package/dist/steps/execute/06-write-code.md +8 -0
- package/dist/steps/execute/07-run-test.md +26 -0
- package/dist/steps/execute/08-fix-issues.md +28 -0
- package/dist/steps/execute/09-next-task.md +33 -0
- package/dist/steps/execute/manifest.yaml +28 -0
- package/dist/steps/plan/01-load-context.md +22 -0
- package/dist/steps/plan/02-anchor-confirm.md +1 -0
- package/dist/steps/plan/03-expand-tasks.md +33 -0
- package/dist/steps/plan/04-mark-order.md +15 -0
- package/dist/steps/plan/05-e2e-planning.md +17 -0
- package/dist/steps/plan/06-self-check.md +16 -0
- package/dist/steps/plan/07-save.md +1 -0
- package/dist/steps/plan/manifest.yaml +18 -0
- package/dist/steps/scan/01-env-detect.md +51 -0
- package/dist/steps/scan/02-tech-stack.md +16 -0
- package/dist/steps/scan/03-conventions.md +16 -0
- package/dist/steps/scan/04-structure.md +19 -0
- package/dist/steps/scan/05-quality.md +18 -0
- package/dist/steps/scan/06-complete.md +49 -0
- package/dist/steps/scan/manifest.yaml +16 -0
- package/dist/steps/verify/01-load-specs.md +28 -0
- package/dist/steps/verify/02-check-tasks.md +1 -0
- package/dist/steps/verify/03-check-design.md +6 -0
- package/dist/steps/verify/04-run-tests.md +7 -0
- package/dist/steps/verify/05-e2e-tests.md +27 -0
- package/dist/steps/verify/05b-e2e-fix.md +33 -0
- package/dist/steps/verify/06-code-quality.md +25 -0
- package/dist/steps/verify/07-lint-check.md +27 -0
- package/dist/steps/verify/08-output-report.md +14 -0
- package/dist/steps/verify/manifest.yaml +22 -0
- package/package.json +2 -8
- package/packages/dashboard/dist/assets/{index-Bq_Z2hne.js → index-D1EVTLmc.js} +1264 -1264
- package/packages/dashboard/dist/assets/index-DGe8CqeP.css +1 -0
- package/packages/dashboard/dist/index.html +2 -2
- package/packages/dashboard/package-lock.json +6 -6
- package/packages/dashboard/server/executor.js +1 -1
- package/packages/dashboard/server/index.js +18 -98
- package/packages/dashboard/server/parser.js +71 -139
- package/packages/dashboard/server/watcher.js +6 -14
- package/packages/dashboard/src/App.vue +185 -418
- package/packages/dashboard/src/components/ActionBar.vue +1 -10
- package/packages/dashboard/src/components/CommandPalette.vue +1 -5
- package/packages/dashboard/src/components/DocPreview.vue +8 -105
- package/packages/dashboard/src/components/DocTree.vue +19 -75
- package/packages/dashboard/src/components/PipelineStage.vue +2 -22
- package/packages/dashboard/src/components/PipelineView.vue +5 -32
- package/packages/dashboard/src/components/ProjectOverview.vue +139 -113
- package/packages/dashboard/src/components/StageBadge.vue +3 -17
- package/packages/dashboard/src/components/StepCard.vue +2 -7
- package/packages/dashboard/src/composables/useDashboard.js +0 -28
- package/src/derive.js +147 -0
- package/src/index.js +40 -685
- package/src/init.js +63 -119
- package/src/migrate.js +7 -7
- package/src/progress.js +248 -1474
- package/src/run.js +302 -2920
- package/src/setup.js +64 -2
- package/src/stages/archive.js +17 -123
- package/src/stages/brainstorm.js +48 -454
- package/src/stages/doctor.js +46 -99
- package/src/stages/execute.js +59 -420
- package/src/stages/index.js +18 -12
- package/src/stages/plan.js +189 -492
- package/src/stages/propose.js +11 -70
- package/src/stages/quick.js +13 -52
- package/src/stages/scan.js +68 -468
- package/src/stages/status.js +1 -1
- package/src/stages/verify.js +16 -203
- package/src/step.js +543 -0
- package/.claude/skills/sillyspec-knowledge/SKILL.md +0 -270
- package/.husky/pre-push +0 -13
- package/.npmrc-tmp +0 -1
- package/CLAUDE.md +0 -18
- package/docs/brainstorm-plan-contract.md +0 -64
- package/docs/plan-execute-contract.md +0 -123
- package/docs/platform-scan-protocol.md +0 -298
- package/docs/revision-mode.md +0 -115
- package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +0 -99
- package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +0 -218
- package/docs/sillyspec/file-lifecycle/stage-artifacts.md +0 -167
- package/docs/sillyspec/file-lifecycle/storage-and-state.md +0 -148
- package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +0 -193
- package/docs/sillyspec/file-lifecycle.md +0 -125
- package/docs/workflow-contract-regression.md +0 -106
- package/docs/worktree-isolation.md +0 -252
- package/packages/dashboard/dist/assets/index-O2W5RV4z.css +0 -1
- package/packages/dashboard/dist/prototype-dashboard.html +0 -836
- package/packages/dashboard/dist/prototype-overview.html +0 -256
- package/packages/dashboard/public/prototype-dashboard.html +0 -836
- package/packages/dashboard/public/prototype-overview.html +0 -256
- package/packages/dashboard/src/components/HResizeHandle.vue +0 -48
- package/packages/dashboard/src/components/ProjectCard.vue +0 -187
- package/packages/dashboard/src/components/VResizeHandle.vue +0 -61
- package/packages/dashboard/src/composables/useLayout.js +0 -131
- package/src/brainstorm-postcheck.js +0 -158
- package/src/change-list.js +0 -52
- package/src/change-risk-profile.js +0 -352
- package/src/classify-change.js +0 -73
- package/src/constants.js +0 -70
- package/src/contract-matrix.js +0 -278
- package/src/db.js +0 -201
- package/src/endpoint-extractor.js +0 -315
- package/src/hooks/claude-pre-tool-use.cjs +0 -125
- package/src/hooks/worktree-guard.js +0 -761
- package/src/knowledge-match.js +0 -130
- package/src/modules.js +0 -482
- package/src/scan-postcheck.js +0 -380
- package/src/stage-contract.js +0 -700
- package/src/stages/brainstorm-auto.js +0 -229
- package/src/stages/explore.js +0 -34
- package/src/stages/knowledge.js +0 -498
- package/src/stages/plan-postcheck.js +0 -513
- package/src/sync.js +0 -497
- package/src/task-review.js +0 -346
- package/src/workflow.js +0 -785
- package/src/worktree-apply.js +0 -549
- package/src/worktree.js +0 -932
- package/templates/workflows/archive-impact.yaml +0 -79
- package/templates/workflows/scan-docs.yaml +0 -132
- package/test/brainstorm-plan-contract.test.mjs +0 -273
- package/test/check-syntax.mjs +0 -26
- package/test/cli-top-level-aliases.test.mjs +0 -174
- package/test/contract-artifacts.test.mjs +0 -323
- package/test/decision-supersede.test.mjs +0 -277
- package/test/knowledge-match.test.mjs +0 -231
- package/test/plan-execute-contract.test.mjs +0 -330
- package/test/plan-optimization.test.mjs +0 -572
- package/test/platform-artifacts.test.mjs +0 -190
- package/test/platform-failure-samples.test.mjs +0 -199
- package/test/platform-recovery-chain.test.mjs +0 -179
- package/test/platform-recovery.test.mjs +0 -167
- package/test/platform-scan-p0.test.mjs +0 -175
- package/test/revision-v1.test.mjs +0 -1145
- package/test/run-sanitize-project-name.test.mjs +0 -51
- package/test/run-scan-postcheck-fail.test.mjs +0 -64
- package/test/run-scan-project-parse.test.mjs +0 -77
- package/test/run-tests.mjs +0 -48
- package/test/scan-docs-yaml-placeholders.test.mjs +0 -84
- package/test/scan-knowledge.test.mjs +0 -175
- package/test/scan-paths.test.mjs +0 -68
- package/test/scan-postcheck-project-priority.test.mjs +0 -85
- package/test/scan-postcheck.test.mjs +0 -183
- package/test/scan-workflow-anyfailed-block.test.mjs +0 -52
- package/test/spec-dir.test.mjs +0 -206
- package/test/stage-contract-failed-post-check.test.mjs +0 -102
- package/test/stage-contract.test.mjs +0 -299
- package/test/stage-definitions.test.mjs +0 -39
- package/test/wait-gates.test.mjs +0 -501
- package/test/workflow-spec-base.test.mjs +0 -142
- package/test/worktree-guard.test.mjs +0 -136
- 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
|
-
}
|
package/src/change-list.js
DELETED
|
@@ -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
|
-
}
|
package/src/classify-change.js
DELETED
|
@@ -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
|
-
}
|