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,51 +0,0 @@
1
- /**
2
- * task-09: sanitizeProjectName 字母校验 + 长度≥2 + 编号正则收紧
3
- *
4
- * 覆盖:
5
- * - 纯数字 "0"/"7" → null(核心目标,scan-projects.json 脏数据来源)
6
- * - "0/7" 清洗 "07" 无字母 → null
7
- * - "a" 长度<2 → null
8
- * - "fe" 含字母长度≥2 → 通过
9
- * - "frontend" / "order-service" → 通过
10
- * - "前端项目" 全中文清洗后 "" → null
11
- * - "" → null
12
- */
13
- import { sanitizeProjectName } from '../src/run.js'
14
-
15
- let passed = 0
16
- let failed = 0
17
- function assertEqual (actual, expected, msg) {
18
- const ok = actual === expected
19
- if (ok) { console.log(`✅ PASS: ${msg}`); passed++ }
20
- else { console.error(`❌ FAIL: ${msg}\n expected: ${JSON.stringify(expected)}\n actual: ${JSON.stringify(actual)}`); failed++ }
21
- }
22
-
23
- // 通过用例
24
- assertEqual(sanitizeProjectName('frontend'), 'frontend', '"frontend" 含字母长度≥2 通过')
25
- assertEqual(sanitizeProjectName('order-service'), 'order-service', '"order-service" 含字母+横线 通过')
26
- assertEqual(sanitizeProjectName('fe'), 'fe', '"fe" 最小长度 2 通过')
27
- assertEqual(sanitizeProjectName('user_service'), 'user_service', '"user_service" 含下划线 通过')
28
- assertEqual(sanitizeProjectName('app.v2'), 'app.v2', '"app.v2" 含点+数字 通过')
29
-
30
- // 拒绝用例(核心目标)
31
- assertEqual(sanitizeProjectName('0'), null, '"0" 纯数字拒绝')
32
- assertEqual(sanitizeProjectName('7'), null, '"7" 纯数字拒绝')
33
- assertEqual(sanitizeProjectName('07'), null, '"07" 纯数字拒绝(无字母)')
34
- assertEqual(sanitizeProjectName('0/7'), null, '"0/7" 清洗后 "07" 无字母拒绝')
35
- assertEqual(sanitizeProjectName('123'), null, '"123" 纯数字拒绝')
36
-
37
- // 长度<2
38
- assertEqual(sanitizeProjectName('a'), null, '"a" 长度<2 拒绝(即使含字母)')
39
- assertEqual(sanitizeProjectName('z'), null, '"z" 长度<2 拒绝')
40
-
41
- // 中文 / 空
42
- assertEqual(sanitizeProjectName('前端项目'), null, '"前端项目" 全中文清洗后 "" 拒绝')
43
- assertEqual(sanitizeProjectName(''), null, '空字符串拒绝')
44
- assertEqual(sanitizeProjectName(' '), null, '纯空白拒绝')
45
- assertEqual(sanitizeProjectName(null), null, 'null 拒绝')
46
- assertEqual(sanitizeProjectName(undefined), null, 'undefined 拒绝')
47
-
48
- console.log(`\n${'='.repeat(50)}`)
49
- console.log(`✅ 通过: ${passed} ❌ 失败: ${failed}`)
50
- console.log(`${'='.repeat(50)}`)
51
- if (failed > 0) process.exit(1)
@@ -1,64 +0,0 @@
1
- /**
2
- * task-06: scan post-check 失败补 return + completed 标记推迟 + 平台模式 exit(1)
3
- *
4
- * 验证点:
5
- * - AC-1: run.js:2433-2438 失败分支末尾补 return { stageCompleted:false, currentIdx, nextPendingIdx: currentIdx }
6
- * - AC-2: 平台模式 (platformOpts.specRoot || platformOpts.runtimeRoot) 时 process.exit(1)
7
- * - AC-3: 返回结构与 plan contract (run.js:2551 附近) 完全一致
8
- * - AC-4: 非平台模式不 exit(只有 if platformOpts 守卫内的 exit)
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 check (cond, msg) {
21
- if (cond) { console.log(`✅ PASS: ${msg}`); passed++ }
22
- else { console.error(`❌ FAIL: ${msg}`); failed++ }
23
- }
24
-
25
- // 定位 scan post-check failed_post_check 分支
26
- // 已知关键锚:stageData.status = SCAN_STATUS.FAILED_POST_CHECK
27
- const failedAnchor = src.indexOf("postResult.status === 'failed_post_check'")
28
- check(failedAnchor > 0, "源码含 'failed_post_check' 失败分支锚点")
29
-
30
- // 从锚点向后 800 字符内应该有 return { stageCompleted: false ... }
31
- const tail = src.slice(failedAnchor, failedAnchor + 1200)
32
- const hasReturnFalse = /return\s*\{\s*stageCompleted:\s*false/.test(tail)
33
- check(hasReturnFalse, '失败分支末尾补 return { stageCompleted: false, ... }')
34
-
35
- // nextPendingIdx: currentIdx 字面出现(与 plan contract 一致)
36
- const hasNextPendingCurrent = /nextPendingIdx:\s*currentIdx/.test(tail)
37
- check(hasNextPendingCurrent, '失败分支返回 nextPendingIdx: currentIdx(plan contract 对齐)')
38
-
39
- // 平台模式 process.exit(1)
40
- const hasExitGuard = /platformOpts\.specRoot\s*\|\|\s*platformOpts\.runtimeRoot/.test(tail)
41
- const hasExitOne = /process\.exit\(1\)/.test(tail)
42
- check(hasExitGuard && hasExitOne, '失败分支含平台模式守卫 + process.exit(1)')
43
-
44
- // 验证 exit(1) 在 if 守卫内(不是裸调)—— 取 exit(1) 位置向前回溯到最近的 } 或 {
45
- const exitPos = tail.indexOf('process.exit(1)')
46
- const segmentBeforeExit = tail.slice(0, exitPos)
47
- const lastOpenBrace = segmentBeforeExit.lastIndexOf('{')
48
- const lastCloseBrace = segmentBeforeExit.lastIndexOf('}')
49
- check(lastOpenBrace > lastCloseBrace, 'process.exit(1) 在某个 if 块的 { ... } 内(受守卫保护)')
50
-
51
- // 验证 exit 守卫表达式在 exit 之前的同一块内
52
- const guardSegment = tail.slice(Math.max(0, exitPos - 300), exitPos)
53
- check(/platformOpts\.(specRoot|runtimeRoot)/.test(guardSegment),
54
- 'process.exit(1) 前的代码块内含平台模式守卫条件')
55
-
56
- // 对照 plan contract(run.js 内 plan 失败分支 return 结构)
57
- // 定位 plan 阶段失败分支的 stageCompleted:false return
58
- const planFailMatch = src.match(/plan[\s\S]{0,2000}?return\s*\{\s*stageCompleted:\s*false[\s\S]{0,200}?\}/)
59
- check(!!planFailMatch, 'plan contract 存在 stageCompleted:false return(对照基准)')
60
-
61
- console.log(`\n${'='.repeat(50)}`)
62
- console.log(`✅ 通过: ${passed} ❌ 失败: ${failed}`)
63
- console.log(`${'='.repeat(50)}`)
64
- if (failed > 0) process.exit(1)
@@ -1,200 +0,0 @@
1
- /**
2
- * scan step 2 项目列表解析 — 强契约测试
3
- *
4
- * 解析只接受两种结构化格式:
5
- * A) YAML block: scan_projects:\n - id: name
6
- * B) BEGIN_PROJECT_LIST ... END_PROJECT_LIST
7
- *
8
- * 自由文本列表不再解析,防止误识别垃圾项目名。
9
- */
10
- import { sanitizeProjectName, validateParsedProjects } from '../src/run.js'
11
-
12
- let passed = 0
13
- let failed = 0
14
- function assertDeepEqual (actual, expected, msg) {
15
- const a = JSON.stringify(actual)
16
- const e = JSON.stringify(expected)
17
- if (a === e) { console.log(`✅ PASS: ${msg}`); passed++ }
18
- else { console.error(`❌ FAIL: ${msg}\n expected: ${e}\n actual: ${a}`); failed++ }
19
- }
20
- function assertEqual (actual, expected, msg) {
21
- if (actual === expected) { console.log(`✅ PASS: ${msg}`); passed++ }
22
- else { console.error(`❌ FAIL: ${msg}\n expected: ${expected}\n actual: ${actual}`); failed++ }
23
- }
24
- function assertIncludes (str, sub, msg) {
25
- if (str.includes(sub)) { console.log(`✅ PASS: ${msg}`); passed++ }
26
- else { console.error(`❌ FAIL: ${msg}\n expected to include: ${sub}\n actual: ${str}`); failed++ }
27
- }
28
-
29
- // ---- 复刻 run.js 中新的解析逻辑 ----
30
- function parseProjectListFromOutput(outputText) {
31
- let parsedProjects = [] // Array<{id, path?}>
32
- let parsedFromStructuredOutput = false
33
- if (outputText) {
34
- // 格式 A: YAML block
35
- const yamlBlock = outputText.match(/scan_projects:\s*\n([\s\S]+?)(?=$|\n[^\s])/)
36
- if (yamlBlock) {
37
- const entries = [...yamlBlock[1].matchAll(/-\s+id:\s*(\S+)(?:[\s\S]*?)(?=\n\s+-\s+id:|$)/g)]
38
- for (const m of entries) {
39
- const id = sanitizeProjectName(m[1])
40
- if (!id) continue
41
- const pathMatch = m[0].match(/path:\s*(\S+)/)
42
- const entry = pathMatch ? { id, path: pathMatch[1].trim() } : { id }
43
- parsedProjects.push(entry)
44
- }
45
- parsedFromStructuredOutput = parsedProjects.length > 0
46
- }
47
- // 格式 B: BEGIN_PROJECT_LIST
48
- if (!parsedFromStructuredOutput) {
49
- const blockMatch = outputText.match(/BEGIN_PROJECT_LIST\s*\n([\s\S]*?)\n*END_PROJECT_LIST/)
50
- if (blockMatch) {
51
- const raw = [...blockMatch[1].matchAll(/^-\s+(\S+)/gm)].map(m => m[1])
52
- parsedProjects = raw.map(s => sanitizeProjectName(s)).filter(Boolean).map(id => ({ id }))
53
- parsedFromStructuredOutput = parsedProjects.length > 0
54
- }
55
- }
56
- }
57
- return { parsedProjects, parsedFromStructuredOutput }
58
- }
59
-
60
- // ======== 解析测试 ========
61
-
62
- // 1. YAML block 正常解析
63
- assertDeepEqual(
64
- parseProjectListFromOutput(`scan_projects:
65
- - id: backend
66
- - id: frontend
67
- - id: daemon
68
- `).parsedProjects,
69
- [{ id: 'backend' }, { id: 'frontend' }, { id: 'daemon' }],
70
- 'YAML block 正常解析 3 个项目'
71
- )
72
-
73
- // 2. YAML block 带 path 字段(多行属性)
74
- const r2 = parseProjectListFromOutput(`scan_projects:
75
- - id: api
76
- path: backend/
77
- - id: web
78
- path: frontend/
79
- `).parsedProjects
80
- assertDeepEqual(r2, [{ id: 'api', path: 'backend/' }, { id: 'web', path: 'frontend/' }],
81
- 'YAML block 带 path 字段正常解析')
82
-
83
- // 3. BEGIN_PROJECT_LIST 正常解析
84
- assertDeepEqual(
85
- parseProjectListFromOutput(`BEGIN_PROJECT_LIST
86
- - backend
87
- - frontend
88
- - daemon
89
- END_PROJECT_LIST`).parsedProjects,
90
- [{ id: 'backend' }, { id: 'frontend' }, { id: 'daemon' }],
91
- 'BEGIN_PROJECT_LIST 标记块正常解析'
92
- )
93
-
94
- // 4. 自由文本编号列表 → 不解析
95
- const { parsedProjects: fn, parsedFromStructuredOutput: fnParsed } =
96
- parseProjectListFromOutput('扫描项目列表:\n1. scan1backendmulti-agent-platform-api FastAPI Python\n2. frontendmulti-agent-platform-web Next.js TS\n3. sillyhub-daemon Node.js TS')
97
- assertEqual(fnParsed, false, '自由文本编号列表不触发解析')
98
- assertDeepEqual(fn, [], '自由文本编号列表不产生任何项目名')
99
-
100
- // 5. 自由文本括号枚举 → 不解析
101
- const { parsedProjects: fn2, parsedFromStructuredOutput: fn2Parsed } =
102
- parseProjectListFromOutput('子项目: backend / frontend / user-service')
103
- assertEqual(fn2Parsed, false, '自由文本括号枚举不触发解析')
104
- assertDeepEqual(fn2, [], '自由文本括号枚举不产生任何项目名')
105
-
106
- // 6. 空 outputText → 不解析
107
- const { parsedFromStructuredOutput: emptyParsed } = parseProjectListFromOutput('')
108
- assertEqual(emptyParsed, false, '空 outputText 不触发解析')
109
-
110
- // 7. 普通摘要文本 → 不解析
111
- const { parsedProjects: summary, parsedFromStructuredOutput: summaryParsed } =
112
- parseProjectListFromOutput('确认扫描 3 个子项目:backend、frontend、daemon,全部重新扫描')
113
- assertEqual(summaryParsed, false, '普通摘要文本不触发解析')
114
- assertDeepEqual(summary, [], '普通摘要文本不产生任何项目名')
115
-
116
- // ======== validateParsedProjects — 基础校验 ========
117
-
118
- // 8. 正常列表(无 path)
119
- assertDeepEqual(
120
- validateParsedProjects([{ id: 'backend' }, { id: 'frontend' }, { id: 'daemon' }], '/tmp/proj'),
121
- { ok: true, errors: [] },
122
- '正常列表校验通过(无 path)'
123
- )
124
-
125
- // 9. 正常列表(有合法 path)——需要用真实路径
126
- import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs'
127
- import { join } from 'path'
128
- import os from 'os'
129
- const tmpDir = mkdtempSync(join(os.tmpdir(), 'sillyspec-test-'))
130
- mkdirSync(join(tmpDir, 'backend'))
131
- mkdirSync(join(tmpDir, 'frontend'))
132
- assertDeepEqual(
133
- validateParsedProjects([
134
- { id: 'backend', path: 'backend' },
135
- { id: 'frontend', path: 'frontend' }
136
- ], tmpDir),
137
- { ok: true, errors: [] },
138
- '合法 path 校验通过'
139
- )
140
-
141
- // 10. path 不存在 → 失败
142
- const v10 = validateParsedProjects([{ id: 'backend', path: 'nonexistent' }], tmpDir)
143
- assertEqual(v10.ok, false, 'path 不存在校验失败')
144
- assertIncludes(v10.errors[0], '不存在', '错误提示包含"不存在"')
145
-
146
- // 11. path 含 .. 越界 → 失败
147
- const v11 = validateParsedProjects([{ id: 'backend', path: '../etc/passwd' }], tmpDir)
148
- assertEqual(v11.ok, false, 'path 包含 .. 校验失败')
149
- assertIncludes(v11.errors[0], '..', '错误提示包含 ..')
150
-
151
- // 12. path 解析后超出 source_root → 失败
152
- const v12 = validateParsedProjects([{ id: 'backend', path: '/etc/passwd' }], tmpDir)
153
- assertEqual(v12.ok, false, '绝对路径超出 source_root 校验失败')
154
- assertIncludes(v12.errors[0], '超出', '错误提示包含"超出"')
155
-
156
- // 13. 超过 10 个项目 → 失败
157
- const many = Array.from({ length: 11 }, (_, i) => ({ id: `project${i}` }))
158
- const v13 = validateParsedProjects(many, tmpDir)
159
- assertEqual(v13.ok, false, '超过 10 个项目校验失败')
160
- assertIncludes(v13.errors[0], '超过上限', '错误提示包含"超过上限"')
161
-
162
- // 14. 重复项目名 → 失败
163
- const v14 = validateParsedProjects([{ id: 'backend' }, { id: 'frontend' }, { id: 'backend' }], tmpDir)
164
- assertEqual(v14.ok, false, '重复项目名校验失败')
165
- assertIncludes(v14.errors[0], '重复', '错误提示包含"重复"')
166
-
167
- // 15. 非法 slug(中文)→ 失败
168
- const v15 = validateParsedProjects([{ id: 'backend' }, { id: '前端服务' }], tmpDir)
169
- assertEqual(v15.ok, false, '非法 slug 校验失败')
170
-
171
- // 16. 空列表 → 失败
172
- const v16 = validateParsedProjects([], tmpDir)
173
- assertEqual(v16.ok, false, '空列表校验失败')
174
-
175
- // 17. null 列表 → 失败
176
- const v17 = validateParsedProjects(null, tmpDir)
177
- assertEqual(v17.ok, false, 'null 列表校验失败')
178
-
179
- // 18. 单字符 → 失败
180
- const v18 = validateParsedProjects([{ id: 'a' }], tmpDir)
181
- assertEqual(v18.ok, false, '单字符项目名校验失败')
182
-
183
- // 19. 纯数字 → 失败
184
- const v19 = validateParsedProjects([{ id: '123' }], tmpDir)
185
- assertEqual(v19.ok, false, '纯数字项目名校验失败')
186
-
187
- // 20. 兼容旧 API(纯字符串数组)
188
- assertDeepEqual(
189
- validateParsedProjects(['backend', 'frontend'], tmpDir),
190
- { ok: true, errors: [] },
191
- '旧 API 兼容:纯字符串数组仍通过'
192
- )
193
-
194
- // 清理
195
- rmSync(tmpDir, { recursive: true, force: true })
196
-
197
- console.log(`\n${'='.repeat(50)}`)
198
- console.log(`✅ 通过: ${passed} ❌ 失败: ${failed}`)
199
- console.log(`${'='.repeat(50)}`)
200
- if (failed > 0) process.exit(1)
@@ -1,48 +0,0 @@
1
- import { readdirSync } from 'node:fs'
2
- import { dirname, join } from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
- import { execFileSync } from 'node:child_process'
5
-
6
- const testDir = dirname(fileURLToPath(import.meta.url))
7
- const files = readdirSync(testDir)
8
- .filter(file => file.endsWith('.test.mjs'))
9
- .sort()
10
-
11
- if (files.length === 0) {
12
- console.log('No test files found')
13
- process.exit(0)
14
- }
15
-
16
- let passed = 0
17
- let failed = 0
18
- const failures = []
19
-
20
- for (const file of files) {
21
- const fullPath = join(testDir, file)
22
- console.log(`\nRunning ${file}`)
23
- try {
24
- const output = execFileSync(process.execPath, [fullPath], {
25
- cwd: testDir,
26
- stdio: ['pipe', 'pipe', 'pipe'],
27
- encoding: 'utf8',
28
- timeout: 120_000
29
- })
30
- if (output) process.stdout.write(output)
31
- passed++
32
- } catch (err) {
33
- if (err.stdout) process.stdout.write(err.stdout)
34
- if (err.stderr) process.stderr.write(err.stderr)
35
- failed++
36
- failures.push(file)
37
- console.log(` ❌ ${file} exited with code ${err.status || 1}`)
38
- }
39
- }
40
-
41
- console.log(`\n${'='.repeat(50)}`)
42
- console.log(`✅ 通过: ${passed} ❌ 失败: ${failed}`)
43
- if (failures.length > 0) {
44
- console.log(`失败文件: ${failures.join(', ')}`)
45
- }
46
- console.log(`${'='.repeat(50)}`)
47
-
48
- process.exit(failed > 0 ? 1 : 0)
@@ -1,84 +0,0 @@
1
- /**
2
- * task-05: scan-docs.yaml 占位符 {SPEC_ROOT} + 项目名优先级
3
- *
4
- * 覆盖:
5
- * - AC-01: 8 处 outputs.path 均为 {SPEC_ROOT}/docs/<project>/scan/*.md
6
- * - AC-02: write_scope 含 {SPEC_ROOT}/docs/<project>/scan/
7
- * - AC-07: 旧 yaml(无 {SPEC_ROOT})兼容,replace 不命中也不报错
8
- */
9
- import { readFileSync } from 'fs'
10
- import { join, dirname } from 'path'
11
- import { fileURLToPath } from 'url'
12
-
13
- const __dirname = dirname(fileURLToPath(import.meta.url))
14
- const yamlPath = join(__dirname, '..', 'templates', 'workflows', 'scan-docs.yaml')
15
- const yaml = readFileSync(yamlPath, 'utf8')
16
-
17
- let passed = 0
18
- let failed = 0
19
- function assert (cond, msg) {
20
- if (cond) { console.log(`✅ PASS: ${msg}`); passed++ }
21
- else { console.error(`❌ FAIL: ${msg}`); failed++ }
22
- }
23
-
24
- // AC-01: 8 处 outputs.path 改占位符
25
- const expectedDocs = [
26
- 'ARCHITECTURE.md',
27
- 'CONVENTIONS.md',
28
- 'STRUCTURE.md',
29
- 'INTEGRATIONS.md',
30
- 'TESTING.md',
31
- 'CONCERNS.md',
32
- 'PROJECT.md',
33
- ]
34
- console.log('=== AC-01: outputs.path 改占位符 {SPEC_ROOT} ===')
35
- for (const doc of expectedDocs) {
36
- const expectedLine = `path: "{SPEC_ROOT}/docs/<project>/scan/${doc}"`
37
- assert(yaml.includes(expectedLine), `outputs.path 含 {SPEC_ROOT} 占位符 → ${doc}`)
38
- }
39
-
40
- // AC-01 反向:不应再有硬编码 .sillyspec/docs/<project>/scan/X.md
41
- console.log('\n=== AC-01 反向:不再有硬编码 .sillyspec/docs/<project>/scan/ ===')
42
- const hardcodedMatches = yaml.match(/\.sillyspec\/docs\/<project>\/scan\//g) || []
43
- assert(hardcodedMatches.length === 0,
44
- `outputs.path/write_scope 不再含 ".sillyspec/docs/<project>/scan/"(找到 ${hardcodedMatches.length} 处)`)
45
-
46
- // AC-02: write_scope 含 {SPEC_ROOT}
47
- console.log('\n=== AC-02: write_scope 含 {SPEC_ROOT} ===')
48
- assert(yaml.includes('- "{SPEC_ROOT}/docs/<project>/scan/"'),
49
- 'write_scope 含 {SPEC_ROOT}/docs/<project>/scan/')
50
-
51
- // AC-07 兼容:旧 yaml(无 {SPEC_ROOT})replace 不命中(用代码模拟)
52
- console.log('\n=== AC-07: 旧 yaml 占位符兼容性 ===')
53
- {
54
- const legacyPrompt = '写文件到 .sillyspec/docs/<project>/scan/X.md'
55
- const replaced = legacyPrompt.replace(/\{SPEC_ROOT\}/g, '/tmp/spec')
56
- assert(!replaced.includes('{SPEC_ROOT}'), '旧 yaml 无 {SPEC_ROOT} 字面:replace 后无残留')
57
- assert(replaced.includes('.sillyspec/docs/'), '旧 yaml 行为:保留原 .sillyspec/docs/ 路径(向后兼容)')
58
- }
59
-
60
- // AC-03: 新 yaml 替换后路径正确
61
- console.log('\n=== AC-03: 新 yaml 占位符替换 ===')
62
- {
63
- const newPrompt = '写文件到 {SPEC_ROOT}/docs/<project>/scan/X.md'
64
- const step1 = newPrompt.replace(/\{SPEC_ROOT\}/g, '/tmp/spec')
65
- const step2 = step1.replace(/<project>/g, 'myaaa')
66
- assert(step2 === '写文件到 /tmp/spec/docs/myaaa/scan/X.md',
67
- `占位符替换正确:${step2}`)
68
- assert(!step2.includes('{SPEC_ROOT}'), '替换后无 {SPEC_ROOT} 残留')
69
- assert(!step2.includes('<project>'), '替换后无 <project> 残留')
70
- }
71
-
72
- // 结构完整性:其他字段未动
73
- console.log('\n=== 结构完整性:其他字段未变 ===')
74
- assert(yaml.includes('name: scan-docs'), 'yaml name 字段保留')
75
- assert(yaml.includes('checks:'), 'checks 段保留')
76
- assert(yaml.includes('path: "scan/"'), 'workflow_level.path "scan/" 相对子路径保留(不动)')
77
- assert(yaml.includes('retry:'), 'retry 段保留')
78
- assert(yaml.includes('on_check_failure: prompt_retry'), 'on_check_failure 保留')
79
- assert(yaml.includes('allow_shell: true'), 'permissions.allow_shell 保留')
80
-
81
- console.log(`\n${'='.repeat(50)}`)
82
- console.log(`✅ 通过: ${passed} ❌ 失败: ${failed}`)
83
- console.log(`${'='.repeat(50)}`)
84
- if (failed > 0) process.exit(1)
@@ -1,175 +0,0 @@
1
- /**
2
- * scan-knowledge.test.mjs — knowledge 产物校验测试
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(), `kn-${name}`)
25
- mkdirSync(cwd, { recursive: true })
26
- return cwd
27
- }
28
- function specSetup(name) {
29
- const d = join(tmpdir(), `kn-${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
- function writeFull(cwd, specDir) {
38
- const proj = basename(cwd)
39
- for (const d of DOCS) {
40
- const p = join(specDir, 'docs', proj, 'scan', d)
41
- mkdirSync(dirname(p), { recursive: true })
42
- writeFileSync(p, 'author: bot\ncreated_at: 2026-06-19 10:00:00\n# doc\n')
43
- }
44
- }
45
-
46
- // ── 1: knowledge INDEX.md 存在,引用真实文件 → 通过 ──
47
- console.log('\n=== Test 1: knowledge INDEX.md 存在且引用有效 → no knowledge warning ===')
48
- {
49
- const cwd = setup('t1'), spec = specSetup('t1')
50
- try {
51
- writeFull(cwd, spec)
52
- const knowledgeDir = join(spec, 'knowledge')
53
- mkdirSync(knowledgeDir, { recursive: true })
54
- writeFileSync(join(knowledgeDir, 'INDEX.md'),
55
- '# Knowledge Index\n\n## Conventions\n- [kebab-case naming](conventions.md#kebab-case-naming)\n')
56
- writeFileSync(join(knowledgeDir, 'conventions.md'),
57
- '# Conventions\n\n## kebab-case-naming\nFiles use kebab-case.\n')
58
-
59
- const result = runScanPostCheck({ cwd, specDir: spec })
60
- const knowledgeChecks = result.checks.filter(c => c.name.startsWith('knowledge_'))
61
- assert(knowledgeChecks.length === 0, 'no knowledge warnings when INDEX.md + referenced files exist')
62
- } finally { clean(cwd, spec) }
63
- }
64
-
65
- // ── 2: knowledge 目录不存在 → dir_missing 警告 ──
66
- console.log('\n=== Test 2: knowledge 目录不存在 → knowledge_dir_missing ===')
67
- {
68
- const cwd = setup('t2'), spec = specSetup('t2')
69
- try {
70
- writeFull(cwd, spec)
71
-
72
- const result = runScanPostCheck({ cwd, specDir: spec })
73
- const missing = result.checks.find(c => c.name === 'knowledge_dir_missing')
74
- assert(!!missing, 'knowledge_dir_missing check exists')
75
- assert(missing.detail.includes('knowledge/ 目录不存在'), 'detail text correct')
76
- } finally { clean(cwd, spec) }
77
- }
78
-
79
- // ── 3: INDEX.md 不存在 → index_missing 警告 ──
80
- console.log('\n=== Test 3: INDEX.md 不存在 → knowledge_index_missing ===')
81
- {
82
- const cwd = setup('t3'), spec = specSetup('t3')
83
- try {
84
- writeFull(cwd, spec)
85
- const knowledgeDir = join(spec, 'knowledge')
86
- mkdirSync(knowledgeDir, { recursive: true })
87
- // INDEX.md not created
88
-
89
- const result = runScanPostCheck({ cwd, specDir: spec })
90
- const missing = result.checks.find(c => c.name === 'knowledge_index_missing')
91
- assert(!!missing, 'knowledge_index_missing check exists')
92
- assert(missing.detail.includes('INDEX.md 不存在'), 'detail text correct')
93
- } finally { clean(cwd, spec) }
94
- }
95
-
96
- // ── 4: INDEX.md 引用不存在文件 → broken_refs 警告 ──
97
- console.log('\n=== Test 4: INDEX.md 引用不存在的文件 → knowledge_broken_refs ===')
98
- {
99
- const cwd = setup('t4'), spec = specSetup('t4')
100
- try {
101
- writeFull(cwd, spec)
102
- const knowledgeDir = join(spec, 'knowledge')
103
- mkdirSync(knowledgeDir, { recursive: true })
104
- writeFileSync(join(knowledgeDir, 'INDEX.md'),
105
- '# Knowledge Index\n\n## Conventions\n- [naming](conventions.md#naming)\n\n## Patterns\n- [auth pattern](patterns.md#auth-pattern)\n')
106
- // conventions.md exists, patterns.md does not
107
- writeFileSync(join(knowledgeDir, 'conventions.md'), '# Conventions\n')
108
-
109
- const result = runScanPostCheck({ cwd, specDir: spec })
110
- const broken = result.checks.find(c => c.name === 'knowledge_broken_refs')
111
- assert(!!broken, 'knowledge_broken_refs check exists')
112
- assert(broken.detail.includes('patterns.md'), 'reports missing patterns.md')
113
- } finally { clean(cwd, spec) }
114
- }
115
-
116
- // ── 5: uncategorized.md 为空但存在 → 通过 ──
117
- console.log('\n=== Test 5: uncategorized.md 空但存在 → no knowledge warning ===')
118
- {
119
- const cwd = setup('t5'), spec = specSetup('t5')
120
- try {
121
- writeFull(cwd, spec)
122
- const knowledgeDir = join(spec, 'knowledge')
123
- mkdirSync(knowledgeDir, { recursive: true })
124
- writeFileSync(join(knowledgeDir, 'INDEX.md'),
125
- '# Knowledge Index\n\n(no entries yet)\n')
126
- writeFileSync(join(knowledgeDir, 'uncategorized.md'), '# Uncategorized\n')
127
-
128
- const result = runScanPostCheck({ cwd, specDir: spec })
129
- const knowledgeChecks = result.checks.filter(c => c.name.startsWith('knowledge_'))
130
- assert(knowledgeChecks.length === 0, 'empty uncategorized.md passes')
131
- } finally { clean(cwd, spec) }
132
- }
133
-
134
- // ── 6: categorized 文件存在但 INDEX.md 无条目 → 通过(不强求 categorized 非空)──
135
- console.log('\n=== Test 6: categorized 文件存在 + INDEX.md 有效 → no knowledge warning ===')
136
- {
137
- const cwd = setup('t6'), spec = specSetup('t6')
138
- try {
139
- writeFull(cwd, spec)
140
- const knowledgeDir = join(spec, 'knowledge')
141
- mkdirSync(knowledgeDir, { recursive: true })
142
- writeFileSync(join(knowledgeDir, 'INDEX.md'),
143
- '# Knowledge Index\n\n## Known Issues\n- [GLM proxy](known-issues.md#glm-proxy)\n')
144
- writeFileSync(join(knowledgeDir, 'known-issues.md'),
145
- '# Known Issues\n\n## glm-proxy\nGLM proxy has usage metadata limits.\n')
146
- writeFileSync(join(knowledgeDir, 'uncategorized.md'), '# Uncategorized\n')
147
-
148
- const result = runScanPostCheck({ cwd, specDir: spec })
149
- const knowledgeChecks = result.checks.filter(c => c.name.startsWith('knowledge_'))
150
- assert(knowledgeChecks.length === 0, 'at least one categorized file + valid INDEX passes')
151
- } finally { clean(cwd, spec) }
152
- }
153
-
154
- // ── 7: source_root knowledge 泄漏检测(平台模式已有检查)──
155
- console.log('\n=== Test 7: source_root knowledge 泄漏 → source_root_leak ===')
156
- {
157
- const cwd = setup('t7'), spec = specSetup('t7')
158
- try {
159
- writeFull(cwd, spec)
160
- // 写入 source_root/.sillyspec/knowledge/ 泄漏
161
- const leakedKnowledgeDir = join(cwd, '.sillyspec', 'knowledge')
162
- mkdirSync(leakedKnowledgeDir, { recursive: true })
163
- writeFileSync(join(leakedKnowledgeDir, 'INDEX.md'), '# leaked\n')
164
-
165
- const result = runScanPostCheck({ cwd, specDir: spec })
166
- const leak = result.checks.find(c => c.name === 'source_root_leak')
167
- assert(!!leak, 'source_root knowledge leak detected')
168
- assert(leak.severity === 'failed', 'leak severity is failed')
169
- } finally { clean(cwd, spec) }
170
- }
171
-
172
- // ── 汇总 ──
173
- console.log(`\n${'='.repeat(40)}`)
174
- console.log(`scan-knowledge tests: ${passed} passed, ${failed} failed, ${passed + failed} total`)
175
- if (failed > 0) process.exit(1)