sillyspec 3.20.3 → 3.20.5
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.
- package/.claude/skills/sillyspec-archive/SKILL.md +21 -17
- package/.claude/skills/sillyspec-auto/SKILL.md +83 -77
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -17
- package/.claude/skills/sillyspec-commit/SKILL.md +106 -105
- package/.claude/skills/sillyspec-continue/SKILL.md +45 -44
- package/.claude/skills/sillyspec-doctor/SKILL.md +31 -22
- package/.claude/skills/sillyspec-execute/SKILL.md +30 -17
- package/.claude/skills/sillyspec-explore/SKILL.md +109 -96
- package/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
- package/.claude/skills/sillyspec-plan/SKILL.md +21 -52
- package/.claude/skills/sillyspec-propose/SKILL.md +21 -17
- package/.claude/skills/sillyspec-quick/SKILL.md +21 -17
- package/.claude/skills/sillyspec-resume/SKILL.md +68 -111
- package/.claude/skills/sillyspec-scan/SKILL.md +21 -17
- package/.claude/skills/sillyspec-state/SKILL.md +54 -54
- package/.claude/skills/sillyspec-status/SKILL.md +21 -17
- package/.claude/skills/sillyspec-verify/SKILL.md +21 -17
- package/.claude/skills/sillyspec-workspace/SKILL.md +157 -149
- package/.husky/pre-push +13 -0
- package/CLAUDE.md +18 -0
- package/README.md +198 -186
- package/SKILL.md +90 -87
- package/bin/sillyspec.js +2 -2
- package/docs/brainstorm-plan-contract.md +64 -0
- package/docs/plan-execute-contract.md +123 -0
- package/docs/platform-scan-protocol.md +298 -0
- package/docs/revision-mode.md +115 -0
- package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -0
- package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -0
- package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -0
- package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -0
- package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -0
- package/docs/sillyspec/file-lifecycle.md +131 -0
- package/docs/workflow-contract-regression.md +106 -0
- package/docs/worktree-isolation.md +252 -0
- package/package.json +40 -34
- package/packages/dashboard/dist/assets/{index-D1EVTLmc.js → index-Bq_Z2hne.js} +7446 -7446
- package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
- package/packages/dashboard/dist/index.html +16 -16
- package/packages/dashboard/dist/prototype-dashboard.html +836 -0
- package/packages/dashboard/dist/prototype-overview.html +256 -0
- package/packages/dashboard/index.html +15 -15
- package/packages/dashboard/package-lock.json +2384 -2384
- package/packages/dashboard/package.json +25 -25
- package/packages/dashboard/public/prototype-dashboard.html +836 -0
- package/packages/dashboard/public/prototype-overview.html +256 -0
- package/packages/dashboard/server/executor.js +86 -86
- package/packages/dashboard/server/index.js +588 -508
- package/packages/dashboard/server/parser.js +526 -458
- package/packages/dashboard/server/watcher.js +350 -342
- package/packages/dashboard/src/App.vue +558 -325
- package/packages/dashboard/src/components/ActionBar.vue +93 -84
- package/packages/dashboard/src/components/CommandPalette.vue +96 -92
- package/packages/dashboard/src/components/DetailPanel.vue +137 -137
- package/packages/dashboard/src/components/DocPreview.vue +105 -8
- package/packages/dashboard/src/components/DocTree.vue +75 -19
- package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
- package/packages/dashboard/src/components/LogStream.vue +65 -65
- package/packages/dashboard/src/components/PipelineStage.vue +95 -75
- package/packages/dashboard/src/components/PipelineView.vue +156 -129
- package/packages/dashboard/src/components/ProjectCard.vue +187 -0
- package/packages/dashboard/src/components/ProjectList.vue +210 -210
- package/packages/dashboard/src/components/ProjectOverview.vue +113 -139
- package/packages/dashboard/src/components/StageBadge.vue +67 -53
- package/packages/dashboard/src/components/StepCard.vue +94 -89
- package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
- package/packages/dashboard/src/composables/useDashboard.js +192 -164
- package/packages/dashboard/src/composables/useKeyboard.js +119 -119
- package/packages/dashboard/src/composables/useLayout.js +131 -0
- package/packages/dashboard/src/composables/useWebSocket.js +129 -129
- package/packages/dashboard/src/main.js +8 -8
- package/packages/dashboard/src/style.css +132 -132
- package/packages/dashboard/vite.config.js +18 -18
- package/src/brainstorm-postcheck.js +158 -0
- package/src/change-list.js +52 -0
- package/src/change-risk-profile.js +352 -0
- package/src/classify-change.js +73 -0
- package/src/constants.js +70 -0
- package/src/contract-matrix.js +278 -0
- package/src/db.js +201 -0
- package/src/endpoint-extractor.js +315 -0
- package/src/hooks/claude-pre-tool-use.cjs +125 -0
- package/src/hooks/worktree-guard.js +761 -0
- package/src/index.js +922 -252
- package/src/init.js +470 -375
- package/src/knowledge-match.js +130 -0
- package/src/migrate.js +117 -117
- package/src/modules.js +482 -0
- package/src/progress.js +1734 -508
- package/src/run.js +3465 -652
- package/src/scan-postcheck.js +387 -0
- package/src/setup.js +398 -460
- package/src/stage-contract.js +700 -0
- package/src/stages/archive.js +160 -54
- package/src/stages/brainstorm-auto.js +229 -0
- package/src/stages/brainstorm.js +645 -239
- package/src/stages/doctor.js +365 -312
- package/src/stages/execute.js +634 -264
- package/src/stages/explore.js +34 -0
- package/src/stages/index.js +29 -35
- package/src/stages/knowledge.js +498 -0
- package/src/stages/plan-postcheck.js +518 -0
- package/src/stages/plan.js +582 -279
- package/src/stages/propose.js +174 -115
- package/src/stages/quick.js +102 -63
- package/src/stages/scan.js +558 -141
- package/src/stages/status.js +65 -65
- package/src/stages/verify.js +322 -135
- package/src/sync.js +497 -0
- package/src/task-review.js +346 -0
- package/src/workflow.js +785 -0
- package/src/worktree-apply.js +549 -0
- package/src/worktree-deps.js +185 -0
- package/src/worktree.js +982 -0
- package/templates/workflows/archive-impact.yaml +79 -0
- package/templates/workflows/scan-docs.yaml +132 -0
- package/test/brainstorm-plan-contract.test.mjs +273 -0
- package/test/check-syntax.mjs +26 -0
- package/test/cli-top-level-aliases.test.mjs +174 -0
- package/test/contract-artifacts.test.mjs +323 -0
- package/test/decision-supersede.test.mjs +277 -0
- package/test/knowledge-match.test.mjs +231 -0
- package/test/plan-execute-contract.test.mjs +357 -0
- package/test/plan-optimization.test.mjs +572 -0
- package/test/platform-artifacts.test.mjs +190 -0
- package/test/platform-failure-samples.test.mjs +199 -0
- package/test/platform-recovery-chain.test.mjs +179 -0
- package/test/platform-recovery.test.mjs +167 -0
- package/test/platform-scan-p0.test.mjs +175 -0
- package/test/revision-v1.test.mjs +1145 -0
- package/test/run-sanitize-project-name.test.mjs +51 -0
- package/test/run-scan-postcheck-fail.test.mjs +64 -0
- package/test/run-scan-project-parse.test.mjs +200 -0
- package/test/run-tests.mjs +48 -0
- package/test/runtime-cleanup-keeps-worktree.test.mjs +107 -0
- package/test/scan-docs-yaml-placeholders.test.mjs +84 -0
- package/test/scan-knowledge.test.mjs +175 -0
- package/test/scan-paths.test.mjs +68 -0
- package/test/scan-postcheck-project-priority.test.mjs +85 -0
- package/test/scan-postcheck.test.mjs +197 -0
- package/test/scan-workflow-anyfailed-block.test.mjs +52 -0
- package/test/spec-dir.test.mjs +206 -0
- package/test/stage-contract-failed-post-check.test.mjs +102 -0
- package/test/stage-contract.test.mjs +299 -0
- package/test/stage-definitions.test.mjs +39 -0
- package/test/wait-gates.test.mjs +501 -0
- package/test/workflow-spec-base.test.mjs +142 -0
- package/test/worktree-deps-provision.test.mjs +148 -0
- package/test/worktree-guard.test.mjs +136 -0
- package/test/worktree-native-overlay.test.mjs +188 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
- package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
- package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
- package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
- package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
- package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
- package/.sillyspec/changes/auto-mode/design.md +0 -50
- package/.sillyspec/changes/auto-mode/proposal.md +0 -19
- package/.sillyspec/changes/auto-mode/requirements.md +0 -21
- package/.sillyspec/changes/auto-mode/tasks.md +0 -7
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +0 -206
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
- package/.sillyspec/changes/dashboard/design.md +0 -219
- package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
- package/.sillyspec/changes/run-command-design/design.md +0 -1230
- package/.sillyspec/changes/unified-docs-design/design.md +0 -199
- package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
- package/.sillyspec/knowledge/INDEX.md +0 -8
- package/.sillyspec/knowledge/uncategorized.md +0 -3
- package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
- package/.sillyspec/projects/sillyspec.yaml +0 -3
- package/dist/steps/brainstorm/01-load-context.md +0 -30
- package/dist/steps/brainstorm/02-reuse-check.md +0 -6
- package/dist/steps/brainstorm/03-prototype-analysis.md +0 -11
- package/dist/steps/brainstorm/04-module-split.md +0 -23
- package/dist/steps/brainstorm/05-dialog-explore.md +0 -8
- package/dist/steps/brainstorm/06-propose-approaches.md +0 -3
- package/dist/steps/brainstorm/07-present-design.md +0 -3
- package/dist/steps/brainstorm/08-write-design.md +0 -21
- package/dist/steps/brainstorm/09-self-review.md +0 -15
- package/dist/steps/brainstorm/10-user-confirm.md +0 -3
- package/dist/steps/brainstorm/11-output-spec.md +0 -7
- package/dist/steps/brainstorm/manifest.yaml +0 -26
- package/dist/steps/execute/01-load-context.md +0 -41
- package/dist/steps/execute/02-scan-conventions.md +0 -47
- package/dist/steps/execute/03-skill-mcp.md +0 -19
- package/dist/steps/execute/04-assign-task.md +0 -22
- package/dist/steps/execute/04b-prompt-template.md +0 -54
- package/dist/steps/execute/05-write-test.md +0 -7
- package/dist/steps/execute/06-write-code.md +0 -8
- package/dist/steps/execute/07-run-test.md +0 -26
- package/dist/steps/execute/08-fix-issues.md +0 -28
- package/dist/steps/execute/09-next-task.md +0 -33
- package/dist/steps/execute/manifest.yaml +0 -28
- package/dist/steps/plan/01-load-context.md +0 -22
- package/dist/steps/plan/02-anchor-confirm.md +0 -1
- package/dist/steps/plan/03-expand-tasks.md +0 -33
- package/dist/steps/plan/04-mark-order.md +0 -15
- package/dist/steps/plan/05-e2e-planning.md +0 -17
- package/dist/steps/plan/06-self-check.md +0 -16
- package/dist/steps/plan/07-save.md +0 -1
- package/dist/steps/plan/manifest.yaml +0 -18
- package/dist/steps/scan/01-env-detect.md +0 -51
- package/dist/steps/scan/02-tech-stack.md +0 -16
- package/dist/steps/scan/03-conventions.md +0 -16
- package/dist/steps/scan/04-structure.md +0 -19
- package/dist/steps/scan/05-quality.md +0 -18
- package/dist/steps/scan/06-complete.md +0 -49
- package/dist/steps/scan/manifest.yaml +0 -16
- package/dist/steps/verify/01-load-specs.md +0 -28
- package/dist/steps/verify/02-check-tasks.md +0 -1
- package/dist/steps/verify/03-check-design.md +0 -6
- package/dist/steps/verify/04-run-tests.md +0 -7
- package/dist/steps/verify/05-e2e-tests.md +0 -27
- package/dist/steps/verify/05b-e2e-fix.md +0 -33
- package/dist/steps/verify/06-code-quality.md +0 -25
- package/dist/steps/verify/07-lint-check.md +0 -27
- package/dist/steps/verify/08-output-report.md +0 -14
- package/dist/steps/verify/manifest.yaml +0 -22
- package/packages/dashboard/dist/assets/index-DGe8CqeP.css +0 -1
- package/src/derive.js +0 -147
- package/src/step.js +0 -543
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
name: archive-impact
|
|
2
|
+
description: 分析变更影响的模块并同步模块文档
|
|
3
|
+
spec_version: 1
|
|
4
|
+
|
|
5
|
+
roles:
|
|
6
|
+
- id: impact-analyzer
|
|
7
|
+
name: "影响分析"
|
|
8
|
+
task: "分析 git diff,提取变更影响模块"
|
|
9
|
+
inputs:
|
|
10
|
+
paths: [".sillyspec/changes/", ".sillyspec/docs/*/modules/"]
|
|
11
|
+
hints:
|
|
12
|
+
grep_patterns: ["_module-map", "module-impact", "git diff"]
|
|
13
|
+
outputs:
|
|
14
|
+
- path: ".sillyspec/changes/<change-name>/module-impact.md"
|
|
15
|
+
required: true
|
|
16
|
+
checks:
|
|
17
|
+
- type: file_exists
|
|
18
|
+
- type: min_lines
|
|
19
|
+
min: 20
|
|
20
|
+
- type: contains_sections
|
|
21
|
+
sections: ["模块影响矩阵", "未匹配文件"]
|
|
22
|
+
constraints:
|
|
23
|
+
- "以 git diff 为准(真实 > 声明)"
|
|
24
|
+
- "影响类型:逻辑变更/数据结构变更/接口变更/调用关系变更/配置变更/新增"
|
|
25
|
+
- "需要 review 标记不确定的影响"
|
|
26
|
+
|
|
27
|
+
- id: doc-syncer
|
|
28
|
+
name: "文档同步"
|
|
29
|
+
task: "根据 module-impact.md 同步更新模块索引和卡片"
|
|
30
|
+
depends_on:
|
|
31
|
+
- impact-analyzer
|
|
32
|
+
inputs:
|
|
33
|
+
from_role: impact-analyzer
|
|
34
|
+
output: module-impact
|
|
35
|
+
output_description: "变更影响分析结果,包含模块影响矩阵"
|
|
36
|
+
paths: [".sillyspec/docs/*/modules/"]
|
|
37
|
+
hints:
|
|
38
|
+
grep_patterns: ["_module-map", "module-impact"]
|
|
39
|
+
outputs:
|
|
40
|
+
- path: ".sillyspec/docs/sillyspec/modules/_module-map.yaml"
|
|
41
|
+
required: false
|
|
42
|
+
checks:
|
|
43
|
+
- type: file_exists
|
|
44
|
+
- path: ".sillyspec/docs/sillyspec/modules/<module-id>.md"
|
|
45
|
+
required: false
|
|
46
|
+
# checks 跳过:路径含动态 <module-id> 占位符,不适合静态检查
|
|
47
|
+
constraints:
|
|
48
|
+
- "结构化事实改 _module-map.yaml,语义解释改模块卡片"
|
|
49
|
+
- "人工备注区域永远保护,不覆盖"
|
|
50
|
+
- "更新前展示 diff 摘要,请用户确认"
|
|
51
|
+
|
|
52
|
+
orchestration:
|
|
53
|
+
mode: sequential
|
|
54
|
+
timeout_per_role: 180
|
|
55
|
+
|
|
56
|
+
checks:
|
|
57
|
+
role_level:
|
|
58
|
+
- type: file_exists
|
|
59
|
+
- type: min_lines
|
|
60
|
+
min: 10
|
|
61
|
+
workflow_level:
|
|
62
|
+
- type: file_exists
|
|
63
|
+
path: ".sillyspec/changes/<change-name>/module-impact.md"
|
|
64
|
+
|
|
65
|
+
retry:
|
|
66
|
+
max_attempts: 1
|
|
67
|
+
include_failure_context: true
|
|
68
|
+
retry_scope: failed_role_only
|
|
69
|
+
|
|
70
|
+
on_check_failure: prompt_retry
|
|
71
|
+
|
|
72
|
+
permissions:
|
|
73
|
+
write_mode: patch_only
|
|
74
|
+
write_scope:
|
|
75
|
+
- ".sillyspec/docs/"
|
|
76
|
+
- ".sillyspec/changes/<change-name>/"
|
|
77
|
+
allow_shell: true
|
|
78
|
+
allow_network: false
|
|
79
|
+
allow_git: true
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
name: scan-docs
|
|
2
|
+
description: 并行生成项目的扫描文档(7份)
|
|
3
|
+
spec_version: 1
|
|
4
|
+
|
|
5
|
+
roles:
|
|
6
|
+
- id: arch
|
|
7
|
+
name: "技术架构"
|
|
8
|
+
task: "生成 ARCHITECTURE.md"
|
|
9
|
+
inputs:
|
|
10
|
+
paths:
|
|
11
|
+
- "src/*.js"
|
|
12
|
+
- "package.json"
|
|
13
|
+
hints:
|
|
14
|
+
grep_patterns: ["class ", "export ", "import ", "schema", "CREATE TABLE"]
|
|
15
|
+
outputs:
|
|
16
|
+
- path: "{SPEC_ROOT}/docs/<project>/scan/ARCHITECTURE.md"
|
|
17
|
+
required: true
|
|
18
|
+
checks:
|
|
19
|
+
- type: file_exists
|
|
20
|
+
- type: min_lines
|
|
21
|
+
min: 20
|
|
22
|
+
- type: contains_sections
|
|
23
|
+
sections: ["技术栈", "架构概览"]
|
|
24
|
+
constraints:
|
|
25
|
+
- "禁止读源码全文,只用 grep/rg 搜索"
|
|
26
|
+
- "Schema 只记表名+说明+字段数"
|
|
27
|
+
|
|
28
|
+
- id: conventions
|
|
29
|
+
name: "代码约定"
|
|
30
|
+
task: "生成 CONVENTIONS.md"
|
|
31
|
+
inputs:
|
|
32
|
+
paths: ["src/"]
|
|
33
|
+
hints:
|
|
34
|
+
grep_patterns: ["function ", "const ", "async ", "try ", "catch "]
|
|
35
|
+
outputs:
|
|
36
|
+
- path: "{SPEC_ROOT}/docs/<project>/scan/CONVENTIONS.md"
|
|
37
|
+
required: true
|
|
38
|
+
checks:
|
|
39
|
+
- type: file_exists
|
|
40
|
+
- type: min_lines
|
|
41
|
+
min: 15
|
|
42
|
+
- type: contains_sections
|
|
43
|
+
sections: ["框架隐形规则", "代码风格"]
|
|
44
|
+
constraints:
|
|
45
|
+
- "禁止读源码全文"
|
|
46
|
+
- "提取 3-5 个典型模式"
|
|
47
|
+
|
|
48
|
+
- id: structure
|
|
49
|
+
name: "目录结构+外部集成"
|
|
50
|
+
task: "生成 STRUCTURE.md 和 INTEGRATIONS.md"
|
|
51
|
+
inputs:
|
|
52
|
+
paths: ["./"]
|
|
53
|
+
hints:
|
|
54
|
+
grep_patterns: ["fetch", "http", "WebSocket", "ws", "chokidar"]
|
|
55
|
+
outputs:
|
|
56
|
+
- path: "{SPEC_ROOT}/docs/<project>/scan/STRUCTURE.md"
|
|
57
|
+
required: true
|
|
58
|
+
checks:
|
|
59
|
+
- type: file_exists
|
|
60
|
+
- type: min_lines
|
|
61
|
+
min: 15
|
|
62
|
+
- path: "{SPEC_ROOT}/docs/<project>/scan/INTEGRATIONS.md"
|
|
63
|
+
required: true
|
|
64
|
+
checks:
|
|
65
|
+
- type: file_exists
|
|
66
|
+
- type: min_lines
|
|
67
|
+
min: 15
|
|
68
|
+
constraints:
|
|
69
|
+
- "STRUCTURE: 目录树+模块说明"
|
|
70
|
+
- "INTEGRATIONS: 按类型分组外部依赖"
|
|
71
|
+
|
|
72
|
+
- id: quality
|
|
73
|
+
name: "测试+债务+概览"
|
|
74
|
+
task: "生成 TESTING.md、CONCERNS.md、PROJECT.md"
|
|
75
|
+
inputs:
|
|
76
|
+
paths: ["src/", "packages/"]
|
|
77
|
+
hints:
|
|
78
|
+
grep_patterns: ["TODO", "FIXME", "deprecated", "test", "describe"]
|
|
79
|
+
outputs:
|
|
80
|
+
- path: "{SPEC_ROOT}/docs/<project>/scan/TESTING.md"
|
|
81
|
+
required: true
|
|
82
|
+
checks:
|
|
83
|
+
- type: file_exists
|
|
84
|
+
- type: min_lines
|
|
85
|
+
min: 10
|
|
86
|
+
- type: no_placeholder
|
|
87
|
+
- path: "{SPEC_ROOT}/docs/<project>/scan/CONCERNS.md"
|
|
88
|
+
required: true
|
|
89
|
+
checks:
|
|
90
|
+
- type: file_exists
|
|
91
|
+
- type: min_lines
|
|
92
|
+
min: 10
|
|
93
|
+
- type: contains_sections
|
|
94
|
+
sections: ["代码质量", "依赖风险"]
|
|
95
|
+
- path: "{SPEC_ROOT}/docs/<project>/scan/PROJECT.md"
|
|
96
|
+
required: true
|
|
97
|
+
checks:
|
|
98
|
+
- type: file_exists
|
|
99
|
+
- type: min_lines
|
|
100
|
+
min: 15
|
|
101
|
+
- type: contains_sections
|
|
102
|
+
sections: ["项目简介", "技术栈"]
|
|
103
|
+
constraints:
|
|
104
|
+
- "CONCERNS 按严重程度分组(🔴/🟡/🟢)"
|
|
105
|
+
- "不编造不存在的测试"
|
|
106
|
+
|
|
107
|
+
orchestration:
|
|
108
|
+
mode: parallel
|
|
109
|
+
max_concurrent: 4
|
|
110
|
+
timeout_per_role: 120
|
|
111
|
+
|
|
112
|
+
checks:
|
|
113
|
+
workflow_level:
|
|
114
|
+
- type: file_count
|
|
115
|
+
path: "scan/"
|
|
116
|
+
min: 7
|
|
117
|
+
- type: no_empty_files
|
|
118
|
+
|
|
119
|
+
retry:
|
|
120
|
+
max_attempts: 1
|
|
121
|
+
include_failure_context: true
|
|
122
|
+
retry_scope: failed_role_only
|
|
123
|
+
|
|
124
|
+
on_check_failure: prompt_retry
|
|
125
|
+
|
|
126
|
+
permissions:
|
|
127
|
+
write_mode: direct
|
|
128
|
+
write_scope:
|
|
129
|
+
- "{SPEC_ROOT}/docs/<project>/scan/"
|
|
130
|
+
allow_shell: true
|
|
131
|
+
allow_network: false
|
|
132
|
+
allow_git: false
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brainstorm → Plan Contract v1 测试
|
|
3
|
+
*
|
|
4
|
+
* 验证 design.md 到 plan 的输入契约:
|
|
5
|
+
* 1. 合法 design 通过
|
|
6
|
+
* 2. 缺关键章节失败
|
|
7
|
+
* 3. warning 不阻断
|
|
8
|
+
*/
|
|
9
|
+
import { validateDesignForPlan } from '../src/stages/plan.js'
|
|
10
|
+
|
|
11
|
+
let failed = 0
|
|
12
|
+
const failures = []
|
|
13
|
+
|
|
14
|
+
function assert(condition, msg) {
|
|
15
|
+
if (!condition) {
|
|
16
|
+
failed++
|
|
17
|
+
failures.push(msg)
|
|
18
|
+
console.log(` ❌ FAIL: ${msg}`)
|
|
19
|
+
} else {
|
|
20
|
+
console.log(` ✅ PASS: ${msg}`)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log('=== Brainstorm → Plan Contract v1 测试 ===\n')
|
|
25
|
+
|
|
26
|
+
// ─────────────────────────────────────────
|
|
27
|
+
// Case 1: valid design 通过
|
|
28
|
+
// ─────────────────────────────────────────
|
|
29
|
+
console.log('--- Case 1: valid design 通过 ---')
|
|
30
|
+
{
|
|
31
|
+
const design = `# Design: 用户认证系统
|
|
32
|
+
|
|
33
|
+
## 背景
|
|
34
|
+
需要实现用户认证。
|
|
35
|
+
|
|
36
|
+
## 设计目标
|
|
37
|
+
- 支持 OAuth2
|
|
38
|
+
- 支持手机号登录
|
|
39
|
+
|
|
40
|
+
## 非目标
|
|
41
|
+
- 不做 SSO
|
|
42
|
+
|
|
43
|
+
## 总体方案
|
|
44
|
+
使用 JWT + Refresh Token。
|
|
45
|
+
|
|
46
|
+
## 决策
|
|
47
|
+
- D-001@v1: 选择 JWT 而非 Session
|
|
48
|
+
|
|
49
|
+
## 约束
|
|
50
|
+
- 必须兼容现有 API
|
|
51
|
+
|
|
52
|
+
## 文件变更清单
|
|
53
|
+
| 操作 | 文件路径 | 说明 |
|
|
54
|
+
|------|---------|------|
|
|
55
|
+
| 新增 | src/auth.js | 认证模块 |
|
|
56
|
+
`
|
|
57
|
+
const result = validateDesignForPlan(design)
|
|
58
|
+
assert(result.ok, '完整 design 应校验通过')
|
|
59
|
+
assert(result.errors.length === 0, '不应有 errors')
|
|
60
|
+
assert(result.warnings.length === 0, '不应有 warnings')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─────────────────────────────────────────
|
|
64
|
+
// Case 2: empty design 失败
|
|
65
|
+
// ─────────────────────────────────────────
|
|
66
|
+
console.log('\n--- Case 2: empty design 失败 ---')
|
|
67
|
+
{
|
|
68
|
+
assert(!validateDesignForPlan('').ok, '空字符串应失败')
|
|
69
|
+
assert(!validateDesignForPlan(null).ok, 'null 应失败')
|
|
70
|
+
assert(!validateDesignForPlan(' ').ok, '纯空格应失败')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ─────────────────────────────────────────
|
|
74
|
+
// Case 3: missing goal 失败
|
|
75
|
+
// ─────────────────────────────────────────
|
|
76
|
+
console.log('\n--- Case 3: missing goal/背景 失败 ---')
|
|
77
|
+
{
|
|
78
|
+
const design = `# Design
|
|
79
|
+
|
|
80
|
+
## 总体方案
|
|
81
|
+
用 JWT。
|
|
82
|
+
|
|
83
|
+
## 决策
|
|
84
|
+
D-001@v1: 选 JWT
|
|
85
|
+
`
|
|
86
|
+
const result = validateDesignForPlan(design)
|
|
87
|
+
assert(!result.ok, '缺目标/背景应失败')
|
|
88
|
+
assert(result.errors.some(e => e.includes('目标') || e.includes('背景')), '错误应提到目标/背景')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─────────────────────────────────────────
|
|
92
|
+
// Case 4: missing scope/方案 失败
|
|
93
|
+
// ─────────────────────────────────────────
|
|
94
|
+
console.log('\n--- Case 4: missing scope/方案 失败 ---')
|
|
95
|
+
{
|
|
96
|
+
const design = `# Design
|
|
97
|
+
|
|
98
|
+
## 背景
|
|
99
|
+
需要认证。
|
|
100
|
+
|
|
101
|
+
## 决策
|
|
102
|
+
D-001@v1: 选 JWT
|
|
103
|
+
`
|
|
104
|
+
const result = validateDesignForPlan(design)
|
|
105
|
+
assert(!result.ok, '缺范围/方案应失败')
|
|
106
|
+
assert(result.errors.some(e => e.includes('范围') || e.includes('方案')), '错误应提到范围/方案')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ─────────────────────────────────────────
|
|
110
|
+
// Case 5: missing decisions 失败
|
|
111
|
+
// ─────────────────────────────────────────
|
|
112
|
+
console.log('\n--- Case 5: missing decisions 失败 ---')
|
|
113
|
+
{
|
|
114
|
+
const design = `# Design
|
|
115
|
+
|
|
116
|
+
## 背景
|
|
117
|
+
需要认证。
|
|
118
|
+
|
|
119
|
+
## 总体方案
|
|
120
|
+
用 JWT。
|
|
121
|
+
`
|
|
122
|
+
const result = validateDesignForPlan(design)
|
|
123
|
+
assert(!result.ok, '缺决策应失败')
|
|
124
|
+
assert(result.errors.some(e => e.includes('决策')), '错误应提到决策')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ─────────────────────────────────────────
|
|
128
|
+
// Case 6: decisions.md 引用也算决策
|
|
129
|
+
// ─────────────────────────────────────────
|
|
130
|
+
console.log('\n--- Case 6: decisions.md 引用算决策 ---')
|
|
131
|
+
{
|
|
132
|
+
const design = `# Design
|
|
133
|
+
|
|
134
|
+
## 背景
|
|
135
|
+
需要认证。
|
|
136
|
+
|
|
137
|
+
## 总体方案
|
|
138
|
+
用 JWT。详见 decisions.md。
|
|
139
|
+
|
|
140
|
+
## 文件变更清单
|
|
141
|
+
| 操作 | 文件 | 说明 |
|
|
142
|
+
`
|
|
143
|
+
const result = validateDesignForPlan(design)
|
|
144
|
+
assert(result.ok, 'decisions.md 引用应满足决策检查')
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ─────────────────────────────────────────
|
|
148
|
+
// Case 7: missing non-goals 只有 warning
|
|
149
|
+
// ─────────────────────────────────────────
|
|
150
|
+
console.log('\n--- Case 7: missing non-goals warning ---')
|
|
151
|
+
{
|
|
152
|
+
const design = `# Design
|
|
153
|
+
|
|
154
|
+
## 背景
|
|
155
|
+
需要认证。
|
|
156
|
+
|
|
157
|
+
## 总体方案
|
|
158
|
+
用 JWT。
|
|
159
|
+
|
|
160
|
+
## 决策
|
|
161
|
+
D-001@v1: 选 JWT
|
|
162
|
+
|
|
163
|
+
## 约束
|
|
164
|
+
必须兼容现有 API。
|
|
165
|
+
`
|
|
166
|
+
const result = validateDesignForPlan(design)
|
|
167
|
+
assert(result.ok, '缺非目标不应阻断')
|
|
168
|
+
assert(result.warnings.some(w => w.includes('非目标') || w.includes('Non-goals')), '应有非目标 warning')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ─────────────────────────────────────────
|
|
172
|
+
// Case 8: missing constraints 只有 warning
|
|
173
|
+
// ─────────────────────────────────────────
|
|
174
|
+
console.log('\n--- Case 8: missing constraints warning ---')
|
|
175
|
+
{
|
|
176
|
+
const design = `# Design
|
|
177
|
+
|
|
178
|
+
## 目标
|
|
179
|
+
实现认证。
|
|
180
|
+
|
|
181
|
+
## 设计方案
|
|
182
|
+
用 JWT。
|
|
183
|
+
|
|
184
|
+
## 决策
|
|
185
|
+
选择 JWT。
|
|
186
|
+
|
|
187
|
+
## 非目标
|
|
188
|
+
不做 SSO。
|
|
189
|
+
`
|
|
190
|
+
const result = validateDesignForPlan(design)
|
|
191
|
+
assert(result.ok, '缺约束不应阻断')
|
|
192
|
+
assert(result.warnings.some(w => w.includes('约束') || w.includes('风险') || w.includes('Trade-off')), '应有约束/风险 warning')
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ─────────────────────────────────────────
|
|
196
|
+
// Case 9: missing 文件变更清单 warning
|
|
197
|
+
// ─────────────────────────────────────────
|
|
198
|
+
console.log('\n--- Case 9: missing 文件变更清单 warning ---')
|
|
199
|
+
{
|
|
200
|
+
const design = `# Design
|
|
201
|
+
|
|
202
|
+
## 背景
|
|
203
|
+
需要认证。
|
|
204
|
+
|
|
205
|
+
## 总体方案
|
|
206
|
+
用 JWT。
|
|
207
|
+
|
|
208
|
+
## 决策
|
|
209
|
+
D-001@v1: 选 JWT
|
|
210
|
+
`
|
|
211
|
+
const result = validateDesignForPlan(design)
|
|
212
|
+
assert(result.ok, '缺文件变更清单不应阻断')
|
|
213
|
+
assert(result.warnings.some(w => w.includes('文件变更')), '应有文件变更 warning')
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ─────────────────────────────────────────
|
|
217
|
+
// Case 10: 英文 design 通过
|
|
218
|
+
// ─────────────────────────────────────────
|
|
219
|
+
console.log('\n--- Case 10: 英文 design 通过 ---')
|
|
220
|
+
{
|
|
221
|
+
const design = `# Design: Auth System
|
|
222
|
+
|
|
223
|
+
## Background
|
|
224
|
+
We need authentication.
|
|
225
|
+
|
|
226
|
+
## Solution
|
|
227
|
+
Use JWT + Refresh Token.
|
|
228
|
+
|
|
229
|
+
## Decision
|
|
230
|
+
D-001@v1: Choose JWT over Session
|
|
231
|
+
|
|
232
|
+
## Non-goals
|
|
233
|
+
No SSO.
|
|
234
|
+
|
|
235
|
+
## Constraints
|
|
236
|
+
Must be backwards compatible.
|
|
237
|
+
`
|
|
238
|
+
const result = validateDesignForPlan(design)
|
|
239
|
+
assert(result.ok, '英文 design 应校验通过')
|
|
240
|
+
assert(result.errors.length === 0, '不应有 errors')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ─────────────────────────────────────────
|
|
244
|
+
// Case 11: 最小合法 design
|
|
245
|
+
// ─────────────────────────────────────────
|
|
246
|
+
console.log('\n--- Case 11: 最小合法 design ---')
|
|
247
|
+
{
|
|
248
|
+
const design = `# Design
|
|
249
|
+
|
|
250
|
+
## 目标
|
|
251
|
+
修 bug。
|
|
252
|
+
|
|
253
|
+
## 方案
|
|
254
|
+
改代码。
|
|
255
|
+
|
|
256
|
+
## 决策
|
|
257
|
+
D-001@v1: 直接改。
|
|
258
|
+
`
|
|
259
|
+
const result = validateDesignForPlan(design)
|
|
260
|
+
assert(result.ok, '最小合法 design 应通过')
|
|
261
|
+
// 可能有 warning 但 ok
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ── 结果 ──
|
|
265
|
+
console.log(`\n${'='.repeat(50)}`)
|
|
266
|
+
console.log(`✅ 通过: ${11 - failed} ❌ 失败: ${failed}`)
|
|
267
|
+
if (failures.length > 0) {
|
|
268
|
+
console.log(`失败项:`)
|
|
269
|
+
failures.forEach(f => console.log(` - ${f}`))
|
|
270
|
+
}
|
|
271
|
+
console.log(`${'='.repeat(50)}`)
|
|
272
|
+
|
|
273
|
+
if (failed > 0) process.exit(1)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { readdirSync, statSync } from 'node:fs'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
const roots = ['src']
|
|
6
|
+
const files = []
|
|
7
|
+
|
|
8
|
+
function walk(dir) {
|
|
9
|
+
for (const entry of readdirSync(dir)) {
|
|
10
|
+
const full = join(dir, entry)
|
|
11
|
+
const st = statSync(full)
|
|
12
|
+
if (st.isDirectory()) {
|
|
13
|
+
walk(full)
|
|
14
|
+
continue
|
|
15
|
+
}
|
|
16
|
+
if (/\.(js|cjs|mjs)$/.test(entry)) files.push(full)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for (const root of roots) walk(root)
|
|
21
|
+
|
|
22
|
+
for (const file of files.sort()) {
|
|
23
|
+
execFileSync(process.execPath, ['--check', file], { stdio: 'inherit' })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(`Checked ${files.length} JavaScript files`)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task-10: 顶层命令别名 doctor/scan/status/quick/explore 转发 runCommand
|
|
3
|
+
*
|
|
4
|
+
* 设计依据:task-10.md §TDD + §验收标准
|
|
5
|
+
* - sillyspec doctor / scan / status / quick / explore 不再落 default 分支报"未知命令"
|
|
6
|
+
* - 行为与 sillyspec run <stage> 字节一致
|
|
7
|
+
* - sillyspec worktree doctor 仍走 worktree 分支
|
|
8
|
+
* - sillyspec foobar 仍报未知命令
|
|
9
|
+
*
|
|
10
|
+
* 断言策略:
|
|
11
|
+
* stage 在空目录下可能因无 .sillyspec 进度而 exit != 0(这是 stage 自身行为,
|
|
12
|
+
* 不属于本任务路由范围)。因此测试只验证"路由正确"——即 stderr 不含"未知命令"
|
|
13
|
+
* 字样(default 分支的特征文案),并且 doctor/scan 两路 stdout 字节一致。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { spawnSync } from 'node:child_process'
|
|
17
|
+
import { mkdirSync, rmSync } from 'node:fs'
|
|
18
|
+
import { join, resolve, dirname } from 'node:path'
|
|
19
|
+
import { tmpdir } from 'node:os'
|
|
20
|
+
import { fileURLToPath } from 'node:url'
|
|
21
|
+
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
23
|
+
const __dirname = dirname(__filename)
|
|
24
|
+
const cliBin = resolve(__dirname, '..', 'bin', 'sillyspec.js')
|
|
25
|
+
|
|
26
|
+
let passed = 0
|
|
27
|
+
let failed = 0
|
|
28
|
+
|
|
29
|
+
function assert(cond, msg) {
|
|
30
|
+
if (cond) {
|
|
31
|
+
console.log(` ✅ PASS: ${msg}`)
|
|
32
|
+
passed++
|
|
33
|
+
} else {
|
|
34
|
+
console.log(` ❌ FAIL: ${msg}`)
|
|
35
|
+
failed++
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function runCLI(args, cwd) {
|
|
40
|
+
const res = spawnSync(process.execPath, [cliBin, ...args], {
|
|
41
|
+
cwd,
|
|
42
|
+
encoding: 'utf8',
|
|
43
|
+
timeout: 15000,
|
|
44
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
45
|
+
})
|
|
46
|
+
return {
|
|
47
|
+
stdout: res.stdout || '',
|
|
48
|
+
stderr: res.stderr || '',
|
|
49
|
+
status: res.status,
|
|
50
|
+
combined: (res.stdout || '') + (res.stderr || ''),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function cleanSillySpec(cwd) {
|
|
55
|
+
// 清掉 sillyspec 写入 cwd 的进度副作用,保证两路字节级环境一致
|
|
56
|
+
try { rmSync(join(cwd, '.sillyspec'), { recursive: true, force: true }) } catch {}
|
|
57
|
+
try { rmSync(join(cwd, '.sillyspec-platform.json'), { force: true }) } catch {}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const tmpRoot = join(tmpdir(), `sillyspec-cli-aliases-${Date.now()}`)
|
|
61
|
+
mkdirSync(tmpRoot, { recursive: true })
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
// ── Red/Green: 5 个顶层命令不再报"未知命令" ──
|
|
65
|
+
const aliases = ['doctor', 'scan', 'status', 'quick', 'explore']
|
|
66
|
+
console.log('\n=== Test 1: 顶层命令别名不落 default 分支 ===')
|
|
67
|
+
for (const stage of aliases) {
|
|
68
|
+
const res = runCLI([stage], tmpRoot)
|
|
69
|
+
const hitUnknown =
|
|
70
|
+
res.combined.includes('未知命令') ||
|
|
71
|
+
/unknown command/i.test(res.combined)
|
|
72
|
+
assert(
|
|
73
|
+
!hitUnknown,
|
|
74
|
+
`sillyspec ${stage} 不报"未知命令" (exit=${res.status})`
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── Green: doctor 顶层别名 与 sillyspec run doctor 字节一致 ──
|
|
79
|
+
// 两路必须在字节级相同环境下运行:同 cwd + 每次跑前清空 .sillyspec
|
|
80
|
+
// (否则 progress 持久化会让第二次跑读到旧数据触发平台同步检查)
|
|
81
|
+
console.log('\n=== Test 2: sillyspec doctor 与 sillyspec run doctor 等价 ===')
|
|
82
|
+
{
|
|
83
|
+
const cwd = join(tmpRoot, 'doctor-cmp')
|
|
84
|
+
mkdirSync(cwd, { recursive: true })
|
|
85
|
+
cleanSillySpec(cwd)
|
|
86
|
+
const top = runCLI(['doctor'], cwd)
|
|
87
|
+
cleanSillySpec(cwd)
|
|
88
|
+
const viaRun = runCLI(['run', 'doctor'], cwd)
|
|
89
|
+
assert(
|
|
90
|
+
top.status === viaRun.status,
|
|
91
|
+
`exit code 一致: doctor=${top.status}, run doctor=${viaRun.status}`
|
|
92
|
+
)
|
|
93
|
+
assert(
|
|
94
|
+
top.stdout === viaRun.stdout,
|
|
95
|
+
`stdout 字节一致 (len=${top.stdout.length})`
|
|
96
|
+
)
|
|
97
|
+
assert(
|
|
98
|
+
top.stderr === viaRun.stderr,
|
|
99
|
+
`stderr 字节一致 (len=${top.stderr.length})`
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Green: scan 顶层别名 与 sillyspec run scan 字节一致 ──
|
|
104
|
+
console.log('\n=== Test 3: sillyspec scan 与 sillyspec run scan 等价 ===')
|
|
105
|
+
{
|
|
106
|
+
const cwd = join(tmpRoot, 'scan-cmp')
|
|
107
|
+
mkdirSync(cwd, { recursive: true })
|
|
108
|
+
cleanSillySpec(cwd)
|
|
109
|
+
const top = runCLI(['scan'], cwd)
|
|
110
|
+
cleanSillySpec(cwd)
|
|
111
|
+
const viaRun = runCLI(['run', 'scan'], cwd)
|
|
112
|
+
assert(
|
|
113
|
+
top.status === viaRun.status,
|
|
114
|
+
`exit code 一致: scan=${top.status}, run scan=${viaRun.status}`
|
|
115
|
+
)
|
|
116
|
+
assert(
|
|
117
|
+
top.stdout === viaRun.stdout,
|
|
118
|
+
`stdout 字节一致 (len=${top.stdout.length})`
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── 回归: worktree doctor 走 worktree 分支(与顶层 doctor 不同) ──
|
|
123
|
+
console.log('\n=== Test 4: sillyspec worktree doctor 走 worktree 分支 ===')
|
|
124
|
+
{
|
|
125
|
+
const res = runCLI(['worktree', 'doctor'], tmpRoot)
|
|
126
|
+
// worktree doctor 不应报顶层 default 的"未知命令",也不应报"未知阶段"
|
|
127
|
+
const hitUnknownCmd =
|
|
128
|
+
res.combined.includes('未知命令') && !/worktree/.test(res.combined)
|
|
129
|
+
assert(!hitUnknownCmd, `worktree doctor 不报顶层未知命令`)
|
|
130
|
+
// worktree 子命令 default 分支会输出"未知子命令: worktree",这里 doctor 合法不应出现
|
|
131
|
+
assert(
|
|
132
|
+
!res.combined.includes('未知子命令'),
|
|
133
|
+
`worktree doctor 不是未知子命令`
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── 回归: foobar 仍落 default 报未知命令 ──
|
|
138
|
+
console.log('\n=== Test 5: sillyspec foobar 仍报未知命令 ===')
|
|
139
|
+
{
|
|
140
|
+
const res = runCLI(['foobar'], tmpRoot)
|
|
141
|
+
assert(
|
|
142
|
+
res.combined.includes('未知命令'),
|
|
143
|
+
`foobar 命中 default 分支,报"未知命令"`
|
|
144
|
+
)
|
|
145
|
+
assert(
|
|
146
|
+
res.status !== 0,
|
|
147
|
+
`foobar exit code 非 0 (got ${res.status})`
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── 选项透传: sillyspec doctor --json 与 sillyspec run doctor --json 等价 ──
|
|
152
|
+
console.log('\n=== Test 6: doctor --json 选项透传正确 ===')
|
|
153
|
+
{
|
|
154
|
+
const top = runCLI(['doctor', '--json'], tmpRoot)
|
|
155
|
+
const viaRun = runCLI(['run', 'doctor', '--json'], tmpRoot)
|
|
156
|
+
assert(
|
|
157
|
+
top.stdout === viaRun.stdout,
|
|
158
|
+
`doctor --json stdout 与 run doctor --json 一致 (len=${top.stdout.length})`
|
|
159
|
+
)
|
|
160
|
+
assert(
|
|
161
|
+
top.status === viaRun.status,
|
|
162
|
+
`doctor --json exit 与 run doctor --json 一致`
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
} finally {
|
|
166
|
+
try {
|
|
167
|
+
rmSync(tmpRoot, { recursive: true, force: true })
|
|
168
|
+
} catch {}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log(`\n${'='.repeat(50)}`)
|
|
172
|
+
console.log(`✅ 通过: ${passed} ❌ 失败: ${failed}`)
|
|
173
|
+
console.log(`${'='.repeat(50)}`)
|
|
174
|
+
process.exit(failed > 0 ? 1 : 0)
|