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,199 +1,199 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 平台 scan 失败样本测试
|
|
3
|
-
*
|
|
4
|
-
* 验证失败场景下 postcheck + manifest 能稳定表达失败:
|
|
5
|
-
* 1. source_root 污染 → postcheck status = failed_post_check + source_root_leak check
|
|
6
|
-
* 2. spec 缺文档 → postcheck status = failed_post_check + all_docs_missing check
|
|
7
|
-
* 3. 混合场景 → 多个 check,failed 优先
|
|
8
|
-
* 4. postcheck-result.json 结构可被 SillyHub 稳定解析
|
|
9
|
-
*
|
|
10
|
-
* 跑法: node test/platform-failure-samples.test.mjs
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { join, basename } from 'path'
|
|
14
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'fs'
|
|
15
|
-
import { randomUUID } from 'crypto'
|
|
16
|
-
import { tmpdir } from 'os'
|
|
17
|
-
import { SCAN_STATUS, CHECK_SEVERITY } from '../src/constants.js'
|
|
18
|
-
|
|
19
|
-
const passed = []
|
|
20
|
-
const failed = []
|
|
21
|
-
|
|
22
|
-
function assert(label, condition, detail) {
|
|
23
|
-
if (condition) {
|
|
24
|
-
passed.push(label)
|
|
25
|
-
console.log(` ✅ PASS: ${label}`)
|
|
26
|
-
} else {
|
|
27
|
-
failed.push({ label, detail })
|
|
28
|
-
console.log(` ❌ FAIL: ${label}`)
|
|
29
|
-
if (detail) console.log(` ${detail}`)
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function cleanup(dir) {
|
|
34
|
-
try { rmSync(dir, { recursive: true, force: true }) } catch {}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function setup(name) {
|
|
38
|
-
const base = join(tmpdir(), `failure-test-${name}-${randomUUID().slice(0, 8)}`)
|
|
39
|
-
const spec = join(tmpdir(), `failure-test-spec-${name}-${randomUUID().slice(0, 8)}`)
|
|
40
|
-
mkdirSync(base, { recursive: true })
|
|
41
|
-
mkdirSync(spec, { recursive: true })
|
|
42
|
-
writeFileSync(join(base, 'package.json'), '{}')
|
|
43
|
-
return { cwd: base, specDir: spec }
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ── Test 1: source_root 污染 → failed_post_check ──
|
|
47
|
-
console.log('\n=== Test 1: source_root docs 污染 ===')
|
|
48
|
-
{
|
|
49
|
-
const { cwd, specDir } = setup('leak')
|
|
50
|
-
const proj = basename(cwd)
|
|
51
|
-
try {
|
|
52
|
-
// 在 source_root/.sillyspec/docs/ 下创建泄漏文件
|
|
53
|
-
mkdirSync(join(cwd, '.sillyspec', 'docs', proj, 'scan'), { recursive: true })
|
|
54
|
-
writeFileSync(join(cwd, '.sillyspec', 'docs', proj, 'scan', 'ARCHITECTURE.md'), '# leak')
|
|
55
|
-
|
|
56
|
-
const { runScanPostCheck } = await import('../src/scan-postcheck.js')
|
|
57
|
-
const result = runScanPostCheck({ cwd, specDir })
|
|
58
|
-
|
|
59
|
-
assert('status = failed_post_check', result.status === SCAN_STATUS.FAILED_POST_CHECK, `实际: ${result.status}`)
|
|
60
|
-
assert('有 source_root_docs_leak check', result.checks.some(c => c.name === 'source_root_docs_leak'))
|
|
61
|
-
assert('source_root_docs_leak severity = failed',
|
|
62
|
-
result.checks.find(c => c.name === 'source_root_docs_leak')?.severity === CHECK_SEVERITY.FAILED)
|
|
63
|
-
|
|
64
|
-
// 验证结构化输出
|
|
65
|
-
const { formatStructuredResult } = await import('../src/scan-postcheck.js')
|
|
66
|
-
const structured = formatStructuredResult(result, { source_root: cwd })
|
|
67
|
-
assert('结构化输出有 overall_status', !!structured.overall_status)
|
|
68
|
-
assert('结构化输出有 checks', Array.isArray(structured.checks))
|
|
69
|
-
assert('结构化输出 path_pollution 非空', structured.failure_categories.path_pollution.length > 0)
|
|
70
|
-
} finally {
|
|
71
|
-
cleanup(cwd)
|
|
72
|
-
cleanup(specDir)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// ── Test 2: spec 缺文档 → failed_post_check ──
|
|
77
|
-
console.log('\n=== Test 2: spec 无文档 ===')
|
|
78
|
-
{
|
|
79
|
-
const { cwd, specDir } = setup('missing')
|
|
80
|
-
const proj = basename(cwd)
|
|
81
|
-
try {
|
|
82
|
-
const { runScanPostCheck } = await import('../src/scan-postcheck.js')
|
|
83
|
-
const result = runScanPostCheck({ cwd, specDir })
|
|
84
|
-
|
|
85
|
-
assert('status = failed_post_check', result.status === SCAN_STATUS.FAILED_POST_CHECK, `实际: ${result.status}`)
|
|
86
|
-
assert('有 all_docs_missing check', result.checks.some(c => c.name === 'all_docs_missing'))
|
|
87
|
-
|
|
88
|
-
const { formatStructuredResult } = await import('../src/scan-postcheck.js')
|
|
89
|
-
const structured = formatStructuredResult(result, { source_root: cwd })
|
|
90
|
-
assert('结构化输出 missing_outputs 非空', structured.failure_categories.missing_outputs.length > 0)
|
|
91
|
-
} finally {
|
|
92
|
-
cleanup(cwd)
|
|
93
|
-
cleanup(specDir)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// ── Test 3: 混合场景(污染 + 缺文档)→ failed 优先 ──
|
|
98
|
-
console.log('\n=== Test 3: 混合失败场景 ===')
|
|
99
|
-
{
|
|
100
|
-
const { cwd, specDir } = setup('mixed')
|
|
101
|
-
const proj = basename(cwd)
|
|
102
|
-
try {
|
|
103
|
-
// source_root 污染
|
|
104
|
-
mkdirSync(join(cwd, '.sillyspec', 'docs', proj, 'scan'), { recursive: true })
|
|
105
|
-
writeFileSync(join(cwd, '.sillyspec', 'docs', proj, 'scan', 'CONVENTIONS.md'), '# leak')
|
|
106
|
-
// spec 不写文档(缺文档)
|
|
107
|
-
|
|
108
|
-
const { runScanPostCheck } = await import('../src/scan-postcheck.js')
|
|
109
|
-
const result = runScanPostCheck({ cwd, specDir })
|
|
110
|
-
|
|
111
|
-
assert('混合场景 status = failed_post_check', result.status === SCAN_STATUS.FAILED_POST_CHECK, `实际: ${result.status}`)
|
|
112
|
-
assert('混合场景有多个 check', result.checks.length >= 2, `实际: ${result.checks.length}`)
|
|
113
|
-
assert('混合场景包含 source_root_docs_leak', result.checks.some(c => c.name === 'source_root_docs_leak'))
|
|
114
|
-
assert('混合场景包含 all_docs_missing', result.checks.some(c => c.name === 'all_docs_missing'))
|
|
115
|
-
|
|
116
|
-
// 所有 failed check 的 name 列出,确保 SillyHub 能定位问题
|
|
117
|
-
const failedChecks = result.checks.filter(c => c.severity === CHECK_SEVERITY.FAILED)
|
|
118
|
-
assert('混合场景至少 2 个 failed check', failedChecks.length >= 2, `实际: ${failedChecks.length}`)
|
|
119
|
-
for (const fc of failedChecks) {
|
|
120
|
-
assert(`failed check "${fc.name}" 有 detail`, !!fc.detail, `check: ${fc.name}`)
|
|
121
|
-
}
|
|
122
|
-
} finally {
|
|
123
|
-
cleanup(cwd)
|
|
124
|
-
cleanup(specDir)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// ── Test 4: 警告场景(文档缺 header)→ completed_with_warnings ──
|
|
129
|
-
console.log('\n=== Test 4: 文档缺 header → 警告 ===')
|
|
130
|
-
{
|
|
131
|
-
const { cwd, specDir } = setup('warn')
|
|
132
|
-
const proj = basename(cwd)
|
|
133
|
-
try {
|
|
134
|
-
// 写文档但缺少 frontmatter header
|
|
135
|
-
const docs = ['ARCHITECTURE.md', 'CONVENTIONS.md', 'PROJECT.md', 'STRUCTURE.md', 'INTEGRATIONS.md', 'TESTING.md', 'CONCERNS.md']
|
|
136
|
-
for (const doc of docs) {
|
|
137
|
-
mkdirSync(join(specDir, 'docs', proj, 'scan'), { recursive: true })
|
|
138
|
-
writeFileSync(join(specDir, 'docs', proj, 'scan', doc), `# ${doc.replace('.md', '')}\nno header`)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const { runScanPostCheck } = await import('../src/scan-postcheck.js')
|
|
142
|
-
const result = runScanPostCheck({ cwd, specDir })
|
|
143
|
-
|
|
144
|
-
assert('警告场景 status = completed_with_warnings', result.status === SCAN_STATUS.COMPLETED_WITH_WARNINGS, `实际: ${result.status}`)
|
|
145
|
-
assert('警告场景有 docs_missing_header check', result.checks.some(c => c.name === 'docs_missing_header'))
|
|
146
|
-
} finally {
|
|
147
|
-
cleanup(cwd)
|
|
148
|
-
cleanup(specDir)
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// ── Test 5: 正常成功场景 → success ──
|
|
153
|
-
console.log('\n=== Test 5: 正常成功场景 ===')
|
|
154
|
-
{
|
|
155
|
-
const { cwd, specDir } = setup('success')
|
|
156
|
-
const proj = basename(cwd)
|
|
157
|
-
try {
|
|
158
|
-
// 写所有 7 份文档,带正确 header
|
|
159
|
-
const docs = ['ARCHITECTURE.md', 'CONVENTIONS.md', 'PROJECT.md', 'STRUCTURE.md', 'INTEGRATIONS.md', 'TESTING.md', 'CONCERNS.md']
|
|
160
|
-
const now = new Date().toISOString().replace('T', ' ').slice(0, 19)
|
|
161
|
-
for (const doc of docs) {
|
|
162
|
-
mkdirSync(join(specDir, 'docs', proj, 'scan'), { recursive: true })
|
|
163
|
-
writeFileSync(join(specDir, 'docs', proj, 'scan', doc),
|
|
164
|
-
`author: bot\ncreated_at: ${now}\n# ${doc.replace('.md', '')}\n`)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// 在 specDir 写 local.yaml
|
|
168
|
-
writeFileSync(join(specDir, 'local.yaml'), 'build: echo ok\ntest: echo ok\n')
|
|
169
|
-
|
|
170
|
-
// 创建 knowledge 目录和 INDEX.md(scan 已产出知识)
|
|
171
|
-
mkdirSync(join(specDir, 'knowledge'), { recursive: true })
|
|
172
|
-
writeFileSync(join(specDir, 'knowledge', 'INDEX.md'), '# Knowledge Index\n')
|
|
173
|
-
|
|
174
|
-
const { runScanPostCheck } = await import('../src/scan-postcheck.js')
|
|
175
|
-
const result = runScanPostCheck({ cwd, specDir })
|
|
176
|
-
|
|
177
|
-
assert('成功场景 status = success', result.status === SCAN_STATUS.SUCCESS, `实际: ${result.status}`)
|
|
178
|
-
assert('成功场景 checks 全部 passed 或空', result.checks.every(c => c.severity === CHECK_SEVERITY.PASSED) || result.checks.length === 0)
|
|
179
|
-
} finally {
|
|
180
|
-
cleanup(cwd)
|
|
181
|
-
cleanup(specDir)
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ── 结果 ──
|
|
186
|
-
console.log(`\n${'='.repeat(50)}`)
|
|
187
|
-
console.log(`✅ 通过: ${passed.length} ❌ 失败: ${failed.length}`)
|
|
188
|
-
console.log(`${'='.repeat(50)}`)
|
|
189
|
-
|
|
190
|
-
if (failed.length > 0) {
|
|
191
|
-
console.log('\n失败详情:')
|
|
192
|
-
for (const f of failed) {
|
|
193
|
-
console.log(` ❌ ${f.label}`)
|
|
194
|
-
if (f.detail) console.log(` ${f.detail}`)
|
|
195
|
-
}
|
|
196
|
-
throw new Error('platform-failure-samples test failed')
|
|
197
|
-
} else {
|
|
198
|
-
console.log('\n🎉 平台失败样本测试全部通过!')
|
|
199
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 平台 scan 失败样本测试
|
|
3
|
+
*
|
|
4
|
+
* 验证失败场景下 postcheck + manifest 能稳定表达失败:
|
|
5
|
+
* 1. source_root 污染 → postcheck status = failed_post_check + source_root_leak check
|
|
6
|
+
* 2. spec 缺文档 → postcheck status = failed_post_check + all_docs_missing check
|
|
7
|
+
* 3. 混合场景 → 多个 check,failed 优先
|
|
8
|
+
* 4. postcheck-result.json 结构可被 SillyHub 稳定解析
|
|
9
|
+
*
|
|
10
|
+
* 跑法: node test/platform-failure-samples.test.mjs
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { join, basename } from 'path'
|
|
14
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'fs'
|
|
15
|
+
import { randomUUID } from 'crypto'
|
|
16
|
+
import { tmpdir } from 'os'
|
|
17
|
+
import { SCAN_STATUS, CHECK_SEVERITY } from '../src/constants.js'
|
|
18
|
+
|
|
19
|
+
const passed = []
|
|
20
|
+
const failed = []
|
|
21
|
+
|
|
22
|
+
function assert(label, condition, detail) {
|
|
23
|
+
if (condition) {
|
|
24
|
+
passed.push(label)
|
|
25
|
+
console.log(` ✅ PASS: ${label}`)
|
|
26
|
+
} else {
|
|
27
|
+
failed.push({ label, detail })
|
|
28
|
+
console.log(` ❌ FAIL: ${label}`)
|
|
29
|
+
if (detail) console.log(` ${detail}`)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function cleanup(dir) {
|
|
34
|
+
try { rmSync(dir, { recursive: true, force: true }) } catch {}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function setup(name) {
|
|
38
|
+
const base = join(tmpdir(), `failure-test-${name}-${randomUUID().slice(0, 8)}`)
|
|
39
|
+
const spec = join(tmpdir(), `failure-test-spec-${name}-${randomUUID().slice(0, 8)}`)
|
|
40
|
+
mkdirSync(base, { recursive: true })
|
|
41
|
+
mkdirSync(spec, { recursive: true })
|
|
42
|
+
writeFileSync(join(base, 'package.json'), '{}')
|
|
43
|
+
return { cwd: base, specDir: spec }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Test 1: source_root 污染 → failed_post_check ──
|
|
47
|
+
console.log('\n=== Test 1: source_root docs 污染 ===')
|
|
48
|
+
{
|
|
49
|
+
const { cwd, specDir } = setup('leak')
|
|
50
|
+
const proj = basename(cwd)
|
|
51
|
+
try {
|
|
52
|
+
// 在 source_root/.sillyspec/docs/ 下创建泄漏文件
|
|
53
|
+
mkdirSync(join(cwd, '.sillyspec', 'docs', proj, 'scan'), { recursive: true })
|
|
54
|
+
writeFileSync(join(cwd, '.sillyspec', 'docs', proj, 'scan', 'ARCHITECTURE.md'), '# leak')
|
|
55
|
+
|
|
56
|
+
const { runScanPostCheck } = await import('../src/scan-postcheck.js')
|
|
57
|
+
const result = runScanPostCheck({ cwd, specDir })
|
|
58
|
+
|
|
59
|
+
assert('status = failed_post_check', result.status === SCAN_STATUS.FAILED_POST_CHECK, `实际: ${result.status}`)
|
|
60
|
+
assert('有 source_root_docs_leak check', result.checks.some(c => c.name === 'source_root_docs_leak'))
|
|
61
|
+
assert('source_root_docs_leak severity = failed',
|
|
62
|
+
result.checks.find(c => c.name === 'source_root_docs_leak')?.severity === CHECK_SEVERITY.FAILED)
|
|
63
|
+
|
|
64
|
+
// 验证结构化输出
|
|
65
|
+
const { formatStructuredResult } = await import('../src/scan-postcheck.js')
|
|
66
|
+
const structured = formatStructuredResult(result, { source_root: cwd })
|
|
67
|
+
assert('结构化输出有 overall_status', !!structured.overall_status)
|
|
68
|
+
assert('结构化输出有 checks', Array.isArray(structured.checks))
|
|
69
|
+
assert('结构化输出 path_pollution 非空', structured.failure_categories.path_pollution.length > 0)
|
|
70
|
+
} finally {
|
|
71
|
+
cleanup(cwd)
|
|
72
|
+
cleanup(specDir)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── Test 2: spec 缺文档 → failed_post_check ──
|
|
77
|
+
console.log('\n=== Test 2: spec 无文档 ===')
|
|
78
|
+
{
|
|
79
|
+
const { cwd, specDir } = setup('missing')
|
|
80
|
+
const proj = basename(cwd)
|
|
81
|
+
try {
|
|
82
|
+
const { runScanPostCheck } = await import('../src/scan-postcheck.js')
|
|
83
|
+
const result = runScanPostCheck({ cwd, specDir })
|
|
84
|
+
|
|
85
|
+
assert('status = failed_post_check', result.status === SCAN_STATUS.FAILED_POST_CHECK, `实际: ${result.status}`)
|
|
86
|
+
assert('有 all_docs_missing check', result.checks.some(c => c.name === 'all_docs_missing'))
|
|
87
|
+
|
|
88
|
+
const { formatStructuredResult } = await import('../src/scan-postcheck.js')
|
|
89
|
+
const structured = formatStructuredResult(result, { source_root: cwd })
|
|
90
|
+
assert('结构化输出 missing_outputs 非空', structured.failure_categories.missing_outputs.length > 0)
|
|
91
|
+
} finally {
|
|
92
|
+
cleanup(cwd)
|
|
93
|
+
cleanup(specDir)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── Test 3: 混合场景(污染 + 缺文档)→ failed 优先 ──
|
|
98
|
+
console.log('\n=== Test 3: 混合失败场景 ===')
|
|
99
|
+
{
|
|
100
|
+
const { cwd, specDir } = setup('mixed')
|
|
101
|
+
const proj = basename(cwd)
|
|
102
|
+
try {
|
|
103
|
+
// source_root 污染
|
|
104
|
+
mkdirSync(join(cwd, '.sillyspec', 'docs', proj, 'scan'), { recursive: true })
|
|
105
|
+
writeFileSync(join(cwd, '.sillyspec', 'docs', proj, 'scan', 'CONVENTIONS.md'), '# leak')
|
|
106
|
+
// spec 不写文档(缺文档)
|
|
107
|
+
|
|
108
|
+
const { runScanPostCheck } = await import('../src/scan-postcheck.js')
|
|
109
|
+
const result = runScanPostCheck({ cwd, specDir })
|
|
110
|
+
|
|
111
|
+
assert('混合场景 status = failed_post_check', result.status === SCAN_STATUS.FAILED_POST_CHECK, `实际: ${result.status}`)
|
|
112
|
+
assert('混合场景有多个 check', result.checks.length >= 2, `实际: ${result.checks.length}`)
|
|
113
|
+
assert('混合场景包含 source_root_docs_leak', result.checks.some(c => c.name === 'source_root_docs_leak'))
|
|
114
|
+
assert('混合场景包含 all_docs_missing', result.checks.some(c => c.name === 'all_docs_missing'))
|
|
115
|
+
|
|
116
|
+
// 所有 failed check 的 name 列出,确保 SillyHub 能定位问题
|
|
117
|
+
const failedChecks = result.checks.filter(c => c.severity === CHECK_SEVERITY.FAILED)
|
|
118
|
+
assert('混合场景至少 2 个 failed check', failedChecks.length >= 2, `实际: ${failedChecks.length}`)
|
|
119
|
+
for (const fc of failedChecks) {
|
|
120
|
+
assert(`failed check "${fc.name}" 有 detail`, !!fc.detail, `check: ${fc.name}`)
|
|
121
|
+
}
|
|
122
|
+
} finally {
|
|
123
|
+
cleanup(cwd)
|
|
124
|
+
cleanup(specDir)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Test 4: 警告场景(文档缺 header)→ completed_with_warnings ──
|
|
129
|
+
console.log('\n=== Test 4: 文档缺 header → 警告 ===')
|
|
130
|
+
{
|
|
131
|
+
const { cwd, specDir } = setup('warn')
|
|
132
|
+
const proj = basename(cwd)
|
|
133
|
+
try {
|
|
134
|
+
// 写文档但缺少 frontmatter header
|
|
135
|
+
const docs = ['ARCHITECTURE.md', 'CONVENTIONS.md', 'PROJECT.md', 'STRUCTURE.md', 'INTEGRATIONS.md', 'TESTING.md', 'CONCERNS.md']
|
|
136
|
+
for (const doc of docs) {
|
|
137
|
+
mkdirSync(join(specDir, 'docs', proj, 'scan'), { recursive: true })
|
|
138
|
+
writeFileSync(join(specDir, 'docs', proj, 'scan', doc), `# ${doc.replace('.md', '')}\nno header`)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const { runScanPostCheck } = await import('../src/scan-postcheck.js')
|
|
142
|
+
const result = runScanPostCheck({ cwd, specDir })
|
|
143
|
+
|
|
144
|
+
assert('警告场景 status = completed_with_warnings', result.status === SCAN_STATUS.COMPLETED_WITH_WARNINGS, `实际: ${result.status}`)
|
|
145
|
+
assert('警告场景有 docs_missing_header check', result.checks.some(c => c.name === 'docs_missing_header'))
|
|
146
|
+
} finally {
|
|
147
|
+
cleanup(cwd)
|
|
148
|
+
cleanup(specDir)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── Test 5: 正常成功场景 → success ──
|
|
153
|
+
console.log('\n=== Test 5: 正常成功场景 ===')
|
|
154
|
+
{
|
|
155
|
+
const { cwd, specDir } = setup('success')
|
|
156
|
+
const proj = basename(cwd)
|
|
157
|
+
try {
|
|
158
|
+
// 写所有 7 份文档,带正确 header
|
|
159
|
+
const docs = ['ARCHITECTURE.md', 'CONVENTIONS.md', 'PROJECT.md', 'STRUCTURE.md', 'INTEGRATIONS.md', 'TESTING.md', 'CONCERNS.md']
|
|
160
|
+
const now = new Date().toISOString().replace('T', ' ').slice(0, 19)
|
|
161
|
+
for (const doc of docs) {
|
|
162
|
+
mkdirSync(join(specDir, 'docs', proj, 'scan'), { recursive: true })
|
|
163
|
+
writeFileSync(join(specDir, 'docs', proj, 'scan', doc),
|
|
164
|
+
`author: bot\ncreated_at: ${now}\n# ${doc.replace('.md', '')}\n`)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 在 specDir 写 local.yaml
|
|
168
|
+
writeFileSync(join(specDir, 'local.yaml'), 'build: echo ok\ntest: echo ok\n')
|
|
169
|
+
|
|
170
|
+
// 创建 knowledge 目录和 INDEX.md(scan 已产出知识)
|
|
171
|
+
mkdirSync(join(specDir, 'knowledge'), { recursive: true })
|
|
172
|
+
writeFileSync(join(specDir, 'knowledge', 'INDEX.md'), '# Knowledge Index\n')
|
|
173
|
+
|
|
174
|
+
const { runScanPostCheck } = await import('../src/scan-postcheck.js')
|
|
175
|
+
const result = runScanPostCheck({ cwd, specDir })
|
|
176
|
+
|
|
177
|
+
assert('成功场景 status = success', result.status === SCAN_STATUS.SUCCESS, `实际: ${result.status}`)
|
|
178
|
+
assert('成功场景 checks 全部 passed 或空', result.checks.every(c => c.severity === CHECK_SEVERITY.PASSED) || result.checks.length === 0)
|
|
179
|
+
} finally {
|
|
180
|
+
cleanup(cwd)
|
|
181
|
+
cleanup(specDir)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ── 结果 ──
|
|
186
|
+
console.log(`\n${'='.repeat(50)}`)
|
|
187
|
+
console.log(`✅ 通过: ${passed.length} ❌ 失败: ${failed.length}`)
|
|
188
|
+
console.log(`${'='.repeat(50)}`)
|
|
189
|
+
|
|
190
|
+
if (failed.length > 0) {
|
|
191
|
+
console.log('\n失败详情:')
|
|
192
|
+
for (const f of failed) {
|
|
193
|
+
console.log(` ❌ ${f.label}`)
|
|
194
|
+
if (f.detail) console.log(` ${f.detail}`)
|
|
195
|
+
}
|
|
196
|
+
throw new Error('platform-failure-samples test failed')
|
|
197
|
+
} else {
|
|
198
|
+
console.log('\n🎉 平台失败样本测试全部通过!')
|
|
199
|
+
}
|