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,380 +0,0 @@
1
- /**
2
- * scan-postcheck.js — CLI 层 scan 完成后强制校验
3
- *
4
- * 不依赖 AI agent 的自检报告,由 CLI 代码直接检查文件系统。
5
- * 平台模式下必须通过所有 check 才能 success,否则降级。
6
- */
7
-
8
- import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync } from 'fs'
9
- import { join, basename } from 'path'
10
- import { SCAN_STATUS, CHECK_SEVERITY } from './constants.js'
11
-
12
- const REQUIRED_SCAN_DOCS = [
13
- 'ARCHITECTURE.md',
14
- 'CONVENTIONS.md',
15
- 'STRUCTURE.md',
16
- 'INTEGRATIONS.md',
17
- 'TESTING.md',
18
- 'CONCERNS.md',
19
- 'PROJECT.md',
20
- ]
21
-
22
- /**
23
- * @param {object} opts
24
- * @param {string} opts.cwd - 源码项目根目录 (source_root)
25
- * @param {string} opts.specDir - 规范目录 (spec-root),null 时为非平台模式
26
- * @param {string} [opts.outputText] - 最后一步(自检)的 AI 输出文本
27
- * @param {object} [opts.scanMeta] - scan 元数据(由 runCommand 传入)
28
- * @param {boolean} [opts.scanMeta.projectListParsed] - Step 2 项目列表是否成功解析
29
- * @param {boolean} [opts.scanMeta.manifestWritten] - manifest.json 是否写入成功
30
- * @param {number} [opts.scanMeta.projectCount] - 实际展开的项目数量
31
- * @returns {{ status: 'success'|'completed_with_warnings'|'failed_post_check', checks: Array<{name, severity, detail}> }}
32
- */
33
- export function runScanPostCheck({ cwd, specDir, outputText = '', scanMeta = {} } ) {
34
- const isPlatform = !!specDir
35
- const checks = []
36
-
37
- if (!isPlatform) {
38
- // 非平台模式:只做轻量检查
39
- const localSpec = join(cwd, '.sillyspec')
40
- const scanDir = join(localSpec, 'docs', basename(cwd), 'scan')
41
-
42
- // 检查 7 份文档是否存在
43
- const missing = REQUIRED_SCAN_DOCS.filter(f => !existsSync(join(scanDir, f)))
44
- if (missing.length > 0) {
45
- checks.push({ name: 'missing_docs', severity: CHECK_SEVERITY.WARNING, detail: `缺少 ${missing.length} 份 scan 文档: ${missing.join(', ')}` })
46
- }
47
-
48
- const hasWarning = checks.some(c => c.severity === 'warning')
49
- return { status: hasWarning ? 'completed_with_warnings' : 'success', checks }
50
- }
51
-
52
- // ── 平台模式:严格检查 ──
53
-
54
- const projectName = basename(cwd)
55
-
56
- // 1. source_root 污染检查(docs/projects/workflows/knowledge/manifest/local)
57
- const pollutePaths = ['docs', 'projects', 'workflows', 'knowledge']
58
- const polluteFiles = ['manifest.json', 'local.yaml']
59
- for (const sub of pollutePaths) {
60
- const localSub = join(cwd, '.sillyspec', sub)
61
- if (existsSync(localSub)) {
62
- try {
63
- const leaked = readdirSync(localSub, { recursive: true }).filter(e => String(e).endsWith('.md') || String(e).endsWith('.yaml') || String(e).endsWith('.json'))
64
- if (leaked.length > 0) {
65
- checks.push({
66
- name: sub === 'docs' ? 'source_root_docs_leak' : 'source_root_leak',
67
- severity: CHECK_SEVERITY.FAILED,
68
- detail: `source_root/.sillyspec/${sub}/ 下存在 ${leaked.length} 个文件(${localSub}/),agent 写入到了错误路径`
69
- })
70
- }
71
- } catch {}
72
- }
73
- }
74
- for (const file of polluteFiles) {
75
- const filePath = join(cwd, '.sillyspec', file)
76
- if (existsSync(filePath)) {
77
- checks.push({
78
- name: 'source_root_leak',
79
- severity: CHECK_SEVERITY.FAILED,
80
- detail: `source_root/.sillyspec/${file} 存在,agent 写入到了错误路径(${filePath})`
81
- })
82
- }
83
- }
84
-
85
- // 2. spec_root 检查 7 份必需文档
86
- const specScanDir = join(specDir, 'docs', projectName, 'scan')
87
- const missingDocs = REQUIRED_SCAN_DOCS.filter(f => !existsSync(join(specScanDir, f)))
88
- if (missingDocs.length > 0) {
89
- checks.push({
90
- name: missingDocs.length === REQUIRED_SCAN_DOCS.length ? 'all_docs_missing' : 'partial_docs_missing',
91
- severity: CHECK_SEVERITY.FAILED,
92
- detail: missingDocs.length === REQUIRED_SCAN_DOCS.length
93
- ? `spec_root 下无任何 scan 文档(${specScanDir}/),扫描可能未执行`
94
- : `spec_root 缺少必需文档: ${missingDocs.join(', ')}(7 份 scan 文档均为 required)`
95
- })
96
- }
97
-
98
- // 3. 检查文档 header(author / created_at)— 只看文件头部,避免正文出现同名词被误判
99
- const existingDocs = REQUIRED_SCAN_DOCS.filter(f => existsSync(join(specScanDir, f)))
100
- const docsMissingHeader = []
101
- for (const doc of existingDocs) {
102
- const content = readFileSync(join(specScanDir, doc), 'utf8')
103
- const headerSlice = content.slice(0, 512)
104
- if (!/author\s*:/.test(headerSlice) || !/created_at\s*:/.test(headerSlice)) {
105
- docsMissingHeader.push(doc)
106
- }
107
- }
108
- if (docsMissingHeader.length > 0) {
109
- checks.push({
110
- name: 'docs_missing_header',
111
- severity: CHECK_SEVERITY.WARNING,
112
- detail: `${docsMissingHeader.length} 份文档缺少 author/created_at: ${docsMissingHeader.join(', ')}`
113
- })
114
- }
115
-
116
- // 4. local.yaml 校验
117
- const localYamlPath = join(specDir, 'local.yaml')
118
- if (existsSync(localYamlPath)) {
119
- const yamlContent = readFileSync(localYamlPath, 'utf8')
120
- const packageJsonPath = join(cwd, 'package.json')
121
- const invalidCommands = []
122
-
123
- // 简单提取 local.yaml 中的 commands
124
- const commandMatch = yamlContent.match(/build:\s*"([^"]+)"/) ||
125
- yamlContent.match(/test:\s*"([^"]+)"/) ||
126
- yamlContent.match(/lint:\s*"([^"]+)"/)
127
-
128
- if (commandMatch) {
129
- // 提取所有 npm run <script> 形式的命令
130
- const npmRunCommands = yamlContent.match(/npm run (\S+)/g) || []
131
- if (npmRunCommands.length > 0 && existsSync(packageJsonPath)) {
132
- try {
133
- const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
134
- const scripts = pkg.scripts || {}
135
- for (const cmd of npmRunCommands) {
136
- const scriptName = cmd.replace('npm run ', '')
137
- if (!scripts[scriptName]) {
138
- invalidCommands.push(`${cmd} (package.json 无 ${scriptName} script)`)
139
- }
140
- }
141
- } catch {}
142
- }
143
- }
144
-
145
- if (invalidCommands.length > 0) {
146
- checks.push({
147
- name: 'local_config_invalid',
148
- severity: CHECK_SEVERITY.WARNING,
149
- detail: `local.yaml 引用不存在的命令: ${invalidCommands.join('; ')}`
150
- })
151
- }
152
- }
153
-
154
- // 5. 检查 AI 输出中的错误标记
155
- if (outputText) {
156
- const errorPatterns = [
157
- { pattern: /tool_use_error/i, name: 'tool_use_error', detail: 'AI 输出中包含 tool_use_error' },
158
- { pattern: /API Error.*529/i, name: 'api_error_529', detail: 'AI 输出中包含 API Error 529' },
159
- { pattern: /rate.?limit.*exhausted/i, name: 'rate_limit_exhausted', detail: 'AI 输出中包含 rate_limit exhausted' },
160
- { pattern: /fallback|retry.*failed|skipped.*validat/i, name: 'fallback_or_skip', detail: 'AI 输出中出现 fallback/retry failed/skipped validation' },
161
- ]
162
- for (const ep of errorPatterns) {
163
- if (ep.pattern.test(outputText)) {
164
- checks.push({ name: ep.name, severity: CHECK_SEVERITY.WARNING, detail: ep.detail })
165
- }
166
- }
167
- }
168
-
169
- // 6. manifest 写入状态检查
170
- if (scanMeta.manifestWritten === false) {
171
- checks.push({
172
- name: 'manifest_write_failed',
173
- severity: CHECK_SEVERITY.FAILED,
174
- detail: 'manifest.json 写入失败,平台无法消费 scan 结果'
175
- })
176
- }
177
-
178
- // 7. 项目列表解析状态检查
179
- if (scanMeta.projectListParsed === false) {
180
- checks.push({
181
- name: 'project_list_parse_failed',
182
- severity: CHECK_SEVERITY.WARNING,
183
- detail: 'Step 2 项目列表解析失败,回退到注册项目列表,可能遗漏子项目'
184
- })
185
- }
186
-
187
- // 7.5 knowledge 产物校验
188
- const knowledgeDir = join(specDir, 'knowledge')
189
- if (existsSync(knowledgeDir)) {
190
- const indexPath = join(knowledgeDir, 'INDEX.md')
191
- if (!existsSync(indexPath)) {
192
- checks.push({
193
- name: 'knowledge_index_missing',
194
- severity: CHECK_SEVERITY.WARNING,
195
- detail: `knowledge/INDEX.md 不存在`
196
- })
197
- } else {
198
- // 检查 INDEX.md 引用的文件是否真实存在
199
- const indexContent = readFileSync(indexPath, 'utf8')
200
- const referencedFiles = [...indexContent.matchAll(/\(([^)]+\.md)/g)].map(m => m[1])
201
- const missingRefs = referencedFiles.filter(f => !existsSync(join(knowledgeDir, f)))
202
- if (missingRefs.length > 0) {
203
- checks.push({
204
- name: 'knowledge_broken_refs',
205
- severity: CHECK_SEVERITY.WARNING,
206
- detail: `INDEX.md 引用了不存在的文件: ${missingRefs.join(', ')}`
207
- })
208
- }
209
- }
210
- } else {
211
- checks.push({
212
- name: 'knowledge_dir_missing',
213
- severity: CHECK_SEVERITY.WARNING,
214
- detail: `knowledge/ 目录不存在`
215
- })
216
- }
217
-
218
- // 8. 计算 finalStatus
219
- const hasFailed = checks.some(c => c.severity === CHECK_SEVERITY.FAILED)
220
- const hasWarning = checks.some(c => c.severity === CHECK_SEVERITY.WARNING)
221
-
222
- let status
223
- if (hasFailed) {
224
- status = SCAN_STATUS.FAILED_POST_CHECK
225
- } else if (hasWarning) {
226
- status = SCAN_STATUS.COMPLETED_WITH_WARNINGS
227
- } else {
228
- status = SCAN_STATUS.SUCCESS
229
- }
230
-
231
- return { status, checks }
232
- }
233
-
234
- /**
235
- * 将 postcheck 结果转换为结构化 JSON(SillyHub 可消费格式)
236
- *
237
- * failure_category 标准化:
238
- * - warning : 非致命问题,不阻塞流程
239
- * - error : 文档缺失/内容不完整,需要修复
240
- * - critical : 安全问题(source_root 泄漏/路径污染)
241
- *
242
- * 结构化字段:
243
- * - violations : 明确违反约束的条目(source_root 泄漏等)
244
- * - missing_outputs : 预期文件不存在
245
- * - path_pollution : 产物写入了错误路径
246
- * - bad_references : 引用了不存在的命令/资源
247
- * - quality_warnings: AI 输出中包含错误标记等质量信号
248
- *
249
- * @param {object} result - runScanPostCheck 返回值
250
- * @param {object} [meta] - 附带元数据(workspace_id, scan_run_id, timestamp 等)
251
- * @returns {object} 结构化 JSON
252
- */
253
- export function formatStructuredResult(result, meta = {}) {
254
- const structured = {
255
- schema_version: 1,
256
- generated_at: new Date().toISOString(),
257
- overall_status: result.status,
258
- // 路径溯源(供平台消费)
259
- ...(meta.workspace_id ? { workspace_id: meta.workspace_id } : {}),
260
- ...(meta.scan_run_id ? { scan_run_id: meta.scan_run_id } : {}),
261
- ...(meta.source_root ? { source_root: meta.source_root } : {}),
262
- ...(meta.spec_root ? { spec_root: meta.spec_root } : {}),
263
- ...(meta.runtime_root ? { runtime_root: meta.runtime_root } : {}),
264
- summary: {
265
- total_checks: result.checks.length,
266
- critical: 0,
267
- error: 0,
268
- warning: 0,
269
- },
270
- failure_categories: {
271
- violations: [],
272
- missing_outputs: [],
273
- path_pollution: [],
274
- bad_references: [],
275
- quality_warnings: [],
276
- },
277
- checks: result.checks.map(c => ({
278
- name: c.name,
279
- severity: c.severity === 'failed' ? 'critical' : c.severity,
280
- detail: c.detail,
281
- })),
282
- }
283
-
284
- // 分类到 failure_categories
285
- for (const check of result.checks) {
286
- const severity = check.severity === 'failed' ? 'critical' : check.severity
287
- const entry = { name: check.name, detail: check.detail, severity }
288
-
289
- // 路径污染类
290
- if (check.name === 'source_root_leak' || check.name === 'source_root_docs_leak') {
291
- structured.failure_categories.path_pollution.push(entry)
292
- structured.failure_categories.violations.push(entry)
293
- }
294
- // 文档缺失类
295
- else if (check.name === 'all_docs_missing' || check.name === 'partial_docs_missing' || check.name === 'missing_docs') {
296
- structured.failure_categories.missing_outputs.push(entry)
297
- }
298
- // 引用无效类
299
- else if (check.name === 'local_config_invalid') {
300
- structured.failure_categories.bad_references.push(entry)
301
- }
302
- // AI 输出质量类
303
- else if (['tool_use_error', 'api_error_529', 'rate_limit_exhausted', 'fallback_or_skip'].includes(check.name)) {
304
- structured.failure_categories.quality_warnings.push(entry)
305
- }
306
- // manifest/project 列表问题
307
- else if (check.name === 'manifest_write_failed' || check.name === 'project_list_parse_failed') {
308
- structured.failure_categories.violations.push(entry)
309
- }
310
- // 文档缺少 header
311
- else if (check.name === 'docs_missing_header') {
312
- structured.failure_categories.quality_warnings.push(entry)
313
- }
314
- // 兜底:归入 violations
315
- else {
316
- structured.failure_categories.violations.push(entry)
317
- }
318
- }
319
-
320
- // 汇总计数
321
- for (const check of result.checks) {
322
- if (check.severity === 'failed') structured.summary.critical++
323
- else structured.summary.warning++
324
- }
325
-
326
- return structured
327
- }
328
-
329
- /**
330
- * 将结构化结果写入 JSON 文件(平台模式供 SillyHub 消费)
331
- *
332
- * 本地模式:写入 specDir/.runtime/postcheck-result.json
333
- * 平台模式:写入 runtimeRoot/scan-runs/{scan_run_id}/postcheck-result.json
334
- *
335
- * @param {object} structured - formatStructuredResult 返回值
336
- * @param {string} specDir - 规范目录(本地模式使用)
337
- * @param {object} [opts] - 平台模式选项
338
- * @param {string} [opts.runtimeRoot] - 平台模式运行时根目录
339
- * @param {string} [opts.scanRunId] - scan run ID
340
- * @returns {string|null} 写入的文件路径,失败时返回 null
341
- */
342
- export function writeStructuredResult(structured, specDir, opts = {}) {
343
- if (!specDir && !opts.runtimeRoot) return null
344
- try {
345
- let outPath
346
- if (opts.runtimeRoot && opts.scanRunId) {
347
- const scanRunDir = join(opts.runtimeRoot, 'scan-runs', opts.scanRunId)
348
- mkdirSync(scanRunDir, { recursive: true })
349
- outPath = join(scanRunDir, 'postcheck-result.json')
350
- } else if (specDir) {
351
- const runtimeDir = join(specDir, '.runtime')
352
- mkdirSync(runtimeDir, { recursive: true })
353
- outPath = join(runtimeDir, 'postcheck-result.json')
354
- } else {
355
- return null
356
- }
357
-
358
- writeFileSync(outPath, JSON.stringify(structured, null, 2) + '\n')
359
- return outPath
360
- } catch (e) {
361
- console.warn(` ⚠️ postcheck-result.json 写入失败: ${e.message}`)
362
- return null
363
- }
364
- }
365
-
366
- /**
367
- * 打印 post-check 结果到 stdout
368
- */
369
- export function printScanPostCheckResult(result) {
370
- if (result.checks.length === 0) {
371
- console.log(' ✅ CLI post-check: 全部通过')
372
- return
373
- }
374
-
375
- for (const check of result.checks) {
376
- const icon = check.severity === 'failed' ? '❌' : '⚠️'
377
- console.log(` ${icon} CLI post-check [${check.name}]: ${check.detail}`)
378
- }
379
- console.log(` 📋 最终状态: ${result.status}`)
380
- }