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.
- package/.claude/skills/sillyspec-archive/SKILL.md +1 -5
- package/.claude/skills/sillyspec-auto/SKILL.md +2 -8
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -28
- package/.claude/skills/sillyspec-commit/SKILL.md +3 -4
- package/.claude/skills/sillyspec-continue/SKILL.md +4 -5
- package/.claude/skills/sillyspec-doctor/SKILL.md +3 -12
- package/.claude/skills/sillyspec-execute/SKILL.md +2 -15
- package/.claude/skills/sillyspec-explore/SKILL.md +1 -14
- package/.claude/skills/sillyspec-plan/SKILL.md +36 -5
- package/.claude/skills/sillyspec-propose/SKILL.md +0 -4
- package/.claude/skills/sillyspec-quick/SKILL.md +1 -5
- package/.claude/skills/sillyspec-resume/SKILL.md +66 -23
- package/.claude/skills/sillyspec-scan/SKILL.md +1 -5
- package/.claude/skills/sillyspec-state/SKILL.md +8 -8
- package/.claude/skills/sillyspec-status/SKILL.md +1 -5
- package/.claude/skills/sillyspec-verify/SKILL.md +1 -5
- package/.claude/skills/sillyspec-workspace/SKILL.md +3 -11
- package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +97 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +51 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +29 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +34 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +13 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +43 -0
- package/.sillyspec/changes/auto-mode/design.md +50 -0
- package/.sillyspec/changes/auto-mode/proposal.md +19 -0
- package/.sillyspec/changes/auto-mode/requirements.md +21 -0
- package/.sillyspec/changes/auto-mode/tasks.md +7 -0
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +206 -0
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +199 -0
- package/.sillyspec/changes/dashboard/design.md +219 -0
- package/.sillyspec/changes/dashboard/design.md.braindraft +206 -0
- package/.sillyspec/changes/run-command-design/design.md +1230 -0
- package/.sillyspec/changes/unified-docs-design/design.md +199 -0
- package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
- package/.sillyspec/knowledge/INDEX.md +8 -0
- package/.sillyspec/knowledge/uncategorized.md +3 -0
- package/.sillyspec/plans/2026-04-05-dashboard.md +737 -0
- package/.sillyspec/projects/sillyspec.yaml +3 -0
- package/README.md +11 -13
- package/SKILL.md +40 -44
- package/dist/steps/brainstorm/01-load-context.md +30 -0
- package/dist/steps/brainstorm/02-reuse-check.md +6 -0
- package/dist/steps/brainstorm/03-prototype-analysis.md +11 -0
- package/dist/steps/brainstorm/04-module-split.md +23 -0
- package/dist/steps/brainstorm/05-dialog-explore.md +8 -0
- package/dist/steps/brainstorm/06-propose-approaches.md +3 -0
- package/dist/steps/brainstorm/07-present-design.md +3 -0
- package/dist/steps/brainstorm/08-write-design.md +21 -0
- package/dist/steps/brainstorm/09-self-review.md +15 -0
- package/dist/steps/brainstorm/10-user-confirm.md +3 -0
- package/dist/steps/brainstorm/11-output-spec.md +7 -0
- package/dist/steps/brainstorm/manifest.yaml +26 -0
- package/dist/steps/execute/01-load-context.md +41 -0
- package/dist/steps/execute/02-scan-conventions.md +47 -0
- package/dist/steps/execute/03-skill-mcp.md +19 -0
- package/dist/steps/execute/04-assign-task.md +22 -0
- package/dist/steps/execute/04b-prompt-template.md +54 -0
- package/dist/steps/execute/05-write-test.md +7 -0
- package/dist/steps/execute/06-write-code.md +8 -0
- package/dist/steps/execute/07-run-test.md +26 -0
- package/dist/steps/execute/08-fix-issues.md +28 -0
- package/dist/steps/execute/09-next-task.md +33 -0
- package/dist/steps/execute/manifest.yaml +28 -0
- package/dist/steps/plan/01-load-context.md +22 -0
- package/dist/steps/plan/02-anchor-confirm.md +1 -0
- package/dist/steps/plan/03-expand-tasks.md +33 -0
- package/dist/steps/plan/04-mark-order.md +15 -0
- package/dist/steps/plan/05-e2e-planning.md +17 -0
- package/dist/steps/plan/06-self-check.md +16 -0
- package/dist/steps/plan/07-save.md +1 -0
- package/dist/steps/plan/manifest.yaml +18 -0
- package/dist/steps/scan/01-env-detect.md +51 -0
- package/dist/steps/scan/02-tech-stack.md +16 -0
- package/dist/steps/scan/03-conventions.md +16 -0
- package/dist/steps/scan/04-structure.md +19 -0
- package/dist/steps/scan/05-quality.md +18 -0
- package/dist/steps/scan/06-complete.md +49 -0
- package/dist/steps/scan/manifest.yaml +16 -0
- package/dist/steps/verify/01-load-specs.md +28 -0
- package/dist/steps/verify/02-check-tasks.md +1 -0
- package/dist/steps/verify/03-check-design.md +6 -0
- package/dist/steps/verify/04-run-tests.md +7 -0
- package/dist/steps/verify/05-e2e-tests.md +27 -0
- package/dist/steps/verify/05b-e2e-fix.md +33 -0
- package/dist/steps/verify/06-code-quality.md +25 -0
- package/dist/steps/verify/07-lint-check.md +27 -0
- package/dist/steps/verify/08-output-report.md +14 -0
- package/dist/steps/verify/manifest.yaml +22 -0
- package/package.json +1 -7
- package/packages/dashboard/dist/assets/{index-Bq_Z2hne.js → index-D1EVTLmc.js} +1264 -1264
- package/packages/dashboard/dist/assets/index-DGe8CqeP.css +1 -0
- package/packages/dashboard/dist/index.html +2 -2
- package/packages/dashboard/package-lock.json +6 -6
- package/packages/dashboard/server/executor.js +1 -1
- package/packages/dashboard/server/index.js +18 -98
- package/packages/dashboard/server/parser.js +71 -139
- package/packages/dashboard/server/watcher.js +6 -14
- package/packages/dashboard/src/App.vue +185 -418
- package/packages/dashboard/src/components/ActionBar.vue +1 -10
- package/packages/dashboard/src/components/CommandPalette.vue +1 -5
- package/packages/dashboard/src/components/DocPreview.vue +8 -105
- package/packages/dashboard/src/components/DocTree.vue +19 -75
- package/packages/dashboard/src/components/PipelineStage.vue +2 -22
- package/packages/dashboard/src/components/PipelineView.vue +5 -32
- package/packages/dashboard/src/components/ProjectOverview.vue +139 -113
- package/packages/dashboard/src/components/StageBadge.vue +3 -17
- package/packages/dashboard/src/components/StepCard.vue +2 -7
- package/packages/dashboard/src/composables/useDashboard.js +0 -28
- package/src/derive.js +147 -0
- package/src/index.js +40 -688
- package/src/init.js +63 -119
- package/src/migrate.js +7 -7
- package/src/progress.js +248 -1474
- package/src/run.js +302 -3008
- package/src/setup.js +64 -2
- package/src/stages/archive.js +17 -123
- package/src/stages/brainstorm.js +48 -454
- package/src/stages/doctor.js +46 -99
- package/src/stages/execute.js +59 -420
- package/src/stages/index.js +18 -12
- package/src/stages/plan.js +189 -492
- package/src/stages/propose.js +11 -70
- package/src/stages/quick.js +13 -52
- package/src/stages/scan.js +68 -485
- package/src/stages/status.js +1 -1
- package/src/stages/verify.js +16 -203
- package/src/step.js +543 -0
- package/.claude/skills/sillyspec-knowledge/SKILL.md +0 -270
- package/.husky/pre-push +0 -13
- package/CLAUDE.md +0 -18
- package/docs/brainstorm-plan-contract.md +0 -64
- package/docs/plan-execute-contract.md +0 -123
- package/docs/platform-scan-protocol.md +0 -298
- package/docs/revision-mode.md +0 -115
- package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +0 -99
- package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +0 -218
- package/docs/sillyspec/file-lifecycle/stage-artifacts.md +0 -167
- package/docs/sillyspec/file-lifecycle/storage-and-state.md +0 -148
- package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +0 -193
- package/docs/sillyspec/file-lifecycle.md +0 -125
- package/docs/workflow-contract-regression.md +0 -106
- package/docs/worktree-isolation.md +0 -252
- package/packages/dashboard/dist/assets/index-O2W5RV4z.css +0 -1
- package/packages/dashboard/dist/prototype-dashboard.html +0 -836
- package/packages/dashboard/dist/prototype-overview.html +0 -256
- package/packages/dashboard/public/prototype-dashboard.html +0 -836
- package/packages/dashboard/public/prototype-overview.html +0 -256
- package/packages/dashboard/src/components/HResizeHandle.vue +0 -48
- package/packages/dashboard/src/components/ProjectCard.vue +0 -187
- package/packages/dashboard/src/components/VResizeHandle.vue +0 -61
- package/packages/dashboard/src/composables/useLayout.js +0 -131
- package/src/brainstorm-postcheck.js +0 -158
- package/src/change-list.js +0 -52
- package/src/change-risk-profile.js +0 -352
- package/src/classify-change.js +0 -73
- package/src/constants.js +0 -70
- package/src/contract-matrix.js +0 -278
- package/src/db.js +0 -201
- package/src/endpoint-extractor.js +0 -315
- package/src/hooks/claude-pre-tool-use.cjs +0 -125
- package/src/hooks/worktree-guard.js +0 -761
- package/src/knowledge-match.js +0 -130
- package/src/modules.js +0 -482
- package/src/scan-postcheck.js +0 -383
- package/src/stage-contract.js +0 -700
- package/src/stages/brainstorm-auto.js +0 -229
- package/src/stages/explore.js +0 -34
- package/src/stages/knowledge.js +0 -498
- package/src/stages/plan-postcheck.js +0 -513
- package/src/sync.js +0 -497
- package/src/task-review.js +0 -346
- package/src/workflow.js +0 -785
- package/src/worktree-apply.js +0 -549
- package/src/worktree.js +0 -932
- package/templates/workflows/archive-impact.yaml +0 -79
- package/templates/workflows/scan-docs.yaml +0 -132
- package/test/brainstorm-plan-contract.test.mjs +0 -273
- package/test/check-syntax.mjs +0 -26
- package/test/cli-top-level-aliases.test.mjs +0 -174
- package/test/contract-artifacts.test.mjs +0 -323
- package/test/decision-supersede.test.mjs +0 -277
- package/test/knowledge-match.test.mjs +0 -231
- package/test/plan-execute-contract.test.mjs +0 -330
- package/test/plan-optimization.test.mjs +0 -572
- package/test/platform-artifacts.test.mjs +0 -190
- package/test/platform-failure-samples.test.mjs +0 -199
- package/test/platform-recovery-chain.test.mjs +0 -179
- package/test/platform-recovery.test.mjs +0 -167
- package/test/platform-scan-p0.test.mjs +0 -175
- package/test/revision-v1.test.mjs +0 -1145
- package/test/run-sanitize-project-name.test.mjs +0 -51
- package/test/run-scan-postcheck-fail.test.mjs +0 -64
- package/test/run-scan-project-parse.test.mjs +0 -200
- package/test/run-tests.mjs +0 -48
- package/test/scan-docs-yaml-placeholders.test.mjs +0 -84
- package/test/scan-knowledge.test.mjs +0 -175
- package/test/scan-paths.test.mjs +0 -68
- package/test/scan-postcheck-project-priority.test.mjs +0 -85
- package/test/scan-postcheck.test.mjs +0 -197
- package/test/scan-workflow-anyfailed-block.test.mjs +0 -52
- package/test/spec-dir.test.mjs +0 -206
- package/test/stage-contract-failed-post-check.test.mjs +0 -102
- package/test/stage-contract.test.mjs +0 -299
- package/test/stage-definitions.test.mjs +0 -39
- package/test/wait-gates.test.mjs +0 -501
- package/test/workflow-spec-base.test.mjs +0 -142
- package/test/worktree-guard.test.mjs +0 -136
- 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
|
-
* 状态管理通过
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 --
|
|
94
|
-
|
|
95
|
-
sillyspec
|
|
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
|
|
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(
|
|
124
|
+
pm.init(dir);
|
|
203
125
|
break;
|
|
204
126
|
case 'status':
|
|
205
127
|
case 'show':
|
|
206
|
-
pm.show(
|
|
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,
|
|
131
|
+
await pm.validate(dir, filteredArgs.includes('--deep'));
|
|
218
132
|
break;
|
|
219
133
|
case 'reset':
|
|
220
|
-
pm.reset(dir, stage
|
|
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>
|
|
225
|
-
pm.setStage(dir, setStageName
|
|
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>
|
|
232
|
-
pm.addStep(dir, addStepStage, addStepName
|
|
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>]
|
|
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 }
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 '
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
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();
|