sillyspec 3.20.3 → 3.20.4
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 +125 -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 +431 -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 +625 -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 +511 -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 +330 -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/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
package/src/index.js
CHANGED
|
@@ -1,252 +1,922 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SillySpec CLI — 安装工具
|
|
5
|
-
*
|
|
6
|
-
* 只负责两件事:init(安装命令模板)和 setup(安装 MCP 工具)。
|
|
7
|
-
* 状态管理通过
|
|
8
|
-
*/
|
|
9
|
-
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
10
|
-
import { join, resolve } from 'path';
|
|
11
|
-
import { cmdInit, getVersion } from './init.js';
|
|
12
|
-
import { ProgressManager } from './progress.js';
|
|
13
|
-
|
|
14
|
-
// ── CLI 入口 ──
|
|
15
|
-
|
|
16
|
-
function printUsage() {
|
|
17
|
-
console.log(`
|
|
18
|
-
SillySpec CLI — 规范驱动开发工具包
|
|
19
|
-
|
|
20
|
-
用法:
|
|
21
|
-
sillyspec init 初始化(零交互,自动检测工具)
|
|
22
|
-
[--tool <name>] 只安装指定工具
|
|
23
|
-
[--interactive] 交互式引导
|
|
24
|
-
[--dir <path>] 指定目录
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
sillyspec
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
sillyspec
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SillySpec CLI — 安装工具
|
|
5
|
+
*
|
|
6
|
+
* 只负责两件事:init(安装命令模板)和 setup(安装 MCP 工具)。
|
|
7
|
+
* 状态管理通过 sillyspec.db(SQLite)完成,使用 `sillyspec progress` 命令。
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
10
|
+
import { join, resolve } from 'path';
|
|
11
|
+
import { cmdInit, getVersion } from './init.js';
|
|
12
|
+
import { ProgressManager } from './progress.js';
|
|
13
|
+
|
|
14
|
+
// ── CLI 入口 ──
|
|
15
|
+
|
|
16
|
+
function printUsage() {
|
|
17
|
+
console.log(`
|
|
18
|
+
SillySpec CLI — 规范驱动开发工具包
|
|
19
|
+
|
|
20
|
+
用法:
|
|
21
|
+
sillyspec init 初始化(零交互,自动检测工具)
|
|
22
|
+
[--tool <name>] 只安装指定工具
|
|
23
|
+
[--interactive] 交互式引导
|
|
24
|
+
[--dir <path>] 指定目录
|
|
25
|
+
[--spec-dir <path>] 指定规范目录(默认 <项目>/.sillyspec)
|
|
26
|
+
|
|
27
|
+
sillyspec setup [--list] 安装推荐 MCP 工具
|
|
28
|
+
[--list] 查看已安装状态
|
|
29
|
+
|
|
30
|
+
sillyspec run <stage> 执行阶段步骤(核心命令)
|
|
31
|
+
--done --output "..." 完成当前步骤
|
|
32
|
+
--skip 跳过可选步骤
|
|
33
|
+
--status 查看阶段进度
|
|
34
|
+
--reset 重置阶段(从头开始)
|
|
35
|
+
--reopen 重新打开已完成阶段进入修订模式
|
|
36
|
+
--from-step <index|name> 配合 --reopen:从指定步骤开始修订
|
|
37
|
+
--change <name> 设置当前变更名
|
|
38
|
+
--spec-dir <path> 指定规范目录(默认 <项目>/.sillyspec)
|
|
39
|
+
--runtime-root <path> 平台模式:运行时产物根路径
|
|
40
|
+
--workspace-id <id> 平台模式:workspace ID
|
|
41
|
+
--scan-run-id <id> 平台模式:scan run ID
|
|
42
|
+
auto 连续推进 brainstorm→plan→execute→verify
|
|
43
|
+
|
|
44
|
+
可选阶段:
|
|
45
|
+
scan, brainstorm, plan, execute, verify, archive
|
|
46
|
+
quick, explore, status, doctor
|
|
47
|
+
|
|
48
|
+
Revision mode:
|
|
49
|
+
已完成阶段不能直接重跑。使用 --reopen --from-step 进入受控修订。
|
|
50
|
+
重开会使下游阶段自动标记为 stale,但不修改已有产物文件。
|
|
51
|
+
|
|
52
|
+
sillyspec progress <cmd> 进度记录(轻量,不强制顺序)
|
|
53
|
+
init 初始化项目数据库
|
|
54
|
+
show 查看当前进度
|
|
55
|
+
set-stage <stage> 设置当前阶段
|
|
56
|
+
add-step <stage> <name> 添加步骤
|
|
57
|
+
update-step <s> <n> --status <st> [--output <t>]
|
|
58
|
+
complete-stage <stage> 标记阶段完成
|
|
59
|
+
check 状态一致性检查(只报告,不修复)
|
|
60
|
+
repair [--apply] 修复状态元数据(默认 dry-run,--apply 才修改)
|
|
61
|
+
validate 校验并修复
|
|
62
|
+
reset [--stage X] 重置进度
|
|
63
|
+
|
|
64
|
+
sillyspec docs migrate 迁移旧文档到统一结构
|
|
65
|
+
|
|
66
|
+
sillyspec knowledge <cmd> 知识库管理(agent-safe,输出 JSON)
|
|
67
|
+
search --query "<text>" --limit N 搜索知识库
|
|
68
|
+
inspect --id "<id>" 读取知识条目详情
|
|
69
|
+
validate 校验知识库健康度
|
|
70
|
+
refresh 从 scan 文档刷新自动知识(仅写 generated/)
|
|
71
|
+
propose --title "<title>" --category <name> 提议新知识(写入 proposed/)
|
|
72
|
+
|
|
73
|
+
sillyspec platform <cmd> SillyHub 平台同步
|
|
74
|
+
connect <url> [--token <t>] 连接平台
|
|
75
|
+
disconnect 断开连接
|
|
76
|
+
sync [--change <name>] 同步变更状态
|
|
77
|
+
sync-docs [--change <name>] 同步四件套文档
|
|
78
|
+
status 查看同步状态
|
|
79
|
+
approve <change-name> 审批变更
|
|
80
|
+
reject <change-name> [--reason <r>] 拒绝变更
|
|
81
|
+
|
|
82
|
+
sillyspec dashboard 启动 Dashboard Web UI
|
|
83
|
+
[--port <number>] 指定端口(默认 3456)
|
|
84
|
+
[--no-open] 不自动打开浏览器
|
|
85
|
+
|
|
86
|
+
选项:
|
|
87
|
+
--json 输出 JSON(给 AI 程序化读取)
|
|
88
|
+
--dir <path> 指定项目目录(默认当前目录)
|
|
89
|
+
--spec-dir <path> 指定规范目录(默认 <项目目录>/.sillyspec)
|
|
90
|
+
|
|
91
|
+
示例:
|
|
92
|
+
sillyspec init
|
|
93
|
+
sillyspec init --spec-dir /data/specs/my-project
|
|
94
|
+
sillyspec run scan
|
|
95
|
+
sillyspec run brainstorm
|
|
96
|
+
sillyspec run quick
|
|
97
|
+
sillyspec run explore
|
|
98
|
+
sillyspec run brainstorm --done --output "需求已澄清"
|
|
99
|
+
sillyspec setup --list
|
|
100
|
+
sillyspec dashboard --port 8080 --no-open
|
|
101
|
+
`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function main() {
|
|
105
|
+
const args = process.argv.slice(2);
|
|
106
|
+
|
|
107
|
+
if (args[0] === '--version' || args[0] === '-v') {
|
|
108
|
+
console.log(getVersion());
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (args.length === 0 || args[0] === 'help' || args[0] === '--help' || args[0] === '-h') {
|
|
113
|
+
printUsage();
|
|
114
|
+
process.exit(0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 解析全局选项
|
|
118
|
+
let json = false;
|
|
119
|
+
let saveWorkflowRunFlag = false;
|
|
120
|
+
let targetDir = process.cwd();
|
|
121
|
+
let specDir = null;
|
|
122
|
+
let tool = null;
|
|
123
|
+
let interactive = false;
|
|
124
|
+
const filteredArgs = [];
|
|
125
|
+
|
|
126
|
+
for (let i = 0; i < args.length; i++) {
|
|
127
|
+
if (args[i] === '--json') {
|
|
128
|
+
json = true;
|
|
129
|
+
} else if (args[i] === '--save') {
|
|
130
|
+
saveWorkflowRunFlag = true;
|
|
131
|
+
} else if (args[i] === '--dir' && args[i + 1]) {
|
|
132
|
+
targetDir = resolve(args[i + 1]);
|
|
133
|
+
i++;
|
|
134
|
+
} else if (args[i] === '--spec-dir' && args[i + 1]) {
|
|
135
|
+
specDir = resolve(args[i + 1]);
|
|
136
|
+
i++;
|
|
137
|
+
} else if (args[i] === '--tool' && args[i + 1]) {
|
|
138
|
+
tool = args[i + 1];
|
|
139
|
+
i++;
|
|
140
|
+
} else if (args[i] === '--interactive' || args[i] === '-i') {
|
|
141
|
+
interactive = true;
|
|
142
|
+
} else if (args[i] === '--list' || args[i] === '-l') {
|
|
143
|
+
filteredArgs.push('--list');
|
|
144
|
+
} else {
|
|
145
|
+
filteredArgs.push(args[i]);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const command = filteredArgs[0];
|
|
150
|
+
// 支持 sillyspec init /path/to/project 语法:如果第二个参数看起来像路径,当作 targetDir
|
|
151
|
+
if (command === 'init' && filteredArgs[1] && !filteredArgs[1].startsWith('-')) {
|
|
152
|
+
targetDir = resolve(filteredArgs[1]);
|
|
153
|
+
filteredArgs.splice(1, 1);
|
|
154
|
+
}
|
|
155
|
+
// ── 自动纠正 cwd ──
|
|
156
|
+
// 当 agent 在 worktree 内跑 pnpm 等工具后 shell cwd 可能被改变,
|
|
157
|
+
// 导致 sillyspec 命令找不到 .sillyspec。此函数尝试从 git root 解析。
|
|
158
|
+
function resolveEffectiveDir(baseDir) {
|
|
159
|
+
if (existsSync(join(baseDir, '.sillyspec'))) return baseDir
|
|
160
|
+
try {
|
|
161
|
+
const { execSync } = require('child_process')
|
|
162
|
+
const gitRoot = execSync('git rev-parse --show-toplevel', {
|
|
163
|
+
cwd: baseDir, encoding: 'utf8', timeout: 5000
|
|
164
|
+
}).trim()
|
|
165
|
+
if (gitRoot && existsSync(join(gitRoot, '.sillyspec'))) return gitRoot
|
|
166
|
+
} catch {}
|
|
167
|
+
return baseDir
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const dir = targetDir;
|
|
171
|
+
|
|
172
|
+
if (command === 'init' && !existsSync(dir)) {
|
|
173
|
+
const { mkdirSync } = await import('fs');
|
|
174
|
+
mkdirSync(dir, { recursive: true });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!existsSync(dir)) {
|
|
178
|
+
console.error(`❌ 目录不存在: ${dir}`);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
switch (command) {
|
|
183
|
+
case 'init':
|
|
184
|
+
await cmdInit(dir, { tool, interactive, specDir });
|
|
185
|
+
break;
|
|
186
|
+
case 'setup':
|
|
187
|
+
const setupList = filteredArgs.includes('--list') || filteredArgs.includes('-l');
|
|
188
|
+
await (await import('./setup.js')).cmdSetup(dir, { json, list: setupList });
|
|
189
|
+
break;
|
|
190
|
+
case 'progress': {
|
|
191
|
+
const pm = new ProgressManager();
|
|
192
|
+
const progDir = specDir ? dir : resolveEffectiveDir(dir);
|
|
193
|
+
const subCommand = filteredArgs[1];
|
|
194
|
+
const stageIdx = filteredArgs.indexOf('--stage');
|
|
195
|
+
const stage = stageIdx >= 0 && filteredArgs[stageIdx + 1] ? filteredArgs[stageIdx + 1] : null;
|
|
196
|
+
// 解析 --change 参数
|
|
197
|
+
const progChangeIdx = args.indexOf('--change');
|
|
198
|
+
const progChangeName = progChangeIdx >= 0 && args[progChangeIdx + 1] ? args[progChangeIdx + 1] : null;
|
|
199
|
+
|
|
200
|
+
switch (subCommand) {
|
|
201
|
+
case 'init':
|
|
202
|
+
pm.init(progDir);
|
|
203
|
+
break;
|
|
204
|
+
case 'status':
|
|
205
|
+
case 'show':
|
|
206
|
+
pm.show(progDir, progChangeName);
|
|
207
|
+
break;
|
|
208
|
+
case 'check':
|
|
209
|
+
await pm.checkConsistency(progDir, progChangeName);
|
|
210
|
+
break;
|
|
211
|
+
case 'repair': {
|
|
212
|
+
const repairApply = filteredArgs.includes('--apply');
|
|
213
|
+
await pm.repairConsistency(progDir, { apply: repairApply, changeName: progChangeName });
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
case 'validate':
|
|
217
|
+
await pm.validate(dir, progChangeName);
|
|
218
|
+
break;
|
|
219
|
+
case 'reset':
|
|
220
|
+
pm.reset(dir, stage, progChangeName);
|
|
221
|
+
break;
|
|
222
|
+
case 'set-stage': {
|
|
223
|
+
const setStageName = filteredArgs[2];
|
|
224
|
+
if (!setStageName) { console.log('❌ 用法: sillyspec progress set-stage <stage> [--change <name>]'); break; }
|
|
225
|
+
pm.setStage(dir, setStageName, progChangeName);
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
case 'add-step': {
|
|
229
|
+
const addStepStage = filteredArgs[2];
|
|
230
|
+
const addStepName = filteredArgs[3];
|
|
231
|
+
if (!addStepStage || !addStepName) { console.log('❌ 用法: sillyspec progress add-step <stage> <step-name> [--change <name>]'); break; }
|
|
232
|
+
pm.addStep(dir, addStepStage, addStepName, progChangeName);
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
case 'update-step': {
|
|
236
|
+
const updStepStage = filteredArgs[2];
|
|
237
|
+
const updStepName = filteredArgs[3];
|
|
238
|
+
if (!updStepStage || !updStepName) { console.log('❌ 用法: sillyspec progress update-step <stage> <step-name> --status <status> [--output <text>] [--change <name>]'); break; }
|
|
239
|
+
let updStatus = null, updOutput = undefined;
|
|
240
|
+
for (let ai = 0; ai < args.length; ai++) {
|
|
241
|
+
if (args[ai] === '--status' && args[ai + 1]) { updStatus = args[ai + 1]; ai++; }
|
|
242
|
+
if (args[ai] === '--output' && args[ai + 1]) { updOutput = args[ai + 1]; ai++; }
|
|
243
|
+
}
|
|
244
|
+
pm.updateStep(dir, updStepStage, updStepName, { status: updStatus, output: updOutput }, progChangeName);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
case 'complete-stage': {
|
|
248
|
+
const compStageName = filteredArgs[2];
|
|
249
|
+
if (!compStageName) { console.log('❌ 用法: sillyspec progress complete-stage <stage>'); break; }
|
|
250
|
+
pm.completeStage(dir, compStageName, progChangeName);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case 'batch': {
|
|
254
|
+
if (filteredArgs.includes('--status')) {
|
|
255
|
+
const bp = pm.readBatchProgress(dir, progChangeName);
|
|
256
|
+
if (!bp) { console.log('📭 无批量进度数据'); break; }
|
|
257
|
+
const line = pm._renderBatchProgress(bp);
|
|
258
|
+
console.log(line || '📭 无批量进度数据');
|
|
259
|
+
console.log(JSON.stringify(bp, null, 2));
|
|
260
|
+
} else {
|
|
261
|
+
let batchData = {};
|
|
262
|
+
const a = args;
|
|
263
|
+
for (let i = 0; i < a.length; i++) {
|
|
264
|
+
if (a[i] === '--total' && a[i + 1]) { batchData.total = parseInt(a[i + 1]); i++; }
|
|
265
|
+
if (a[i] === '--completed' && a[i + 1]) { batchData.completed = parseInt(a[i + 1]); i++; }
|
|
266
|
+
if (a[i] === '--failed' && a[i + 1]) { batchData.failed = parseInt(a[i + 1]); i++; }
|
|
267
|
+
if (a[i] === '--skipped' && a[i + 1]) { batchData.skipped = parseInt(a[i + 1]); i++; }
|
|
268
|
+
}
|
|
269
|
+
if (Object.keys(batchData).length === 0) {
|
|
270
|
+
console.log('用法: sillyspec progress batch --total 100 --completed 73');
|
|
271
|
+
console.log(' sillyspec progress batch --status');
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
pm.updateBatchProgress(dir, batchData, progChangeName);
|
|
275
|
+
console.log('✅ 批量进度已更新');
|
|
276
|
+
}
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
default:
|
|
280
|
+
console.log('用法: sillyspec progress <init|show|validate|reset|set-stage|add-step|update-step|complete-stage>');
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
case 'docs': {
|
|
285
|
+
const docsSubCmd = filteredArgs[1];
|
|
286
|
+
if (docsSubCmd === 'migrate') {
|
|
287
|
+
const { migrateDocs } = await import('./migrate.js');
|
|
288
|
+
migrateDocs(dir);
|
|
289
|
+
} else {
|
|
290
|
+
console.log('用法: sillyspec docs migrate');
|
|
291
|
+
}
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
case 'run': {
|
|
295
|
+
const { runCommand } = await import('./run.js')
|
|
296
|
+
// 平台模式(--spec-dir 已指定)时,--dir 是明确的 source_root,不应被 resolveEffectiveDir 纠正
|
|
297
|
+
const effectiveDir = specDir ? dir : resolveEffectiveDir(dir)
|
|
298
|
+
await runCommand(filteredArgs.slice(1), effectiveDir, specDir)
|
|
299
|
+
break
|
|
300
|
+
}
|
|
301
|
+
// task-10: 顶层命令别名,转发 runCommand,与 case 'run': 路径行为一致
|
|
302
|
+
// help 文本(:44-46)已宣称这些 stage 可直接使用,这里补齐路由避免落 default 分支。
|
|
303
|
+
// 注意:filteredArgs[0] === command,直接透传 filteredArgs 即可让 runCommand
|
|
304
|
+
// 从 args[0] 取到 stage 名(run.js:1036)。与 case 'run': 的 filteredArgs.slice(1)
|
|
305
|
+
// 区别只在于 slice(1) 去掉的是 'run' 字面量,这里 command 本身就是 stage 名不能丢。
|
|
306
|
+
case 'doctor':
|
|
307
|
+
case 'scan':
|
|
308
|
+
case 'status':
|
|
309
|
+
case 'quick':
|
|
310
|
+
case 'explore': {
|
|
311
|
+
const { runCommand } = await import('./run.js')
|
|
312
|
+
const stageArgs = [command, ...filteredArgs.slice(1)]
|
|
313
|
+
const effectiveDir = specDir ? dir : resolveEffectiveDir(dir)
|
|
314
|
+
await runCommand(stageArgs, effectiveDir, specDir)
|
|
315
|
+
break
|
|
316
|
+
}
|
|
317
|
+
case 'knowledge': {
|
|
318
|
+
const { cmdKnowledge } = await import('./stages/knowledge.js')
|
|
319
|
+
await cmdKnowledge(filteredArgs.slice(1), specDir ? dir : resolveEffectiveDir(dir), { specDir })
|
|
320
|
+
break
|
|
321
|
+
}
|
|
322
|
+
case 'dashboard': {
|
|
323
|
+
// Parse dashboard options
|
|
324
|
+
let port = 3456;
|
|
325
|
+
let openBrowser = true;
|
|
326
|
+
|
|
327
|
+
for (let i = 1; i < args.length; i++) {
|
|
328
|
+
if (args[i] === '--port' && args[i + 1]) {
|
|
329
|
+
port = parseInt(args[i + 1], 10);
|
|
330
|
+
i++;
|
|
331
|
+
} else if (args[i] === '--no-open') {
|
|
332
|
+
openBrowser = false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Import and start dashboard server
|
|
337
|
+
const { startServer } = await import('../packages/dashboard/server/index.js');
|
|
338
|
+
startServer({ port, open: openBrowser });
|
|
339
|
+
|
|
340
|
+
// Keep process alive
|
|
341
|
+
console.log('按 Ctrl+C 停止服务器');
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
case 'worktree': {
|
|
345
|
+
const { WorktreeManager } = await import('./worktree.js');
|
|
346
|
+
const { ProgressManager } = await import('./progress.js');
|
|
347
|
+
const wtSubCmd = filteredArgs[1];
|
|
348
|
+
const wtName = filteredArgs.slice(2).find(a => !a.startsWith('-'));
|
|
349
|
+
const wm = new WorktreeManager({ cwd: dir });
|
|
350
|
+
const pm = new ProgressManager({ specDir });
|
|
351
|
+
|
|
352
|
+
// isolation 写入 DB 的辅助函数
|
|
353
|
+
async function _writeIsolationToDB(cwd, changeName, info) {
|
|
354
|
+
if (info.blocked) {
|
|
355
|
+
await pm.updateChangeIsolation(cwd, changeName, {
|
|
356
|
+
status: 'blocked',
|
|
357
|
+
mode: null,
|
|
358
|
+
reason: info.reason,
|
|
359
|
+
});
|
|
360
|
+
} else {
|
|
361
|
+
const mode = info.mode || 'worktree';
|
|
362
|
+
const statusMap = { 'worktree': 'verified', 'native-worktree': 'verified', 'in-place-fallback': 'degraded' };
|
|
363
|
+
await pm.updateChangeIsolation(cwd, changeName, {
|
|
364
|
+
status: statusMap[mode] || 'verified',
|
|
365
|
+
mode,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (!wtSubCmd || wtSubCmd === 'help' || wtSubCmd === '--help' || wtSubCmd === '-h') {
|
|
371
|
+
console.log(`
|
|
372
|
+
SillySpec worktree — git worktree 隔离管理
|
|
373
|
+
|
|
374
|
+
用法:
|
|
375
|
+
sillyspec worktree create <change-name> [--base <branch>] 创建隔离 worktree
|
|
376
|
+
sillyspec worktree apply <change-name> [--check-only] 校验并应用变更到主工作区
|
|
377
|
+
sillyspec worktree assess <change-name> 风险审计 + 自动 apply
|
|
378
|
+
sillyspec worktree list 列出所有活跃 worktree
|
|
379
|
+
sillyspec worktree meta <change-name> 读取 worktree meta.json
|
|
380
|
+
sillyspec worktree cleanup <change-name> [--force] 强制清理 worktree
|
|
381
|
+
sillyspec worktree doctor [--fix] [--stale-hours N] 健康检查 + 修复
|
|
382
|
+
|
|
383
|
+
选项:
|
|
384
|
+
--base <branch> create: 指定基础分支(默认当前 HEAD)
|
|
385
|
+
--check-only apply: 只输出检查结果,不实际 apply
|
|
386
|
+
`);
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
switch (wtSubCmd) {
|
|
391
|
+
case 'create': {
|
|
392
|
+
if (!wtName) {
|
|
393
|
+
console.error('❌ 用法: sillyspec worktree create <change-name> [--base <branch>]');
|
|
394
|
+
process.exit(1);
|
|
395
|
+
}
|
|
396
|
+
const baseIdx = args.indexOf('--base');
|
|
397
|
+
const base = baseIdx >= 0 && args[baseIdx + 1] ? args[baseIdx + 1] : undefined;
|
|
398
|
+
try {
|
|
399
|
+
const info = wm.create(wtName, { base });
|
|
400
|
+
console.log(`✅ worktree 已创建`);
|
|
401
|
+
console.log(` 分支: ${info.branch}`);
|
|
402
|
+
console.log(` 路径: ${info.worktreePath}`);
|
|
403
|
+
console.log(` 基准: ${info.baseHash.slice(0, 8)}`);
|
|
404
|
+
if (info.mode) {
|
|
405
|
+
console.log(` 模式: ${info.mode}`);
|
|
406
|
+
}
|
|
407
|
+
// 写入 isolation 信息到 gate-status.json
|
|
408
|
+
await _writeIsolationToDB(dir, wtName, info);
|
|
409
|
+
} catch (e) {
|
|
410
|
+
console.error(`❌ ${e.message}`);
|
|
411
|
+
// 写入 blocked 状态到 gate-status.json
|
|
412
|
+
await _writeIsolationToDB(dir, wtName, { blocked: true, reason: e.message });
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
case 'apply': {
|
|
418
|
+
if (!wtName) {
|
|
419
|
+
console.error('❌ 用法: sillyspec worktree apply <change-name> [--check-only]');
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
const checkOnly = args.includes('--check-only');
|
|
423
|
+
const { applyWorktree } = await import('./worktree-apply.js');
|
|
424
|
+
const result = applyWorktree(wtName, { cwd: dir, checkOnly });
|
|
425
|
+
|
|
426
|
+
if (result.errors.length > 0) {
|
|
427
|
+
console.error(`❌ 校验失败:`);
|
|
428
|
+
for (const err of result.errors) {
|
|
429
|
+
console.error(` ${err}`);
|
|
430
|
+
}
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (result.changedFiles.length === 0) {
|
|
435
|
+
console.log('📭 无变更需要应用');
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (checkOnly) {
|
|
440
|
+
console.log(`✅ 检查通过 (${result.changedFiles.length} 个文件):`);
|
|
441
|
+
for (const f of result.changedFiles) {
|
|
442
|
+
console.log(` ${f}`);
|
|
443
|
+
}
|
|
444
|
+
} else {
|
|
445
|
+
console.log(`✅ 已应用 ${result.changedFiles.length} 个文件变更`);
|
|
446
|
+
}
|
|
447
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
448
|
+
for (const w of result.warnings) {
|
|
449
|
+
console.log(`⚠️ ${w}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
case 'assess': {
|
|
455
|
+
if (!wtName) {
|
|
456
|
+
console.error('❌ 用法: sillyspec worktree assess <change-name>');
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
const { assessApplyRisk } = await import('./worktree-apply.js');
|
|
460
|
+
const assessment = assessApplyRisk(wtName, { cwd: dir });
|
|
461
|
+
|
|
462
|
+
const SEPARATOR = '─'.repeat(32);
|
|
463
|
+
console.log('Worktree Apply Decision');
|
|
464
|
+
console.log(SEPARATOR);
|
|
465
|
+
const decisionIcon = assessment.decision === 'SAFE' ? '✅' : assessment.decision === 'WARNING' ? '⚠️ ' : '🚫';
|
|
466
|
+
console.log(`Decision: ${decisionIcon} ${assessment.decision}`);
|
|
467
|
+
console.log(`Changed files: ${assessment.changedFiles.length}`);
|
|
468
|
+
if (assessment.stats.additions > 0 || assessment.stats.deletions > 0) {
|
|
469
|
+
console.log(`Additions: +${assessment.stats.additions} Deletions: -${assessment.stats.deletions}`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (assessment.reasons.length > 0) {
|
|
473
|
+
console.log('');
|
|
474
|
+
console.log('Blocked reasons:');
|
|
475
|
+
for (const r of assessment.reasons) r.split('\n').forEach(l => console.log(` ${l}`));
|
|
476
|
+
}
|
|
477
|
+
if (assessment.warnings.length > 0) {
|
|
478
|
+
console.log('');
|
|
479
|
+
console.log('Warnings:');
|
|
480
|
+
for (const w of assessment.warnings) console.log(` ⚠️ ${w}`);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
console.log('');
|
|
484
|
+
if (assessment.decision === 'SAFE' || assessment.decision === 'WARNING') {
|
|
485
|
+
console.log('Action: auto-applying...');
|
|
486
|
+
const { applyWorktree } = await import('./worktree-apply.js');
|
|
487
|
+
const applyResult = applyWorktree(wtName, { cwd: dir });
|
|
488
|
+
if (applyResult.errors.length > 0) {
|
|
489
|
+
console.error('❌ apply 失败:', applyResult.errors.join('; '));
|
|
490
|
+
} else {
|
|
491
|
+
console.log(`✅ 已自动应用 ${applyResult.changedFiles.length} 个文件变更`);
|
|
492
|
+
}
|
|
493
|
+
} else {
|
|
494
|
+
console.log('Action: blocked');
|
|
495
|
+
console.log(' → 检查变更: sillyspec worktree diff ' + wtName);
|
|
496
|
+
console.log(' → 丢弃变更: sillyspec worktree cleanup ' + wtName);
|
|
497
|
+
}
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
case 'meta': {
|
|
501
|
+
if (!wtName) {
|
|
502
|
+
console.error('❌ 用法: sillyspec worktree meta <change-name>');
|
|
503
|
+
process.exit(1);
|
|
504
|
+
}
|
|
505
|
+
const meta = wm.getMeta(wtName);
|
|
506
|
+
if (!meta) {
|
|
507
|
+
console.error(`❌ 未找到 worktree meta: ${wtName}(可能未创建或已被清理)`);
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
console.log(`change: ${wtName}`);
|
|
511
|
+
console.log(`worktreePath: ${meta.worktreePath || '(未设置)'}`);
|
|
512
|
+
console.log(`branch: ${meta.branch || '(未设置)'}`);
|
|
513
|
+
console.log(`mode: ${meta.mode || '(未设置)'}`);
|
|
514
|
+
if (meta.baseBranch) console.log(`baseBranch: ${meta.baseBranch}`);
|
|
515
|
+
if (meta.baseHash) console.log(`baseHash: ${String(meta.baseHash).slice(0, 8)}`);
|
|
516
|
+
console.log('');
|
|
517
|
+
console.log('JSON:');
|
|
518
|
+
console.log(JSON.stringify(meta, null, 2));
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
case 'list': {
|
|
522
|
+
const items = wm.list();
|
|
523
|
+
if (items.length === 0) {
|
|
524
|
+
console.log('📭 无活跃 worktree');
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
// 计算列宽
|
|
528
|
+
const maxName = Math.max('Change Name'.length, ...items.map(i => i.changeName.length));
|
|
529
|
+
const maxBranch = Math.max('Branch'.length, ...items.map(i => i.branch.length));
|
|
530
|
+
const header = ` ${'Change Name'.padEnd(maxName)} ${'Branch'.padEnd(maxBranch)} Created`;
|
|
531
|
+
const sep = ` ${'─'.repeat(maxName)} ${'─'.repeat(maxBranch)} ${'─'.repeat(19)}`;
|
|
532
|
+
console.log(header);
|
|
533
|
+
console.log(sep);
|
|
534
|
+
for (const item of items) {
|
|
535
|
+
const created = item.createdAt ? item.createdAt.replace('T', ' ').replace('Z', '').slice(0, 19) : '-';
|
|
536
|
+
console.log(` ${item.changeName.padEnd(maxName)} ${item.branch.padEnd(maxBranch)} ${created}`);
|
|
537
|
+
}
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
case 'cleanup': {
|
|
541
|
+
if (!wtName) {
|
|
542
|
+
console.error('❌ 用法: sillyspec worktree cleanup <change-name>');
|
|
543
|
+
process.exit(1);
|
|
544
|
+
}
|
|
545
|
+
const forceFlag = args.includes('--force');
|
|
546
|
+
try {
|
|
547
|
+
const result = wm.cleanup(wtName, { force: forceFlag });
|
|
548
|
+
if (result.result === 'cleaned' || result.result === 'force-cleaned') {
|
|
549
|
+
console.log(`✅ worktree 已清理: ${wtName} (mode: ${result.mode})`);
|
|
550
|
+
if (result.details?.length > 0) {
|
|
551
|
+
for (const d of result.details) {
|
|
552
|
+
if (d.startsWith('⚠️')) console.log(` ${d}`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
} else if (result.result === 'skipped') {
|
|
556
|
+
console.log(`⏭️ worktree 跳过清理: ${wtName} (mode: ${result.mode})`);
|
|
557
|
+
console.log(` 原因: in-place 模式没有隔离目录需要清理`);
|
|
558
|
+
} else {
|
|
559
|
+
console.log(`ℹ️ worktree 未找到: ${wtName}`);
|
|
560
|
+
}
|
|
561
|
+
} catch (e) {
|
|
562
|
+
console.error(`❌ ${e.message}`);
|
|
563
|
+
process.exit(1);
|
|
564
|
+
}
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
case 'doctor': {
|
|
568
|
+
const fixFlag = args.includes('--fix');
|
|
569
|
+
const staleIdx = args.indexOf('--stale-hours');
|
|
570
|
+
const staleHours = staleIdx !== -1 && args[staleIdx + 1] ? parseInt(args[staleIdx + 1], 10) : 24;
|
|
571
|
+
const diag = wm.doctor({ fix: fixFlag, staleHours });
|
|
572
|
+
if (diag.issues.length === 0) {
|
|
573
|
+
console.log('✅ worktree 健康检查通过,无异常');
|
|
574
|
+
} else {
|
|
575
|
+
console.log(`🔍 发现 ${diag.issues.length} 个问题:\n`);
|
|
576
|
+
for (const issue of diag.issues) {
|
|
577
|
+
const icon = issue.fixable ? '⚠️' : '❌';
|
|
578
|
+
console.log(` ${icon} [${issue.type}] ${issue.name}: ${issue.detail}`);
|
|
579
|
+
}
|
|
580
|
+
if (fixFlag) {
|
|
581
|
+
console.log(`\n🔧 修复完成:`);
|
|
582
|
+
for (const f of diag.fixed) console.log(` ✅ ${f}`);
|
|
583
|
+
if (diag.unfixable.length > 0) {
|
|
584
|
+
for (const u of diag.unfixable) console.log(` ❌ ${u}`);
|
|
585
|
+
}
|
|
586
|
+
if (diag.fixed.length === 0 && diag.unfixable.length === 0) {
|
|
587
|
+
console.log(' 无需修复');
|
|
588
|
+
}
|
|
589
|
+
} else {
|
|
590
|
+
console.log(`\n💡 运行 sillyspec worktree doctor --fix 自动修复`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
default:
|
|
596
|
+
console.error(`❌ 未知子命令: worktree ${wtSubCmd}`);
|
|
597
|
+
console.log(' 运行 sillyspec worktree --help 查看帮助');
|
|
598
|
+
process.exit(1);
|
|
599
|
+
}
|
|
600
|
+
break;
|
|
601
|
+
}
|
|
602
|
+
case 'platform': {
|
|
603
|
+
const platformSub = filteredArgs[1];
|
|
604
|
+
const platformArgs = filteredArgs.slice(2);
|
|
605
|
+
|
|
606
|
+
if (!platformSub || platformSub === 'help' || platformSub === '--help' || platformSub === '-h') {
|
|
607
|
+
console.log(`
|
|
608
|
+
SillySpec platform — SillyHub 平台同步
|
|
609
|
+
|
|
610
|
+
用法:
|
|
611
|
+
sillyspec platform connect <url> [--token <token>]
|
|
612
|
+
sillyspec platform disconnect
|
|
613
|
+
sillyspec platform sync [--change <name>]
|
|
614
|
+
sillyspec platform sync-docs [--change <name>]
|
|
615
|
+
sillyspec platform status
|
|
616
|
+
sillyspec platform pointer [--cleanup]
|
|
617
|
+
sillyspec platform approve <change-name>
|
|
618
|
+
sillyspec platform reject <change-name> [--reason <reason>]
|
|
619
|
+
`);
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
let syncModule;
|
|
624
|
+
try {
|
|
625
|
+
syncModule = await import('./sync.js');
|
|
626
|
+
} catch {
|
|
627
|
+
console.error('❌ 平台同步功能不可用(sync.js 未实现)');
|
|
628
|
+
process.exit(1);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
switch (platformSub) {
|
|
632
|
+
case 'pointer': {
|
|
633
|
+
// 指针状态检查(不依赖 sync 模块)
|
|
634
|
+
const { readFileSync, existsSync } = await import('fs')
|
|
635
|
+
const { join } = await import('path')
|
|
636
|
+
const { POINTER_STATUS, isPointerStale, isPointerCorrupted } = await import('./constants.js')
|
|
637
|
+
const pointerPath = join(dir, '.sillyspec-platform.json')
|
|
638
|
+
|
|
639
|
+
if (!existsSync(pointerPath)) {
|
|
640
|
+
console.log('ℹ️ 无平台指针文件。当前不在平台模式或未进行过平台 scan。')
|
|
641
|
+
break
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
try {
|
|
645
|
+
const pointer = JSON.parse(readFileSync(pointerPath, 'utf8'))
|
|
646
|
+
console.log(`📄 指针文件: ${pointerPath}`)
|
|
647
|
+
console.log(` specRoot: ${pointer.specRoot || '(缺失 ❌)'}`)
|
|
648
|
+
console.log(` runtimeRoot: ${pointer.runtimeRoot || '(未设置)'}`)
|
|
649
|
+
console.log(` workspaceId: ${pointer.workspaceId || '(未设置)'}`)
|
|
650
|
+
console.log(` scanRunId: ${pointer.scanRunId || '(未设置)'}`)
|
|
651
|
+
console.log(` savedAt: ${pointer.savedAt || '(未知)'}`)
|
|
652
|
+
|
|
653
|
+
if (isPointerCorrupted(pointer)) {
|
|
654
|
+
console.log(` 状态: ${POINTER_STATUS.CORRUPTED} ❌`)
|
|
655
|
+
console.log(` ⚠️ 指针损坏(缺少 specRoot),建议删除后重新运行平台 scan。`)
|
|
656
|
+
if (platformArgs.includes('--cleanup')) {
|
|
657
|
+
const { unlinkSync } = await import('fs')
|
|
658
|
+
unlinkSync(pointerPath)
|
|
659
|
+
console.log(` 🗑️ 已清理损坏指针。`)
|
|
660
|
+
}
|
|
661
|
+
} else if (pointer.status === POINTER_STATUS.SCAN_COMPLETED) {
|
|
662
|
+
if (isPointerStale(pointer)) {
|
|
663
|
+
console.log(` 状态: ${POINTER_STATUS.STALE} ⚠️`)
|
|
664
|
+
console.log(` completedAt: ${pointer.completedAt}`)
|
|
665
|
+
console.log(` scanStatus: ${pointer.scanStatus || '(未知)'}`)
|
|
666
|
+
console.log(` ⚠️ 指针已过时(完成超过 24h),可以安全删除。`)
|
|
667
|
+
if (platformArgs.includes('--cleanup')) {
|
|
668
|
+
const { unlinkSync } = await import('fs')
|
|
669
|
+
unlinkSync(pointerPath)
|
|
670
|
+
console.log(` 🗑️ 已清理过时指针。`)
|
|
671
|
+
}
|
|
672
|
+
} else {
|
|
673
|
+
console.log(` 状态: ${pointer.status} ✅`)
|
|
674
|
+
console.log(` completedAt: ${pointer.completedAt}`)
|
|
675
|
+
console.log(` scanStatus: ${pointer.scanStatus || '(未知)'}`)
|
|
676
|
+
}
|
|
677
|
+
} else {
|
|
678
|
+
console.log(` 状态: ${POINTER_STATUS.ACTIVE} 🔄`)
|
|
679
|
+
}
|
|
680
|
+
} catch (e) {
|
|
681
|
+
console.log(` 状态: ${POINTER_STATUS.CORRUPTED} ❌`)
|
|
682
|
+
console.log(` ⚠️ 指针文件损坏: ${e.message}`)
|
|
683
|
+
}
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
case 'connect': {
|
|
687
|
+
const url = platformArgs[0];
|
|
688
|
+
if (!url) {
|
|
689
|
+
console.error('❌ 用法: sillyspec platform connect <url> [--token <token>]');
|
|
690
|
+
process.exit(1);
|
|
691
|
+
}
|
|
692
|
+
const tokenIdx = args.indexOf('--token');
|
|
693
|
+
const token = tokenIdx >= 0 && args[tokenIdx + 1] ? args[tokenIdx + 1] : undefined;
|
|
694
|
+
if (!token) {
|
|
695
|
+
console.error('⚠️ 未提供 --token,将使用交互式输入(TODO: task-11)');
|
|
696
|
+
}
|
|
697
|
+
await syncModule.connect(url, token, dir);
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
case 'disconnect':
|
|
701
|
+
await syncModule.disconnect(dir);
|
|
702
|
+
break;
|
|
703
|
+
case 'sync': {
|
|
704
|
+
const syncChangeIdx = args.indexOf('--change');
|
|
705
|
+
const syncChangeName = syncChangeIdx >= 0 && args[syncChangeIdx + 1] ? args[syncChangeIdx + 1] : null;
|
|
706
|
+
await syncModule.sync(syncChangeName, dir);
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
case 'sync-docs': {
|
|
710
|
+
const syncDocsChangeIdx = args.indexOf('--change');
|
|
711
|
+
const syncDocsChangeName = syncDocsChangeIdx >= 0 && args[syncDocsChangeIdx + 1] ? args[syncDocsChangeIdx + 1] : null;
|
|
712
|
+
await syncModule.syncDocuments(syncDocsChangeName, dir);
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
case 'status':
|
|
716
|
+
await syncModule.status(dir);
|
|
717
|
+
break;
|
|
718
|
+
case 'approve': {
|
|
719
|
+
const approveName = platformArgs[0];
|
|
720
|
+
if (!approveName) {
|
|
721
|
+
console.error('❌ 用法: sillyspec platform approve <change-name>');
|
|
722
|
+
process.exit(1);
|
|
723
|
+
}
|
|
724
|
+
await syncModule.approve(approveName, dir);
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
case 'reject': {
|
|
728
|
+
const rejectName = platformArgs[0];
|
|
729
|
+
if (!rejectName) {
|
|
730
|
+
console.error('❌ 用法: sillyspec platform reject <change-name> [--reason <reason>]');
|
|
731
|
+
process.exit(1);
|
|
732
|
+
}
|
|
733
|
+
const reasonIdx = args.indexOf('--reason');
|
|
734
|
+
const reason = reasonIdx >= 0 && args[reasonIdx + 1] ? args[reasonIdx + 1] : undefined;
|
|
735
|
+
await syncModule.reject(rejectName, reason, dir);
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
default:
|
|
739
|
+
console.error(`❌ 未知子命令: platform ${platformSub}`);
|
|
740
|
+
console.log(' 运行 sillyspec platform --help 查看帮助');
|
|
741
|
+
process.exit(1);
|
|
742
|
+
}
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
case 'change-rename': {
|
|
746
|
+
const oldName = filteredArgs[1];
|
|
747
|
+
const newName = filteredArgs[2];
|
|
748
|
+
if (!oldName || !newName) {
|
|
749
|
+
console.error('❌ 用法: sillyspec change-rename <旧变更名> <新变更名>');
|
|
750
|
+
process.exit(1);
|
|
751
|
+
}
|
|
752
|
+
const pm = new ProgressManager({ specDir });
|
|
753
|
+
await pm.renameChange(dir, oldName, newName);
|
|
754
|
+
break;
|
|
755
|
+
}
|
|
756
|
+
case 'workflow': {
|
|
757
|
+
const wfSub = filteredArgs[1];
|
|
758
|
+
if (!wfSub || wfSub === 'help' || wfSub === '--help') {
|
|
759
|
+
console.log(`
|
|
760
|
+
SillySpec workflow — 工作流管理
|
|
761
|
+
|
|
762
|
+
用法:
|
|
763
|
+
sillyspec workflow check <name> [--project <project>] [--json]
|
|
764
|
+
sillyspec workflow list
|
|
765
|
+
`);
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
if (wfSub === 'list') {
|
|
769
|
+
const { listWorkflows } = await import('./workflow.js');
|
|
770
|
+
const names = listWorkflows(dir);
|
|
771
|
+
if (names.length === 0) {
|
|
772
|
+
console.log('未找到 workflow 定义(.sillyspec/workflows/*.yaml)');
|
|
773
|
+
} else {
|
|
774
|
+
console.log(`可用 workflow:`);
|
|
775
|
+
for (const name of names) {
|
|
776
|
+
const { loadWorkflow } = await import('./workflow.js');
|
|
777
|
+
const wf = loadWorkflow(dir, name);
|
|
778
|
+
const specVer = wf?.spec_version || wf?.version || '?';
|
|
779
|
+
const mode = wf?.orchestration?.mode || '?';
|
|
780
|
+
const roles = wf?.roles?.length || 0;
|
|
781
|
+
console.log(` ${name} (spec v${specVer}, ${mode}, ${roles} roles)`);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
break;
|
|
785
|
+
}
|
|
786
|
+
if (wfSub === 'check') {
|
|
787
|
+
const { loadWorkflow, runPostCheck, listWorkflows, saveWorkflowRun } = await import('./workflow.js');
|
|
788
|
+
const wfName = filteredArgs[2];
|
|
789
|
+
if (!wfName) {
|
|
790
|
+
console.error('❌ 请指定 workflow 名称,例如:sillyspec workflow check scan-docs --project sillyspec');
|
|
791
|
+
process.exit(2);
|
|
792
|
+
}
|
|
793
|
+
const wf = loadWorkflow(dir, wfName);
|
|
794
|
+
if (!wf) {
|
|
795
|
+
console.error(`❌ 未找到 workflow: ${wfName}`);
|
|
796
|
+
console.error(`可用 workflow:${listWorkflows(dir).join(', ') || '无'}`);
|
|
797
|
+
process.exit(2);
|
|
798
|
+
}
|
|
799
|
+
// depends_on 校验
|
|
800
|
+
if (wf._validationErrors && wf._validationErrors.length > 0) {
|
|
801
|
+
console.error('❌ workflow YAML 校验失败:');
|
|
802
|
+
for (const err of wf._validationErrors) {
|
|
803
|
+
console.error(` ${err}`);
|
|
804
|
+
}
|
|
805
|
+
process.exit(2);
|
|
806
|
+
}
|
|
807
|
+
// spec_version 校验
|
|
808
|
+
const specVer = wf.spec_version || wf.version;
|
|
809
|
+
if (!specVer) {
|
|
810
|
+
console.error('❌ workflow YAML 缺少 spec_version 字段');
|
|
811
|
+
process.exit(2);
|
|
812
|
+
}
|
|
813
|
+
const SUPPORTED_SPECS = [1];
|
|
814
|
+
if (!SUPPORTED_SPECS.includes(specVer)) {
|
|
815
|
+
console.error(`❌ 不支持的 spec_version: ${specVer}(支持: ${SUPPORTED_SPECS.join(', ')})`);
|
|
816
|
+
process.exit(2);
|
|
817
|
+
}
|
|
818
|
+
// 解析 --project
|
|
819
|
+
const projectIdx = filteredArgs.indexOf('--project');
|
|
820
|
+
const project = projectIdx !== -1 && filteredArgs[projectIdx + 1] ? filteredArgs[projectIdx + 1] : null;
|
|
821
|
+
// 解析 --json(已在顶层解析)
|
|
822
|
+
const isJson = json;
|
|
823
|
+
// 解析 --change
|
|
824
|
+
const changeIdx = filteredArgs.indexOf('--change');
|
|
825
|
+
const changeName = changeIdx !== -1 && filteredArgs[changeIdx + 1] ? filteredArgs[changeIdx + 1] : null;
|
|
826
|
+
|
|
827
|
+
if (!project && wfName !== 'archive-impact') {
|
|
828
|
+
console.error('❌ 请指定 --project,例如:--project sillyspec');
|
|
829
|
+
process.exit(2);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// 执行检查
|
|
833
|
+
let resolvedWf = wf;
|
|
834
|
+
const placeholders = {};
|
|
835
|
+
if (changeName) placeholders['change-name'] = changeName;
|
|
836
|
+
// 替换占位符
|
|
837
|
+
let jsonStr = JSON.stringify(resolvedWf);
|
|
838
|
+
if (changeName) jsonStr = jsonStr.replace(/<change-name>/g, changeName);
|
|
839
|
+
resolvedWf = JSON.parse(jsonStr);
|
|
840
|
+
|
|
841
|
+
const projectName = project || 'sillyspec';
|
|
842
|
+
const result = runPostCheck(resolvedWf, dir, projectName, placeholders);
|
|
843
|
+
|
|
844
|
+
if (isJson) {
|
|
845
|
+
console.log(JSON.stringify(result, null, 2));
|
|
846
|
+
} else {
|
|
847
|
+
// 带项目维度前缀的输出(从统一结果对象格式化)
|
|
848
|
+
const lines = [`\n📋 Workflow Post-Check: ${result.workflow} (project: ${result.project})\n`];
|
|
849
|
+
for (const r of (result.roles || [])) {
|
|
850
|
+
const icon = r.status === 'pass' ? '✅' : '❌';
|
|
851
|
+
lines.push(`${icon} [${result.project}] ${r.name} (${r.id})`);
|
|
852
|
+
const roleFailures = (result.failures || []).filter(f => f.role_id === r.id);
|
|
853
|
+
for (const f of roleFailures) {
|
|
854
|
+
lines.push(` └─ ${f.message}`);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
const wfFailures = (result.workflow_checks || []).filter(c => c.status === 'fail');
|
|
858
|
+
if (wfFailures.length > 0) {
|
|
859
|
+
lines.push('');
|
|
860
|
+
for (const f of wfFailures) {
|
|
861
|
+
lines.push(`❌ [${result.project}] 全局: ${f.detail}`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
lines.push('');
|
|
865
|
+
if (result.status === 'pass') {
|
|
866
|
+
lines.push('✅ 全部检查通过');
|
|
867
|
+
} else {
|
|
868
|
+
lines.push('❌ 存在失败项');
|
|
869
|
+
}
|
|
870
|
+
console.log(lines.join('\n'));
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// exit code: 0=通过, 1=检查失败, 2=参数/YAML错误
|
|
874
|
+
if (saveWorkflowRunFlag) {
|
|
875
|
+
const saved = saveWorkflowRun(result, { cwd: dir, source: 'cli' });
|
|
876
|
+
if (saved) {
|
|
877
|
+
if (!isJson) console.log(`\n📁 结果已归档:${saved}`);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
process.exit(result.status === 'pass' ? 0 : 1);
|
|
881
|
+
} else {
|
|
882
|
+
console.error(`❌ 未知子命令: workflow ${wfSub}`);
|
|
883
|
+
process.exit(1);
|
|
884
|
+
}
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
case 'modules': {
|
|
888
|
+
const modulesSub = filteredArgs[1];
|
|
889
|
+
if (!modulesSub || modulesSub === 'help' || modulesSub === '--help') {
|
|
890
|
+
console.log(`
|
|
891
|
+
SillySpec modules — 模块文档管理
|
|
892
|
+
|
|
893
|
+
用法:
|
|
894
|
+
sillyspec modules rebuild 从模块卡片 + 源码重建 _module-map.yaml
|
|
895
|
+
sillyspec modules status 显示模块索引状态
|
|
896
|
+
sillyspec modules migrate 旧格式模块文档迁移到新格式
|
|
897
|
+
`);
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
900
|
+
if (modulesSub === 'rebuild') {
|
|
901
|
+
const { rebuildModuleMap } = await import('./modules.js');
|
|
902
|
+
await rebuildModuleMap(dir);
|
|
903
|
+
} else if (modulesSub === 'status') {
|
|
904
|
+
const { showModuleStatus } = await import('./modules.js');
|
|
905
|
+
await showModuleStatus(dir);
|
|
906
|
+
} else if (modulesSub === 'migrate') {
|
|
907
|
+
const { migrateModuleDocs } = await import('./modules.js');
|
|
908
|
+
await migrateModuleDocs(dir);
|
|
909
|
+
} else {
|
|
910
|
+
console.error(`❌ 未知子命令: modules ${modulesSub}`);
|
|
911
|
+
process.exit(1);
|
|
912
|
+
}
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
default:
|
|
916
|
+
console.error(`❌ 未知命令: ${command}`);
|
|
917
|
+
printUsage();
|
|
918
|
+
process.exit(1);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
main();
|