sillyspec 3.20.2 → 3.20.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +1 -5
  2. package/.claude/skills/sillyspec-auto/SKILL.md +2 -8
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -28
  4. package/.claude/skills/sillyspec-commit/SKILL.md +3 -4
  5. package/.claude/skills/sillyspec-continue/SKILL.md +4 -5
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +3 -12
  7. package/.claude/skills/sillyspec-execute/SKILL.md +2 -15
  8. package/.claude/skills/sillyspec-explore/SKILL.md +1 -14
  9. package/.claude/skills/sillyspec-plan/SKILL.md +36 -5
  10. package/.claude/skills/sillyspec-propose/SKILL.md +0 -4
  11. package/.claude/skills/sillyspec-quick/SKILL.md +1 -5
  12. package/.claude/skills/sillyspec-resume/SKILL.md +66 -23
  13. package/.claude/skills/sillyspec-scan/SKILL.md +1 -5
  14. package/.claude/skills/sillyspec-state/SKILL.md +8 -8
  15. package/.claude/skills/sillyspec-status/SKILL.md +1 -5
  16. package/.claude/skills/sillyspec-verify/SKILL.md +1 -5
  17. package/.claude/skills/sillyspec-workspace/SKILL.md +3 -11
  18. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +97 -0
  19. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +51 -0
  20. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +29 -0
  21. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +34 -0
  22. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +13 -0
  23. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +43 -0
  24. package/.sillyspec/changes/auto-mode/design.md +50 -0
  25. package/.sillyspec/changes/auto-mode/proposal.md +19 -0
  26. package/.sillyspec/changes/auto-mode/requirements.md +21 -0
  27. package/.sillyspec/changes/auto-mode/tasks.md +7 -0
  28. package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +206 -0
  29. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +199 -0
  30. package/.sillyspec/changes/dashboard/design.md +219 -0
  31. package/.sillyspec/changes/dashboard/design.md.braindraft +206 -0
  32. package/.sillyspec/changes/run-command-design/design.md +1230 -0
  33. package/.sillyspec/changes/unified-docs-design/design.md +199 -0
  34. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  35. package/.sillyspec/knowledge/INDEX.md +8 -0
  36. package/.sillyspec/knowledge/uncategorized.md +3 -0
  37. package/.sillyspec/plans/2026-04-05-dashboard.md +737 -0
  38. package/.sillyspec/projects/sillyspec.yaml +3 -0
  39. package/README.md +11 -13
  40. package/SKILL.md +40 -44
  41. package/dist/steps/brainstorm/01-load-context.md +30 -0
  42. package/dist/steps/brainstorm/02-reuse-check.md +6 -0
  43. package/dist/steps/brainstorm/03-prototype-analysis.md +11 -0
  44. package/dist/steps/brainstorm/04-module-split.md +23 -0
  45. package/dist/steps/brainstorm/05-dialog-explore.md +8 -0
  46. package/dist/steps/brainstorm/06-propose-approaches.md +3 -0
  47. package/dist/steps/brainstorm/07-present-design.md +3 -0
  48. package/dist/steps/brainstorm/08-write-design.md +21 -0
  49. package/dist/steps/brainstorm/09-self-review.md +15 -0
  50. package/dist/steps/brainstorm/10-user-confirm.md +3 -0
  51. package/dist/steps/brainstorm/11-output-spec.md +7 -0
  52. package/dist/steps/brainstorm/manifest.yaml +26 -0
  53. package/dist/steps/execute/01-load-context.md +41 -0
  54. package/dist/steps/execute/02-scan-conventions.md +47 -0
  55. package/dist/steps/execute/03-skill-mcp.md +19 -0
  56. package/dist/steps/execute/04-assign-task.md +22 -0
  57. package/dist/steps/execute/04b-prompt-template.md +54 -0
  58. package/dist/steps/execute/05-write-test.md +7 -0
  59. package/dist/steps/execute/06-write-code.md +8 -0
  60. package/dist/steps/execute/07-run-test.md +26 -0
  61. package/dist/steps/execute/08-fix-issues.md +28 -0
  62. package/dist/steps/execute/09-next-task.md +33 -0
  63. package/dist/steps/execute/manifest.yaml +28 -0
  64. package/dist/steps/plan/01-load-context.md +22 -0
  65. package/dist/steps/plan/02-anchor-confirm.md +1 -0
  66. package/dist/steps/plan/03-expand-tasks.md +33 -0
  67. package/dist/steps/plan/04-mark-order.md +15 -0
  68. package/dist/steps/plan/05-e2e-planning.md +17 -0
  69. package/dist/steps/plan/06-self-check.md +16 -0
  70. package/dist/steps/plan/07-save.md +1 -0
  71. package/dist/steps/plan/manifest.yaml +18 -0
  72. package/dist/steps/scan/01-env-detect.md +51 -0
  73. package/dist/steps/scan/02-tech-stack.md +16 -0
  74. package/dist/steps/scan/03-conventions.md +16 -0
  75. package/dist/steps/scan/04-structure.md +19 -0
  76. package/dist/steps/scan/05-quality.md +18 -0
  77. package/dist/steps/scan/06-complete.md +49 -0
  78. package/dist/steps/scan/manifest.yaml +16 -0
  79. package/dist/steps/verify/01-load-specs.md +28 -0
  80. package/dist/steps/verify/02-check-tasks.md +1 -0
  81. package/dist/steps/verify/03-check-design.md +6 -0
  82. package/dist/steps/verify/04-run-tests.md +7 -0
  83. package/dist/steps/verify/05-e2e-tests.md +27 -0
  84. package/dist/steps/verify/05b-e2e-fix.md +33 -0
  85. package/dist/steps/verify/06-code-quality.md +25 -0
  86. package/dist/steps/verify/07-lint-check.md +27 -0
  87. package/dist/steps/verify/08-output-report.md +14 -0
  88. package/dist/steps/verify/manifest.yaml +22 -0
  89. package/package.json +1 -7
  90. package/packages/dashboard/dist/assets/{index-Bq_Z2hne.js → index-D1EVTLmc.js} +1264 -1264
  91. package/packages/dashboard/dist/assets/index-DGe8CqeP.css +1 -0
  92. package/packages/dashboard/dist/index.html +2 -2
  93. package/packages/dashboard/package-lock.json +6 -6
  94. package/packages/dashboard/server/executor.js +1 -1
  95. package/packages/dashboard/server/index.js +18 -98
  96. package/packages/dashboard/server/parser.js +71 -139
  97. package/packages/dashboard/server/watcher.js +6 -14
  98. package/packages/dashboard/src/App.vue +185 -418
  99. package/packages/dashboard/src/components/ActionBar.vue +1 -10
  100. package/packages/dashboard/src/components/CommandPalette.vue +1 -5
  101. package/packages/dashboard/src/components/DocPreview.vue +8 -105
  102. package/packages/dashboard/src/components/DocTree.vue +19 -75
  103. package/packages/dashboard/src/components/PipelineStage.vue +2 -22
  104. package/packages/dashboard/src/components/PipelineView.vue +5 -32
  105. package/packages/dashboard/src/components/ProjectOverview.vue +139 -113
  106. package/packages/dashboard/src/components/StageBadge.vue +3 -17
  107. package/packages/dashboard/src/components/StepCard.vue +2 -7
  108. package/packages/dashboard/src/composables/useDashboard.js +0 -28
  109. package/src/derive.js +147 -0
  110. package/src/index.js +40 -688
  111. package/src/init.js +63 -119
  112. package/src/migrate.js +7 -7
  113. package/src/progress.js +248 -1474
  114. package/src/run.js +302 -3008
  115. package/src/setup.js +64 -2
  116. package/src/stages/archive.js +17 -123
  117. package/src/stages/brainstorm.js +48 -454
  118. package/src/stages/doctor.js +46 -99
  119. package/src/stages/execute.js +59 -420
  120. package/src/stages/index.js +18 -12
  121. package/src/stages/plan.js +189 -492
  122. package/src/stages/propose.js +11 -70
  123. package/src/stages/quick.js +13 -52
  124. package/src/stages/scan.js +68 -485
  125. package/src/stages/status.js +1 -1
  126. package/src/stages/verify.js +16 -203
  127. package/src/step.js +543 -0
  128. package/.claude/skills/sillyspec-knowledge/SKILL.md +0 -270
  129. package/.husky/pre-push +0 -13
  130. package/CLAUDE.md +0 -18
  131. package/docs/brainstorm-plan-contract.md +0 -64
  132. package/docs/plan-execute-contract.md +0 -123
  133. package/docs/platform-scan-protocol.md +0 -298
  134. package/docs/revision-mode.md +0 -115
  135. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +0 -99
  136. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +0 -218
  137. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +0 -167
  138. package/docs/sillyspec/file-lifecycle/storage-and-state.md +0 -148
  139. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +0 -193
  140. package/docs/sillyspec/file-lifecycle.md +0 -125
  141. package/docs/workflow-contract-regression.md +0 -106
  142. package/docs/worktree-isolation.md +0 -252
  143. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +0 -1
  144. package/packages/dashboard/dist/prototype-dashboard.html +0 -836
  145. package/packages/dashboard/dist/prototype-overview.html +0 -256
  146. package/packages/dashboard/public/prototype-dashboard.html +0 -836
  147. package/packages/dashboard/public/prototype-overview.html +0 -256
  148. package/packages/dashboard/src/components/HResizeHandle.vue +0 -48
  149. package/packages/dashboard/src/components/ProjectCard.vue +0 -187
  150. package/packages/dashboard/src/components/VResizeHandle.vue +0 -61
  151. package/packages/dashboard/src/composables/useLayout.js +0 -131
  152. package/src/brainstorm-postcheck.js +0 -158
  153. package/src/change-list.js +0 -52
  154. package/src/change-risk-profile.js +0 -352
  155. package/src/classify-change.js +0 -73
  156. package/src/constants.js +0 -70
  157. package/src/contract-matrix.js +0 -278
  158. package/src/db.js +0 -201
  159. package/src/endpoint-extractor.js +0 -315
  160. package/src/hooks/claude-pre-tool-use.cjs +0 -125
  161. package/src/hooks/worktree-guard.js +0 -761
  162. package/src/knowledge-match.js +0 -130
  163. package/src/modules.js +0 -482
  164. package/src/scan-postcheck.js +0 -383
  165. package/src/stage-contract.js +0 -700
  166. package/src/stages/brainstorm-auto.js +0 -229
  167. package/src/stages/explore.js +0 -34
  168. package/src/stages/knowledge.js +0 -498
  169. package/src/stages/plan-postcheck.js +0 -513
  170. package/src/sync.js +0 -497
  171. package/src/task-review.js +0 -346
  172. package/src/workflow.js +0 -785
  173. package/src/worktree-apply.js +0 -549
  174. package/src/worktree.js +0 -932
  175. package/templates/workflows/archive-impact.yaml +0 -79
  176. package/templates/workflows/scan-docs.yaml +0 -132
  177. package/test/brainstorm-plan-contract.test.mjs +0 -273
  178. package/test/check-syntax.mjs +0 -26
  179. package/test/cli-top-level-aliases.test.mjs +0 -174
  180. package/test/contract-artifacts.test.mjs +0 -323
  181. package/test/decision-supersede.test.mjs +0 -277
  182. package/test/knowledge-match.test.mjs +0 -231
  183. package/test/plan-execute-contract.test.mjs +0 -330
  184. package/test/plan-optimization.test.mjs +0 -572
  185. package/test/platform-artifacts.test.mjs +0 -190
  186. package/test/platform-failure-samples.test.mjs +0 -199
  187. package/test/platform-recovery-chain.test.mjs +0 -179
  188. package/test/platform-recovery.test.mjs +0 -167
  189. package/test/platform-scan-p0.test.mjs +0 -175
  190. package/test/revision-v1.test.mjs +0 -1145
  191. package/test/run-sanitize-project-name.test.mjs +0 -51
  192. package/test/run-scan-postcheck-fail.test.mjs +0 -64
  193. package/test/run-scan-project-parse.test.mjs +0 -200
  194. package/test/run-tests.mjs +0 -48
  195. package/test/scan-docs-yaml-placeholders.test.mjs +0 -84
  196. package/test/scan-knowledge.test.mjs +0 -175
  197. package/test/scan-paths.test.mjs +0 -68
  198. package/test/scan-postcheck-project-priority.test.mjs +0 -85
  199. package/test/scan-postcheck.test.mjs +0 -197
  200. package/test/scan-workflow-anyfailed-block.test.mjs +0 -52
  201. package/test/spec-dir.test.mjs +0 -206
  202. package/test/stage-contract-failed-post-check.test.mjs +0 -102
  203. package/test/stage-contract.test.mjs +0 -299
  204. package/test/stage-definitions.test.mjs +0 -39
  205. package/test/wait-gates.test.mjs +0 -501
  206. package/test/workflow-spec-base.test.mjs +0 -142
  207. package/test/worktree-guard.test.mjs +0 -136
  208. package/test/worktree-native-overlay.test.mjs +0 -188
package/src/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * SillySpec CLI — 安装工具
5
5
  *
6
6
  * 只负责两件事:init(安装命令模板)和 setup(安装 MCP 工具)。
7
- * 状态管理通过 sillyspec.db(SQLite)完成,使用 `sillyspec progress` 命令。
7
+ * 状态管理通过 progress.json 完成,使用 `sillyspec progress` 命令。
8
8
  */
9
9
  import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
10
10
  import { join, resolve } from 'path';
@@ -22,81 +22,34 @@ SillySpec CLI — 规范驱动开发工具包
22
22
  [--tool <name>] 只安装指定工具
23
23
  [--interactive] 交互式引导
24
24
  [--dir <path>] 指定目录
25
- [--spec-dir <path>] 指定规范目录(默认 <项目>/.sillyspec)
26
-
27
25
  sillyspec setup [--list] 安装推荐 MCP 工具
28
26
  [--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> 添加步骤
27
+ sillyspec progress <cmd> 进度管理
28
+ init 初始化 progress.json
29
+ show 查看当前进度
30
+ set-stage <stage> 设置当前阶段
31
+ add-step <stage> <name> 添加步骤
57
32
  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
-
33
+ complete-stage <stage> 完成阶段并推进
34
+ validate 校验并修复
35
+ reset [--stage X] 重置进度
36
+ complete --stage X 归档已完成阶段
64
37
  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
38
+ sillyspec dashboard 启动 Dashboard Web UI
83
39
  [--port <number>] 指定端口(默认 3456)
84
40
  [--no-open] 不自动打开浏览器
85
41
 
86
42
  选项:
87
43
  --json 输出 JSON(给 AI 程序化读取)
88
44
  --dir <path> 指定项目目录(默认当前目录)
89
- --spec-dir <path> 指定规范目录(默认 <项目目录>/.sillyspec)
90
45
 
91
46
  示例:
92
47
  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 "需求已澄清"
48
+ sillyspec init --tool claude
49
+
50
+ sillyspec setup
99
51
  sillyspec setup --list
52
+ sillyspec dashboard
100
53
  sillyspec dashboard --port 8080 --no-open
101
54
  `);
102
55
  }
@@ -116,9 +69,7 @@ async function main() {
116
69
 
117
70
  // 解析全局选项
118
71
  let json = false;
119
- let saveWorkflowRunFlag = false;
120
72
  let targetDir = process.cwd();
121
- let specDir = null;
122
73
  let tool = null;
123
74
  let interactive = false;
124
75
  const filteredArgs = [];
@@ -126,14 +77,9 @@ async function main() {
126
77
  for (let i = 0; i < args.length; i++) {
127
78
  if (args[i] === '--json') {
128
79
  json = true;
129
- } else if (args[i] === '--save') {
130
- saveWorkflowRunFlag = true;
131
80
  } else if (args[i] === '--dir' && args[i + 1]) {
132
81
  targetDir = resolve(args[i + 1]);
133
82
  i++;
134
- } else if (args[i] === '--spec-dir' && args[i + 1]) {
135
- specDir = resolve(args[i + 1]);
136
- i++;
137
83
  } else if (args[i] === '--tool' && args[i + 1]) {
138
84
  tool = args[i + 1];
139
85
  i++;
@@ -152,28 +98,8 @@ async function main() {
152
98
  targetDir = resolve(filteredArgs[1]);
153
99
  filteredArgs.splice(1, 1);
154
100
  }
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
101
  const dir = targetDir;
171
102
 
172
- if (command === 'init' && !existsSync(dir)) {
173
- const { mkdirSync } = await import('fs');
174
- mkdirSync(dir, { recursive: true });
175
- }
176
-
177
103
  if (!existsSync(dir)) {
178
104
  console.error(`❌ 目录不存在: ${dir}`);
179
105
  process.exit(1);
@@ -181,7 +107,7 @@ async function main() {
181
107
 
182
108
  switch (command) {
183
109
  case 'init':
184
- await cmdInit(dir, { tool, interactive, specDir });
110
+ await cmdInit(dir, { tool, interactive });
185
111
  break;
186
112
  case 'setup':
187
113
  const setupList = filteredArgs.includes('--list') || filteredArgs.includes('-l');
@@ -189,70 +115,62 @@ async function main() {
189
115
  break;
190
116
  case 'progress': {
191
117
  const pm = new ProgressManager();
192
- const progDir = specDir ? dir : resolveEffectiveDir(dir);
193
118
  const subCommand = filteredArgs[1];
194
119
  const stageIdx = filteredArgs.indexOf('--stage');
195
120
  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
121
 
200
122
  switch (subCommand) {
201
123
  case 'init':
202
- pm.init(progDir);
124
+ pm.init(dir);
203
125
  break;
204
126
  case 'status':
205
127
  case 'show':
206
- pm.show(progDir, progChangeName);
207
- break;
208
- case 'check':
209
- await pm.checkConsistency(progDir, progChangeName);
128
+ pm.show(dir);
210
129
  break;
211
- case 'repair': {
212
- const repairApply = filteredArgs.includes('--apply');
213
- await pm.repairConsistency(progDir, { apply: repairApply, changeName: progChangeName });
214
- break;
215
- }
216
130
  case 'validate':
217
- await pm.validate(dir, progChangeName);
131
+ await pm.validate(dir, filteredArgs.includes('--deep'));
218
132
  break;
219
133
  case 'reset':
220
- pm.reset(dir, stage, progChangeName);
134
+ pm.reset(dir, stage);
135
+ break;
136
+ case 'complete':
137
+ pm.complete(dir, stage);
221
138
  break;
222
139
  case 'set-stage': {
223
140
  const setStageName = filteredArgs[2];
224
- if (!setStageName) { console.log('❌ 用法: sillyspec progress set-stage <stage> [--change <name>]'); break; }
225
- pm.setStage(dir, setStageName, progChangeName);
141
+ if (!setStageName) { console.log('❌ 用法: sillyspec progress set-stage <stage>'); break; }
142
+ pm.setStage(dir, setStageName);
226
143
  break;
227
144
  }
228
145
  case 'add-step': {
229
146
  const addStepStage = filteredArgs[2];
230
147
  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);
148
+ if (!addStepStage || !addStepName) { console.log('❌ 用法: sillyspec progress add-step <stage> <step-name>'); break; }
149
+ pm.addStep(dir, addStepStage, addStepName);
233
150
  break;
234
151
  }
235
152
  case 'update-step': {
236
153
  const updStepStage = filteredArgs[2];
237
154
  const updStepName = filteredArgs[3];
238
- if (!updStepStage || !updStepName) { console.log('❌ 用法: sillyspec progress update-step <stage> <step-name> --status <status> [--output <text>] [--change <name>]'); break; }
155
+ if (!updStepStage || !updStepName) { console.log('❌ 用法: sillyspec progress update-step <stage> <step-name> --status <status> [--output <text>]'); break; }
156
+ // Parse --status and --output from args
239
157
  let updStatus = null, updOutput = undefined;
240
158
  for (let ai = 0; ai < args.length; ai++) {
241
159
  if (args[ai] === '--status' && args[ai + 1]) { updStatus = args[ai + 1]; ai++; }
242
160
  if (args[ai] === '--output' && args[ai + 1]) { updOutput = args[ai + 1]; ai++; }
243
161
  }
244
- pm.updateStep(dir, updStepStage, updStepName, { status: updStatus, output: updOutput }, progChangeName);
162
+ pm.updateStep(dir, updStepStage, updStepName, { status: updStatus, output: updOutput });
245
163
  break;
246
164
  }
247
165
  case 'complete-stage': {
248
166
  const compStageName = filteredArgs[2];
249
167
  if (!compStageName) { console.log('❌ 用法: sillyspec progress complete-stage <stage>'); break; }
250
- pm.completeStage(dir, compStageName, progChangeName);
168
+ pm.completeStage(dir, compStageName);
251
169
  break;
252
170
  }
253
171
  case 'batch': {
254
172
  if (filteredArgs.includes('--status')) {
255
- const bp = pm.readBatchProgress(dir, progChangeName);
173
+ const bp = pm.readBatchProgress(dir);
256
174
  if (!bp) { console.log('📭 无批量进度数据'); break; }
257
175
  const line = pm._renderBatchProgress(bp);
258
176
  console.log(line || '📭 无批量进度数据');
@@ -271,13 +189,13 @@ async function main() {
271
189
  console.log(' sillyspec progress batch --status');
272
190
  break;
273
191
  }
274
- pm.updateBatchProgress(dir, batchData, progChangeName);
192
+ pm.updateBatchProgress(dir, batchData);
275
193
  console.log('✅ 批量进度已更新');
276
194
  }
277
195
  break;
278
196
  }
279
197
  default:
280
- console.log('用法: sillyspec progress <init|show|validate|reset|set-stage|add-step|update-step|complete-stage>');
198
+ console.log('用法: sillyspec progress <init|show|validate|reset|complete|set-stage|add-step|update-step|complete-stage>');
281
199
  }
282
200
  break;
283
201
  }
@@ -293,31 +211,14 @@ async function main() {
293
211
  }
294
212
  case 'run': {
295
213
  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)
214
+ await runCommand(filteredArgs.slice(1), dir)
315
215
  break
316
216
  }
317
- case 'knowledge': {
318
- const { cmdKnowledge } = await import('./stages/knowledge.js')
319
- await cmdKnowledge(filteredArgs.slice(1), specDir ? dir : resolveEffectiveDir(dir), { specDir })
320
- break
217
+ case 'step': {
218
+ const stepArgs = filteredArgs.slice(1);
219
+ if (json) stepArgs.push('--json');
220
+ await (await import('./step.js')).cmdStep(dir, stepArgs);
221
+ break;
321
222
  }
322
223
  case 'dashboard': {
323
224
  // Parse dashboard options
@@ -341,555 +242,6 @@ async function main() {
341
242
  console.log('按 Ctrl+C 停止服务器');
342
243
  break;
343
244
  }
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 cleanup <change-name> [--force] 强制清理 worktree
380
- sillyspec worktree doctor [--fix] [--stale-hours N] 健康检查 + 修复
381
-
382
- 选项:
383
- --base <branch> create: 指定基础分支(默认当前 HEAD)
384
- --check-only apply: 只输出检查结果,不实际 apply
385
- `);
386
- break;
387
- }
388
-
389
- switch (wtSubCmd) {
390
- case 'create': {
391
- if (!wtName) {
392
- console.error('❌ 用法: sillyspec worktree create <change-name> [--base <branch>]');
393
- process.exit(1);
394
- }
395
- const baseIdx = args.indexOf('--base');
396
- const base = baseIdx >= 0 && args[baseIdx + 1] ? args[baseIdx + 1] : undefined;
397
- try {
398
- const info = wm.create(wtName, { base });
399
- console.log(`✅ worktree 已创建`);
400
- console.log(` 分支: ${info.branch}`);
401
- console.log(` 路径: ${info.worktreePath}`);
402
- console.log(` 基准: ${info.baseHash.slice(0, 8)}`);
403
- if (info.mode) {
404
- console.log(` 模式: ${info.mode}`);
405
- }
406
- // 写入 isolation 信息到 gate-status.json
407
- await _writeIsolationToDB(dir, wtName, info);
408
- } catch (e) {
409
- console.error(`❌ ${e.message}`);
410
- // 写入 blocked 状态到 gate-status.json
411
- await _writeIsolationToDB(dir, wtName, { blocked: true, reason: e.message });
412
- process.exit(1);
413
- }
414
- break;
415
- }
416
- case 'apply': {
417
- if (!wtName) {
418
- console.error('❌ 用法: sillyspec worktree apply <change-name> [--check-only]');
419
- process.exit(1);
420
- }
421
- const checkOnly = args.includes('--check-only');
422
- const { applyWorktree } = await import('./worktree-apply.js');
423
- const result = applyWorktree(wtName, { cwd: dir, checkOnly });
424
-
425
- if (result.errors.length > 0) {
426
- console.error(`❌ 校验失败:`);
427
- for (const err of result.errors) {
428
- console.error(` ${err}`);
429
- }
430
- process.exit(1);
431
- }
432
-
433
- if (result.changedFiles.length === 0) {
434
- console.log('📭 无变更需要应用');
435
- break;
436
- }
437
-
438
- if (checkOnly) {
439
- console.log(`✅ 检查通过 (${result.changedFiles.length} 个文件):`);
440
- for (const f of result.changedFiles) {
441
- console.log(` ${f}`);
442
- }
443
- } else {
444
- console.log(`✅ 已应用 ${result.changedFiles.length} 个文件变更`);
445
- }
446
- if (result.warnings && result.warnings.length > 0) {
447
- for (const w of result.warnings) {
448
- console.log(`⚠️ ${w}`);
449
- }
450
- }
451
- break;
452
- }
453
- case 'assess': {
454
- if (!wtName) {
455
- console.error('❌ 用法: sillyspec worktree assess <change-name>');
456
- process.exit(1);
457
- }
458
- const { assessApplyRisk } = await import('./worktree-apply.js');
459
- const assessment = assessApplyRisk(wtName, { cwd: dir });
460
-
461
- const SEPARATOR = '─'.repeat(32);
462
- console.log('Worktree Apply Decision');
463
- console.log(SEPARATOR);
464
- const decisionIcon = assessment.decision === 'SAFE' ? '✅' : assessment.decision === 'WARNING' ? '⚠️ ' : '🚫';
465
- console.log(`Decision: ${decisionIcon} ${assessment.decision}`);
466
- console.log(`Changed files: ${assessment.changedFiles.length}`);
467
- if (assessment.stats.additions > 0 || assessment.stats.deletions > 0) {
468
- console.log(`Additions: +${assessment.stats.additions} Deletions: -${assessment.stats.deletions}`);
469
- }
470
-
471
- if (assessment.reasons.length > 0) {
472
- console.log('');
473
- console.log('Blocked reasons:');
474
- for (const r of assessment.reasons) r.split('\n').forEach(l => console.log(` ${l}`));
475
- }
476
- if (assessment.warnings.length > 0) {
477
- console.log('');
478
- console.log('Warnings:');
479
- for (const w of assessment.warnings) console.log(` ⚠️ ${w}`);
480
- }
481
-
482
- console.log('');
483
- if (assessment.decision === 'SAFE' || assessment.decision === 'WARNING') {
484
- console.log('Action: auto-applying...');
485
- const { applyWorktree } = await import('./worktree-apply.js');
486
- const applyResult = applyWorktree(wtName, { cwd: dir });
487
- if (applyResult.errors.length > 0) {
488
- console.error('❌ apply 失败:', applyResult.errors.join('; '));
489
- } else {
490
- console.log(`✅ 已自动应用 ${applyResult.changedFiles.length} 个文件变更`);
491
- }
492
- } else {
493
- console.log('Action: blocked');
494
- console.log(' → 检查变更: sillyspec worktree diff ' + wtName);
495
- console.log(' → 丢弃变更: sillyspec worktree cleanup ' + wtName);
496
- }
497
- break;
498
- }
499
- case 'list': {
500
- const items = wm.list();
501
- if (items.length === 0) {
502
- console.log('📭 无活跃 worktree');
503
- break;
504
- }
505
- // 计算列宽
506
- const maxName = Math.max('Change Name'.length, ...items.map(i => i.changeName.length));
507
- const maxBranch = Math.max('Branch'.length, ...items.map(i => i.branch.length));
508
- const header = ` ${'Change Name'.padEnd(maxName)} ${'Branch'.padEnd(maxBranch)} Created`;
509
- const sep = ` ${'─'.repeat(maxName)} ${'─'.repeat(maxBranch)} ${'─'.repeat(19)}`;
510
- console.log(header);
511
- console.log(sep);
512
- for (const item of items) {
513
- const created = item.createdAt ? item.createdAt.replace('T', ' ').replace('Z', '').slice(0, 19) : '-';
514
- console.log(` ${item.changeName.padEnd(maxName)} ${item.branch.padEnd(maxBranch)} ${created}`);
515
- }
516
- break;
517
- }
518
- case 'cleanup': {
519
- if (!wtName) {
520
- console.error('❌ 用法: sillyspec worktree cleanup <change-name>');
521
- process.exit(1);
522
- }
523
- const forceFlag = args.includes('--force');
524
- try {
525
- const result = wm.cleanup(wtName, { force: forceFlag });
526
- if (result.result === 'cleaned' || result.result === 'force-cleaned') {
527
- console.log(`✅ worktree 已清理: ${wtName} (mode: ${result.mode})`);
528
- if (result.details?.length > 0) {
529
- for (const d of result.details) {
530
- if (d.startsWith('⚠️')) console.log(` ${d}`);
531
- }
532
- }
533
- } else if (result.result === 'skipped') {
534
- console.log(`⏭️ worktree 跳过清理: ${wtName} (mode: ${result.mode})`);
535
- console.log(` 原因: in-place 模式没有隔离目录需要清理`);
536
- } else {
537
- console.log(`ℹ️ worktree 未找到: ${wtName}`);
538
- }
539
- } catch (e) {
540
- console.error(`❌ ${e.message}`);
541
- process.exit(1);
542
- }
543
- break;
544
- }
545
- case 'doctor': {
546
- const fixFlag = args.includes('--fix');
547
- const staleIdx = args.indexOf('--stale-hours');
548
- const staleHours = staleIdx !== -1 && args[staleIdx + 1] ? parseInt(args[staleIdx + 1], 10) : 24;
549
- const diag = wm.doctor({ fix: fixFlag, staleHours });
550
- if (diag.issues.length === 0) {
551
- console.log('✅ worktree 健康检查通过,无异常');
552
- } else {
553
- console.log(`🔍 发现 ${diag.issues.length} 个问题:\n`);
554
- for (const issue of diag.issues) {
555
- const icon = issue.fixable ? '⚠️' : '❌';
556
- console.log(` ${icon} [${issue.type}] ${issue.name}: ${issue.detail}`);
557
- }
558
- if (fixFlag) {
559
- console.log(`\n🔧 修复完成:`);
560
- for (const f of diag.fixed) console.log(` ✅ ${f}`);
561
- if (diag.unfixable.length > 0) {
562
- for (const u of diag.unfixable) console.log(` ❌ ${u}`);
563
- }
564
- if (diag.fixed.length === 0 && diag.unfixable.length === 0) {
565
- console.log(' 无需修复');
566
- }
567
- } else {
568
- console.log(`\n💡 运行 sillyspec worktree doctor --fix 自动修复`);
569
- }
570
- }
571
- break;
572
- }
573
- default:
574
- console.error(`❌ 未知子命令: worktree ${wtSubCmd}`);
575
- console.log(' 运行 sillyspec worktree --help 查看帮助');
576
- process.exit(1);
577
- }
578
- break;
579
- }
580
- case 'platform': {
581
- const platformSub = filteredArgs[1];
582
- const platformArgs = filteredArgs.slice(2);
583
-
584
- if (!platformSub || platformSub === 'help' || platformSub === '--help' || platformSub === '-h') {
585
- console.log(`
586
- SillySpec platform — SillyHub 平台同步
587
-
588
- 用法:
589
- sillyspec platform connect <url> [--token <token>]
590
- sillyspec platform disconnect
591
- sillyspec platform sync [--change <name>]
592
- sillyspec platform sync-docs [--change <name>]
593
- sillyspec platform status
594
- sillyspec platform pointer [--cleanup]
595
- sillyspec platform approve <change-name>
596
- sillyspec platform reject <change-name> [--reason <reason>]
597
- `);
598
- break;
599
- }
600
-
601
- let syncModule;
602
- try {
603
- syncModule = await import('./sync.js');
604
- } catch {
605
- console.error('❌ 平台同步功能不可用(sync.js 未实现)');
606
- process.exit(1);
607
- }
608
-
609
- switch (platformSub) {
610
- case 'pointer': {
611
- // 指针状态检查(不依赖 sync 模块)
612
- const { readFileSync, existsSync } = await import('fs')
613
- const { join } = await import('path')
614
- const { POINTER_STATUS, isPointerStale, isPointerCorrupted } = await import('./constants.js')
615
- const pointerPath = join(dir, '.sillyspec-platform.json')
616
-
617
- if (!existsSync(pointerPath)) {
618
- console.log('ℹ️ 无平台指针文件。当前不在平台模式或未进行过平台 scan。')
619
- break
620
- }
621
-
622
- try {
623
- const pointer = JSON.parse(readFileSync(pointerPath, 'utf8'))
624
- console.log(`📄 指针文件: ${pointerPath}`)
625
- console.log(` specRoot: ${pointer.specRoot || '(缺失 ❌)'}`)
626
- console.log(` runtimeRoot: ${pointer.runtimeRoot || '(未设置)'}`)
627
- console.log(` workspaceId: ${pointer.workspaceId || '(未设置)'}`)
628
- console.log(` scanRunId: ${pointer.scanRunId || '(未设置)'}`)
629
- console.log(` savedAt: ${pointer.savedAt || '(未知)'}`)
630
-
631
- if (isPointerCorrupted(pointer)) {
632
- console.log(` 状态: ${POINTER_STATUS.CORRUPTED} ❌`)
633
- console.log(` ⚠️ 指针损坏(缺少 specRoot),建议删除后重新运行平台 scan。`)
634
- if (platformArgs.includes('--cleanup')) {
635
- const { unlinkSync } = await import('fs')
636
- unlinkSync(pointerPath)
637
- console.log(` 🗑️ 已清理损坏指针。`)
638
- }
639
- } else if (pointer.status === POINTER_STATUS.SCAN_COMPLETED) {
640
- if (isPointerStale(pointer)) {
641
- console.log(` 状态: ${POINTER_STATUS.STALE} ⚠️`)
642
- console.log(` completedAt: ${pointer.completedAt}`)
643
- console.log(` scanStatus: ${pointer.scanStatus || '(未知)'}`)
644
- console.log(` ⚠️ 指针已过时(完成超过 24h),可以安全删除。`)
645
- if (platformArgs.includes('--cleanup')) {
646
- const { unlinkSync } = await import('fs')
647
- unlinkSync(pointerPath)
648
- console.log(` 🗑️ 已清理过时指针。`)
649
- }
650
- } else {
651
- console.log(` 状态: ${pointer.status} ✅`)
652
- console.log(` completedAt: ${pointer.completedAt}`)
653
- console.log(` scanStatus: ${pointer.scanStatus || '(未知)'}`)
654
- }
655
- } else {
656
- console.log(` 状态: ${POINTER_STATUS.ACTIVE} 🔄`)
657
- }
658
- } catch (e) {
659
- console.log(` 状态: ${POINTER_STATUS.CORRUPTED} ❌`)
660
- console.log(` ⚠️ 指针文件损坏: ${e.message}`)
661
- }
662
- break;
663
- }
664
- case 'connect': {
665
- const url = platformArgs[0];
666
- if (!url) {
667
- console.error('❌ 用法: sillyspec platform connect <url> [--token <token>]');
668
- process.exit(1);
669
- }
670
- const tokenIdx = args.indexOf('--token');
671
- const token = tokenIdx >= 0 && args[tokenIdx + 1] ? args[tokenIdx + 1] : undefined;
672
- if (!token) {
673
- console.error('⚠️ 未提供 --token,将使用交互式输入(TODO: task-11)');
674
- }
675
- await syncModule.connect(url, token, dir);
676
- break;
677
- }
678
- case 'disconnect':
679
- await syncModule.disconnect(dir);
680
- break;
681
- case 'sync': {
682
- const syncChangeIdx = args.indexOf('--change');
683
- const syncChangeName = syncChangeIdx >= 0 && args[syncChangeIdx + 1] ? args[syncChangeIdx + 1] : null;
684
- await syncModule.sync(syncChangeName, dir);
685
- break;
686
- }
687
- case 'sync-docs': {
688
- const syncDocsChangeIdx = args.indexOf('--change');
689
- const syncDocsChangeName = syncDocsChangeIdx >= 0 && args[syncDocsChangeIdx + 1] ? args[syncDocsChangeIdx + 1] : null;
690
- await syncModule.syncDocuments(syncDocsChangeName, dir);
691
- break;
692
- }
693
- case 'status':
694
- await syncModule.status(dir);
695
- break;
696
- case 'approve': {
697
- const approveName = platformArgs[0];
698
- if (!approveName) {
699
- console.error('❌ 用法: sillyspec platform approve <change-name>');
700
- process.exit(1);
701
- }
702
- await syncModule.approve(approveName, dir);
703
- break;
704
- }
705
- case 'reject': {
706
- const rejectName = platformArgs[0];
707
- if (!rejectName) {
708
- console.error('❌ 用法: sillyspec platform reject <change-name> [--reason <reason>]');
709
- process.exit(1);
710
- }
711
- const reasonIdx = args.indexOf('--reason');
712
- const reason = reasonIdx >= 0 && args[reasonIdx + 1] ? args[reasonIdx + 1] : undefined;
713
- await syncModule.reject(rejectName, reason, dir);
714
- break;
715
- }
716
- default:
717
- console.error(`❌ 未知子命令: platform ${platformSub}`);
718
- console.log(' 运行 sillyspec platform --help 查看帮助');
719
- process.exit(1);
720
- }
721
- break;
722
- }
723
- case 'change-rename': {
724
- const oldName = filteredArgs[1];
725
- const newName = filteredArgs[2];
726
- if (!oldName || !newName) {
727
- console.error('❌ 用法: sillyspec change-rename <旧变更名> <新变更名>');
728
- process.exit(1);
729
- }
730
- const pm = new ProgressManager({ specDir });
731
- await pm.renameChange(dir, oldName, newName);
732
- break;
733
- }
734
- case 'workflow': {
735
- const wfSub = filteredArgs[1];
736
- if (!wfSub || wfSub === 'help' || wfSub === '--help') {
737
- console.log(`
738
- SillySpec workflow — 工作流管理
739
-
740
- 用法:
741
- sillyspec workflow check <name> [--project <project>] [--json]
742
- sillyspec workflow list
743
- `);
744
- break;
745
- }
746
- if (wfSub === 'list') {
747
- const { listWorkflows } = await import('./workflow.js');
748
- const names = listWorkflows(dir);
749
- if (names.length === 0) {
750
- console.log('未找到 workflow 定义(.sillyspec/workflows/*.yaml)');
751
- } else {
752
- console.log(`可用 workflow:`);
753
- for (const name of names) {
754
- const { loadWorkflow } = await import('./workflow.js');
755
- const wf = loadWorkflow(dir, name);
756
- const specVer = wf?.spec_version || wf?.version || '?';
757
- const mode = wf?.orchestration?.mode || '?';
758
- const roles = wf?.roles?.length || 0;
759
- console.log(` ${name} (spec v${specVer}, ${mode}, ${roles} roles)`);
760
- }
761
- }
762
- break;
763
- }
764
- if (wfSub === 'check') {
765
- const { loadWorkflow, runPostCheck, listWorkflows, saveWorkflowRun } = await import('./workflow.js');
766
- const wfName = filteredArgs[2];
767
- if (!wfName) {
768
- console.error('❌ 请指定 workflow 名称,例如:sillyspec workflow check scan-docs --project sillyspec');
769
- process.exit(2);
770
- }
771
- const wf = loadWorkflow(dir, wfName);
772
- if (!wf) {
773
- console.error(`❌ 未找到 workflow: ${wfName}`);
774
- console.error(`可用 workflow:${listWorkflows(dir).join(', ') || '无'}`);
775
- process.exit(2);
776
- }
777
- // depends_on 校验
778
- if (wf._validationErrors && wf._validationErrors.length > 0) {
779
- console.error('❌ workflow YAML 校验失败:');
780
- for (const err of wf._validationErrors) {
781
- console.error(` ${err}`);
782
- }
783
- process.exit(2);
784
- }
785
- // spec_version 校验
786
- const specVer = wf.spec_version || wf.version;
787
- if (!specVer) {
788
- console.error('❌ workflow YAML 缺少 spec_version 字段');
789
- process.exit(2);
790
- }
791
- const SUPPORTED_SPECS = [1];
792
- if (!SUPPORTED_SPECS.includes(specVer)) {
793
- console.error(`❌ 不支持的 spec_version: ${specVer}(支持: ${SUPPORTED_SPECS.join(', ')})`);
794
- process.exit(2);
795
- }
796
- // 解析 --project
797
- const projectIdx = filteredArgs.indexOf('--project');
798
- const project = projectIdx !== -1 && filteredArgs[projectIdx + 1] ? filteredArgs[projectIdx + 1] : null;
799
- // 解析 --json(已在顶层解析)
800
- const isJson = json;
801
- // 解析 --change
802
- const changeIdx = filteredArgs.indexOf('--change');
803
- const changeName = changeIdx !== -1 && filteredArgs[changeIdx + 1] ? filteredArgs[changeIdx + 1] : null;
804
-
805
- if (!project && wfName !== 'archive-impact') {
806
- console.error('❌ 请指定 --project,例如:--project sillyspec');
807
- process.exit(2);
808
- }
809
-
810
- // 执行检查
811
- let resolvedWf = wf;
812
- const placeholders = {};
813
- if (changeName) placeholders['change-name'] = changeName;
814
- // 替换占位符
815
- let jsonStr = JSON.stringify(resolvedWf);
816
- if (changeName) jsonStr = jsonStr.replace(/<change-name>/g, changeName);
817
- resolvedWf = JSON.parse(jsonStr);
818
-
819
- const projectName = project || 'sillyspec';
820
- const result = runPostCheck(resolvedWf, dir, projectName, placeholders);
821
-
822
- if (isJson) {
823
- console.log(JSON.stringify(result, null, 2));
824
- } else {
825
- // 带项目维度前缀的输出(从统一结果对象格式化)
826
- const lines = [`\n📋 Workflow Post-Check: ${result.workflow} (project: ${result.project})\n`];
827
- for (const r of (result.roles || [])) {
828
- const icon = r.status === 'pass' ? '✅' : '❌';
829
- lines.push(`${icon} [${result.project}] ${r.name} (${r.id})`);
830
- const roleFailures = (result.failures || []).filter(f => f.role_id === r.id);
831
- for (const f of roleFailures) {
832
- lines.push(` └─ ${f.message}`);
833
- }
834
- }
835
- const wfFailures = (result.workflow_checks || []).filter(c => c.status === 'fail');
836
- if (wfFailures.length > 0) {
837
- lines.push('');
838
- for (const f of wfFailures) {
839
- lines.push(`❌ [${result.project}] 全局: ${f.detail}`);
840
- }
841
- }
842
- lines.push('');
843
- if (result.status === 'pass') {
844
- lines.push('✅ 全部检查通过');
845
- } else {
846
- lines.push('❌ 存在失败项');
847
- }
848
- console.log(lines.join('\n'));
849
- }
850
-
851
- // exit code: 0=通过, 1=检查失败, 2=参数/YAML错误
852
- if (saveWorkflowRunFlag) {
853
- const saved = saveWorkflowRun(result, { cwd: dir, source: 'cli' });
854
- if (saved) {
855
- if (!isJson) console.log(`\n📁 结果已归档:${saved}`);
856
- }
857
- }
858
- process.exit(result.status === 'pass' ? 0 : 1);
859
- } else {
860
- console.error(`❌ 未知子命令: workflow ${wfSub}`);
861
- process.exit(1);
862
- }
863
- break;
864
- }
865
- case 'modules': {
866
- const modulesSub = filteredArgs[1];
867
- if (!modulesSub || modulesSub === 'help' || modulesSub === '--help') {
868
- console.log(`
869
- SillySpec modules — 模块文档管理
870
-
871
- 用法:
872
- sillyspec modules rebuild 从模块卡片 + 源码重建 _module-map.yaml
873
- sillyspec modules status 显示模块索引状态
874
- sillyspec modules migrate 旧格式模块文档迁移到新格式
875
- `);
876
- break;
877
- }
878
- if (modulesSub === 'rebuild') {
879
- const { rebuildModuleMap } = await import('./modules.js');
880
- await rebuildModuleMap(dir);
881
- } else if (modulesSub === 'status') {
882
- const { showModuleStatus } = await import('./modules.js');
883
- await showModuleStatus(dir);
884
- } else if (modulesSub === 'migrate') {
885
- const { migrateModuleDocs } = await import('./modules.js');
886
- await migrateModuleDocs(dir);
887
- } else {
888
- console.error(`❌ 未知子命令: modules ${modulesSub}`);
889
- process.exit(1);
890
- }
891
- break;
892
- }
893
245
  default:
894
246
  console.error(`❌ 未知命令: ${command}`);
895
247
  printUsage();