sillyspec 3.20.2 → 3.20.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/sillyspec-archive/SKILL.md +21 -21
- package/.claude/skills/sillyspec-auto/SKILL.md +83 -83
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -44
- package/.claude/skills/sillyspec-commit/SKILL.md +106 -106
- package/.claude/skills/sillyspec-continue/SKILL.md +45 -45
- package/.claude/skills/sillyspec-doctor/SKILL.md +31 -31
- package/.claude/skills/sillyspec-execute/SKILL.md +30 -30
- package/.claude/skills/sillyspec-explore/SKILL.md +109 -109
- package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
- package/.claude/skills/sillyspec-plan/SKILL.md +21 -21
- package/.claude/skills/sillyspec-propose/SKILL.md +21 -21
- package/.claude/skills/sillyspec-quick/SKILL.md +21 -21
- package/.claude/skills/sillyspec-resume/SKILL.md +68 -68
- package/.claude/skills/sillyspec-scan/SKILL.md +21 -21
- package/.claude/skills/sillyspec-state/SKILL.md +54 -54
- package/.claude/skills/sillyspec-status/SKILL.md +21 -21
- package/.claude/skills/sillyspec-verify/SKILL.md +21 -21
- package/.claude/skills/sillyspec-workspace/SKILL.md +157 -157
- package/.husky/pre-push +13 -13
- package/CLAUDE.md +18 -18
- package/README.md +198 -188
- package/SKILL.md +90 -91
- package/bin/sillyspec.js +2 -2
- package/docs/brainstorm-plan-contract.md +64 -64
- package/docs/plan-execute-contract.md +123 -123
- package/docs/platform-scan-protocol.md +298 -298
- package/docs/revision-mode.md +115 -115
- package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -99
- package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -218
- package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -167
- package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -148
- package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -193
- package/docs/sillyspec/file-lifecycle.md +125 -125
- package/docs/workflow-contract-regression.md +106 -106
- package/docs/worktree-isolation.md +252 -252
- package/package.json +40 -40
- package/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -7446
- package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -1
- package/packages/dashboard/dist/index.html +16 -16
- package/packages/dashboard/index.html +15 -15
- package/packages/dashboard/package-lock.json +2384 -2384
- package/packages/dashboard/package.json +25 -25
- package/packages/dashboard/server/executor.js +86 -86
- package/packages/dashboard/server/index.js +588 -588
- package/packages/dashboard/server/parser.js +526 -526
- package/packages/dashboard/server/watcher.js +344 -344
- package/packages/dashboard/src/App.vue +558 -558
- package/packages/dashboard/src/components/ActionBar.vue +93 -93
- package/packages/dashboard/src/components/CommandPalette.vue +96 -96
- package/packages/dashboard/src/components/DetailPanel.vue +137 -137
- package/packages/dashboard/src/components/LogStream.vue +65 -65
- package/packages/dashboard/src/components/PipelineStage.vue +95 -95
- package/packages/dashboard/src/components/PipelineView.vue +156 -156
- package/packages/dashboard/src/components/ProjectList.vue +210 -210
- package/packages/dashboard/src/components/StageBadge.vue +67 -67
- package/packages/dashboard/src/components/StepCard.vue +94 -94
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
- package/packages/dashboard/src/composables/useDashboard.js +170 -170
- package/packages/dashboard/src/composables/useKeyboard.js +119 -119
- package/packages/dashboard/src/composables/useWebSocket.js +129 -129
- package/packages/dashboard/src/main.js +8 -8
- package/packages/dashboard/src/style.css +132 -132
- package/packages/dashboard/vite.config.js +18 -18
- package/src/brainstorm-postcheck.js +158 -158
- package/src/change-list.js +52 -52
- package/src/change-risk-profile.js +352 -352
- package/src/classify-change.js +73 -73
- package/src/constants.js +70 -70
- package/src/contract-matrix.js +278 -278
- package/src/db.js +201 -201
- package/src/endpoint-extractor.js +315 -315
- package/src/hooks/claude-pre-tool-use.cjs +125 -125
- package/src/hooks/worktree-guard.js +653 -653
- package/src/index.js +922 -900
- package/src/init.js +431 -431
- package/src/knowledge-match.js +130 -130
- package/src/migrate.js +117 -117
- package/src/modules.js +482 -482
- package/src/progress.js +1734 -1734
- package/src/run.js +3465 -3358
- package/src/scan-postcheck.js +387 -383
- package/src/setup.js +398 -398
- package/src/stage-contract.js +700 -700
- package/src/stages/archive.js +160 -160
- package/src/stages/brainstorm-auto.js +229 -229
- package/src/stages/brainstorm.js +645 -645
- package/src/stages/doctor.js +365 -365
- package/src/stages/execute.js +625 -625
- package/src/stages/explore.js +34 -34
- package/src/stages/index.js +29 -29
- package/src/stages/knowledge.js +498 -498
- package/src/stages/plan-postcheck.js +511 -513
- package/src/stages/plan.js +582 -582
- package/src/stages/propose.js +174 -174
- package/src/stages/quick.js +82 -82
- package/src/stages/scan.js +558 -558
- package/src/stages/status.js +65 -65
- package/src/stages/verify.js +322 -322
- package/src/sync.js +497 -497
- package/src/task-review.js +346 -346
- package/src/workflow.js +785 -785
- package/src/worktree-apply.js +549 -549
- package/src/worktree-deps.js +185 -0
- package/src/worktree.js +982 -932
- package/templates/workflows/archive-impact.yaml +79 -79
- package/templates/workflows/scan-docs.yaml +132 -132
- package/test/brainstorm-plan-contract.test.mjs +273 -273
- package/test/check-syntax.mjs +26 -26
- package/test/contract-artifacts.test.mjs +323 -323
- package/test/decision-supersede.test.mjs +277 -277
- package/test/knowledge-match.test.mjs +231 -231
- package/test/plan-execute-contract.test.mjs +330 -330
- package/test/plan-optimization.test.mjs +572 -572
- package/test/platform-artifacts.test.mjs +166 -166
- package/test/platform-failure-samples.test.mjs +199 -199
- package/test/platform-recovery-chain.test.mjs +167 -167
- package/test/platform-recovery.test.mjs +136 -136
- package/test/platform-scan-p0.test.mjs +168 -168
- package/test/revision-v1.test.mjs +1145 -1145
- package/test/run-scan-project-parse.test.mjs +200 -200
- package/test/run-tests.mjs +48 -48
- package/test/scan-knowledge.test.mjs +175 -175
- package/test/scan-paths.test.mjs +68 -68
- package/test/scan-postcheck.test.mjs +197 -197
- package/test/spec-dir.test.mjs +206 -206
- package/test/stage-contract.test.mjs +299 -299
- package/test/stage-definitions.test.mjs +39 -39
- package/test/wait-gates.test.mjs +496 -496
- package/test/worktree-deps-provision.test.mjs +148 -0
- package/test/worktree-guard.test.mjs +71 -71
- package/test/worktree-native-overlay.test.mjs +188 -188
|
@@ -1,299 +1,299 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* StageContract 状态转换 + validator 测试
|
|
3
|
-
*/
|
|
4
|
-
import { checkTransition, runValidators, getContract } from '../src/stage-contract.js'
|
|
5
|
-
|
|
6
|
-
let failed = 0
|
|
7
|
-
|
|
8
|
-
// === 状态转换测试 ===
|
|
9
|
-
const transitionTests = [
|
|
10
|
-
// [from, to, expectedAllowed]
|
|
11
|
-
// 主流程正常顺序
|
|
12
|
-
['', 'brainstorm', true],
|
|
13
|
-
['brainstorm', 'plan', true],
|
|
14
|
-
['plan', 'execute', true],
|
|
15
|
-
['execute', 'verify', true],
|
|
16
|
-
['verify', 'archive', true],
|
|
17
|
-
|
|
18
|
-
// 跳步应被拦截
|
|
19
|
-
['', 'plan', false],
|
|
20
|
-
['', 'execute', false],
|
|
21
|
-
['brainstorm', 'execute', false],
|
|
22
|
-
['plan', 'verify', false],
|
|
23
|
-
['execute', 'archive', false],
|
|
24
|
-
|
|
25
|
-
// 回退应被拦截
|
|
26
|
-
['plan', 'brainstorm', false],
|
|
27
|
-
['execute', 'plan', false],
|
|
28
|
-
['verify', 'execute', false],
|
|
29
|
-
|
|
30
|
-
// 辅助阶段随时可执行
|
|
31
|
-
['', 'scan', true],
|
|
32
|
-
['', 'quick', true],
|
|
33
|
-
['', 'explore', true],
|
|
34
|
-
['', 'doctor', true],
|
|
35
|
-
['', 'archive', true],
|
|
36
|
-
['brainstorm', 'scan', true],
|
|
37
|
-
['plan', 'quick', true],
|
|
38
|
-
['execute', 'doctor', true],
|
|
39
|
-
|
|
40
|
-
// 从辅助阶段进入主流程允许
|
|
41
|
-
['scan', 'plan', true],
|
|
42
|
-
['scan', 'brainstorm', true],
|
|
43
|
-
['quick', 'plan', true],
|
|
44
|
-
['doctor', 'brainstorm', true],
|
|
45
|
-
|
|
46
|
-
// archive 特殊:verify 后允许,其他主流程不允许直接跳
|
|
47
|
-
['verify', 'archive', true],
|
|
48
|
-
['execute', 'archive', false],
|
|
49
|
-
['plan', 'archive', false],
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
console.log('=== 状态转换测试 ===')
|
|
53
|
-
for (const [from, to, expected] of transitionTests) {
|
|
54
|
-
const r = checkTransition(from, to)
|
|
55
|
-
const ok = r.allowed === expected
|
|
56
|
-
if (!ok) failed++
|
|
57
|
-
console.log(ok ? '✅' : '❌', `${from || '(起始)'} → ${to}: allowed=${r.allowed} (exp ${expected})${ok ? '' : ' reason: ' + r.reason}`)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// === Validator 测试 ===
|
|
61
|
-
console.log('\n=== Validator 测试 ===')
|
|
62
|
-
|
|
63
|
-
// plan validator:plan.md 不存在应报错
|
|
64
|
-
const planResult = runValidators('plan', '.', 'nonexistent-change')
|
|
65
|
-
if (planResult.ok === false && planResult.errors.length > 0) {
|
|
66
|
-
console.log('✅ plan validator 检测到缺失 plan.md')
|
|
67
|
-
} else {
|
|
68
|
-
console.log('❌ plan validator 未检测到缺失 plan.md')
|
|
69
|
-
failed++
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// verify validator:变更目录不存在应报错
|
|
73
|
-
const verifyResult = runValidators('verify', '.', 'nonexistent-change')
|
|
74
|
-
if (verifyResult.ok === false && verifyResult.errors.length > 0) {
|
|
75
|
-
console.log('✅ verify validator 检测到缺失变更目录')
|
|
76
|
-
} else {
|
|
77
|
-
console.log('❌ verify validator 未检测到缺失变更目录')
|
|
78
|
-
failed++
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// scan validator:文档目录不存在应报错
|
|
82
|
-
const scanResult = runValidators('scan', join(tmpdir(), 'nonexistent-project'), 'test', { projectName: 'test' })
|
|
83
|
-
if (scanResult.ok === false && scanResult.errors.length > 0) {
|
|
84
|
-
console.log('✅ scan validator 检测到缺失 scan 文档')
|
|
85
|
-
} else {
|
|
86
|
-
console.log('❌ scan validator 未检测到缺失 scan 文档')
|
|
87
|
-
failed++
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// brainstorm 有 validator,但变更目录不存在时应该报错(因为产物不存在)
|
|
91
|
-
const brainstormResult = runValidators('brainstorm', '.', 'test')
|
|
92
|
-
if (brainstormResult.ok === false && brainstormResult.errors.length > 0) {
|
|
93
|
-
console.log('✅ brainstorm validator 检测到缺失产物文件')
|
|
94
|
-
} else {
|
|
95
|
-
console.log('❌ brainstorm validator 未检测到缺失产物')
|
|
96
|
-
failed++
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// === scan validator 平台模式 specRoot 测试 ===
|
|
100
|
-
console.log('\n=== scan validator specRoot 测试 ===')
|
|
101
|
-
|
|
102
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs'
|
|
103
|
-
import { join } from 'path'
|
|
104
|
-
import { tmpdir } from 'os'
|
|
105
|
-
|
|
106
|
-
// 创建临时 specRoot 结构
|
|
107
|
-
const specRoot = mkdtempSync(join(tmpdir(), 'sillyspec-test-'))
|
|
108
|
-
const sourceRoot = mkdtempSync(join(tmpdir(), 'sillyspec-source-'))
|
|
109
|
-
const projectName = 'myaaa'
|
|
110
|
-
|
|
111
|
-
// specRoot 在实际代码中等价于 .sillyspec 目录本身(run.js: join(specRoot, 'docs', ...))
|
|
112
|
-
// 所以 scan 文档路径为 specRoot/docs/<project>/scan/,不需要额外的 .sillyspec/ 前缀
|
|
113
|
-
const specDocsDir = join(specRoot, 'docs', projectName, 'scan')
|
|
114
|
-
mkdirSync(specDocsDir, { recursive: true })
|
|
115
|
-
for (const doc of ['ARCHITECTURE.md', 'CONVENTIONS.md', 'STRUCTURE.md', 'INTEGRATIONS.md', 'TESTING.md', 'CONCERNS.md', 'PROJECT.md']) {
|
|
116
|
-
writeFileSync(join(specDocsDir, doc), '# ' + doc)
|
|
117
|
-
}
|
|
118
|
-
mkdirSync(join(specRoot, 'docs', projectName, 'modules'), { recursive: true })
|
|
119
|
-
writeFileSync(join(specRoot, 'docs', projectName, 'modules', 'app.md'), '# app')
|
|
120
|
-
|
|
121
|
-
// 测试1:使用 specRoot 校验成功
|
|
122
|
-
const specResult = runValidators('scan', sourceRoot, 'test', { projectName, specRoot })
|
|
123
|
-
if (specResult.ok === true) {
|
|
124
|
-
console.log('✅ scan validator 使用 specRoot 校验通过')
|
|
125
|
-
} else {
|
|
126
|
-
console.log('❌ scan validator specRoot 校验失败:', specResult.errors)
|
|
127
|
-
failed++
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// 测试2:使用 sourceRoot 校验(不传 specRoot)应失败
|
|
131
|
-
const localResult = runValidators('scan', sourceRoot, 'test', { projectName })
|
|
132
|
-
if (localResult.ok === false && localResult.errors.length > 0) {
|
|
133
|
-
console.log('✅ scan validator 使用 sourceRoot 校验正确失败(文档不在 source_root 下)')
|
|
134
|
-
} else {
|
|
135
|
-
console.log('❌ scan validator sourceRoot 校验未正确失败')
|
|
136
|
-
failed++
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// 测试3:校验路径指向 specRoot 而非 sourceRoot
|
|
140
|
-
const errors1 = localResult.errors.join(' ')
|
|
141
|
-
const errors2 = specResult.errors.join(' ')
|
|
142
|
-
if (errors1.includes(sourceRoot) || errors1.includes(join(sourceRoot, '.sillyspec'))) {
|
|
143
|
-
console.log('✅ 未传 specRoot 时校验路径指向 source_root')
|
|
144
|
-
} else {
|
|
145
|
-
console.log('✅ 未传 specRoot 时校验失败(文档确实不在 source_root 下)')
|
|
146
|
-
}
|
|
147
|
-
if (!errors2.includes(specRoot)) {
|
|
148
|
-
console.log('✅ 传 specRoot 时校验路径指向 specRoot(无错误=不包含路径)')
|
|
149
|
-
} else {
|
|
150
|
-
console.log('✅ 传 specRoot 时校验路径正确')
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// 清理临时目录
|
|
154
|
-
rmSync(specRoot, { recursive: true })
|
|
155
|
-
rmSync(sourceRoot, { recursive: true })
|
|
156
|
-
|
|
157
|
-
// === decisions.md traceability validator 测试 ===
|
|
158
|
-
console.log('\n=== decisions traceability validator 测试 ===')
|
|
159
|
-
|
|
160
|
-
const traceRoot = mkdtempSync(join(tmpdir(), 'sillyspec-trace-'))
|
|
161
|
-
const traceDir = join(traceRoot, '.sillyspec', 'changes', 'trace')
|
|
162
|
-
mkdirSync(traceDir, { recursive: true })
|
|
163
|
-
writeFileSync(join(traceDir, 'proposal.md'), '# Proposal\n\n## 不在范围内\n- none\n')
|
|
164
|
-
writeFileSync(join(traceDir, 'design.md'), '# Design\n\n## 文件变更清单\n\n## 风险登记\n\n## 自审\n\nD-001@v1\n')
|
|
165
|
-
writeFileSync(join(traceDir, 'decisions.md'), '# Decisions\n\n## D-001@v1: Choose canonical account term\n- priority: P1\n- status: accepted\n')
|
|
166
|
-
writeFileSync(join(traceDir, 'requirements.md'), '# Requirements\n\n### FR-01: Account naming\nGiven x\nWhen y\nThen z\n')
|
|
167
|
-
writeFileSync(join(traceDir, 'tasks.md'), '- [ ] task-01: implement naming (D-001@v1)\n')
|
|
168
|
-
|
|
169
|
-
const brainstormTrace = runValidators('brainstorm', traceRoot, 'trace')
|
|
170
|
-
if (brainstormTrace.ok === true && brainstormTrace.warnings.some(w => w.includes('requirements.md 未引用') && w.includes('D-001@V1'))) {
|
|
171
|
-
console.log('✅ brainstorm validator 检测到 requirements.md 缺少 D-001@v1 引用')
|
|
172
|
-
} else {
|
|
173
|
-
console.log('❌ brainstorm validator 未检测到 requirements.md 缺少 D-001@v1 引用', brainstormTrace)
|
|
174
|
-
failed++
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
writeFileSync(join(traceDir, 'requirements.md'), '# Requirements\n\n### FR-01: Account naming\n覆盖决策:D-001@v1\nGiven x\nWhen y\nThen z\n')
|
|
178
|
-
writeFileSync(join(traceDir, 'plan.md'), '# Plan\n\n- [ ] task-01: implement naming\n')
|
|
179
|
-
|
|
180
|
-
const planTrace = runValidators('plan', traceRoot, 'trace')
|
|
181
|
-
if (planTrace.ok === true
|
|
182
|
-
&& planTrace.warnings.some(w => w.includes('plan.md 未引用') && w.includes('FR-01'))
|
|
183
|
-
&& planTrace.warnings.some(w => w.includes('plan.md 未引用') && w.includes('D-001@V1'))) {
|
|
184
|
-
console.log('✅ plan validator 检测到 plan.md 缺少 FR-01/D-001@v1 引用')
|
|
185
|
-
} else {
|
|
186
|
-
console.log('❌ plan validator 未检测到 plan.md 缺少追踪 ID', planTrace)
|
|
187
|
-
failed++
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
writeFileSync(join(traceDir, 'plan.md'), '# Plan\n\n- [ ] task-01: implement naming(覆盖:FR-01, D-001@v1)\n')
|
|
191
|
-
writeFileSync(join(traceDir, 'verify-result.md'), '# Verify\n\nPASS\n')
|
|
192
|
-
|
|
193
|
-
const verifyTrace = runValidators('verify', traceRoot, 'trace')
|
|
194
|
-
if (verifyTrace.ok === true && verifyTrace.warnings.some(w => w.includes('verify-result.md 未引用') && w.includes('D-001@V1'))) {
|
|
195
|
-
console.log('✅ verify validator 检测到 verify-result.md 缺少 D-001@v1 引用')
|
|
196
|
-
} else {
|
|
197
|
-
console.log('❌ verify validator 未检测到 verify-result.md 缺少 D-001@v1 引用', verifyTrace)
|
|
198
|
-
failed++
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
writeFileSync(join(traceDir, 'verify-result.md'), '# Verify\n\n## 决策追踪矩阵\n| D-001@v1 | FR-01 | task-01 | evidence | PASS |\n')
|
|
202
|
-
const verifyTraceOk = runValidators('verify', traceRoot, 'trace')
|
|
203
|
-
if (verifyTraceOk.ok === true && !verifyTraceOk.warnings.some(w => w.includes('D-001@V1'))) {
|
|
204
|
-
console.log('✅ verify validator 在 D-001@v1 已覆盖时不再报警')
|
|
205
|
-
} else {
|
|
206
|
-
console.log('❌ verify validator 覆盖后仍报警', verifyTraceOk)
|
|
207
|
-
failed++
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
writeFileSync(join(traceDir, 'decisions.md'), '# Decisions\n\n## D-002@v1: Unresolved schema conflict\n- priority: P0\n- status: unresolved\n')
|
|
211
|
-
const blockerTrace = runValidators('plan', traceRoot, 'trace')
|
|
212
|
-
if (blockerTrace.ok === false && blockerTrace.errors.some(e => e.includes('P0/P1 未决阻塞') && e.includes('D-002@V1'))) {
|
|
213
|
-
console.log('✅ plan validator 阻止 P0 unresolved decision 进入 plan')
|
|
214
|
-
} else {
|
|
215
|
-
console.log('❌ plan validator 未阻止 P0 unresolved decision', blockerTrace)
|
|
216
|
-
failed++
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
writeFileSync(join(traceDir, 'decisions.md'), '# Decisions\n\n- id: D-003@v1\n priority: P1\n status: blocking\n type: boundary\n')
|
|
220
|
-
const yamlBlockerTrace = runValidators('plan', traceRoot, 'trace')
|
|
221
|
-
if (yamlBlockerTrace.ok === false && yamlBlockerTrace.errors.some(e => e.includes('P0/P1 未决阻塞') && e.includes('D-003@V1'))) {
|
|
222
|
-
console.log('✅ plan validator 支持 list/YAML 风格 decision record')
|
|
223
|
-
} else {
|
|
224
|
-
console.log('❌ plan validator 未识别 list/YAML 风格 decision record', yamlBlockerTrace)
|
|
225
|
-
failed++
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
writeFileSync(join(traceDir, 'decisions.md'), '# Decisions\n\n- id: D-004@v1\n status: blocking\n type: boundary\n')
|
|
229
|
-
const missingPriorityTrace = runValidators('plan', traceRoot, 'trace')
|
|
230
|
-
if (missingPriorityTrace.ok === false
|
|
231
|
-
&& missingPriorityTrace.errors.some(e => e.includes('P0/P1 未决阻塞') && e.includes('D-004@V1') && e.includes('priority=missing->P1'))) {
|
|
232
|
-
console.log('✅ plan validator 将缺 priority 的 blocking decision 按 P1 阻断')
|
|
233
|
-
} else {
|
|
234
|
-
console.log('❌ plan validator 未阻断缺 priority 的 blocking decision', missingPriorityTrace)
|
|
235
|
-
failed++
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
writeFileSync(join(traceDir, 'decisions.md'), '# Decisions\n\n- id: D-005@v1\n status: accepted\n type: term\n')
|
|
239
|
-
writeFileSync(join(traceDir, 'plan.md'), '# Plan\n\n- [ ] task-01: implement naming(覆盖:FR-01)\n')
|
|
240
|
-
const yamlAcceptedTrace = runValidators('plan', traceRoot, 'trace')
|
|
241
|
-
if (yamlAcceptedTrace.ok === true && yamlAcceptedTrace.warnings.some(w => w.includes('plan.md 未引用') && w.includes('D-005@V1'))) {
|
|
242
|
-
console.log('✅ plan validator 将 YAML accepted decision 纳入追踪')
|
|
243
|
-
} else {
|
|
244
|
-
console.log('❌ plan validator 未追踪 YAML accepted decision', yamlAcceptedTrace)
|
|
245
|
-
failed++
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
writeFileSync(join(traceDir, 'decisions.md'), '# Decisions\n')
|
|
249
|
-
writeFileSync(join(traceDir, 'requirements.md'), '# Requirements\n\n普通说明提到 https://example.test/spec/FR-404 和注释里的 FR-405,但它们不是结构化需求 ID。\n')
|
|
250
|
-
writeFileSync(join(traceDir, 'plan.md'), '# Plan\n\nNo structured requirement IDs here.\n')
|
|
251
|
-
const looseIdTrace = runValidators('plan', traceRoot, 'trace')
|
|
252
|
-
if (!looseIdTrace.warnings.some(w => w.includes('FR-404') || w.includes('FR-405'))) {
|
|
253
|
-
console.log('✅ plan validator 忽略普通正文/URL 中的 FR ID')
|
|
254
|
-
} else {
|
|
255
|
-
console.log('❌ plan validator 误提取普通正文/URL 中的 FR ID', looseIdTrace)
|
|
256
|
-
failed++
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
writeFileSync(join(traceDir, 'decisions.md'), '# Decisions\n\n普通说明提到 https://example.test/spec/D-404@v1 和注释里的 D-405@v1,但它们不是结构化决策 ID。\n')
|
|
260
|
-
const looseBrainstormTrace = runValidators('brainstorm', traceRoot, 'trace')
|
|
261
|
-
if (!looseBrainstormTrace.warnings.some(w => w.includes('D-404@V1') || w.includes('D-405@V1'))) {
|
|
262
|
-
console.log('✅ brainstorm validator 忽略普通正文/URL 中的 D ID')
|
|
263
|
-
} else {
|
|
264
|
-
console.log('❌ brainstorm validator 误提取普通正文/URL 中的 D ID', looseBrainstormTrace)
|
|
265
|
-
failed++
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
rmSync(traceRoot, { recursive: true })
|
|
269
|
-
|
|
270
|
-
// === StageContract 结构测试 ===
|
|
271
|
-
console.log('\n=== Contract 结构测试 ===')
|
|
272
|
-
|
|
273
|
-
const plan = getContract('plan')
|
|
274
|
-
if (plan.allowedFrom.includes('brainstorm') && plan.allowedTo.includes('execute') && plan.validators.length === 1) {
|
|
275
|
-
console.log('✅ plan contract 结构正确')
|
|
276
|
-
} else {
|
|
277
|
-
console.log('❌ plan contract 结构异常:', JSON.stringify(plan))
|
|
278
|
-
failed++
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const verify = getContract('verify')
|
|
282
|
-
if (verify.allowedFrom.includes('execute') && verify.allowedTo.includes('archive')) {
|
|
283
|
-
console.log('✅ verify contract 结构正确')
|
|
284
|
-
} else {
|
|
285
|
-
console.log('❌ verify contract 结构异常')
|
|
286
|
-
failed++
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const unknown = getContract('nonexistent')
|
|
290
|
-
if (unknown === null) {
|
|
291
|
-
console.log('✅ 未知阶段返回 null')
|
|
292
|
-
} else {
|
|
293
|
-
console.log('❌ 未知阶段应返回 null')
|
|
294
|
-
failed++
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// === 结果 ===
|
|
298
|
-
console.log(`\n${failed === 0 ? '✅ 全部通过' : `❌ ${failed} 项失败`}`)
|
|
299
|
-
if (failed > 0) throw new Error(`${failed} test(s) failed`)
|
|
1
|
+
/**
|
|
2
|
+
* StageContract 状态转换 + validator 测试
|
|
3
|
+
*/
|
|
4
|
+
import { checkTransition, runValidators, getContract } from '../src/stage-contract.js'
|
|
5
|
+
|
|
6
|
+
let failed = 0
|
|
7
|
+
|
|
8
|
+
// === 状态转换测试 ===
|
|
9
|
+
const transitionTests = [
|
|
10
|
+
// [from, to, expectedAllowed]
|
|
11
|
+
// 主流程正常顺序
|
|
12
|
+
['', 'brainstorm', true],
|
|
13
|
+
['brainstorm', 'plan', true],
|
|
14
|
+
['plan', 'execute', true],
|
|
15
|
+
['execute', 'verify', true],
|
|
16
|
+
['verify', 'archive', true],
|
|
17
|
+
|
|
18
|
+
// 跳步应被拦截
|
|
19
|
+
['', 'plan', false],
|
|
20
|
+
['', 'execute', false],
|
|
21
|
+
['brainstorm', 'execute', false],
|
|
22
|
+
['plan', 'verify', false],
|
|
23
|
+
['execute', 'archive', false],
|
|
24
|
+
|
|
25
|
+
// 回退应被拦截
|
|
26
|
+
['plan', 'brainstorm', false],
|
|
27
|
+
['execute', 'plan', false],
|
|
28
|
+
['verify', 'execute', false],
|
|
29
|
+
|
|
30
|
+
// 辅助阶段随时可执行
|
|
31
|
+
['', 'scan', true],
|
|
32
|
+
['', 'quick', true],
|
|
33
|
+
['', 'explore', true],
|
|
34
|
+
['', 'doctor', true],
|
|
35
|
+
['', 'archive', true],
|
|
36
|
+
['brainstorm', 'scan', true],
|
|
37
|
+
['plan', 'quick', true],
|
|
38
|
+
['execute', 'doctor', true],
|
|
39
|
+
|
|
40
|
+
// 从辅助阶段进入主流程允许
|
|
41
|
+
['scan', 'plan', true],
|
|
42
|
+
['scan', 'brainstorm', true],
|
|
43
|
+
['quick', 'plan', true],
|
|
44
|
+
['doctor', 'brainstorm', true],
|
|
45
|
+
|
|
46
|
+
// archive 特殊:verify 后允许,其他主流程不允许直接跳
|
|
47
|
+
['verify', 'archive', true],
|
|
48
|
+
['execute', 'archive', false],
|
|
49
|
+
['plan', 'archive', false],
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
console.log('=== 状态转换测试 ===')
|
|
53
|
+
for (const [from, to, expected] of transitionTests) {
|
|
54
|
+
const r = checkTransition(from, to)
|
|
55
|
+
const ok = r.allowed === expected
|
|
56
|
+
if (!ok) failed++
|
|
57
|
+
console.log(ok ? '✅' : '❌', `${from || '(起始)'} → ${to}: allowed=${r.allowed} (exp ${expected})${ok ? '' : ' reason: ' + r.reason}`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// === Validator 测试 ===
|
|
61
|
+
console.log('\n=== Validator 测试 ===')
|
|
62
|
+
|
|
63
|
+
// plan validator:plan.md 不存在应报错
|
|
64
|
+
const planResult = runValidators('plan', '.', 'nonexistent-change')
|
|
65
|
+
if (planResult.ok === false && planResult.errors.length > 0) {
|
|
66
|
+
console.log('✅ plan validator 检测到缺失 plan.md')
|
|
67
|
+
} else {
|
|
68
|
+
console.log('❌ plan validator 未检测到缺失 plan.md')
|
|
69
|
+
failed++
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// verify validator:变更目录不存在应报错
|
|
73
|
+
const verifyResult = runValidators('verify', '.', 'nonexistent-change')
|
|
74
|
+
if (verifyResult.ok === false && verifyResult.errors.length > 0) {
|
|
75
|
+
console.log('✅ verify validator 检测到缺失变更目录')
|
|
76
|
+
} else {
|
|
77
|
+
console.log('❌ verify validator 未检测到缺失变更目录')
|
|
78
|
+
failed++
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// scan validator:文档目录不存在应报错
|
|
82
|
+
const scanResult = runValidators('scan', join(tmpdir(), 'nonexistent-project'), 'test', { projectName: 'test' })
|
|
83
|
+
if (scanResult.ok === false && scanResult.errors.length > 0) {
|
|
84
|
+
console.log('✅ scan validator 检测到缺失 scan 文档')
|
|
85
|
+
} else {
|
|
86
|
+
console.log('❌ scan validator 未检测到缺失 scan 文档')
|
|
87
|
+
failed++
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// brainstorm 有 validator,但变更目录不存在时应该报错(因为产物不存在)
|
|
91
|
+
const brainstormResult = runValidators('brainstorm', '.', 'test')
|
|
92
|
+
if (brainstormResult.ok === false && brainstormResult.errors.length > 0) {
|
|
93
|
+
console.log('✅ brainstorm validator 检测到缺失产物文件')
|
|
94
|
+
} else {
|
|
95
|
+
console.log('❌ brainstorm validator 未检测到缺失产物')
|
|
96
|
+
failed++
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// === scan validator 平台模式 specRoot 测试 ===
|
|
100
|
+
console.log('\n=== scan validator specRoot 测试 ===')
|
|
101
|
+
|
|
102
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs'
|
|
103
|
+
import { join } from 'path'
|
|
104
|
+
import { tmpdir } from 'os'
|
|
105
|
+
|
|
106
|
+
// 创建临时 specRoot 结构
|
|
107
|
+
const specRoot = mkdtempSync(join(tmpdir(), 'sillyspec-test-'))
|
|
108
|
+
const sourceRoot = mkdtempSync(join(tmpdir(), 'sillyspec-source-'))
|
|
109
|
+
const projectName = 'myaaa'
|
|
110
|
+
|
|
111
|
+
// specRoot 在实际代码中等价于 .sillyspec 目录本身(run.js: join(specRoot, 'docs', ...))
|
|
112
|
+
// 所以 scan 文档路径为 specRoot/docs/<project>/scan/,不需要额外的 .sillyspec/ 前缀
|
|
113
|
+
const specDocsDir = join(specRoot, 'docs', projectName, 'scan')
|
|
114
|
+
mkdirSync(specDocsDir, { recursive: true })
|
|
115
|
+
for (const doc of ['ARCHITECTURE.md', 'CONVENTIONS.md', 'STRUCTURE.md', 'INTEGRATIONS.md', 'TESTING.md', 'CONCERNS.md', 'PROJECT.md']) {
|
|
116
|
+
writeFileSync(join(specDocsDir, doc), '# ' + doc)
|
|
117
|
+
}
|
|
118
|
+
mkdirSync(join(specRoot, 'docs', projectName, 'modules'), { recursive: true })
|
|
119
|
+
writeFileSync(join(specRoot, 'docs', projectName, 'modules', 'app.md'), '# app')
|
|
120
|
+
|
|
121
|
+
// 测试1:使用 specRoot 校验成功
|
|
122
|
+
const specResult = runValidators('scan', sourceRoot, 'test', { projectName, specRoot })
|
|
123
|
+
if (specResult.ok === true) {
|
|
124
|
+
console.log('✅ scan validator 使用 specRoot 校验通过')
|
|
125
|
+
} else {
|
|
126
|
+
console.log('❌ scan validator specRoot 校验失败:', specResult.errors)
|
|
127
|
+
failed++
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 测试2:使用 sourceRoot 校验(不传 specRoot)应失败
|
|
131
|
+
const localResult = runValidators('scan', sourceRoot, 'test', { projectName })
|
|
132
|
+
if (localResult.ok === false && localResult.errors.length > 0) {
|
|
133
|
+
console.log('✅ scan validator 使用 sourceRoot 校验正确失败(文档不在 source_root 下)')
|
|
134
|
+
} else {
|
|
135
|
+
console.log('❌ scan validator sourceRoot 校验未正确失败')
|
|
136
|
+
failed++
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 测试3:校验路径指向 specRoot 而非 sourceRoot
|
|
140
|
+
const errors1 = localResult.errors.join(' ')
|
|
141
|
+
const errors2 = specResult.errors.join(' ')
|
|
142
|
+
if (errors1.includes(sourceRoot) || errors1.includes(join(sourceRoot, '.sillyspec'))) {
|
|
143
|
+
console.log('✅ 未传 specRoot 时校验路径指向 source_root')
|
|
144
|
+
} else {
|
|
145
|
+
console.log('✅ 未传 specRoot 时校验失败(文档确实不在 source_root 下)')
|
|
146
|
+
}
|
|
147
|
+
if (!errors2.includes(specRoot)) {
|
|
148
|
+
console.log('✅ 传 specRoot 时校验路径指向 specRoot(无错误=不包含路径)')
|
|
149
|
+
} else {
|
|
150
|
+
console.log('✅ 传 specRoot 时校验路径正确')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 清理临时目录
|
|
154
|
+
rmSync(specRoot, { recursive: true })
|
|
155
|
+
rmSync(sourceRoot, { recursive: true })
|
|
156
|
+
|
|
157
|
+
// === decisions.md traceability validator 测试 ===
|
|
158
|
+
console.log('\n=== decisions traceability validator 测试 ===')
|
|
159
|
+
|
|
160
|
+
const traceRoot = mkdtempSync(join(tmpdir(), 'sillyspec-trace-'))
|
|
161
|
+
const traceDir = join(traceRoot, '.sillyspec', 'changes', 'trace')
|
|
162
|
+
mkdirSync(traceDir, { recursive: true })
|
|
163
|
+
writeFileSync(join(traceDir, 'proposal.md'), '# Proposal\n\n## 不在范围内\n- none\n')
|
|
164
|
+
writeFileSync(join(traceDir, 'design.md'), '# Design\n\n## 文件变更清单\n\n## 风险登记\n\n## 自审\n\nD-001@v1\n')
|
|
165
|
+
writeFileSync(join(traceDir, 'decisions.md'), '# Decisions\n\n## D-001@v1: Choose canonical account term\n- priority: P1\n- status: accepted\n')
|
|
166
|
+
writeFileSync(join(traceDir, 'requirements.md'), '# Requirements\n\n### FR-01: Account naming\nGiven x\nWhen y\nThen z\n')
|
|
167
|
+
writeFileSync(join(traceDir, 'tasks.md'), '- [ ] task-01: implement naming (D-001@v1)\n')
|
|
168
|
+
|
|
169
|
+
const brainstormTrace = runValidators('brainstorm', traceRoot, 'trace')
|
|
170
|
+
if (brainstormTrace.ok === true && brainstormTrace.warnings.some(w => w.includes('requirements.md 未引用') && w.includes('D-001@V1'))) {
|
|
171
|
+
console.log('✅ brainstorm validator 检测到 requirements.md 缺少 D-001@v1 引用')
|
|
172
|
+
} else {
|
|
173
|
+
console.log('❌ brainstorm validator 未检测到 requirements.md 缺少 D-001@v1 引用', brainstormTrace)
|
|
174
|
+
failed++
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
writeFileSync(join(traceDir, 'requirements.md'), '# Requirements\n\n### FR-01: Account naming\n覆盖决策:D-001@v1\nGiven x\nWhen y\nThen z\n')
|
|
178
|
+
writeFileSync(join(traceDir, 'plan.md'), '# Plan\n\n- [ ] task-01: implement naming\n')
|
|
179
|
+
|
|
180
|
+
const planTrace = runValidators('plan', traceRoot, 'trace')
|
|
181
|
+
if (planTrace.ok === true
|
|
182
|
+
&& planTrace.warnings.some(w => w.includes('plan.md 未引用') && w.includes('FR-01'))
|
|
183
|
+
&& planTrace.warnings.some(w => w.includes('plan.md 未引用') && w.includes('D-001@V1'))) {
|
|
184
|
+
console.log('✅ plan validator 检测到 plan.md 缺少 FR-01/D-001@v1 引用')
|
|
185
|
+
} else {
|
|
186
|
+
console.log('❌ plan validator 未检测到 plan.md 缺少追踪 ID', planTrace)
|
|
187
|
+
failed++
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
writeFileSync(join(traceDir, 'plan.md'), '# Plan\n\n- [ ] task-01: implement naming(覆盖:FR-01, D-001@v1)\n')
|
|
191
|
+
writeFileSync(join(traceDir, 'verify-result.md'), '# Verify\n\nPASS\n')
|
|
192
|
+
|
|
193
|
+
const verifyTrace = runValidators('verify', traceRoot, 'trace')
|
|
194
|
+
if (verifyTrace.ok === true && verifyTrace.warnings.some(w => w.includes('verify-result.md 未引用') && w.includes('D-001@V1'))) {
|
|
195
|
+
console.log('✅ verify validator 检测到 verify-result.md 缺少 D-001@v1 引用')
|
|
196
|
+
} else {
|
|
197
|
+
console.log('❌ verify validator 未检测到 verify-result.md 缺少 D-001@v1 引用', verifyTrace)
|
|
198
|
+
failed++
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
writeFileSync(join(traceDir, 'verify-result.md'), '# Verify\n\n## 决策追踪矩阵\n| D-001@v1 | FR-01 | task-01 | evidence | PASS |\n')
|
|
202
|
+
const verifyTraceOk = runValidators('verify', traceRoot, 'trace')
|
|
203
|
+
if (verifyTraceOk.ok === true && !verifyTraceOk.warnings.some(w => w.includes('D-001@V1'))) {
|
|
204
|
+
console.log('✅ verify validator 在 D-001@v1 已覆盖时不再报警')
|
|
205
|
+
} else {
|
|
206
|
+
console.log('❌ verify validator 覆盖后仍报警', verifyTraceOk)
|
|
207
|
+
failed++
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
writeFileSync(join(traceDir, 'decisions.md'), '# Decisions\n\n## D-002@v1: Unresolved schema conflict\n- priority: P0\n- status: unresolved\n')
|
|
211
|
+
const blockerTrace = runValidators('plan', traceRoot, 'trace')
|
|
212
|
+
if (blockerTrace.ok === false && blockerTrace.errors.some(e => e.includes('P0/P1 未决阻塞') && e.includes('D-002@V1'))) {
|
|
213
|
+
console.log('✅ plan validator 阻止 P0 unresolved decision 进入 plan')
|
|
214
|
+
} else {
|
|
215
|
+
console.log('❌ plan validator 未阻止 P0 unresolved decision', blockerTrace)
|
|
216
|
+
failed++
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
writeFileSync(join(traceDir, 'decisions.md'), '# Decisions\n\n- id: D-003@v1\n priority: P1\n status: blocking\n type: boundary\n')
|
|
220
|
+
const yamlBlockerTrace = runValidators('plan', traceRoot, 'trace')
|
|
221
|
+
if (yamlBlockerTrace.ok === false && yamlBlockerTrace.errors.some(e => e.includes('P0/P1 未决阻塞') && e.includes('D-003@V1'))) {
|
|
222
|
+
console.log('✅ plan validator 支持 list/YAML 风格 decision record')
|
|
223
|
+
} else {
|
|
224
|
+
console.log('❌ plan validator 未识别 list/YAML 风格 decision record', yamlBlockerTrace)
|
|
225
|
+
failed++
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
writeFileSync(join(traceDir, 'decisions.md'), '# Decisions\n\n- id: D-004@v1\n status: blocking\n type: boundary\n')
|
|
229
|
+
const missingPriorityTrace = runValidators('plan', traceRoot, 'trace')
|
|
230
|
+
if (missingPriorityTrace.ok === false
|
|
231
|
+
&& missingPriorityTrace.errors.some(e => e.includes('P0/P1 未决阻塞') && e.includes('D-004@V1') && e.includes('priority=missing->P1'))) {
|
|
232
|
+
console.log('✅ plan validator 将缺 priority 的 blocking decision 按 P1 阻断')
|
|
233
|
+
} else {
|
|
234
|
+
console.log('❌ plan validator 未阻断缺 priority 的 blocking decision', missingPriorityTrace)
|
|
235
|
+
failed++
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
writeFileSync(join(traceDir, 'decisions.md'), '# Decisions\n\n- id: D-005@v1\n status: accepted\n type: term\n')
|
|
239
|
+
writeFileSync(join(traceDir, 'plan.md'), '# Plan\n\n- [ ] task-01: implement naming(覆盖:FR-01)\n')
|
|
240
|
+
const yamlAcceptedTrace = runValidators('plan', traceRoot, 'trace')
|
|
241
|
+
if (yamlAcceptedTrace.ok === true && yamlAcceptedTrace.warnings.some(w => w.includes('plan.md 未引用') && w.includes('D-005@V1'))) {
|
|
242
|
+
console.log('✅ plan validator 将 YAML accepted decision 纳入追踪')
|
|
243
|
+
} else {
|
|
244
|
+
console.log('❌ plan validator 未追踪 YAML accepted decision', yamlAcceptedTrace)
|
|
245
|
+
failed++
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
writeFileSync(join(traceDir, 'decisions.md'), '# Decisions\n')
|
|
249
|
+
writeFileSync(join(traceDir, 'requirements.md'), '# Requirements\n\n普通说明提到 https://example.test/spec/FR-404 和注释里的 FR-405,但它们不是结构化需求 ID。\n')
|
|
250
|
+
writeFileSync(join(traceDir, 'plan.md'), '# Plan\n\nNo structured requirement IDs here.\n')
|
|
251
|
+
const looseIdTrace = runValidators('plan', traceRoot, 'trace')
|
|
252
|
+
if (!looseIdTrace.warnings.some(w => w.includes('FR-404') || w.includes('FR-405'))) {
|
|
253
|
+
console.log('✅ plan validator 忽略普通正文/URL 中的 FR ID')
|
|
254
|
+
} else {
|
|
255
|
+
console.log('❌ plan validator 误提取普通正文/URL 中的 FR ID', looseIdTrace)
|
|
256
|
+
failed++
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
writeFileSync(join(traceDir, 'decisions.md'), '# Decisions\n\n普通说明提到 https://example.test/spec/D-404@v1 和注释里的 D-405@v1,但它们不是结构化决策 ID。\n')
|
|
260
|
+
const looseBrainstormTrace = runValidators('brainstorm', traceRoot, 'trace')
|
|
261
|
+
if (!looseBrainstormTrace.warnings.some(w => w.includes('D-404@V1') || w.includes('D-405@V1'))) {
|
|
262
|
+
console.log('✅ brainstorm validator 忽略普通正文/URL 中的 D ID')
|
|
263
|
+
} else {
|
|
264
|
+
console.log('❌ brainstorm validator 误提取普通正文/URL 中的 D ID', looseBrainstormTrace)
|
|
265
|
+
failed++
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
rmSync(traceRoot, { recursive: true })
|
|
269
|
+
|
|
270
|
+
// === StageContract 结构测试 ===
|
|
271
|
+
console.log('\n=== Contract 结构测试 ===')
|
|
272
|
+
|
|
273
|
+
const plan = getContract('plan')
|
|
274
|
+
if (plan.allowedFrom.includes('brainstorm') && plan.allowedTo.includes('execute') && plan.validators.length === 1) {
|
|
275
|
+
console.log('✅ plan contract 结构正确')
|
|
276
|
+
} else {
|
|
277
|
+
console.log('❌ plan contract 结构异常:', JSON.stringify(plan))
|
|
278
|
+
failed++
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const verify = getContract('verify')
|
|
282
|
+
if (verify.allowedFrom.includes('execute') && verify.allowedTo.includes('archive')) {
|
|
283
|
+
console.log('✅ verify contract 结构正确')
|
|
284
|
+
} else {
|
|
285
|
+
console.log('❌ verify contract 结构异常')
|
|
286
|
+
failed++
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const unknown = getContract('nonexistent')
|
|
290
|
+
if (unknown === null) {
|
|
291
|
+
console.log('✅ 未知阶段返回 null')
|
|
292
|
+
} else {
|
|
293
|
+
console.log('❌ 未知阶段应返回 null')
|
|
294
|
+
failed++
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// === 结果 ===
|
|
298
|
+
console.log(`\n${failed === 0 ? '✅ 全部通过' : `❌ ${failed} 项失败`}`)
|
|
299
|
+
if (failed > 0) throw new Error(`${failed} test(s) failed`)
|