sillyspec 3.20.2 → 3.20.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +1 -5
  2. package/.claude/skills/sillyspec-auto/SKILL.md +2 -8
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -28
  4. package/.claude/skills/sillyspec-commit/SKILL.md +3 -4
  5. package/.claude/skills/sillyspec-continue/SKILL.md +4 -5
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +3 -12
  7. package/.claude/skills/sillyspec-execute/SKILL.md +2 -15
  8. package/.claude/skills/sillyspec-explore/SKILL.md +1 -14
  9. package/.claude/skills/sillyspec-plan/SKILL.md +36 -5
  10. package/.claude/skills/sillyspec-propose/SKILL.md +0 -4
  11. package/.claude/skills/sillyspec-quick/SKILL.md +1 -5
  12. package/.claude/skills/sillyspec-resume/SKILL.md +66 -23
  13. package/.claude/skills/sillyspec-scan/SKILL.md +1 -5
  14. package/.claude/skills/sillyspec-state/SKILL.md +8 -8
  15. package/.claude/skills/sillyspec-status/SKILL.md +1 -5
  16. package/.claude/skills/sillyspec-verify/SKILL.md +1 -5
  17. package/.claude/skills/sillyspec-workspace/SKILL.md +3 -11
  18. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +97 -0
  19. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +51 -0
  20. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +29 -0
  21. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +34 -0
  22. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +13 -0
  23. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +43 -0
  24. package/.sillyspec/changes/auto-mode/design.md +50 -0
  25. package/.sillyspec/changes/auto-mode/proposal.md +19 -0
  26. package/.sillyspec/changes/auto-mode/requirements.md +21 -0
  27. package/.sillyspec/changes/auto-mode/tasks.md +7 -0
  28. package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +206 -0
  29. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +199 -0
  30. package/.sillyspec/changes/dashboard/design.md +219 -0
  31. package/.sillyspec/changes/dashboard/design.md.braindraft +206 -0
  32. package/.sillyspec/changes/run-command-design/design.md +1230 -0
  33. package/.sillyspec/changes/unified-docs-design/design.md +199 -0
  34. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  35. package/.sillyspec/knowledge/INDEX.md +8 -0
  36. package/.sillyspec/knowledge/uncategorized.md +3 -0
  37. package/.sillyspec/plans/2026-04-05-dashboard.md +737 -0
  38. package/.sillyspec/projects/sillyspec.yaml +3 -0
  39. package/README.md +11 -13
  40. package/SKILL.md +40 -44
  41. package/dist/steps/brainstorm/01-load-context.md +30 -0
  42. package/dist/steps/brainstorm/02-reuse-check.md +6 -0
  43. package/dist/steps/brainstorm/03-prototype-analysis.md +11 -0
  44. package/dist/steps/brainstorm/04-module-split.md +23 -0
  45. package/dist/steps/brainstorm/05-dialog-explore.md +8 -0
  46. package/dist/steps/brainstorm/06-propose-approaches.md +3 -0
  47. package/dist/steps/brainstorm/07-present-design.md +3 -0
  48. package/dist/steps/brainstorm/08-write-design.md +21 -0
  49. package/dist/steps/brainstorm/09-self-review.md +15 -0
  50. package/dist/steps/brainstorm/10-user-confirm.md +3 -0
  51. package/dist/steps/brainstorm/11-output-spec.md +7 -0
  52. package/dist/steps/brainstorm/manifest.yaml +26 -0
  53. package/dist/steps/execute/01-load-context.md +41 -0
  54. package/dist/steps/execute/02-scan-conventions.md +47 -0
  55. package/dist/steps/execute/03-skill-mcp.md +19 -0
  56. package/dist/steps/execute/04-assign-task.md +22 -0
  57. package/dist/steps/execute/04b-prompt-template.md +54 -0
  58. package/dist/steps/execute/05-write-test.md +7 -0
  59. package/dist/steps/execute/06-write-code.md +8 -0
  60. package/dist/steps/execute/07-run-test.md +26 -0
  61. package/dist/steps/execute/08-fix-issues.md +28 -0
  62. package/dist/steps/execute/09-next-task.md +33 -0
  63. package/dist/steps/execute/manifest.yaml +28 -0
  64. package/dist/steps/plan/01-load-context.md +22 -0
  65. package/dist/steps/plan/02-anchor-confirm.md +1 -0
  66. package/dist/steps/plan/03-expand-tasks.md +33 -0
  67. package/dist/steps/plan/04-mark-order.md +15 -0
  68. package/dist/steps/plan/05-e2e-planning.md +17 -0
  69. package/dist/steps/plan/06-self-check.md +16 -0
  70. package/dist/steps/plan/07-save.md +1 -0
  71. package/dist/steps/plan/manifest.yaml +18 -0
  72. package/dist/steps/scan/01-env-detect.md +51 -0
  73. package/dist/steps/scan/02-tech-stack.md +16 -0
  74. package/dist/steps/scan/03-conventions.md +16 -0
  75. package/dist/steps/scan/04-structure.md +19 -0
  76. package/dist/steps/scan/05-quality.md +18 -0
  77. package/dist/steps/scan/06-complete.md +49 -0
  78. package/dist/steps/scan/manifest.yaml +16 -0
  79. package/dist/steps/verify/01-load-specs.md +28 -0
  80. package/dist/steps/verify/02-check-tasks.md +1 -0
  81. package/dist/steps/verify/03-check-design.md +6 -0
  82. package/dist/steps/verify/04-run-tests.md +7 -0
  83. package/dist/steps/verify/05-e2e-tests.md +27 -0
  84. package/dist/steps/verify/05b-e2e-fix.md +33 -0
  85. package/dist/steps/verify/06-code-quality.md +25 -0
  86. package/dist/steps/verify/07-lint-check.md +27 -0
  87. package/dist/steps/verify/08-output-report.md +14 -0
  88. package/dist/steps/verify/manifest.yaml +22 -0
  89. package/package.json +1 -7
  90. package/packages/dashboard/dist/assets/{index-Bq_Z2hne.js → index-D1EVTLmc.js} +1264 -1264
  91. package/packages/dashboard/dist/assets/index-DGe8CqeP.css +1 -0
  92. package/packages/dashboard/dist/index.html +2 -2
  93. package/packages/dashboard/package-lock.json +6 -6
  94. package/packages/dashboard/server/executor.js +1 -1
  95. package/packages/dashboard/server/index.js +18 -98
  96. package/packages/dashboard/server/parser.js +71 -139
  97. package/packages/dashboard/server/watcher.js +6 -14
  98. package/packages/dashboard/src/App.vue +185 -418
  99. package/packages/dashboard/src/components/ActionBar.vue +1 -10
  100. package/packages/dashboard/src/components/CommandPalette.vue +1 -5
  101. package/packages/dashboard/src/components/DocPreview.vue +8 -105
  102. package/packages/dashboard/src/components/DocTree.vue +19 -75
  103. package/packages/dashboard/src/components/PipelineStage.vue +2 -22
  104. package/packages/dashboard/src/components/PipelineView.vue +5 -32
  105. package/packages/dashboard/src/components/ProjectOverview.vue +139 -113
  106. package/packages/dashboard/src/components/StageBadge.vue +3 -17
  107. package/packages/dashboard/src/components/StepCard.vue +2 -7
  108. package/packages/dashboard/src/composables/useDashboard.js +0 -28
  109. package/src/derive.js +147 -0
  110. package/src/index.js +40 -688
  111. package/src/init.js +63 -119
  112. package/src/migrate.js +7 -7
  113. package/src/progress.js +248 -1474
  114. package/src/run.js +302 -3008
  115. package/src/setup.js +64 -2
  116. package/src/stages/archive.js +17 -123
  117. package/src/stages/brainstorm.js +48 -454
  118. package/src/stages/doctor.js +46 -99
  119. package/src/stages/execute.js +59 -420
  120. package/src/stages/index.js +18 -12
  121. package/src/stages/plan.js +189 -492
  122. package/src/stages/propose.js +11 -70
  123. package/src/stages/quick.js +13 -52
  124. package/src/stages/scan.js +68 -485
  125. package/src/stages/status.js +1 -1
  126. package/src/stages/verify.js +16 -203
  127. package/src/step.js +543 -0
  128. package/.claude/skills/sillyspec-knowledge/SKILL.md +0 -270
  129. package/.husky/pre-push +0 -13
  130. package/CLAUDE.md +0 -18
  131. package/docs/brainstorm-plan-contract.md +0 -64
  132. package/docs/plan-execute-contract.md +0 -123
  133. package/docs/platform-scan-protocol.md +0 -298
  134. package/docs/revision-mode.md +0 -115
  135. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +0 -99
  136. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +0 -218
  137. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +0 -167
  138. package/docs/sillyspec/file-lifecycle/storage-and-state.md +0 -148
  139. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +0 -193
  140. package/docs/sillyspec/file-lifecycle.md +0 -125
  141. package/docs/workflow-contract-regression.md +0 -106
  142. package/docs/worktree-isolation.md +0 -252
  143. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +0 -1
  144. package/packages/dashboard/dist/prototype-dashboard.html +0 -836
  145. package/packages/dashboard/dist/prototype-overview.html +0 -256
  146. package/packages/dashboard/public/prototype-dashboard.html +0 -836
  147. package/packages/dashboard/public/prototype-overview.html +0 -256
  148. package/packages/dashboard/src/components/HResizeHandle.vue +0 -48
  149. package/packages/dashboard/src/components/ProjectCard.vue +0 -187
  150. package/packages/dashboard/src/components/VResizeHandle.vue +0 -61
  151. package/packages/dashboard/src/composables/useLayout.js +0 -131
  152. package/src/brainstorm-postcheck.js +0 -158
  153. package/src/change-list.js +0 -52
  154. package/src/change-risk-profile.js +0 -352
  155. package/src/classify-change.js +0 -73
  156. package/src/constants.js +0 -70
  157. package/src/contract-matrix.js +0 -278
  158. package/src/db.js +0 -201
  159. package/src/endpoint-extractor.js +0 -315
  160. package/src/hooks/claude-pre-tool-use.cjs +0 -125
  161. package/src/hooks/worktree-guard.js +0 -761
  162. package/src/knowledge-match.js +0 -130
  163. package/src/modules.js +0 -482
  164. package/src/scan-postcheck.js +0 -383
  165. package/src/stage-contract.js +0 -700
  166. package/src/stages/brainstorm-auto.js +0 -229
  167. package/src/stages/explore.js +0 -34
  168. package/src/stages/knowledge.js +0 -498
  169. package/src/stages/plan-postcheck.js +0 -513
  170. package/src/sync.js +0 -497
  171. package/src/task-review.js +0 -346
  172. package/src/workflow.js +0 -785
  173. package/src/worktree-apply.js +0 -549
  174. package/src/worktree.js +0 -932
  175. package/templates/workflows/archive-impact.yaml +0 -79
  176. package/templates/workflows/scan-docs.yaml +0 -132
  177. package/test/brainstorm-plan-contract.test.mjs +0 -273
  178. package/test/check-syntax.mjs +0 -26
  179. package/test/cli-top-level-aliases.test.mjs +0 -174
  180. package/test/contract-artifacts.test.mjs +0 -323
  181. package/test/decision-supersede.test.mjs +0 -277
  182. package/test/knowledge-match.test.mjs +0 -231
  183. package/test/plan-execute-contract.test.mjs +0 -330
  184. package/test/plan-optimization.test.mjs +0 -572
  185. package/test/platform-artifacts.test.mjs +0 -190
  186. package/test/platform-failure-samples.test.mjs +0 -199
  187. package/test/platform-recovery-chain.test.mjs +0 -179
  188. package/test/platform-recovery.test.mjs +0 -167
  189. package/test/platform-scan-p0.test.mjs +0 -175
  190. package/test/revision-v1.test.mjs +0 -1145
  191. package/test/run-sanitize-project-name.test.mjs +0 -51
  192. package/test/run-scan-postcheck-fail.test.mjs +0 -64
  193. package/test/run-scan-project-parse.test.mjs +0 -200
  194. package/test/run-tests.mjs +0 -48
  195. package/test/scan-docs-yaml-placeholders.test.mjs +0 -84
  196. package/test/scan-knowledge.test.mjs +0 -175
  197. package/test/scan-paths.test.mjs +0 -68
  198. package/test/scan-postcheck-project-priority.test.mjs +0 -85
  199. package/test/scan-postcheck.test.mjs +0 -197
  200. package/test/scan-workflow-anyfailed-block.test.mjs +0 -52
  201. package/test/spec-dir.test.mjs +0 -206
  202. package/test/stage-contract-failed-post-check.test.mjs +0 -102
  203. package/test/stage-contract.test.mjs +0 -299
  204. package/test/stage-definitions.test.mjs +0 -39
  205. package/test/wait-gates.test.mjs +0 -501
  206. package/test/workflow-spec-base.test.mjs +0 -142
  207. package/test/worktree-guard.test.mjs +0 -136
  208. package/test/worktree-native-overlay.test.mjs +0 -188
@@ -1,190 +0,0 @@
1
- /**
2
- * 平台 scan 产物协议测试
3
- *
4
- * 验证:
5
- * 1. saveWorkflowRun 传入 runtimeRoot + scanRunId 时写到正确路径
6
- * 2. manifest.json 结构包含产物指针(postcheck_result_path, workflow_runs_dir)
7
- * 3. 非平台模式下 workflow-runs 写入 cwd/.sillyspec/.runtime/
8
- *
9
- * 跑法: node test/platform-artifacts.test.mjs
10
- */
11
-
12
- import { join, dirname } from 'path'
13
- import { existsSync, mkdirSync, rmSync, readFileSync, readdirSync, writeFileSync } from 'fs'
14
- import { fileURLToPath } from 'url'
15
- import { randomUUID } from 'crypto'
16
- import { tmpdir } from 'os'
17
-
18
- const __dirname = dirname(fileURLToPath(import.meta.url))
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 hasPathSegments(value, segments) {
38
- const parts = value.split(/[\\/]+/)
39
- for (let i = 0; i <= parts.length - segments.length; i++) {
40
- if (segments.every((segment, offset) => parts[i + offset] === segment)) return true
41
- }
42
- return false
43
- }
44
-
45
- // ── 测试 1:saveWorkflowRun 平台模式路径正确 ──
46
- console.log('\n=== Test 1: saveWorkflowRun 平台模式写入路径 ===')
47
- {
48
- const { saveWorkflowRun } = await import('../src/workflow.js')
49
- const tmpRoot = join(tmpdir(), `test-artifacts-${randomUUID().slice(0, 8)}`)
50
- const runtimeRoot = join(tmpRoot, 'runtime')
51
- const scanRunId = 'scan-20260614-test-001'
52
-
53
- const result = {
54
- workflow: 'scan-docs',
55
- project: 'test-project',
56
- status: 'pass',
57
- spec_version: 1,
58
- roles: [],
59
- workflow_checks: [],
60
- failures: [],
61
- }
62
-
63
- const saved = saveWorkflowRun(result, {
64
- cwd: '/fake/cwd',
65
- source: 'test',
66
- stage: 'scan',
67
- runtimeRoot,
68
- scanRunId,
69
- })
70
-
71
- const expectedDir = join(runtimeRoot, 'scan-runs', scanRunId, 'workflow-runs')
72
- assert('workflow-runs 目录存在', existsSync(expectedDir))
73
- assert('workflow-runs 文件存在', existsSync(saved), `路径: ${saved}`)
74
- assert('路径在 runtime-root 下', saved.startsWith(runtimeRoot), `路径: ${saved}`)
75
- assert('路径包含 scan-runs', hasPathSegments(saved, ['scan-runs', scanRunId, 'workflow-runs']), `路径: ${saved}`)
76
- assert('路径包含 scanRunId', hasPathSegments(saved, [scanRunId]), `路径: ${saved}`)
77
-
78
- // 验证 JSON 内容
79
- const content = JSON.parse(readFileSync(saved, 'utf8'))
80
- assert('JSON 有 run_id', !!content.run_id)
81
- assert('JSON 有 created_at', !!content.created_at)
82
- assert('JSON source = test', content.source === 'test')
83
- assert('JSON stage = scan', content.stage === 'scan')
84
- assert('JSON workflow = scan-docs', content.workflow === 'scan-docs')
85
-
86
- cleanup(tmpRoot)
87
- }
88
-
89
- // ── 测试 2:saveWorkflowRun 本地模式路径正确 ──
90
- console.log('\n=== Test 2: saveWorkflowRun 本地模式写入路径 ===')
91
- {
92
- const { saveWorkflowRun } = await import('../src/workflow.js')
93
- const tmpCwd = join(tmpdir(), `test-artifacts-local-${randomUUID().slice(0, 8)}`)
94
- const sillyspecDir = join(tmpCwd, '.sillyspec', '.runtime', 'workflow-runs')
95
-
96
- const result = {
97
- workflow: 'test-wf',
98
- project: 'default',
99
- status: 'fail',
100
- spec_version: 1,
101
- roles: [],
102
- workflow_checks: [],
103
- failures: ['check-1 failed'],
104
- }
105
-
106
- const saved = saveWorkflowRun(result, {
107
- cwd: tmpCwd,
108
- source: 'test',
109
- stage: 'scan',
110
- // 不传 runtimeRoot 和 scanRunId
111
- })
112
-
113
- assert('本地模式文件存在', existsSync(saved))
114
- assert('本地路径在 .sillyspec/.runtime 下', hasPathSegments(saved, ['.sillyspec', '.runtime', 'workflow-runs']), `路径: ${saved}`)
115
-
116
- const content = JSON.parse(readFileSync(saved, 'utf8'))
117
- assert('本地 JSON status = fail', content.status === 'fail')
118
- assert('本地 JSON 有 failures', Array.isArray(content.failures))
119
-
120
- cleanup(tmpCwd)
121
- }
122
-
123
- // ── 测试 3:manifest 结构验证(从 run.js 源码静态检查) ──
124
- console.log('\n=== Test 3: manifest.json 结构字段 ===')
125
- {
126
- const { readFile } = await import('fs/promises')
127
- const runSrc = await readFile(join(__dirname, '..', 'src', 'run.js'), 'utf8')
128
-
129
- // 平台模式 manifest 初始化
130
- assert('manifest 包含 workspace_id', runSrc.includes('workspace_id:'))
131
- assert('manifest 包含 scan_run_id', runSrc.includes('scan_run_id:'))
132
- assert('manifest 包含 source_commit', runSrc.includes('source_commit:'))
133
- assert('manifest 包含 source_commit_error', runSrc.includes('source_commit_error:'))
134
- assert('manifest 包含 generated_at', runSrc.includes('generated_at:'))
135
- assert('manifest 包含 schema_version', runSrc.includes('schema_version:'))
136
- assert('manifest 包含 postcheck_result_path', runSrc.includes('postcheck_result_path:'))
137
- assert('manifest 包含 workflow_runs_dir', runSrc.includes('workflow_runs_dir:'))
138
-
139
- // postcheck_result_path 在 postcheck 写入后填充
140
- assert('manifest.postcheck_result_path 下游填充', runSrc.includes('manifest.postcheck_result_path = postcheckJsonPath'))
141
-
142
- // workflow_runs_dir 使用 runtimeRoot + scanRunId
143
- assert('workflow_runs_dir 基于 runtimeRoot', runSrc.includes("join(platformOpts.runtimeRoot, 'scan-runs'"))
144
- }
145
-
146
- // ── 测试 4:平台指针状态更新(源码检查) ──
147
- console.log('\n=== Test 4: 平台指针 scan 完成后状态更新 ===')
148
- {
149
- const { readFile } = await import('fs/promises')
150
- const runSrc = await readFile(join(__dirname, '..', 'src', 'run.js'), 'utf8')
151
-
152
- assert('scan 完成后读取 pointer 文件', runSrc.includes('pointerPath'))
153
- assert('pointer status 使用 POINTER_STATUS 枚举', runSrc.includes('POINTER_STATUS'))
154
- assert('pointer 记录 completedAt', runSrc.includes('pointer.completedAt'))
155
- assert('pointer 记录 scanStatus', runSrc.includes('pointer.scanStatus'))
156
- }
157
-
158
- // ── 测试 5:saveWorkflowRun 调用点传入 runtimeRoot(源码检查) ──
159
- console.log('\n=== Test 5: run.js 调用 saveWorkflowRun 传入平台参数 ===')
160
- {
161
- const { readFile } = await import('fs/promises')
162
- const runSrc = await readFile(join(__dirname, '..', 'src', 'run.js'), 'utf8')
163
-
164
- // 找到 saveWorkflowRun 调用
165
- const calls = runSrc.match(/saveWorkflowRun\([^)]+\{[^}]+\}/gs)
166
- assert('至少有 2 处 saveWorkflowRun 调用', calls && calls.length >= 2, `实际: ${calls?.length}`)
167
-
168
- // 检查调用是否包含 runtimeRoot 传递
169
- const hasRuntimeRoot = runSrc.includes('platformOpts.runtimeRoot ? { runtimeRoot: platformOpts.runtimeRoot }')
170
- assert('saveWorkflowRun 调用传入 runtimeRoot', hasRuntimeRoot, '未发现 runtimeRoot 传递')
171
-
172
- const hasScanRunId = runSrc.includes('platformOpts.scanRunId ? { scanRunId: platformOpts.scanRunId }')
173
- assert('saveWorkflowRun 调用传入 scanRunId', hasScanRunId, '未发现 scanRunId 传递')
174
- }
175
-
176
- // ── 结果 ──
177
- console.log(`\n${'='.repeat(50)}`)
178
- console.log(`✅ 通过: ${passed.length} ❌ 失败: ${failed.length}`)
179
- console.log(`${'='.repeat(50)}`)
180
-
181
- if (failed.length > 0) {
182
- console.log('\n失败详情:')
183
- for (const f of failed) {
184
- console.log(` ❌ ${f.label}`)
185
- if (f.detail) console.log(` ${f.detail}`)
186
- }
187
- throw new Error('platform-artifacts test failed')
188
- } else {
189
- console.log('\n🎉 平台产物协议测试全部通过!')
190
- }
@@ -1,199 +0,0 @@
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,179 +0,0 @@
1
- /**
2
- * 平台 scan 恢复链路测试
3
- *
4
- * 验证:
5
- * 1. 首次带 --spec-dir 跑 scan,pointer 文件被创建
6
- * 2. 后续 --done 不带参数能从 pointer 恢复平台参数
7
- * 3. scan 完成后 pointer 状态标记为 scan_completed
8
- * 4. pointer 异常残留能被检测
9
- *
10
- * 跑法: node test/platform-recovery-chain.test.mjs
11
- */
12
-
13
- import { join, basename } from 'path'
14
- import { existsSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'fs'
15
- import { execSync } from 'child_process'
16
- import { fileURLToPath } from 'url'
17
- import { dirname } from 'path'
18
- import { randomUUID } from 'crypto'
19
- import { tmpdir } from 'os'
20
-
21
- const __dirname = dirname(fileURLToPath(import.meta.url))
22
- const binCLI = join(__dirname, '..', 'src', 'index.js')
23
- const passed = []
24
- const failed = []
25
-
26
- function assert(label, condition, detail) {
27
- if (condition) {
28
- passed.push(label)
29
- console.log(` ✅ PASS: ${label}`)
30
- } else {
31
- failed.push({ label, detail })
32
- console.log(` ❌ FAIL: ${label}`)
33
- if (detail) console.log(` ${detail}`)
34
- }
35
- }
36
-
37
- function cleanup(dir) {
38
- try { rmSync(dir, { recursive: true, force: true }) } catch {}
39
- }
40
-
41
- function run(cmd, opts = {}) {
42
- try {
43
- return execSync(cmd, { encoding: 'utf8', timeout: 15_000, ...opts })
44
- } catch (e) {
45
- return e.stdout || e.message
46
- }
47
- }
48
-
49
- // ── 测试 1:pointer 文件创建和内容 ──
50
- console.log('\n=== Test 1: pointer 文件创建 ===')
51
- {
52
- const tmpCwd = join(tmpdir(), `recovery-test-${randomUUID().slice(0, 8)}`)
53
- const tmpSpec = join(tmpdir(), `recovery-test-spec-${randomUUID().slice(0, 8)}`)
54
- const tmpRuntime = join(tmpdir(), `recovery-test-rt-${randomUUID().slice(0, 8)}`)
55
-
56
- try {
57
- mkdirSync(tmpCwd, { recursive: true })
58
- writeFileSync(join(tmpCwd, 'package.json'), '{}')
59
-
60
- // init 项目(使用外部 specDir)
61
- const initOut = run(`node "${binCLI}" init "${tmpCwd}" --spec-root "${tmpSpec}"`)
62
- assert('init 成功', !initOut.includes('❌'))
63
-
64
- // run scan 会触发参数持久化
65
- run(`node "${binCLI}" --dir "${tmpCwd}" run scan --spec-root "${tmpSpec}" 2>&1 || true`)
66
-
67
- const pointerPath = join(tmpCwd, '.sillyspec-platform.json')
68
- assert('pointer 文件存在', existsSync(pointerPath))
69
-
70
- const pointer = JSON.parse(readFileSync(pointerPath, 'utf8'))
71
- assert('pointer 有 specRoot', !!pointer.specRoot)
72
- assert('pointer 有 savedAt', !!pointer.savedAt)
73
- assert('pointer specRoot 指向外部', pointer.specRoot === tmpSpec)
74
-
75
- // pointer 不应包含 status(初始创建时)
76
- assert('初始 pointer 无 status 字段', !('status' in pointer))
77
- } finally {
78
- cleanup(tmpCwd)
79
- cleanup(tmpSpec)
80
- }
81
- }
82
-
83
- // ── 测试 2:--done 不带参数能恢复 ──
84
- console.log('\n=== Test 2: --done 恢复平台参数 ===')
85
- {
86
- const tmpCwd = join(tmpdir(), `recovery-test2-${randomUUID().slice(0, 8)}`)
87
- const tmpSpec = join(tmpdir(), `recovery-test2-spec-${randomUUID().slice(0, 8)}`)
88
- const tmpRuntime = join(tmpdir(), `recovery-test2-rt-${randomUUID().slice(0, 8)}`)
89
- const scanRunId = `scan-${Date.now()}`
90
-
91
- try {
92
- mkdirSync(tmpCwd, { recursive: true })
93
- writeFileSync(join(tmpCwd, 'package.json'), '{}')
94
-
95
- // init + 触发 run 写入 pointer
96
- run(`node "${binCLI}" init "${tmpCwd}" --spec-root "${tmpSpec}"`)
97
- run(`node "${binCLI}" --dir "${tmpCwd}" run scan --spec-root "${tmpSpec}" 2>&1 || true`)
98
-
99
- // 手动模拟一个"scan 第一步 --done"(不带 --spec-root)
100
- // 关键验证:--done 时能从 pointer 恢复参数
101
- const doneOut = run(`node "${binCLI}" --dir "${tmpCwd}" run scan --done --input "test" --output "test output" 2>&1`, { cwd: tmpCwd })
102
-
103
- // --done 应该能找到平台参数,不应该报"需要 --spec-root"
104
- assert('--done 恢复成功', !doneOut.includes('需要 --spec-root') && !doneOut.includes('缺少 specRoot'))
105
- } finally {
106
- cleanup(tmpCwd)
107
- cleanup(tmpSpec)
108
- cleanup(tmpRuntime)
109
- }
110
- }
111
-
112
- // ── 测试 3:manifest 包含路径和 pointer 信息 ──
113
- console.log('\n=== Test 3: manifest 路径字段 ===')
114
- {
115
- const { readFile } = await import('fs/promises')
116
- const runSrc = await readFile(join(__dirname, '..', 'src', 'run.js'), 'utf8')
117
-
118
- // manifest 初始化中包含三路径
119
- assert('manifest 有 source_root: cwd', runSrc.includes('source_root: cwd'))
120
- assert('manifest 有 spec_root', runSrc.includes('spec_root: platformOpts'))
121
- assert('manifest 有 runtime_root', runSrc.includes('runtime_root: platformOpts'))
122
- assert('manifest 有 platform_pointer_path', runSrc.includes('platform_pointer_path:'))
123
- assert('manifest platform_pointer_status 使用枚举', runSrc.includes('POINTER_STATUS') || runSrc.includes('POINTER_STATUS.ACTIVE'))
124
- }
125
-
126
- // ── 测试 4:异常 pointer 检测 ──
127
- console.log('\n=== Test 4: 异常 pointer 残留检测 ===')
128
- {
129
- const tmpCwd = join(tmpdir(), `recovery-test4-${randomUUID().slice(0, 8)}`)
130
- const tmpSpec = join(tmpdir(), `recovery-test4-spec-${randomUUID().slice(0, 8)}`)
131
-
132
- try {
133
- mkdirSync(tmpCwd, { recursive: true })
134
- writeFileSync(join(tmpCwd, 'package.json'), '{}')
135
-
136
- // init + 触发 run 写入 pointer
137
- run(`node "${binCLI}" init "${tmpCwd}" --spec-root "${tmpSpec}"`)
138
- run(`node "${binCLI}" --dir "${tmpCwd}" run scan --spec-root "${tmpSpec}" 2>&1 || true`)
139
-
140
- const pointerPath = join(tmpCwd, '.sillyspec-platform.json')
141
- assert('pointer 文件存在', existsSync(pointerPath))
142
-
143
- // 模拟损坏的 pointer(缺少 specRoot)
144
- writeFileSync(pointerPath, JSON.stringify({ workspaceId: 'fake', savedAt: new Date().toISOString() }))
145
- const badOut = run(`node "${binCLI}" --dir "${tmpCwd}" run scan 2>&1`)
146
- assert('损坏 pointer 报错', badOut.includes('缺少 specRoot') || badOut.includes('❌'))
147
-
148
- // 模拟有效 pointer(手动修复)
149
- writeFileSync(pointerPath, JSON.stringify({
150
- specRoot: tmpSpec,
151
- runtimeRoot: join(tmpdir(), 'fake-rt'),
152
- workspaceId: 'test',
153
- scanRunId: 'scan-test',
154
- savedAt: new Date().toISOString(),
155
- }))
156
- // init 不应报错
157
- const goodOut = run(`node "${binCLI}" --dir "${tmpCwd}" run scan --help 2>&1`)
158
- assert('有效 pointer 不报错', !goodOut.includes('缺少 specRoot'))
159
- } finally {
160
- cleanup(tmpCwd)
161
- cleanup(tmpSpec)
162
- }
163
- }
164
-
165
- // ── 结果 ──
166
- console.log(`\n${'='.repeat(50)}`)
167
- console.log(`✅ 通过: ${passed.length} ❌ 失败: ${failed.length}`)
168
- console.log(`${'='.repeat(50)}`)
169
-
170
- if (failed.length > 0) {
171
- console.log('\n失败详情:')
172
- for (const f of failed) {
173
- console.log(` ❌ ${f.label}`)
174
- if (f.detail) console.log(` ${f.detail}`)
175
- }
176
- throw new Error('platform-recovery-chain test failed')
177
- } else {
178
- console.log('\n🎉 平台恢复链路测试全部通过!')
179
- }