sillyspec 3.20.1 → 3.20.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) 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 +2 -8
  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 -685
  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 -2920
  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 -468
  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/.npmrc-tmp +0 -1
  131. package/CLAUDE.md +0 -18
  132. package/docs/brainstorm-plan-contract.md +0 -64
  133. package/docs/plan-execute-contract.md +0 -123
  134. package/docs/platform-scan-protocol.md +0 -298
  135. package/docs/revision-mode.md +0 -115
  136. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +0 -99
  137. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +0 -218
  138. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +0 -167
  139. package/docs/sillyspec/file-lifecycle/storage-and-state.md +0 -148
  140. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +0 -193
  141. package/docs/sillyspec/file-lifecycle.md +0 -125
  142. package/docs/workflow-contract-regression.md +0 -106
  143. package/docs/worktree-isolation.md +0 -252
  144. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +0 -1
  145. package/packages/dashboard/dist/prototype-dashboard.html +0 -836
  146. package/packages/dashboard/dist/prototype-overview.html +0 -256
  147. package/packages/dashboard/public/prototype-dashboard.html +0 -836
  148. package/packages/dashboard/public/prototype-overview.html +0 -256
  149. package/packages/dashboard/src/components/HResizeHandle.vue +0 -48
  150. package/packages/dashboard/src/components/ProjectCard.vue +0 -187
  151. package/packages/dashboard/src/components/VResizeHandle.vue +0 -61
  152. package/packages/dashboard/src/composables/useLayout.js +0 -131
  153. package/src/brainstorm-postcheck.js +0 -158
  154. package/src/change-list.js +0 -52
  155. package/src/change-risk-profile.js +0 -352
  156. package/src/classify-change.js +0 -73
  157. package/src/constants.js +0 -70
  158. package/src/contract-matrix.js +0 -278
  159. package/src/db.js +0 -201
  160. package/src/endpoint-extractor.js +0 -315
  161. package/src/hooks/claude-pre-tool-use.cjs +0 -125
  162. package/src/hooks/worktree-guard.js +0 -761
  163. package/src/knowledge-match.js +0 -130
  164. package/src/modules.js +0 -482
  165. package/src/scan-postcheck.js +0 -380
  166. package/src/stage-contract.js +0 -700
  167. package/src/stages/brainstorm-auto.js +0 -229
  168. package/src/stages/explore.js +0 -34
  169. package/src/stages/knowledge.js +0 -498
  170. package/src/stages/plan-postcheck.js +0 -513
  171. package/src/sync.js +0 -497
  172. package/src/task-review.js +0 -346
  173. package/src/workflow.js +0 -785
  174. package/src/worktree-apply.js +0 -549
  175. package/src/worktree.js +0 -932
  176. package/templates/workflows/archive-impact.yaml +0 -79
  177. package/templates/workflows/scan-docs.yaml +0 -132
  178. package/test/brainstorm-plan-contract.test.mjs +0 -273
  179. package/test/check-syntax.mjs +0 -26
  180. package/test/cli-top-level-aliases.test.mjs +0 -174
  181. package/test/contract-artifacts.test.mjs +0 -323
  182. package/test/decision-supersede.test.mjs +0 -277
  183. package/test/knowledge-match.test.mjs +0 -231
  184. package/test/plan-execute-contract.test.mjs +0 -330
  185. package/test/plan-optimization.test.mjs +0 -572
  186. package/test/platform-artifacts.test.mjs +0 -190
  187. package/test/platform-failure-samples.test.mjs +0 -199
  188. package/test/platform-recovery-chain.test.mjs +0 -179
  189. package/test/platform-recovery.test.mjs +0 -167
  190. package/test/platform-scan-p0.test.mjs +0 -175
  191. package/test/revision-v1.test.mjs +0 -1145
  192. package/test/run-sanitize-project-name.test.mjs +0 -51
  193. package/test/run-scan-postcheck-fail.test.mjs +0 -64
  194. package/test/run-scan-project-parse.test.mjs +0 -77
  195. package/test/run-tests.mjs +0 -48
  196. package/test/scan-docs-yaml-placeholders.test.mjs +0 -84
  197. package/test/scan-knowledge.test.mjs +0 -175
  198. package/test/scan-paths.test.mjs +0 -68
  199. package/test/scan-postcheck-project-priority.test.mjs +0 -85
  200. package/test/scan-postcheck.test.mjs +0 -183
  201. package/test/scan-workflow-anyfailed-block.test.mjs +0 -52
  202. package/test/spec-dir.test.mjs +0 -206
  203. package/test/stage-contract-failed-post-check.test.mjs +0 -102
  204. package/test/stage-contract.test.mjs +0 -299
  205. package/test/stage-definitions.test.mjs +0 -39
  206. package/test/wait-gates.test.mjs +0 -501
  207. package/test/workflow-spec-base.test.mjs +0 -142
  208. package/test/worktree-guard.test.mjs +0 -136
  209. package/test/worktree-native-overlay.test.mjs +0 -188
@@ -1,183 +0,0 @@
1
- /**
2
- * scan-postcheck.test.mjs — CLI 层 post-check 测试
3
- */
4
-
5
- import { join, resolve, dirname, basename } from 'path'
6
- import { existsSync, mkdirSync, writeFileSync, rmSync } from 'fs'
7
- import { fileURLToPath, pathToFileURL } from 'url'
8
- import { tmpdir } from 'os'
9
-
10
- const __filename = fileURLToPath(import.meta.url)
11
- const __dirname = dirname(__filename)
12
- const root = resolve(__dirname, '..')
13
-
14
- const { runScanPostCheck } = await import(pathToFileURL(join(root, 'src', 'scan-postcheck.js')).href)
15
-
16
- let passed = 0, failed = 0
17
-
18
- function assert(cond, msg) {
19
- if (cond) { console.log(` ✅ PASS: ${msg}`); passed++ }
20
- else { console.log(` ❌ FAIL: ${msg}`); failed++ }
21
- }
22
-
23
- function setup(name) {
24
- const cwd = join(tmpdir(), `pc-${name}`)
25
- mkdirSync(cwd, { recursive: true })
26
- return cwd
27
- }
28
- function specSetup(name) {
29
- const d = join(tmpdir(), `pc-${name}-spec`)
30
- mkdirSync(d, { recursive: true })
31
- return d
32
- }
33
- function clean(...dirs) { for (const d of dirs) try { rmSync(d, { recursive: true, force: true }) } catch {} }
34
-
35
- const DOCS = ['ARCHITECTURE.md','CONVENTIONS.md','STRUCTURE.md','INTEGRATIONS.md','TESTING.md','CONCERNS.md','PROJECT.md']
36
-
37
- // 写入全部 7 份文档,项目名 = basename(cwd)
38
- function writeFull(cwd, specDir) {
39
- const proj = basename(cwd)
40
- for (const d of DOCS) {
41
- const p = join(specDir, 'docs', proj, 'scan', d)
42
- mkdirSync(dirname(p), { recursive: true })
43
- writeFileSync(p, 'author: bot\ncreated_at: 2026-06-08 10:00:00\n# doc\n')
44
- }
45
- // knowledge 目录 + INDEX.md(scan 已产出知识)
46
- mkdirSync(join(specDir, 'knowledge'), { recursive: true })
47
- writeFileSync(join(specDir, 'knowledge', 'INDEX.md'), '# Knowledge Index\n')
48
- }
49
-
50
- // 写入前 N 份文档
51
- function writeN(cwd, specDir, n) {
52
- const proj = basename(cwd)
53
- for (let i = 0; i < n; i++) {
54
- const p = join(specDir, 'docs', proj, 'scan', DOCS[i])
55
- mkdirSync(dirname(p), { recursive: true })
56
- writeFileSync(p, 'author: bot\ncreated_at: 2026-06-08 10:00:00\n# doc\n')
57
- }
58
- }
59
-
60
- // ── 1: source_root 有文档 → failed ──
61
- console.log('\n=== Test 1: source_root 泄漏 → failed_post_check ===')
62
- {
63
- const cwd = setup('t1'), spec = specSetup('t1')
64
- const proj = basename(cwd)
65
- mkdirSync(join(cwd, '.sillyspec/docs', proj, 'scan'), { recursive: true })
66
- writeFileSync(join(cwd, '.sillyspec/docs', proj, 'scan', 'ARCHITECTURE.md'), '# leak')
67
- const r = runScanPostCheck({ cwd, specDir: spec })
68
- assert(r.status === 'failed_post_check', `状态: ${r.status}`)
69
- assert(r.checks.some(c => c.name === 'source_root_docs_leak'), `source_root_docs_leak`)
70
- clean(cwd, spec)
71
- }
72
-
73
- // ── 2: spec 无文档 → failed ──
74
- console.log('\n=== Test 2: spec 无文档 → failed_post_check ===')
75
- {
76
- const cwd = setup('t2'), spec = specSetup('t2')
77
- const r = runScanPostCheck({ cwd, specDir: spec })
78
- assert(r.status === 'failed_post_check', `状态: ${r.status}`)
79
- assert(r.checks.some(c => c.name === 'all_docs_missing'), `all_docs_missing`)
80
- clean(cwd, spec)
81
- }
82
-
83
- // ── 3: 缺部分 required 文档 → failed ──
84
- console.log('\n=== Test 3: 部分缺失 → failed_post_check ===')
85
- {
86
- const cwd = setup('t3'), spec = specSetup('t3')
87
- writeN(cwd, spec, 6)
88
- const r = runScanPostCheck({ cwd, specDir: spec })
89
- assert(r.status === 'failed_post_check', `状态: ${r.status}`)
90
- assert(r.checks.some(c => c.name === 'partial_docs_missing'), `partial_docs_missing`)
91
- clean(cwd, spec)
92
- }
93
-
94
- // ── 4: local.yaml 命令不存在 → warnings ──
95
- console.log('\n=== Test 4: local.yaml 命令不存在 → completed_with_warnings ===')
96
- {
97
- const cwd = setup('t4'), spec = specSetup('t4')
98
- writeFull(cwd, spec)
99
- writeFileSync(join(spec, 'local.yaml'),
100
- 'project:\n type: nodejs\ncommands:\n build: "npm run build"\n test: "npm run test"\n lint: "npm run lint"\n')
101
- writeFileSync(join(cwd, 'package.json'), '{"name":"t4","scripts":{"start":"node server.js"}}')
102
- const r = runScanPostCheck({ cwd, specDir: spec })
103
- assert(r.status === 'completed_with_warnings', `状态: ${r.status}`)
104
- assert(r.checks.some(c => c.name === 'local_config_invalid'), `local_config_invalid`)
105
- clean(cwd, spec)
106
- }
107
-
108
- // ── 5-8: AI 输出错误标记 → warnings ──
109
- const errorCases = [
110
- { id: 'e5', name: 'tool_use_error', output: 'tool_use_error: file not found' },
111
- { id: 'e6', name: 'API Error 529', output: 'API Error 529 server overloaded' },
112
- { id: 'e7', name: 'rate_limit', output: 'rate limit exhausted' },
113
- { id: 'e8', name: 'fallback', output: 'fallback to default, skipped validation' },
114
- ]
115
- for (const ec of errorCases) {
116
- console.log(`\n=== Test: ${ec.name} → completed_with_warnings ===`)
117
- const cwd = setup(ec.id), spec = specSetup(ec.id)
118
- writeFull(cwd, spec)
119
- const r = runScanPostCheck({ cwd, specDir: spec, outputText: ec.output })
120
- assert(r.status === 'completed_with_warnings', `${ec.name}: 状态 ${r.status}`)
121
- clean(cwd, spec)
122
- }
123
-
124
- // ── 9: 文档缺 header → warnings ──
125
- console.log('\n=== Test 9: 文档缺 header → completed_with_warnings ===')
126
- {
127
- const cwd = setup('t9'), spec = specSetup('t9')
128
- const proj = basename(cwd)
129
- for (const d of DOCS) {
130
- const p = join(spec, 'docs', proj, 'scan', d)
131
- mkdirSync(dirname(p), { recursive: true })
132
- writeFileSync(p, '# no header\n')
133
- }
134
- const r = runScanPostCheck({ cwd, specDir: spec })
135
- assert(r.status === 'completed_with_warnings', `状态: ${r.status}`)
136
- assert(r.checks.some(c => c.name === 'docs_missing_header'), `docs_missing_header`)
137
- clean(cwd, spec)
138
- }
139
-
140
- // ── 10: 全部通过 → success ──
141
- console.log('\n=== Test 10: 全部通过 → success ===')
142
- {
143
- const cwd = setup('t10'), spec = specSetup('t10')
144
- writeFull(cwd, spec)
145
- const r = runScanPostCheck({ cwd, specDir: spec, outputText: 'done' })
146
- assert(r.status === 'success', `状态: ${r.status}`)
147
- assert(r.checks.length === 0, `checks.length=0`)
148
- clean(cwd, spec)
149
- }
150
-
151
- // ── 11: 非平台模式 ──
152
- console.log('\n=== Test 11: 非平台模式 ===')
153
- {
154
- const cwd = setup('t11')
155
- const proj = basename(cwd)
156
- for (let i = 0; i < 5; i++) {
157
- const p = join(cwd, '.sillyspec', 'docs', proj, 'scan', DOCS[i])
158
- mkdirSync(dirname(p), { recursive: true })
159
- writeFileSync(p, 'author: bot\ncreated_at: now\n# doc\n')
160
- }
161
- const r = runScanPostCheck({ cwd, specDir: null })
162
- assert(r.status === 'completed_with_warnings', `非平台: ${r.status}`)
163
- assert(!r.checks.some(c => c.name === 'source_root_docs_leak'), `无 source_root_leak`)
164
- clean(cwd)
165
- }
166
-
167
- // ── 12: 多问题 failed 优先 ──
168
- console.log('\n=== Test 12: failed 优先 ===')
169
- {
170
- const cwd = setup('t12'), spec = specSetup('t12')
171
- const proj = basename(cwd)
172
- mkdirSync(join(cwd, '.sillyspec/docs', proj, 'scan'), { recursive: true })
173
- writeFileSync(join(cwd, '.sillyspec/docs', proj, 'scan', 'ARCHITECTURE.md'), '# leak')
174
- writeN(cwd, spec, 3)
175
- const r = runScanPostCheck({ cwd, specDir: spec })
176
- assert(r.status === 'failed_post_check', `failed 优先: ${r.status}`)
177
- clean(cwd, spec)
178
- }
179
-
180
- console.log(`\n${'='.repeat(50)}`)
181
- console.log(`✅ 通过: ${passed} ❌ 失败: ${failed}`)
182
- console.log(`${'='.repeat(50)}`)
183
- if (failed > 0) throw new Error(`${failed} test(s) failed`)
@@ -1,52 +0,0 @@
1
- /**
2
- * task-07: run.js workflow post_check anyFailed 阻断
3
- *
4
- * 覆盖:
5
- * - AC: workflow post_check anyFailed 时返回 { stageCompleted:false, currentIdx, nextPendingIdx: currentIdx }
6
- * (与 task-06 平台模式 scan-postcheck 失败分支 return 结构对齐)
7
- *
8
- * 用源码字符串匹配 + 行为模拟。
9
- */
10
- import { readFileSync } from 'fs'
11
- import { join, dirname } from 'path'
12
- import { fileURLToPath } from 'url'
13
-
14
- const __dirname = dirname(fileURLToPath(import.meta.url))
15
- const runPath = join(__dirname, '..', 'src', 'run.js')
16
- const src = readFileSync(runPath, 'utf8')
17
-
18
- let passed = 0
19
- let failed = 0
20
- function assert (cond, msg) {
21
- if (cond) { console.log(`✅ PASS: ${msg}`); passed++ }
22
- else { console.error(`❌ FAIL: ${msg}`); failed++ }
23
- }
24
-
25
- console.log('=== workflow post_check anyFailed 阻断 ===')
26
-
27
- // 锚点:scan 深度扫描 workflow post_check 段
28
- const anchor = src.indexOf('if (anyFailed)')
29
- assert(anchor > 0, '找到 if (anyFailed) 锚点')
30
-
31
- // 取该 if 块前后 500 字符(向前找变量声明,向后找 return)
32
- const tail = src.slice(anchor, anchor + 500)
33
-
34
- // 必须有 return { stageCompleted: false ... }
35
- assert(/return\s*\{\s*stageCompleted:\s*false/.test(tail),
36
- 'anyFailed 分支含 return { stageCompleted: false }')
37
-
38
- // 必须有 nextPendingIdx: currentIdx
39
- assert(/nextPendingIdx:\s*currentIdx/.test(tail),
40
- 'anyFailed 分支返回 nextPendingIdx: currentIdx')
41
-
42
- // 必须有 currentIdx(保持当前 step 不推进)
43
- assert(/currentIdx/.test(tail), 'anyFailed 分支保留 currentIdx 字段')
44
-
45
- // 必须有 console.log 警告(保留用户可见提示)
46
- assert(/console\.log/.test(tail) && /存在检查失败项|重试提示/.test(src.slice(anchor - 200, anchor + 300)),
47
- 'anyFailed 分支保留 console.log 用户提示')
48
-
49
- console.log(`\n${'='.repeat(50)}`)
50
- console.log(`✅ 通过: ${passed} ❌ 失败: ${failed}`)
51
- console.log(`${'='.repeat(50)}`)
52
- if (failed > 0) process.exit(1)
@@ -1,206 +0,0 @@
1
- /**
2
- * --spec-dir 功能测试
3
- *
4
- * 测试点:
5
- * 1. ProgressManager 外部 specDir 路径正确
6
- * 2. init 外部 specDir 不污染源码
7
- * 3. 默认模式不受影响
8
- * 4. 平台模式 prompt 注入(scan/brainstorm/plan/execute/verify/quick)
9
- * 5. 非 platform 模式占位符替换(无 undefined/null)
10
- * 6. --spec-dir 与 --spec-root 兼容
11
- * 7. progress 使用外部 specDir
12
- */
13
-
14
- import { join, resolve, basename, dirname } from 'path'
15
- import { existsSync, mkdirSync, writeFileSync, rmSync } from 'fs'
16
- import { fileURLToPath, pathToFileURL } from 'url'
17
- import { execSync } from 'child_process'
18
- import { tmpdir } from 'os'
19
-
20
- const __filename = fileURLToPath(import.meta.url)
21
- const __dirname = dirname(__filename)
22
- const root = resolve(__dirname, '..')
23
- const binCLI = join(root, 'bin', 'sillyspec.js')
24
-
25
- function imp(path) {
26
- return import(pathToFileURL(path).href)
27
- }
28
-
29
- let passed = 0
30
- let failed = 0
31
-
32
- function assert(condition, msg) {
33
- if (condition) {
34
- console.log(` ✅ PASS: ${msg}`)
35
- passed++
36
- } else {
37
- console.log(` ❌ FAIL: ${msg}`)
38
- failed++
39
- }
40
- }
41
-
42
- function tmpDir(name) {
43
- const dir = join(tmpdir(), `spec-dir-test-${name}-${Date.now()}`)
44
- mkdirSync(dir, { recursive: true })
45
- return dir
46
- }
47
-
48
- function cleanup(dir) {
49
- try { rmSync(dir, { recursive: true, force: true }) } catch {}
50
- }
51
-
52
- function run(cmd) {
53
- return execSync(cmd, { encoding: 'utf8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] })
54
- }
55
-
56
- // ── Test 1: ProgressManager 外部 specDir ──
57
- console.log('\n=== Test 1: ProgressManager 外部 specDir ===')
58
- {
59
- const { ProgressManager } = await imp(join(root, 'src', 'progress.js'))
60
- const tmp = tmpDir('pm')
61
- const specDir = join(tmp, 'external-spec')
62
-
63
- const pm = new ProgressManager({ specDir })
64
- assert(pm._getSpecDir(tmp) === specDir, `_getSpecDir 返回自定义路径`)
65
-
66
- // 无自定义 specDir 时,resolveSpecDir 会向上查找 .sillyspec 目录
67
- // 在测试环境中可能命中上层已有的 .sillyspec,所以只检查返回值是否有效路径
68
- const pm2 = new ProgressManager()
69
- const resolved = pm2._getSpecDir(tmp)
70
- assert(typeof resolved === 'string' && resolved.length > 0 && resolved.endsWith('.sillyspec'),
71
- `_getSpecDir 无自定义时返回有效 .sillyspec 路径 (got: ${resolved})`)
72
-
73
- assert(pm._runtimePath(tmp) === join(specDir, '.runtime'), `_runtimePath 基于 specDir`)
74
- assert(pm._changePath(tmp, 'c') === join(specDir, 'changes', 'c'), `_changePath 基于 specDir`)
75
-
76
- // 外部 specDir 时 _ensureGitignore 应跳过
77
- const gitignoreResult = pm._ensureGitignore(tmp)
78
- assert(gitignoreResult === undefined, `外部 specDir 时 _ensureGitignore 跳过`)
79
-
80
- cleanup(tmp)
81
- }
82
-
83
- // ── Test 2: init 外部 specDir 不污染源码 ──
84
- console.log('\n=== Test 2: init 外部 specDir 不污染源码 ===')
85
- {
86
- const { cmdInit } = await imp(join(root, 'src', 'init.js'))
87
- const projectDir = tmpDir('project')
88
- const specDir = tmpDir('spec')
89
-
90
- await cmdInit(projectDir, { specDir })
91
-
92
- assert(!existsSync(join(projectDir, '.sillyspec')), '源码目录不含 .sillyspec')
93
- assert(!existsSync(join(projectDir, '.gitignore')), '外部 specDir 时不创建 .gitignore')
94
- assert(existsSync(join(specDir, 'projects')), `specDir/projects 存在`)
95
- assert(existsSync(join(specDir, 'docs')), `specDir/docs 存在`)
96
- assert(existsSync(join(specDir, '.runtime', 'sillyspec.db')), `specDir/.runtime/sillyspec.db 存在`)
97
- assert(existsSync(join(specDir, 'workflows')), `specDir/workflows 存在`)
98
- assert(existsSync(join(projectDir, '.claude')), `源码目录 .claude 存在(工具指令)`)
99
-
100
- cleanup(projectDir)
101
- cleanup(specDir)
102
- }
103
-
104
- // ── Test 3: 默认模式不受影响 ──
105
- console.log('\n=== Test 3: 默认模式不受影响 ===')
106
- {
107
- const { cmdInit } = await imp(join(root, 'src', 'init.js'))
108
- const projectDir = tmpDir('default')
109
-
110
- await cmdInit(projectDir, {})
111
-
112
- assert(existsSync(join(projectDir, '.sillyspec')), '默认模式创建 .sillyspec 在项目内')
113
- assert(existsSync(join(projectDir, '.sillyspec', '.runtime', 'sillyspec.db')), '默认模式 DB 在项目内')
114
- assert(existsSync(join(projectDir, '.gitignore')), '默认模式创建 .gitignore')
115
-
116
- cleanup(projectDir)
117
- }
118
-
119
- // ── Test 4: 平台模式 prompt 注入(多 stage) ──
120
- console.log('\n=== Test 4: 平台模式 prompt 注入 ===')
121
- {
122
- const projectDir = tmpDir('prompt-p')
123
- const specDir = tmpDir('prompt-s')
124
-
125
- run(`node "${binCLI}" init "${projectDir}" --spec-dir "${specDir}"`)
126
-
127
- // execute 阶段会自动创建 worktree,在非 git 环境下会失败,跳过
128
- const stages = ['scan', 'brainstorm', 'plan', 'verify', 'quick']
129
- for (const stage of stages) {
130
- const output = run(`node "${binCLI}" --dir "${projectDir}" --spec-dir "${specDir}" run ${stage} --skip-approval`)
131
- assert(output.includes('平台模式'), `${stage}: 包含平台模式指令`)
132
- assert(output.includes(`规范目录(specDir): \`${specDir}\``), `${stage}: 包含正确的 specDir 路径`)
133
- }
134
-
135
- // scan 额外检查
136
- const scanOutput = run(`node "${binCLI}" --dir "${projectDir}" --spec-dir "${specDir}" run scan`)
137
- assert(scanOutput.includes('严禁写入源码目录'), 'scan: 包含严禁写入源码目录')
138
- assert(scanOutput.includes('Write 工具失败时,不允许'), 'scan: 包含 Write 工具规则')
139
- assert(scanOutput.includes('变更目录'), 'scan: 包含变更目录')
140
-
141
- cleanup(projectDir)
142
- cleanup(specDir)
143
- }
144
-
145
- // ── Test 5: 非 platform 模式占位符替换 ──
146
- console.log('\n=== Test 5: 非 platform 模式占位符替换 ===')
147
- {
148
- const projectDir = tmpDir('noplatform')
149
-
150
- run(`node "${binCLI}" init "${projectDir}"`)
151
-
152
- const output = run(`node "${binCLI}" --dir "${projectDir}" run scan`)
153
-
154
- assert(!output.includes('平台模式 — 写入路径约束'), '非 platform 模式不含平台指令')
155
- assert(!output.includes('{DOCS_ROOT}'), '{DOCS_ROOT} 被正确替换')
156
- assert(!output.includes('undefined'), '输出不含 undefined 路径')
157
- assert(!output.includes('null/.sillyspec'), '输出不含 null 路径')
158
-
159
- cleanup(projectDir)
160
- }
161
-
162
- // ── Test 6: --spec-root 兼容 ──
163
- console.log('\n=== Test 6: --spec-root 兼容 ===')
164
- {
165
- const projectDir = tmpDir('compat-p')
166
- const specDir = tmpDir('compat-s')
167
-
168
- run(`node "${binCLI}" init "${projectDir}" --spec-dir "${specDir}"`)
169
-
170
- const output = run(`node "${binCLI}" --dir "${projectDir}" run scan --spec-root "${specDir}"`)
171
- assert(output.includes('平台模式'), '--spec-root 兼容:仍触发平台模式指令')
172
-
173
- cleanup(projectDir)
174
- cleanup(specDir)
175
- }
176
-
177
- // ── Test 7: progress 使用外部 specDir ──
178
- console.log('\n=== Test 7: progress 使用外部 specDir ===')
179
- {
180
- const { ProgressManager } = await imp(join(root, 'src', 'progress.js'))
181
- const projectDir = tmpDir('progress-p')
182
- const specDir = tmpDir('progress-s')
183
-
184
- const pm = new ProgressManager({ specDir })
185
- await pm.init(projectDir)
186
-
187
- assert(existsSync(join(specDir, '.runtime', 'sillyspec.db')), 'DB 创建在外部 specDir')
188
- assert(!existsSync(join(projectDir, '.sillyspec')), '源码目录不含 .sillyspec')
189
-
190
- await pm.initChange(projectDir, 'test-change')
191
- assert(existsSync(join(specDir, 'changes', 'test-change')), 'changes 创建在外部 specDir')
192
-
193
- const progress = await pm.read(projectDir, 'test-change')
194
- assert(progress !== null, '能从外部 specDir 读取 progress')
195
- assert(progress.currentChange === 'test-change', `currentChange 正确`)
196
-
197
- cleanup(projectDir)
198
- cleanup(specDir)
199
- }
200
-
201
- // ── 汇总 ──
202
- console.log(`\n${'='.repeat(50)}`)
203
- console.log(`✅ 通过: ${passed} ❌ 失败: ${failed}`)
204
- console.log(`${'='.repeat(50)}`)
205
-
206
- if (failed > 0) throw new Error(`${failed} test(s) failed`)
@@ -1,102 +0,0 @@
1
- /**
2
- * task-07: checkTransition failed_post_check 门控
3
- *
4
- * 覆盖:
5
- * - AC-1: scan → brainstorm (failed_post_check) → allowed=false, reason 含"scan post-check 未通过"
6
- * - AC-2: scan → scan (重跑修复) → allowed=true
7
- * - AC-3: scan → doctor/status (辅助阶段) → allowed=true
8
- * - AC-4: 旧数据无 status 字段 → 行为同旧版(按 allowedFrom)
9
- * - AC-5: status='completed' 不被拦
10
- */
11
- import { checkTransition } from '../src/stage-contract.js'
12
-
13
- let passed = 0
14
- let failed = 0
15
- function assertEqual (actual, expected, msg) {
16
- const ok = actual === expected
17
- if (ok) { console.log(`✅ PASS: ${msg}`); passed++ }
18
- else { console.error(`❌ FAIL: ${msg}\n expected: ${JSON.stringify(expected)}\n actual: ${JSON.stringify(actual)}`); failed++ }
19
- }
20
- function assertMatch (actual, regex, msg) {
21
- if (regex.test(actual)) { console.log(`✅ PASS: ${msg}`); passed++ }
22
- else { console.error(`❌ FAIL: ${msg}\n actual: ${JSON.stringify(actual)} 不匹配 ${regex}`); failed++ }
23
- }
24
- function assert (cond, msg) {
25
- if (cond) { console.log(`✅ PASS: ${msg}`); passed++ }
26
- else { console.error(`❌ FAIL: ${msg}`); failed++ }
27
- }
28
-
29
- console.log('=== AC-1: failed_post_check 状态下进 brainstorm/plan/execute 被拦 ===')
30
-
31
- // scan → brainstorm, failed_post_check
32
- {
33
- const r = checkTransition('scan', 'brainstorm', { fromStageData: { status: 'failed_post_check' } })
34
- assertEqual(r.allowed, false, "scan→brainstorm failed_post_check: allowed=false")
35
- assertMatch(r.reason || '', /scan post-check 未通过/, 'reason 含 "scan post-check 未通过"')
36
- assertMatch(r.reason || '', /重跑\s*scan|重跑\s*scan/, 'reason 含 "重跑 scan" 提示')
37
- }
38
-
39
- // scan → plan / execute 同样被拦
40
- {
41
- const r1 = checkTransition('scan', 'plan', { fromStageData: { status: 'failed_post_check' } })
42
- assertEqual(r1.allowed, false, "scan→plan failed_post_check: allowed=false")
43
- const r2 = checkTransition('scan', 'execute', { fromStageData: { status: 'failed_post_check' } })
44
- assertEqual(r2.allowed, false, "scan→execute failed_post_check: allowed=false")
45
- }
46
-
47
- console.log('\n=== AC-2: 允许 scan → scan 重跑修复(fromStage===toStage) ===')
48
- {
49
- const r = checkTransition('scan', 'scan', { fromStageData: { status: 'failed_post_check' } })
50
- assertEqual(r.allowed, true, "scan→scan failed_post_check: allowed=true(允许重跑修复)")
51
- }
52
-
53
- console.log('\n=== AC-3: failed_post_check 下辅助阶段(doctor/status)仍可执行 ===')
54
- {
55
- const r1 = checkTransition('scan', 'doctor', { fromStageData: { status: 'failed_post_check' } })
56
- assertEqual(r1.allowed, true, "scan→doctor failed_post_check: allowed=true(辅助阶段)")
57
- const r2 = checkTransition('scan', 'status', { fromStageData: { status: 'failed_post_check' } })
58
- assertEqual(r2.allowed, true, "scan→status failed_post_check: allowed=true(辅助阶段)")
59
- const r3 = checkTransition('scan', 'quick', { fromStageData: { status: 'failed_post_check' } })
60
- assertEqual(r3.allowed, true, "scan→quick failed_post_check: allowed=true(辅助阶段)")
61
- const r4 = checkTransition('scan', 'explore', { fromStageData: { status: 'failed_post_check' } })
62
- assertEqual(r4.allowed, true, "scan→explore failed_post_check: allowed=true(辅助阶段)")
63
- }
64
-
65
- console.log('\n=== AC-4: 旧数据兼容(无 options 或 status 缺失,行为同旧版) ===')
66
- {
67
- // 无 options(旧调用)— scan→brainstorm 按 allowedFrom 规则允许(scan 是辅助阶段,可进主流程)
68
- const r1 = checkTransition('scan', 'brainstorm')
69
- assertEqual(r1.allowed, true, "scan→brainstorm 无 options: 行为同旧版(allowed=true)")
70
-
71
- // options 提供 fromStageData 但 status 为 undefined
72
- const r2 = checkTransition('scan', 'brainstorm', { fromStageData: { /* 无 status */ } })
73
- assertEqual(r2.allowed, true, "scan→brainstorm status=undefined: 行为同旧版(allowed=true)")
74
-
75
- // options 为空对象
76
- const r3 = checkTransition('scan', 'brainstorm', {})
77
- assertEqual(r3.allowed, true, "scan→brainstorm options={}: 行为同旧版(allowed=true)")
78
- }
79
-
80
- console.log('\n=== AC-5: status="completed" 不被门控拦截 ===')
81
- {
82
- const r = checkTransition('scan', 'brainstorm', { fromStageData: { status: 'completed' } })
83
- assertEqual(r.allowed, true, "scan→brainstorm status=completed: allowed=true")
84
- }
85
-
86
- console.log('\n=== AC-6: fromStage 非 scan 时门控不触发(门控只针对 scan 源) ===')
87
- {
88
- const r = checkTransition('brainstorm', 'plan', { fromStageData: { status: 'failed_post_check' } })
89
- assertEqual(r.allowed, true, "brainstorm→plan failed_post_check: 门控不触发(非 scan 源)")
90
- }
91
-
92
- console.log('\n=== 接口向后兼容:options 第 3 位可选 ===')
93
- {
94
- // 仅 2 参调用(最常见旧用法)
95
- const r = checkTransition('brainstorm', 'plan')
96
- assert(r && typeof r.allowed === 'boolean', '2 参调用返回 { allowed: boolean }')
97
- }
98
-
99
- console.log(`\n${'='.repeat(50)}`)
100
- console.log(`✅ 通过: ${passed} ❌ 失败: ${failed}`)
101
- console.log(`${'='.repeat(50)}`)
102
- if (failed > 0) process.exit(1)