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,167 +0,0 @@
1
- /**
2
- * platform-recovery.test.mjs — 平台模式参数恢复 + stage-contract 路径测试
3
- */
4
-
5
- import { join, resolve, dirname, basename } from 'path'
6
- import { existsSync, mkdirSync, writeFileSync, rmSync, readFileSync } from 'fs'
7
- import { fileURLToPath, pathToFileURL } from 'url'
8
- import { execSync } from 'child_process'
9
- import { tmpdir } from 'os'
10
-
11
- const __filename = fileURLToPath(import.meta.url)
12
- const __dirname = dirname(__filename)
13
- const root = resolve(__dirname, '..')
14
- const binCLI = join(root, 'bin', 'sillyspec.js')
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
- const P = 'recover'
24
- function setup(name) {
25
- const d = join(tmpdir(), `${P}-${name}`)
26
- mkdirSync(d, { recursive: true })
27
- return d
28
- }
29
- function spec(name) {
30
- const d = join(tmpdir(), `${P}-${name}-spec`)
31
- mkdirSync(d, { recursive: true })
32
- return d
33
- }
34
- function clean(...dirs) { for (const d of dirs) try { rmSync(d, { recursive: true, force: true }) } catch {} }
35
- function hasPathSegments(value, segments) {
36
- const parts = value.split(/[\\/]+/)
37
- for (let i = 0; i <= parts.length - segments.length; i++) {
38
- if (segments.every((segment, offset) => parts[i + offset] === segment)) return true
39
- }
40
- return false
41
- }
42
-
43
- function run(cmd) {
44
- return execSync(cmd, { encoding: 'utf8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'] })
45
- }
46
-
47
- const DOCS = ['ARCHITECTURE.md','CONVENTIONS.md','STRUCTURE.md','INTEGRATIONS.md','TESTING.md','CONCERNS.md','PROJECT.md']
48
- function writeSpecDocs(dir) {
49
- for (const d of DOCS) {
50
- const p = join(dir, 'scan', d)
51
- mkdirSync(dirname(p), { recursive: true })
52
- writeFileSync(p, 'author: bot\ncreated_at: now\n# doc\n')
53
- }
54
- }
55
- function writeLocalDocs(cwd) {
56
- for (const d of DOCS) {
57
- const p = join(cwd, '.sillyspec', 'docs', basename(cwd), 'scan', d)
58
- mkdirSync(dirname(p), { recursive: true })
59
- writeFileSync(p, 'author: bot\ncreated_at: now\n# doc\n')
60
- }
61
- }
62
-
63
- // ── Test 1: platform-scan.json 写入位置 ──
64
- console.log('\n=== Test 1: platform-scan.json 写入位置 ===')
65
- {
66
- const cwd = setup('t1'), sd = spec('t1')
67
- run(`node "${binCLI}" init "${cwd}" --spec-dir "${sd}"`)
68
- run(`node "${binCLI}" --dir "${cwd}" --spec-dir "${sd}" run scan --spec-root "${sd}" --runtime-root "${sd}/runtime" --workspace-id ws1 --scan-run-id sr1`)
69
-
70
- const inSpecDir = join(sd, '.runtime', 'platform-scan.json')
71
- const pointerFile = join(cwd, '.sillyspec-platform.json')
72
- assert(existsSync(inSpecDir), `platform-scan.json 在 specDir/.runtime/`)
73
- assert(existsSync(pointerFile), `恢复指针在 cwd/.sillyspec-platform.json(不在 .sillyspec 内)`)
74
-
75
- const content = JSON.parse(readFileSync(inSpecDir, 'utf8'))
76
- assert(content.specRoot === sd, `specRoot 指向 specDir`)
77
- assert(content.workspaceId === 'ws1', `workspaceId 保存正确`)
78
- assert(content.scanRunId === 'sr1', `scanRunId 保存正确`)
79
- // 关键:cwd/.sillyspec/ 不应被创建
80
- assert(!existsSync(join(cwd, '.sillyspec')), `cwd/.sillyspec/ 未被创建(源码零污染)`)
81
- clean(cwd, sd)
82
- }
83
-
84
- // ── Test 2: 残留清理:旧版本创建的 cwd/.sillyspec 会被自动删除 ──
85
- console.log('\n=== Test 2: 旧版本残留清理 ===')
86
- {
87
- const cwd = setup('t2'), sd = spec('t2')
88
- // 模拟旧版本创建的残留
89
- mkdirSync(join(cwd, '.sillyspec', '.runtime'), { recursive: true })
90
- writeFileSync(join(cwd, '.sillyspec', '.runtime', 'old.db'), 'x')
91
- mkdirSync(join(cwd, '.sillyspec', 'changes'), { recursive: true })
92
- assert(existsSync(join(cwd, '.sillyspec')), `残留存在`)
93
- // init 时应清理
94
- run(`node "${binCLI}" init "${cwd}" --spec-dir "${sd}"`)
95
- assert(!existsSync(join(cwd, '.sillyspec')), `init 清理了 cwd/.sillyspec/`)
96
- // run 时也不应再创建
97
- run(`node "${binCLI}" --dir "${cwd}" --spec-dir "${sd}" run scan --spec-root "${sd}" --runtime-root "${sd}/runtime" --workspace-id ws --scan-run-id sr 2>&1`)
98
- assert(!existsSync(join(cwd, '.sillyspec')), `run 后 cwd/.sillyspec/ 仍不存在`)
99
- clean(cwd, sd)
100
- }
101
-
102
- // ── Test 3: --done 不带 --spec-root 时恢复 ──
103
- console.log('\n=== Test 3: --done 恢复平台参数 ===')
104
- {
105
- const cwd = setup('t2'), sd = spec('t2')
106
- run(`node "${binCLI}" init "${cwd}" --spec-dir "${sd}"`)
107
- run(`node "${binCLI}" --dir "${cwd}" --spec-dir "${sd}" run scan --spec-root "${sd}" --runtime-root "${sd}/runtime" --workspace-id ws2 --scan-run-id sr2`)
108
- // --done 不带任何平台参数
109
- const output = run(`node "${binCLI}" --dir "${cwd}" run scan --done --change default --dir "${cwd}" --input "test" --output "test done" 2>&1`)
110
- assert(output.includes('平台模式'), `恢复成功:包含平台模式指令`)
111
- assert(output.includes(sd), `恢复成功:包含 specDir 路径`)
112
- clean(cwd, sd)
113
- }
114
-
115
- // ── Test 3-6: stage-contract 路径(通过 runValidators) ──
116
- const { runValidators } = await import(pathToFileURL(join(root, 'src', 'stage-contract.js')).href)
117
-
118
- console.log('\n=== Test 5: specDir 有文档 → 校验通过 ===')
119
- {
120
- const cwd = setup('t3'), sd = spec('t3')
121
- const proj = basename(cwd)
122
- const scanDir = join(sd, 'docs', proj)
123
- writeSpecDocs(scanDir)
124
- const result = runValidators('scan', cwd, 'default', { projectName: proj, specRoot: sd })
125
- assert(result.ok, `specDir 有文档: ok=${result.ok}, errors=${JSON.stringify(result.errors)}`)
126
- clean(cwd, sd)
127
- }
128
-
129
- console.log('\n=== Test 5: specDir 缺文档 → 校验失败,路径不含 .sillyspec ===')
130
- {
131
- const cwd = setup('t4'), sd = spec('t4')
132
- const proj = basename(cwd)
133
- mkdirSync(join(sd, 'docs'), { recursive: true })
134
- const result = runValidators('scan', cwd, 'default', { projectName: proj, specRoot: sd })
135
- assert(!result.ok, `specDir 缺文档: ok=${result.ok}`)
136
- assert(result.errors.length > 0, `有 errors`)
137
- const errMsg = result.errors[0]
138
- assert(!hasPathSegments(errMsg, ['.sillyspec', 'docs']), `路径不含 .sillyspec: ${errMsg}`)
139
- assert(hasPathSegments(errMsg, ['docs', proj, 'scan']), `路径含 docs/${proj}/scan: ${errMsg}`)
140
- clean(cwd, sd)
141
- }
142
-
143
- console.log('\n=== Test 7: 非平台模式有文档 → 校验通过 ===')
144
- {
145
- const cwd = setup('t5')
146
- const proj = basename(cwd)
147
- writeLocalDocs(cwd)
148
- const result = runValidators('scan', cwd, 'default', { projectName: proj })
149
- assert(result.ok, `非平台有文档: ok=${result.ok}`)
150
- clean(cwd)
151
- }
152
-
153
- console.log('\n=== Test 7: 非平台模式缺文档 → 路径含 .sillyspec ===')
154
- {
155
- const cwd = setup('t6')
156
- const proj = basename(cwd)
157
- const result = runValidators('scan', cwd, 'default', { projectName: proj })
158
- assert(!result.ok, `非平台缺文档: ok=${result.ok}`)
159
- const errMsg = result.errors[0]
160
- assert(hasPathSegments(errMsg, ['.sillyspec', 'docs']), `路径含 .sillyspec/docs: ${errMsg}`)
161
- clean(cwd)
162
- }
163
-
164
- console.log(`\n${'='.repeat(50)}`)
165
- console.log(`✅ 通过: ${passed} ❌ 失败: ${failed}`)
166
- console.log(`${'='.repeat(50)}`)
167
- if (failed > 0) throw new Error(`${failed} test(s) failed`)
@@ -1,175 +0,0 @@
1
- /**
2
- * P0 补丁验证 — 4 类测试
3
- *
4
- * 1. 平台 scan prompt 不含正向写入 .sillyspec/ 指令
5
- * 2. 安全说明文字不被 prompt 自检误杀
6
- * 3. 平台模式不执行 git add {SPEC_ROOT}
7
- * 4. source_root 污染检查覆盖所有子目录/文件
8
- * 5. run.js 占位符替换补齐(平台+本地)
9
- *
10
- * 跑法: node test/platform-scan-p0.test.mjs
11
- */
12
-
13
- import { join, basename } from 'path'
14
- import { existsSync, mkdirSync, writeFileSync, rmSync, readdirSync } from 'fs'
15
- import { execSync } from 'child_process'
16
- import { readFile } from 'fs/promises'
17
- import { fileURLToPath } from 'url'
18
-
19
- const __dirname = fileURLToPath(new URL('.', import.meta.url))
20
- const passed = []
21
- const failed = []
22
-
23
- function assert(label, condition, detail) {
24
- if (condition) {
25
- passed.push(label)
26
- } else {
27
- failed.push({ label, detail })
28
- }
29
- }
30
-
31
- // ── 测试 1:scan.js 模板不含裸 .sillyspec 输出路径 ──
32
- {
33
- const { definition } = await import('../src/stages/scan.js')
34
- const prompts = definition.steps.map(s => s.prompt).join('\n')
35
-
36
- // 所有 .sillyspec 出现都应该是「禁止说明」,不是写入指令
37
- const writePatterns = [
38
- /write.*\.sillyspec\/docs/i,
39
- /save.*\.sillyspec\/docs/i,
40
- /create.*\.sillyspec\/docs/i,
41
- /mkdir.*\.sillyspec\/docs/i,
42
- ]
43
- for (const p of writePatterns) {
44
- assert(`scan 模板无写入 .sillyspec/docs: ${p}`, !p.test(prompts),
45
- p.test(prompts) ? `命中: ${prompts.match(p)[0]}` : '')
46
- }
47
-
48
- // 应该使用占位符
49
- assert('scan 模板使用 {WORKFLOWS_ROOT}', prompts.includes('{WORKFLOWS_ROOT}'))
50
- assert('scan 模板使用 {SPEC_ROOT}', prompts.includes('{SPEC_ROOT}'))
51
- assert('scan 模板使用 {DOCS_ROOT}', prompts.includes('{DOCS_ROOT}'))
52
-
53
- // 平台模式下 git add 应该是条件判断
54
- assert('scan 模板平台模式跳过 git add', prompts.includes('如果平台模式:跳过 git add'))
55
- assert('scan 模板非平台模式 git add .sillyspec/', prompts.includes('git add .sillyspec/'))
56
- }
57
-
58
- // ── 测试 2:prompt 自检不误杀安全说明 ──
59
- {
60
- // 导入 run.js 中的正则
61
- const writeCtxRe = /(?<!不要|禁止|严禁)(?:save[\s.]+to|write|create|mkdir|git add|写入|保存到|写入到)[^a-zA-Z]*\.sillyspec\/[a-z]/i
62
-
63
- // 安全说明 — 不应命中
64
- const safeLines = [
65
- '严禁写入源码目录或相对路径 `.sillyspec/`',
66
- '⚠️ 不要写入 .sillyspec/docs',
67
- 'source_root 下存在 .sillyspec/docs 文件',
68
- '不允许从 cwd 推导 .sillyspec 路径',
69
- ]
70
- for (const line of safeLines) {
71
- assert(`安全说明不误杀: "${line.slice(0, 40)}"`, !writeCtxRe.test(line),
72
- '误杀! 命中了安全说明文字')
73
- }
74
-
75
- // 写入指令 — 应该命中
76
- const badLines = [
77
- '写入 `.sillyspec/docs/ARCHITECTURE.md`',
78
- 'save to `.sillyspec/docs/ARCHITECTURE.md`',
79
- 'create `.sillyspec/docs/ARCH.md`',
80
- 'git add .sillyspec/docs/',
81
- 'write 到 .sillyspec/docs/',
82
- ]
83
- for (const line of badLines) {
84
- assert(`写入指令应命中: "${line.slice(0, 40)}"`, writeCtxRe.test(line),
85
- '漏杀! 没有捕获写入指令')
86
- }
87
- }
88
-
89
- // ── 测试 3:safeGit 使用 -c safe.directory(不污染全局 config) ──
90
- {
91
- const runSrc = await readFile(join(__dirname, '..', 'src', 'run.js'), 'utf8')
92
-
93
- assert('safeGit 不含 --global', !runSrc.includes('git config --global'),
94
- '发现 --global,会污染容器 git config')
95
-
96
- assert('safeGit 使用 -c safe.directory', runSrc.includes("safe.directory=${cwd}"),
97
- '未发现 -c safe.directory per-command 参数')
98
-
99
- assert('safeGit 使用 -C cwd', runSrc.includes("-C', cwd"),
100
- '未发现 -C cwd 参数')
101
-
102
- assert('safeGit 返回 { value, error }', runSrc.includes("return { value, error: "),
103
- 'safeGit 返回值不是 { value, error } 结构')
104
-
105
- assert('manifest 包含 source_commit_error 字段',
106
- runSrc.includes("source_commit_error:"), 'manifest 缺少 source_commit_error')
107
- }
108
-
109
- // ── 测试 4:postcheck 污染检查覆盖所有子目录 ──
110
- {
111
- const postcheckSrc = await readFile(join(__dirname, '..', 'src', 'scan-postcheck.js'), 'utf8')
112
-
113
- const requiredSubs = ['docs', 'projects', 'workflows', 'knowledge']
114
- for (const sub of requiredSubs) {
115
- assert(`postcheck 检查 ${sub} 污染`,
116
- postcheckSrc.includes(`'${sub}'`) && postcheckSrc.includes("'.sillyspec', sub)"),
117
- `postcheck 未检查 .sillyspec/${sub}/ 污染`)
118
- }
119
-
120
- assert('postcheck 检查 manifest.json', postcheckSrc.includes('manifest.json'))
121
- assert('postcheck 检查 local.yaml', postcheckSrc.includes('local.yaml'))
122
- assert('污染 severity 使用枚举', postcheckSrc.includes('CHECK_SEVERITY'))
123
- }
124
-
125
- // ── 测试 5:run.js 占位符替换补齐 ──
126
- {
127
- const runSrc = await readFile(join(__dirname, '..', 'src', 'run.js'), 'utf8')
128
-
129
- // 平台模式块
130
- const platformMarker = 'promptText.replace(/\\{WORKFLOWS_ROOT\\}'
131
- assert('平台模式替换 {WORKFLOWS_ROOT}', runSrc.includes('{WORKFLOWS_ROOT}') && runSrc.includes('workflowsRoot'))
132
- assert('平台模式替换 {KNOWLEDGE_ROOT}', runSrc.includes('{KNOWLEDGE_ROOT}') && runSrc.includes('knowledgeRoot'))
133
- assert('平台模式替换 {SPEC_ROOT}', runSrc.includes('{SPEC_ROOT}') && runSrc.includes("specSillyspec"))
134
-
135
- // 非平台模式块
136
- assert('非平台模式替换 {WORKFLOWS_ROOT}', runSrc.includes('workflowsRoot'))
137
- assert('非平台模式替换 {KNOWLEDGE_ROOT}', runSrc.includes('knowledgeRoot'))
138
- assert('非平台模式替换 {SPEC_ROOT}', runSrc.includes('{SPEC_ROOT}'))
139
- }
140
-
141
- // ── 测试 6:quick step 1 prompt 强制要求 quicklog ──
142
- {
143
- const { definition } = await import('../src/stages/quick.js')
144
- const step1Prompt = definition.steps[0].prompt
145
- const step3Prompt = definition.steps[2].prompt
146
-
147
- assert('quick step 1 包含 ⛔ 标记', step1Prompt.includes('⛔'))
148
- assert('quick step 1 包含「不能跳过」', step1Prompt.includes('不能跳过'))
149
- assert('quick step 1 包含 quicklog 未创建 warning', step1Prompt.includes('quicklog 未创建'))
150
- assert('quick step 1 输出要求 quicklog 第一行', step1Prompt.includes('第一行确认'))
151
- assert('quick step 3 禁止 git add -A', step3Prompt.includes('禁止使用 `git add -A`'))
152
- assert('quick step 3 使用 scoped git add', step3Prompt.includes('git add -- <file...>'))
153
-
154
- // run.js 审计包含 quicklog 检查
155
- const runSrc = await readFile(join(__dirname, '..', 'src', 'run.js'), 'utf8')
156
- assert('quick 审计检查 quicklog 目录存在', runSrc.includes('quicklog 目录不存在'))
157
- assert('quick 审计检查 quicklog 为空', runSrc.includes('quicklog 目录为空'))
158
- }
159
-
160
- // ── 结果 ──
161
- console.log(`\n${'='.repeat(50)}`)
162
- console.log(`✅ 通过: ${passed.length}`)
163
- console.log(`❌ 失败: ${failed.length}`)
164
- console.log(`${'='.repeat(50)}`)
165
-
166
- if (failed.length > 0) {
167
- console.log('\n失败详情:')
168
- for (const f of failed) {
169
- console.log(` ❌ ${f.label}`)
170
- if (f.detail) console.log(` ${f.detail}`)
171
- }
172
- throw new Error("test failed")
173
- } else {
174
- console.log('\n🎉 全部 P0 测试通过!')
175
- }