sillyspec 3.25.9 → 3.26.0
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-auto/SKILL.md +8 -13
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +8 -6
- package/.claude/skills/sillyspec-continue/SKILL.md +3 -3
- package/.claude/skills/sillyspec-doctor/SKILL.md +1 -1
- package/.claude/skills/sillyspec-execute/SKILL.md +10 -4
- package/.claude/skills/sillyspec-plan/SKILL.md +1 -1
- package/.claude/skills/sillyspec-quick/SKILL.md +3 -1
- package/.claude/skills/sillyspec-verify/SKILL.md +1 -1
- package/.sillyspec-platform-cleaned +1 -0
- package/.sillyspec-platform.json +7 -0
- package/README.md +9 -0
- package/package.json +2 -2
- package/src/change-list.js +11 -8
- package/src/change-risk-profile.js +22 -0
- package/src/db.js +170 -161
- package/src/dispatch/backends/local-agent.js +152 -0
- package/src/dispatch/backends/sillyhub-mcp.js +242 -0
- package/src/dispatch/probe.js +210 -0
- package/src/dispatch/strategy.js +121 -0
- package/src/doctor-diagnostics.js +19 -25
- package/src/fs-atomic.js +69 -69
- package/src/git-helper.js +66 -0
- package/src/hooks/worktree-guard.js +64 -55
- package/src/index.js +326 -36
- package/src/init.js +7 -14
- package/src/local-detect.js +120 -110
- package/src/machine-interface.js +54 -19
- package/src/migrate.js +14 -4
- package/src/modules.js +16 -5
- package/src/progress/change-registry.js +85 -70
- package/src/progress/consistency-doctor.js +71 -14
- package/src/progress/shared.js +8 -5
- package/src/progress/stage-machine.js +67 -65
- package/src/progress/step-store.js +72 -67
- package/src/progress.js +448 -236
- package/src/quicklog.js +59 -12
- package/src/run/command.js +78 -39
- package/src/run/complete-handlers.js +1028 -889
- package/src/run/complete.js +132 -212
- package/src/run/concurrent-detect.js +132 -0
- package/src/run/gates.js +302 -19
- package/src/run/prompt.js +22 -68
- package/src/run/quick-audit.js +10 -9
- package/src/run/shared.js +722 -600
- package/src/run/stage.js +43 -24
- package/src/scan-postcheck.js +9 -31
- package/src/sillyhub-mcp/client.js +394 -0
- package/src/sillyhub-mcp/config.js +62 -0
- package/src/spec-dir-typo.js +71 -0
- package/src/stage-contract-engine.js +22 -1
- package/src/stage-contract-spec.js +11 -4
- package/src/stage-contract.js +26 -3
- package/src/stage-review.js +118 -10
- package/src/stages/archive.js +4 -6
- package/src/stages/brainstorm-auto.js +26 -20
- package/src/stages/brainstorm.js +29 -5
- package/src/stages/cmd-existence.js +145 -0
- package/src/stages/doctor.js +10 -7
- package/src/stages/execute.js +64 -22
- package/src/stages/plan-postcheck.js +152 -0
- package/src/stages/plan.js +16 -31
- package/src/stages/quick.js +25 -9
- package/src/stages/scan.js +53 -8
- package/src/stages/status.js +0 -1
- package/src/stages/verify.js +14 -9
- package/src/sync.js +433 -16
- package/src/task-review.js +59 -1
- package/src/verify-postcheck.js +938 -862
- package/src/version.js +19 -0
- package/src/worktree-apply.js +218 -93
- package/src/worktree-deps.js +89 -5
- package/src/worktree.js +213 -111
- package/templates/prompts/taskcard-rules.md +27 -0
- package/templates/prompts/verify-probes.md +3 -3
- package/.claude/skills/sillyspec-propose/SKILL.md +0 -87
- package/src/stages/propose.js +0 -181
package/src/index.js
CHANGED
|
@@ -10,14 +10,14 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync,
|
|
|
10
10
|
import { writeAtomicSync } from './fs-atomic.js';
|
|
11
11
|
import { join, resolve } from 'path';
|
|
12
12
|
import { execSync } from 'child_process';
|
|
13
|
-
import {
|
|
13
|
+
import { git } from './git-helper.js';
|
|
14
|
+
import { getVersion } from './version.js';
|
|
14
15
|
import { ProgressManager, resolvePlatformSpecDir } from './progress.js';
|
|
15
|
-
import { detectLocalYaml } from './local-detect.js';
|
|
16
16
|
|
|
17
17
|
// ── CLI 入口 ──
|
|
18
18
|
|
|
19
19
|
// did-you-mean 从 ./run/shared.js 复用(命令级 + flag 级 typo 建议)。
|
|
20
|
-
import { didYouMean, assertSafeChangeName } from './run/shared.js'
|
|
20
|
+
import { didYouMean, assertSafeChangeName, resolveSpecDir, detectWorktreeSpecDrift } from './run/shared.js'
|
|
21
21
|
function printUsage() {
|
|
22
22
|
console.log(`
|
|
23
23
|
SillySpec CLI — 规范驱动开发工具包
|
|
@@ -76,6 +76,7 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
76
76
|
|
|
77
77
|
sillyspec local detect [--dir <path>] 生成本地配置 local.yaml(纯 fs 嗅探,零 token、不跑 scan)
|
|
78
78
|
sillyspec runtime list [--json] 枚举 .sillyspec/.runtime/ 运行时产物(只读,看手上有哪些证据/状态文件)
|
|
79
|
+
sillyspec dispatch <probe | hint> SillyHub 派发能力探测 + 策略生成(agent 调用桥,仅渲染不执行 tool)
|
|
79
80
|
|
|
80
81
|
sillyspec workflow check <name> [--project <p>] [--change <c>] [--json] [--save]
|
|
81
82
|
全局 workflow 校验(--save 归档到 .sillyspec/.runtime/workflow-runs/)
|
|
@@ -83,6 +84,8 @@ SillySpec CLI — 规范驱动开发工具包
|
|
|
83
84
|
sillyspec gate <stage> --change <name> [--json] 机器门控:阶段能否标记完成(只读)
|
|
84
85
|
sillyspec derive <facet> --change <name> [--json] 单项事实核验(facet: execute-evidence|verify-test|task-reviews|artifacts)
|
|
85
86
|
sillyspec backfill-reviews --change <name> [--json] 为手动补的 task 生成 review.json 草稿(cannot_verify,解 archive 客观完成度阻断)
|
|
87
|
+
sillyspec register-stage-review --change <name> --stage <brainstorm|plan|execute> [--from <review.json>] [--json]
|
|
88
|
+
生成/adopt stage 级 review.json(docHash 自动算 + 写 marker,治 tier=independent marker 死锁)
|
|
86
89
|
|
|
87
90
|
sillyspec doctor [子命令] 进度库健康检查 + 修复(顶层命令,非 worktree doctor)
|
|
88
91
|
(无子命令) 跑诊断,列出问题
|
|
@@ -228,7 +231,7 @@ async function main() {
|
|
|
228
231
|
|
|
229
232
|
switch (command) {
|
|
230
233
|
case 'init':
|
|
231
|
-
await cmdInit(dir, { tool, interactive, specDir });
|
|
234
|
+
await (await import('./init.js')).cmdInit(dir, { tool, interactive, specDir });
|
|
232
235
|
break;
|
|
233
236
|
case 'setup':
|
|
234
237
|
const setupList = filteredArgs.includes('--list') || filteredArgs.includes('-l');
|
|
@@ -253,7 +256,7 @@ async function main() {
|
|
|
253
256
|
pm.show(progDir, progChangeName);
|
|
254
257
|
break;
|
|
255
258
|
case 'check': {
|
|
256
|
-
const result =
|
|
259
|
+
const result = pm.checkConsistency(progDir, progChangeName);
|
|
257
260
|
// checkConsistency 只返回 {ok, issues, warnings},不打印——必须在此输出,
|
|
258
261
|
// 否则 agent 跑只读健康检查得到一片寂静,无法分辨「正常」还是「已损坏」。
|
|
259
262
|
if (!result || result.ok) {
|
|
@@ -267,11 +270,11 @@ async function main() {
|
|
|
267
270
|
}
|
|
268
271
|
case 'repair': {
|
|
269
272
|
const repairApply = filteredArgs.includes('--apply');
|
|
270
|
-
|
|
273
|
+
pm.repairConsistency(progDir, { apply: repairApply, changeName: progChangeName });
|
|
271
274
|
break;
|
|
272
275
|
}
|
|
273
276
|
case 'validate':
|
|
274
|
-
|
|
277
|
+
pm.validate(dir, progChangeName);
|
|
275
278
|
break;
|
|
276
279
|
case 'reset':
|
|
277
280
|
pm.reset(dir, stage, progChangeName);
|
|
@@ -299,18 +302,18 @@ async function main() {
|
|
|
299
302
|
if (args[ai] === '--output' && args[ai + 1]) { updOutput = args[ai + 1]; ai++; }
|
|
300
303
|
}
|
|
301
304
|
const updForce = args.includes('--force');
|
|
302
|
-
|
|
305
|
+
pm.updateStep(dir, updStepStage, updStepName, { status: updStatus, output: updOutput, force: updForce }, progChangeName);
|
|
303
306
|
break;
|
|
304
307
|
}
|
|
305
308
|
case 'complete-stage': {
|
|
306
309
|
const compStageName = filteredArgs[2];
|
|
307
310
|
if (!compStageName) { console.log('❌ 用法: sillyspec progress complete-stage <stage> [--force]'); break; }
|
|
308
|
-
|
|
311
|
+
pm.completeStage(dir, compStageName, progChangeName, { force: args.includes('--force') });
|
|
309
312
|
break;
|
|
310
313
|
}
|
|
311
314
|
case 'batch': {
|
|
312
315
|
if (filteredArgs.includes('--status')) {
|
|
313
|
-
const bp =
|
|
316
|
+
const bp = pm.readBatchProgress(dir, progChangeName);
|
|
314
317
|
if (!bp) { console.log('📭 无批量进度数据'); break; }
|
|
315
318
|
const line = pm._renderBatchProgress(bp);
|
|
316
319
|
console.log(line || '📭 无批量进度数据');
|
|
@@ -329,7 +332,7 @@ async function main() {
|
|
|
329
332
|
console.log(' sillyspec progress batch --status');
|
|
330
333
|
break;
|
|
331
334
|
}
|
|
332
|
-
|
|
335
|
+
pm.updateBatchProgress(dir, batchData, progChangeName);
|
|
333
336
|
console.log('✅ 批量进度已更新');
|
|
334
337
|
}
|
|
335
338
|
break;
|
|
@@ -356,6 +359,15 @@ async function main() {
|
|
|
356
359
|
// resolveSpecDir(cwd),向后兼容。
|
|
357
360
|
const gateOpts = { cwd: dir };
|
|
358
361
|
if (specDir) gateOpts.specBase = specDir;
|
|
362
|
+
// drift 锚定(坑 execute-runs-isolation,对齐 command.js execute drift 守卫 + machine-interface
|
|
363
|
+
// runGate 已扩展的 specDriftAnchor 入参):gate 是顶层命令不走 runCommand/drift 守卫,worktree cwd
|
|
364
|
+
// 下 execute 段 marker 读取会落副本 .runtime。仅在未显式 --spec-dir 时算(对齐守卫条件 !specDir),
|
|
365
|
+
// 命中则 specDriftAnchor 锚主仓——runGate execute 段的 resolveRuntimeRoot 读主仓
|
|
366
|
+
// current-execute-run-id marker,不读 worktree 副本(副本随 cleanup 整目录删消失)。
|
|
367
|
+
if (!specDir) {
|
|
368
|
+
const wt = detectWorktreeSpecDrift(resolveSpecDir(dir));
|
|
369
|
+
if (wt) gateOpts.specDriftAnchor = wt.mainSpecBase;
|
|
370
|
+
}
|
|
359
371
|
const { envelope, exitCode } = await withJsonOutput(json, () => runGate(gateStage, gateChange, gateOpts));
|
|
360
372
|
if (json) {
|
|
361
373
|
process.stdout.write(JSON.stringify(envelope));
|
|
@@ -390,6 +402,13 @@ async function main() {
|
|
|
390
402
|
// 下需透传;不传 --spec-dir 时走默认 resolveSpecDir(cwd),向后兼容。
|
|
391
403
|
const deriveOpts = { cwd: dir };
|
|
392
404
|
if (specDir) deriveOpts.specBase = specDir;
|
|
405
|
+
// drift 锚定(同 gate case,对齐 command.js drift 守卫 + machine-interface runDerive 的 specDriftAnchor
|
|
406
|
+
// 入参):derive 顶层命令不走 drift 守卫,worktree cwd 下 task-reviews facet 的 execute-run-id marker
|
|
407
|
+
// 读取会落副本。未显式 --spec-dir 时算 anchor,runDerive task-reviews 的 resolveRuntimeRoot 读主仓 marker。
|
|
408
|
+
if (!specDir) {
|
|
409
|
+
const wt = detectWorktreeSpecDrift(resolveSpecDir(dir));
|
|
410
|
+
if (wt) deriveOpts.specDriftAnchor = wt.mainSpecBase;
|
|
411
|
+
}
|
|
393
412
|
const { envelope, exitCode } = await withJsonOutput(json, () => runDerive(facet, deriveChange, deriveOpts));
|
|
394
413
|
if (json) {
|
|
395
414
|
process.stdout.write(JSON.stringify(envelope));
|
|
@@ -441,6 +460,41 @@ async function main() {
|
|
|
441
460
|
}
|
|
442
461
|
break;
|
|
443
462
|
}
|
|
463
|
+
case 'register-stage-review': {
|
|
464
|
+
// 手动注册 stage 级 review(brainstorm/plan/execute-acceptance)的确定性 writer ——
|
|
465
|
+
// 生成 run 目录 + review.json 骨架(cannot_verify,待独立审查子代理填 verdict)或 adopt
|
|
466
|
+
// agent 草稿(--from,保留 verdict/checklist 重算 docHash)+ 写 marker。治 tier=independent 时
|
|
467
|
+
// 调度者手动派独立子代理不写 marker 的死锁。docHash 由 CLI computeDocHash 算(部分实现 P6.1b defer)。
|
|
468
|
+
// 纯新增,不改 gate 语义。与 task 级 backfill-reviews 对称(stage 级等价物)。
|
|
469
|
+
const rsrChangeIdx = args.indexOf('--change');
|
|
470
|
+
const rsrChange = rsrChangeIdx >= 0 && args[rsrChangeIdx + 1] ? args[rsrChangeIdx + 1] : null;
|
|
471
|
+
const rsrStageIdx = args.indexOf('--stage');
|
|
472
|
+
const rsrStage = rsrStageIdx >= 0 && args[rsrStageIdx + 1] ? args[rsrStageIdx + 1] : null;
|
|
473
|
+
const rsrFromIdx = args.indexOf('--from');
|
|
474
|
+
const rsrFrom = rsrFromIdx >= 0 && args[rsrFromIdx + 1] ? args[rsrFromIdx + 1] : null;
|
|
475
|
+
if (!rsrChange || !rsrStage) {
|
|
476
|
+
console.error('用法: sillyspec register-stage-review --change <名> --stage <brainstorm|plan|execute> [--from <review.json>] [--spec-dir <path>] [--json]\n 生成/adopt stage 级 review.json(docHash 自动算 + 写 marker),治 tier=independent marker 死锁');
|
|
477
|
+
process.exit(2);
|
|
478
|
+
}
|
|
479
|
+
const { registerStageReview } = await import('./stage-review.js');
|
|
480
|
+
const rsrPlatformOpts = {};
|
|
481
|
+
if (specDir) rsrPlatformOpts.specRoot = specDir;
|
|
482
|
+
try {
|
|
483
|
+
const result = registerStageReview({ changeName: rsrChange, stage: rsrStage, fromFile: rsrFrom, cwd: dir, platformOpts: rsrPlatformOpts });
|
|
484
|
+
if (json) {
|
|
485
|
+
process.stdout.write(JSON.stringify({ ok: true, command: 'register-stage-review', change: rsrChange, stage: rsrStage, ...result }));
|
|
486
|
+
} else {
|
|
487
|
+
console.log(`✅ 已注册 ${rsrStage} stage review [${result.reviewRunId}] → ${result.reviewPath}(mode: ${result.mode})`);
|
|
488
|
+
console.log(` marker → ${result.markerPath}`);
|
|
489
|
+
console.log(` 下一步:独立审查子代理对照 ${result.mainDoc} 填 verdict/checklist 后重跑 --done`);
|
|
490
|
+
}
|
|
491
|
+
} catch (e) {
|
|
492
|
+
if (json) process.stdout.write(JSON.stringify({ ok: false, command: 'register-stage-review', error: e.message }));
|
|
493
|
+
else console.error('❌ ' + e.message);
|
|
494
|
+
process.exitCode = 1;
|
|
495
|
+
}
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
444
498
|
case 'docs': {
|
|
445
499
|
const docsSubCmd = filteredArgs[1];
|
|
446
500
|
if (docsSubCmd === 'migrate') {
|
|
@@ -513,7 +567,7 @@ async function main() {
|
|
|
513
567
|
const specBase = resolvePlatformSpecDir(doctorEffectiveDir, specDir) || join(doctorEffectiveDir, '.sillyspec');
|
|
514
568
|
let r;
|
|
515
569
|
try {
|
|
516
|
-
r =
|
|
570
|
+
r = pm.alignExecuteToPlan(doctorEffectiveDir, alignChange, specBase, { confirm: doctorConfirm });
|
|
517
571
|
} catch (e) {
|
|
518
572
|
console.error(`❌ 对齐失败:${e.message}`);
|
|
519
573
|
process.exitCode = 1;
|
|
@@ -595,6 +649,10 @@ async function main() {
|
|
|
595
649
|
const { runCommand } = await import('./run.js')
|
|
596
650
|
const stageArgs = [command, ...filteredArgs.slice(1)]
|
|
597
651
|
const effectiveDir = specDir ? dir : resolveEffectiveDir(dir)
|
|
652
|
+
// 下行 pull:stage 命令(run/--done,含 archive)启动时拉一次(task-10 / D-009 / FR-04)
|
|
653
|
+
// 不在每步 pull,仅低频边界点;未连接平台静默跳过
|
|
654
|
+
const { triggerPullActiveChange } = await import('./run/shared.js')
|
|
655
|
+
await triggerPullActiveChange(effectiveDir)
|
|
598
656
|
await runCommand(stageArgs, effectiveDir, specDir, { json })
|
|
599
657
|
break
|
|
600
658
|
}
|
|
@@ -634,9 +692,9 @@ async function main() {
|
|
|
634
692
|
const pm = new ProgressManager({ specDir: resolvePlatformSpecDir(dir, specDir) });
|
|
635
693
|
|
|
636
694
|
// isolation 写入 DB 的辅助函数
|
|
637
|
-
|
|
695
|
+
function _writeIsolationToDB(cwd, changeName, info) {
|
|
638
696
|
if (info.blocked) {
|
|
639
|
-
|
|
697
|
+
pm.updateChangeIsolation(cwd, changeName, {
|
|
640
698
|
status: 'blocked',
|
|
641
699
|
mode: null,
|
|
642
700
|
reason: info.reason,
|
|
@@ -644,7 +702,7 @@ async function main() {
|
|
|
644
702
|
} else {
|
|
645
703
|
const mode = info.mode || 'worktree';
|
|
646
704
|
const statusMap = { 'worktree': 'verified', 'native-worktree': 'verified', 'in-place-fallback': 'degraded' };
|
|
647
|
-
|
|
705
|
+
pm.updateChangeIsolation(cwd, changeName, {
|
|
648
706
|
status: statusMap[mode] || 'verified',
|
|
649
707
|
mode,
|
|
650
708
|
});
|
|
@@ -659,10 +717,11 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
659
717
|
sillyspec worktree create <change-name> [--base <branch>] 创建隔离 worktree
|
|
660
718
|
sillyspec worktree apply <change-name> [--check-only] 校验并应用变更到主工作区
|
|
661
719
|
sillyspec worktree assess <change-name> 风险审计 + 自动 apply
|
|
720
|
+
sillyspec worktree diff <change-name> [--base <commit>] 查看 worktree 相对 base 的变更
|
|
662
721
|
sillyspec worktree list 列出所有活跃 worktree
|
|
663
722
|
sillyspec worktree meta <change-name> 读取 worktree meta.json
|
|
664
723
|
sillyspec worktree cleanup <change-name> [--force] 强制清理 worktree
|
|
665
|
-
sillyspec worktree doctor [--fix] [--stale-hours N]
|
|
724
|
+
sillyspec worktree doctor [--fix] [--stale-hours N] [--change <name>] 健康检查 + 修复(--change 仅扫指定 change)
|
|
666
725
|
|
|
667
726
|
选项:
|
|
668
727
|
--base <branch> create: 指定基础分支(默认当前 HEAD)
|
|
@@ -691,11 +750,11 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
691
750
|
if (info.mode) {
|
|
692
751
|
console.log(` 模式: ${info.mode}`);
|
|
693
752
|
}
|
|
694
|
-
// 写入 isolation 信息到
|
|
753
|
+
// 写入 isolation 信息到 sillyspec.db(_writeIsolationToDB)
|
|
695
754
|
await _writeIsolationToDB(dir, wtName, info);
|
|
696
755
|
} catch (e) {
|
|
697
756
|
console.error(`❌ ${e.message}`);
|
|
698
|
-
// 写入 blocked 状态到
|
|
757
|
+
// 写入 blocked 状态到 sillyspec.db(_writeIsolationToDB)
|
|
699
758
|
await _writeIsolationToDB(dir, wtName, { blocked: true, reason: e.message });
|
|
700
759
|
process.exit(1);
|
|
701
760
|
}
|
|
@@ -716,6 +775,12 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
716
775
|
for (const err of result.errors) {
|
|
717
776
|
console.error(` ${err}`);
|
|
718
777
|
}
|
|
778
|
+
if (result.rescueCommands) {
|
|
779
|
+
console.error('');
|
|
780
|
+
console.error(`🆘 Rescue commands (${result.rescueCommands.cpFileCount} safe / ${result.rescueCommands.excludedCount} excluded,旁路 git apply,cp 后需手动 sillyspec worktree cleanup ${wtName}):`);
|
|
781
|
+
for (const c of result.rescueCommands.commands) console.error(` ${c}`);
|
|
782
|
+
for (const w of result.rescueCommands.warnings) console.error(` ⚠️ ${w}`);
|
|
783
|
+
}
|
|
719
784
|
process.exit(1);
|
|
720
785
|
}
|
|
721
786
|
|
|
@@ -736,10 +801,10 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
736
801
|
for (const f of result.changedFiles) {
|
|
737
802
|
console.log(` ${f}`);
|
|
738
803
|
}
|
|
739
|
-
// filterDeliverableFiles
|
|
740
|
-
//
|
|
741
|
-
//
|
|
742
|
-
console.log(` ℹ️ 注:.sillyspec/
|
|
804
|
+
// filterDeliverableFiles 精细化排除(保留 .sillyspec/docs/,排 changes/+.runtime/+quicklog/ + meta.json):
|
|
805
|
+
// 模块文档(.sillyspec/docs/)= 交付物,会 auto-apply 到主工作区;变更文档/运行时/quicklog 不 apply。
|
|
806
|
+
// 须告知 agent 哪些未落地,否则它期望这些文件落地却找不到(memory: worktree-apply-excludes-module-docs)。
|
|
807
|
+
console.log(` ℹ️ 注:.sillyspec/changes、.sillyspec/.runtime、.sillyspec/quicklog 下的文件按规则不自动 apply(模块文档 .sillyspec/docs/ 会自动 apply),如需请手动从 worktree 分支或 dangling commit 取(git show sillyspec/${wtName}:<path>)。`);
|
|
743
808
|
}
|
|
744
809
|
if (result.warnings && result.warnings.length > 0) {
|
|
745
810
|
for (const w of result.warnings) {
|
|
@@ -777,6 +842,12 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
777
842
|
for (const w of assessment.warnings) console.log(` ⚠️ ${w}`);
|
|
778
843
|
}
|
|
779
844
|
|
|
845
|
+
if (assessment.rescueCommands) {
|
|
846
|
+
console.log('');
|
|
847
|
+
console.log(`🆘 Rescue commands (${assessment.rescueCommands.cpFileCount} safe / ${assessment.rescueCommands.excludedCount} excluded,旁路 git apply,cp 后需手动 sillyspec worktree cleanup ${wtName}):`);
|
|
848
|
+
for (const c of assessment.rescueCommands.commands) console.log(` ${c}`);
|
|
849
|
+
for (const w of assessment.rescueCommands.warnings) console.log(` ⚠️ ${w}`);
|
|
850
|
+
}
|
|
780
851
|
console.log('');
|
|
781
852
|
if (assessment.decision === 'SAFE' || assessment.decision === 'WARNING') {
|
|
782
853
|
console.log('Action: auto-applying...');
|
|
@@ -787,7 +858,7 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
787
858
|
} else {
|
|
788
859
|
console.log(`✅ 已自动应用 ${applyResult.changedFiles.length} 个文件变更:`);
|
|
789
860
|
for (const f of applyResult.changedFiles) console.log(` ${f}`);
|
|
790
|
-
console.log(` ℹ️ 注:.sillyspec/ 下的文件按规则不自动 apply
|
|
861
|
+
console.log(` ℹ️ 注:.sillyspec/changes、.sillyspec/.runtime、.sillyspec/quicklog 下的文件按规则不自动 apply(模块文档 .sillyspec/docs/ 会自动 apply),如需请手动从分支取(git show sillyspec/${wtName}:<path>)。`);
|
|
791
862
|
}
|
|
792
863
|
} else {
|
|
793
864
|
console.log('Action: blocked');
|
|
@@ -818,6 +889,42 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
818
889
|
console.log(JSON.stringify(meta, null, 2));
|
|
819
890
|
break;
|
|
820
891
|
}
|
|
892
|
+
case 'diff': {
|
|
893
|
+
if (!wtName) {
|
|
894
|
+
console.error('❌ 用法: sillyspec worktree diff <change-name> [--base <commit>]');
|
|
895
|
+
process.exit(1);
|
|
896
|
+
}
|
|
897
|
+
const meta = wm.getMeta(wtName);
|
|
898
|
+
if (!meta) {
|
|
899
|
+
console.error(`❌ 未找到 worktree meta: ${wtName}(可能未创建或已被清理)`);
|
|
900
|
+
process.exit(1);
|
|
901
|
+
}
|
|
902
|
+
if (!meta.worktreePath || !existsSync(meta.worktreePath)) {
|
|
903
|
+
console.error(`❌ worktree 目录不存在: ${meta.worktreePath || '(未设置)'}(可能已被清理,可跑 sillyspec worktree doctor)`);
|
|
904
|
+
process.exit(1);
|
|
905
|
+
}
|
|
906
|
+
// base 优先级:显式 --base > meta.baseHash(create 时锚的主仓 HEAD)> 当前 HEAD。
|
|
907
|
+
// diff 比较 worktree 工作区(含未提交)vs base,与 _changesAlreadyOnMain 同口径。
|
|
908
|
+
const baseIdx = filteredArgs.indexOf('--base');
|
|
909
|
+
const explicitBase = baseIdx >= 0 ? filteredArgs[baseIdx + 1] : null;
|
|
910
|
+
const base = explicitBase || meta.baseHash || 'HEAD';
|
|
911
|
+
let out = '';
|
|
912
|
+
try {
|
|
913
|
+
// base 作为独立 argv 元素传入,不经 shell 拆词/注入;worktreePath 经 cwd,不插值。
|
|
914
|
+
// trim:false 保留 git diff 原始输出(含尾换行)供 stdout.write;空检查靠临时 .trim()。
|
|
915
|
+
// timeout 30s:diff 属潜在长操作(对齐 design R3 与 verify-postcheck 口径)。
|
|
916
|
+
out = git(meta.worktreePath, ['--no-pager', 'diff', '--no-renames', base], { trim: false, timeout: 30000 });
|
|
917
|
+
} catch (e) {
|
|
918
|
+
console.error(`❌ git diff 失败(base=${String(base).slice(0, 8)}): ${e.message}`);
|
|
919
|
+
process.exit(1);
|
|
920
|
+
}
|
|
921
|
+
if (!out.trim()) {
|
|
922
|
+
console.log(`(无变更:worktree 工作区与 base ${String(base).slice(0, 8)} 一致)`);
|
|
923
|
+
} else {
|
|
924
|
+
process.stdout.write(out);
|
|
925
|
+
}
|
|
926
|
+
break;
|
|
927
|
+
}
|
|
821
928
|
case 'list': {
|
|
822
929
|
const items = wm.list();
|
|
823
930
|
if (items.length === 0) {
|
|
@@ -868,7 +975,9 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
868
975
|
const fixFlag = args.includes('--fix');
|
|
869
976
|
const staleIdx = args.indexOf('--stale-hours');
|
|
870
977
|
const staleHours = staleIdx !== -1 && args[staleIdx + 1] ? parseInt(args[staleIdx + 1], 10) : 24;
|
|
871
|
-
const
|
|
978
|
+
const changeIdx = args.indexOf('--change');
|
|
979
|
+
const changeName = changeIdx !== -1 && args[changeIdx + 1] ? args[changeIdx + 1] : null;
|
|
980
|
+
const diag = wm.doctor({ fix: fixFlag, staleHours, changeName });
|
|
872
981
|
if (diag.issues.length === 0) {
|
|
873
982
|
console.log('✅ worktree 健康检查通过,无异常');
|
|
874
983
|
} else {
|
|
@@ -899,6 +1008,83 @@ SillySpec worktree — git worktree 隔离管理
|
|
|
899
1008
|
}
|
|
900
1009
|
break;
|
|
901
1010
|
}
|
|
1011
|
+
case 'dispatch': {
|
|
1012
|
+
// SillyHub 派发抽象层的 agent 调用桥(design.md §Phase2 / D-007@v1):
|
|
1013
|
+
// 仅做能力探测(probe)与派发策略生成(hint),**不执行任何 tool 调用**——
|
|
1014
|
+
// 实际 tool 调用(本机 Agent tool / SillyHub MCP tool)由 agent 据指令执行。
|
|
1015
|
+
// 仿 worktree 子命令的参数解析(filteredArgs[1] 取子命令)与错误处理模式。
|
|
1016
|
+
const dispatchSubCmd = filteredArgs[1];
|
|
1017
|
+
const dispatchSubs = ['probe', 'hint'];
|
|
1018
|
+
|
|
1019
|
+
if (!dispatchSubCmd || dispatchSubCmd === 'help' || dispatchSubCmd === '--help' || dispatchSubCmd === '-h') {
|
|
1020
|
+
console.log(`
|
|
1021
|
+
SillySpec dispatch — SillyHub 派发能力探测与策略生成(agent 调用桥,仅渲染与探测)
|
|
1022
|
+
|
|
1023
|
+
用法:
|
|
1024
|
+
sillyspec dispatch probe [--json] 能力探测,输出 ProbeResult
|
|
1025
|
+
sillyspec dispatch hint --contract <json> [--json] 派发策略,输出 {instruction, backend}
|
|
1026
|
+
|
|
1027
|
+
子命令:
|
|
1028
|
+
probe 探测 SillyHub MCP 是否可用(无配置返回 available=false,正常降级不报错)
|
|
1029
|
+
hint 据 contract + probe 结果生成派发指令文本(agent 拿指令后执行实际 tool 调用)
|
|
1030
|
+
|
|
1031
|
+
选项:
|
|
1032
|
+
--contract <json> hint 必填:DispatchContract(brief/worktreePath/branch/allowedPaths/readOnly/...)
|
|
1033
|
+
--json 程序化输出(probe → ProbeResult;hint → {backend, instruction})
|
|
1034
|
+
`);
|
|
1035
|
+
break;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
if (dispatchSubCmd === 'probe') {
|
|
1039
|
+
const { probeSillyHub } = await import('./dispatch/probe.js');
|
|
1040
|
+
const result = await probeSillyHub();
|
|
1041
|
+
// result = { available, reason? };无配置时 {available:false,reason:'no-config'} 不报错(正常降级)
|
|
1042
|
+
if (json) {
|
|
1043
|
+
process.stdout.write(JSON.stringify(result));
|
|
1044
|
+
} else if (result.available) {
|
|
1045
|
+
console.log('✅ SillyHub 可用');
|
|
1046
|
+
} else {
|
|
1047
|
+
console.log(`⚠️ SillyHub 不可用:${result.reason || 'unknown'}`);
|
|
1048
|
+
}
|
|
1049
|
+
break;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
if (dispatchSubCmd === 'hint') {
|
|
1053
|
+
// 解析 --contract <json>(仿 worktree 用 args.indexOf 取 flag 后一个值)
|
|
1054
|
+
const contractIdx = args.indexOf('--contract');
|
|
1055
|
+
const contractRaw = contractIdx >= 0 && args[contractIdx + 1] ? args[contractIdx + 1] : null;
|
|
1056
|
+
if (!contractRaw) {
|
|
1057
|
+
console.error('用法: sillyspec dispatch hint --contract <json> [--json]\n 生成派发指令(DispatchContract:brief/worktreePath/branch/allowedPaths/readOnly/...)');
|
|
1058
|
+
process.exit(2);
|
|
1059
|
+
}
|
|
1060
|
+
let contract;
|
|
1061
|
+
try {
|
|
1062
|
+
contract = JSON.parse(contractRaw);
|
|
1063
|
+
} catch (e) {
|
|
1064
|
+
console.error(`❌ --contract JSON 解析失败: ${e.message}`);
|
|
1065
|
+
process.exit(2);
|
|
1066
|
+
}
|
|
1067
|
+
const { probeSillyHub } = await import('./dispatch/probe.js');
|
|
1068
|
+
const { renderDispatchInstruction } = await import('./dispatch/strategy.js');
|
|
1069
|
+
const probe = await probeSillyHub();
|
|
1070
|
+
const { instruction, backend } = renderDispatchInstruction(contract, probe);
|
|
1071
|
+
if (json) {
|
|
1072
|
+
process.stdout.write(JSON.stringify({ backend, instruction }));
|
|
1073
|
+
} else {
|
|
1074
|
+
console.log(`Backend: ${backend}`);
|
|
1075
|
+
console.log(instruction);
|
|
1076
|
+
}
|
|
1077
|
+
break;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// 未知子命令 → did-you-mean 建议 + 退出 1(仿 worktree default)
|
|
1081
|
+
const sug = didYouMean(dispatchSubCmd, dispatchSubs);
|
|
1082
|
+
console.error(`❌ 未知子命令: dispatch ${dispatchSubCmd}`);
|
|
1083
|
+
if (sug) console.error(` 你是想输入「dispatch ${sug}」吗?`);
|
|
1084
|
+
console.error(` 可用子命令:${dispatchSubs.join(' | ')}(运行 sillyspec dispatch 查看用法)`);
|
|
1085
|
+
process.exit(1);
|
|
1086
|
+
break;
|
|
1087
|
+
}
|
|
902
1088
|
case 'platform': {
|
|
903
1089
|
const platformSub = filteredArgs[1];
|
|
904
1090
|
const platformArgs = filteredArgs.slice(2);
|
|
@@ -912,6 +1098,8 @@ SillySpec platform — SillyHub 平台同步
|
|
|
912
1098
|
sillyspec platform disconnect
|
|
913
1099
|
sillyspec platform sync [--change <name>]
|
|
914
1100
|
sillyspec platform sync-docs [--change <name>]
|
|
1101
|
+
sillyspec platform pull [--change <name>]
|
|
1102
|
+
sillyspec platform resolve <change-name> <--keep-local|--take-platform|--abort>
|
|
915
1103
|
sillyspec platform status
|
|
916
1104
|
sillyspec platform pointer [--cleanup]
|
|
917
1105
|
sillyspec platform approve <change-name>
|
|
@@ -1012,15 +1200,108 @@ SillySpec platform — SillyHub 平台同步
|
|
|
1012
1200
|
await syncModule.syncDocuments(syncDocsChangeName, dir);
|
|
1013
1201
|
break;
|
|
1014
1202
|
}
|
|
1015
|
-
case 'status':
|
|
1016
|
-
|
|
1203
|
+
case 'status': {
|
|
1204
|
+
// task-14 / FR-05:扩展 status(落后标记 + 未决冲突列表);未连接输出与现状一致
|
|
1205
|
+
const st = await syncModule.collectStatus(dir);
|
|
1206
|
+
if (!st.connected) {
|
|
1207
|
+
console.log('平台: 未连接');
|
|
1208
|
+
break;
|
|
1209
|
+
}
|
|
1210
|
+
console.log(`平台: ${st.url}`);
|
|
1211
|
+
console.log(`上次连接: ${st.lastSync || '未知'}`);
|
|
1212
|
+
if (st.listFailed) {
|
|
1213
|
+
console.log('ℹ️ 无法连接平台获取落后状态');
|
|
1214
|
+
} else if (st.behind.length > 0) {
|
|
1215
|
+
console.log(`⚠️ 本地可能落后(${st.behind.length} 个变更平台有更新):`);
|
|
1216
|
+
for (const b of st.behind) {
|
|
1217
|
+
console.log(` - ${b.name}: 平台 ${b.platform_pushed_at} > 本地 ${b.local_synced}`);
|
|
1218
|
+
}
|
|
1219
|
+
} else {
|
|
1220
|
+
console.log('✅ 本地与平台进度同步');
|
|
1221
|
+
}
|
|
1222
|
+
if (st.conflicts.length > 0) {
|
|
1223
|
+
console.log(`⚠️ 未决冲突(${st.conflicts.length} 个,请 platform resolve 处理):`);
|
|
1224
|
+
for (const c of st.conflicts) {
|
|
1225
|
+
console.log(` - ${c.change}${c.created_at ? ` (创建于 ${c.created_at})` : ''}`);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
break;
|
|
1229
|
+
}
|
|
1230
|
+
case 'pull': {
|
|
1231
|
+
// task-11 / D-006 / D-009 / FR-03:手动下行拉取平台进度到本地。
|
|
1232
|
+
// 与自动 triggerPull 共用 syncModule.pull(SyncManager.pull 实例方法),行为一致。
|
|
1233
|
+
const pullChangeIdx = args.indexOf('--change');
|
|
1234
|
+
const pullChangeName = pullChangeIdx >= 0 && args[pullChangeIdx + 1] ? args[pullChangeIdx + 1] : null;
|
|
1235
|
+
// 未连接明确提示不崩(acceptance:未连接平台输出明确提示)
|
|
1236
|
+
const smForCheck = new syncModule.SyncManager(dir);
|
|
1237
|
+
if (!smForCheck._getPlatform()) {
|
|
1238
|
+
console.error('❌ 未连接平台,请先 sillyspec platform connect');
|
|
1239
|
+
process.exit(1);
|
|
1240
|
+
}
|
|
1241
|
+
if (pullChangeName) {
|
|
1242
|
+
// 单变更完整 pull(两级 pull 第二级)
|
|
1243
|
+
const r = await syncModule.pull(pullChangeName, {}, dir);
|
|
1244
|
+
if (r.conflict) {
|
|
1245
|
+
console.log(`⚠️ ${pullChangeName}: 冲突(已写 sync-conflict 文件),请 sillyspec platform resolve 处理`);
|
|
1246
|
+
} else if (r.imported) {
|
|
1247
|
+
console.log(`✅ ${pullChangeName}: 已拉取并 import`);
|
|
1248
|
+
} else {
|
|
1249
|
+
console.log(`⏭️ ${pullChangeName}: 未 import(${r.reason || '无变更'})`);
|
|
1250
|
+
}
|
|
1251
|
+
} else {
|
|
1252
|
+
// 无 --change:先拉轻量列表(两级 pull 第一级),再对每个 change 按需完整 pull
|
|
1253
|
+
const list = await syncModule.pullList(dir);
|
|
1254
|
+
if (!list.ok) {
|
|
1255
|
+
console.error(`❌ 拉取变更列表失败: ${list.reason || '未知'}`);
|
|
1256
|
+
process.exit(1);
|
|
1257
|
+
}
|
|
1258
|
+
console.log(`平台变更 ${list.changes.length} 个:`);
|
|
1259
|
+
let importedCount = 0, conflictCount = 0;
|
|
1260
|
+
for (const ch of list.changes) {
|
|
1261
|
+
const name = typeof ch === 'string' ? ch : ch.name;
|
|
1262
|
+
if (!name) continue;
|
|
1263
|
+
const r = await syncModule.pull(name, {}, dir);
|
|
1264
|
+
if (r.imported) { importedCount++; console.log(` ✅ ${name}: 已 import`); }
|
|
1265
|
+
else if (r.conflict) { conflictCount++; console.log(` ⚠️ ${name}: 冲突,请 platform resolve`); }
|
|
1266
|
+
else { console.log(` ⏭️ ${name}: ${r.reason || '未 import'}`); }
|
|
1267
|
+
}
|
|
1268
|
+
console.log(`拉取完成:imported ${importedCount},冲突 ${conflictCount}`);
|
|
1269
|
+
}
|
|
1017
1270
|
break;
|
|
1271
|
+
}
|
|
1272
|
+
case 'resolve': {
|
|
1273
|
+
// task-13 / D-002 / D-010 / D-013 / FR-05:冲突解决三选一(绝不字段级 auto-merge)
|
|
1274
|
+
const resolveName = platformArgs[0];
|
|
1275
|
+
if (!resolveName) {
|
|
1276
|
+
console.error('❌ 用法: sillyspec platform resolve <change-name> <--keep-local|--take-platform|--abort>');
|
|
1277
|
+
process.exit(1);
|
|
1278
|
+
}
|
|
1279
|
+
// 解析 mode flag(三选一互斥,多传/不传均报错)
|
|
1280
|
+
const resolveFlags = ['--keep-local', '--take-platform', '--abort'].filter((f) => args.includes(f));
|
|
1281
|
+
if (resolveFlags.length !== 1) {
|
|
1282
|
+
console.error('❌ 必须恰好指定 --keep-local / --take-platform / --abort 之一');
|
|
1283
|
+
process.exit(1);
|
|
1284
|
+
}
|
|
1285
|
+
const modeMap = { '--keep-local': 'keep-local', '--take-platform': 'take-platform', '--abort': 'abort' };
|
|
1286
|
+
const resolveMode = modeMap[resolveFlags[0]];
|
|
1287
|
+
const r = await syncModule.resolve(resolveName, resolveMode, dir);
|
|
1288
|
+
if (r.ok && r.resolved) {
|
|
1289
|
+
console.log(`✅ ${resolveName} [${resolveMode}]: ${r.reason}`);
|
|
1290
|
+
} else {
|
|
1291
|
+
console.error(`❌ ${resolveName}: ${r.reason}`);
|
|
1292
|
+
process.exit(1);
|
|
1293
|
+
}
|
|
1294
|
+
break;
|
|
1295
|
+
}
|
|
1018
1296
|
case 'approve': {
|
|
1019
1297
|
const approveName = platformArgs[0];
|
|
1020
1298
|
if (!approveName) {
|
|
1021
1299
|
console.error('❌ 用法: sillyspec platform approve <change-name>');
|
|
1022
1300
|
process.exit(1);
|
|
1023
1301
|
}
|
|
1302
|
+
// 审批前下行 pull(task-10 / D-009 / FR-06):拉最新进度避免基于过期状态审批;未连接跳过
|
|
1303
|
+
const { triggerPull } = await import('./run/shared.js');
|
|
1304
|
+
await triggerPull(dir, approveName);
|
|
1024
1305
|
await syncModule.approve(approveName, dir);
|
|
1025
1306
|
break;
|
|
1026
1307
|
}
|
|
@@ -1058,7 +1339,7 @@ SillySpec platform — SillyHub 平台同步
|
|
|
1058
1339
|
process.exit(2);
|
|
1059
1340
|
}
|
|
1060
1341
|
const pm = new ProgressManager({ specDir: resolvePlatformSpecDir(dir, specDir) });
|
|
1061
|
-
|
|
1342
|
+
pm.renameChange(dir, oldName, newName);
|
|
1062
1343
|
break;
|
|
1063
1344
|
}
|
|
1064
1345
|
case 'workflow': {
|
|
@@ -1203,7 +1484,7 @@ SillySpec workflow — 工作流管理
|
|
|
1203
1484
|
SillySpec modules — 模块文档管理
|
|
1204
1485
|
|
|
1205
1486
|
用法:
|
|
1206
|
-
sillyspec modules rebuild
|
|
1487
|
+
sillyspec modules rebuild [--force] 从模块卡片 + 源码重建 _module-map.yaml(默认 dry-run 预览,--force 才覆盖)
|
|
1207
1488
|
sillyspec modules status 显示模块索引状态
|
|
1208
1489
|
sillyspec modules migrate 旧格式模块文档迁移到新格式
|
|
1209
1490
|
`);
|
|
@@ -1211,7 +1492,8 @@ SillySpec modules — 模块文档管理
|
|
|
1211
1492
|
}
|
|
1212
1493
|
if (modulesSub === 'rebuild') {
|
|
1213
1494
|
const { rebuildModuleMap } = await import('./modules.js');
|
|
1214
|
-
|
|
1495
|
+
const isForce = filteredArgs.includes('--force');
|
|
1496
|
+
await rebuildModuleMap(dir, { force: isForce });
|
|
1215
1497
|
} else if (modulesSub === 'status') {
|
|
1216
1498
|
const { showModuleStatus } = await import('./modules.js');
|
|
1217
1499
|
await showModuleStatus(dir);
|
|
@@ -1237,7 +1519,7 @@ SillySpec modules — 模块文档管理
|
|
|
1237
1519
|
process.exit(2);
|
|
1238
1520
|
}
|
|
1239
1521
|
|
|
1240
|
-
const detected = detectLocalYaml(dir);
|
|
1522
|
+
const detected = (await import('./local-detect.js')).detectLocalYaml(dir);
|
|
1241
1523
|
const specRoot = resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec');
|
|
1242
1524
|
const localYamlPath = join(specRoot, 'local.yaml');
|
|
1243
1525
|
|
|
@@ -1280,7 +1562,7 @@ SillySpec modules — 模块文档管理
|
|
|
1280
1562
|
}
|
|
1281
1563
|
case 'runtime': {
|
|
1282
1564
|
// D2:.runtime/ 产物索引。CLI 往 .runtime/ 写各种证据/状态文件(db、doctor-diagnosis、
|
|
1283
|
-
// workflow-runs、
|
|
1565
|
+
// workflow-runs、user-inputs…),却没有命令让 agent 一眼看到「我手上有哪些产物」。
|
|
1284
1566
|
// 多会话/压缩后尤其痛:上轮写的证据文件,下轮不知去哪找。纯只读枚举,不写盘。
|
|
1285
1567
|
const runtimeSubCmd = filteredArgs[1];
|
|
1286
1568
|
if (runtimeSubCmd && runtimeSubCmd !== 'list') {
|
|
@@ -1305,7 +1587,6 @@ SillySpec modules — 模块文档管理
|
|
|
1305
1587
|
'doctor-diagnosis.json': 'doctor --json 结构化诊断快照',
|
|
1306
1588
|
'doctor-dumps': 'doctor --dump-db 取证输出目录',
|
|
1307
1589
|
'workflow-runs': 'workflow check --save 归档目录',
|
|
1308
|
-
'gate-status.json': '门控结果缓存',
|
|
1309
1590
|
'scan-guard.json': 'scan 覆盖保护记录',
|
|
1310
1591
|
'quick-sessions': 'quick 会话记录目录',
|
|
1311
1592
|
'quick-guard.json': 'quick baseline 守卫(旧位置)',
|
|
@@ -1348,10 +1629,15 @@ SillySpec modules — 模块文档管理
|
|
|
1348
1629
|
break;
|
|
1349
1630
|
}
|
|
1350
1631
|
default: {
|
|
1351
|
-
const topCommands = ['init', 'setup', 'run', 'progress', 'worktree', 'local', 'workflow', 'gate', 'derive', 'backfill-reviews', 'modules', 'change-rename', 'knowledge', 'platform', 'scan', 'brainstorm', 'plan', 'execute', 'verify', 'archive', 'quick', 'explore', 'status', 'doctor', 'auto', 'runtime'];
|
|
1632
|
+
const topCommands = ['init', 'setup', 'run', 'progress', 'worktree', 'dispatch', 'local', 'workflow', 'gate', 'derive', 'backfill-reviews', 'register-stage-review', 'modules', 'change-rename', 'knowledge', 'platform', 'scan', 'brainstorm', 'plan', 'execute', 'verify', 'archive', 'quick', 'explore', 'status', 'doctor', 'auto', 'runtime'];
|
|
1352
1633
|
const suggestion = didYouMean(command, topCommands);
|
|
1353
1634
|
console.error(`❌ 未知命令: ${command}`);
|
|
1354
|
-
if (
|
|
1635
|
+
if (command === '--status') {
|
|
1636
|
+
// --status 是阶段内 flag(sillyspec run <stage> --status),裸用会落到 default
|
|
1637
|
+
console.error(' --status 是阶段内 flag。查看进度请用:sillyspec status 或 sillyspec progress show');
|
|
1638
|
+
} else if (suggestion) {
|
|
1639
|
+
console.error(` 你是想输入「${suggestion}」吗?`);
|
|
1640
|
+
}
|
|
1355
1641
|
printUsage();
|
|
1356
1642
|
process.exit(1);
|
|
1357
1643
|
}
|
|
@@ -1364,6 +1650,10 @@ main().catch((err) => {
|
|
|
1364
1650
|
console.error(`❌ ${err.message}`);
|
|
1365
1651
|
process.exit(1);
|
|
1366
1652
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1653
|
+
// message 提首行(Agent 可直接解析 stderr 首行判断成败),堆栈降级为附件;
|
|
1654
|
+
// 用 exitCode 而非 exit(1) 让事件循环自然 drain,缓解 Windows libuv teardown
|
|
1655
|
+
// 把退出码污染成 127(与 shell "command not found" 撞码)的问题。
|
|
1656
|
+
console.error(`❌ SillySpec 发生错误:${err?.message || err}`);
|
|
1657
|
+
if (err?.stack) console.error(err.stack);
|
|
1658
|
+
process.exitCode = 1;
|
|
1369
1659
|
});
|