sillyspec 3.28.6 → 3.28.8

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 (62) hide show
  1. package/.claude/skills/sillyspec-execute/SKILL.md +224 -222
  2. package/.claude/skills/sillyspec-plan/SKILL.md +156 -156
  3. package/.claude/skills/sillyspec-quick/SKILL.md +117 -116
  4. package/.husky/pre-push +3 -1
  5. package/.patch16-tmp.mjs +169 -0
  6. package/attachments/2ab2fd87cfa568ca6acadbb934a651d293a0ad8f3227c66a70b56dcf53a0ea3d.pptx +0 -0
  7. package/attachments/dc4c2b314ee0c295e1fe5243073eb5c7df7e429074ad7c14424523fbe86e6193.pptx +0 -0
  8. package/meta.json +11 -0
  9. package/package.json +1 -1
  10. package/src/agent-session-log.js +446 -35
  11. package/src/config-cat.js +6 -3
  12. package/src/config-schema.js +13 -0
  13. package/src/constants.js +3 -0
  14. package/src/db.js +12 -3
  15. package/src/decision-distill.js +6 -2
  16. package/src/dispatch/probe.js +8 -1
  17. package/src/docs-check.js +137 -12
  18. package/src/docs-gate.js +3 -0
  19. package/src/doctor-diagnostics.js +19 -7
  20. package/src/friction-tally.js +7 -2
  21. package/src/git-helper.js +6 -4
  22. package/src/hooks/worktree-guard.js +59 -29
  23. package/src/index.js +168 -64
  24. package/src/knowledge-match.js +90 -6
  25. package/src/local-detect.js +30 -15
  26. package/src/local-register.js +13 -4
  27. package/src/mcp-server.js +38 -8
  28. package/src/progress/change-registry.js +58 -26
  29. package/src/progress/consistency-doctor.js +11 -4
  30. package/src/progress/stage-machine.js +3 -1
  31. package/src/progress/step-store.js +30 -17
  32. package/src/progress.js +26 -0
  33. package/src/quicklog.js +44 -18
  34. package/src/review-dispatch.js +7 -1
  35. package/src/run/command.js +17 -3
  36. package/src/run/complete-handlers.js +40 -10
  37. package/src/run/complete.js +48 -17
  38. package/src/run/concurrent-detect.js +1 -1
  39. package/src/run/gate-snapshot.js +115 -5
  40. package/src/run/gates.js +35 -5
  41. package/src/run/prompt.js +61 -1
  42. package/src/run/quick-audit.js +80 -4
  43. package/src/run/shared.js +98 -10
  44. package/src/run/verify-quality-scan.js +14 -3
  45. package/src/scope-audit.js +89 -14
  46. package/src/semantic-guard.js +207 -0
  47. package/src/sillyhub-mcp/client.js +14 -0
  48. package/src/spec-sync.js +19 -14
  49. package/src/stage-contract-spec.js +4 -3
  50. package/src/stage-review.js +6 -5
  51. package/src/stages/brainstorm.js +1 -1
  52. package/src/stages/execute.js +6 -1
  53. package/src/stages/plan-postcheck.js +57 -12
  54. package/src/sync.js +66 -4
  55. package/src/task-review.js +41 -6
  56. package/src/verify-facts-schema.js +10 -3
  57. package/src/verify-postcheck.js +21 -6
  58. package/src/verify-probes.js +8 -3
  59. package/src/worktree-apply.js +56 -18
  60. package/src/worktree-cross.js +12 -18
  61. package/src/worktree.js +138 -25
  62. package/src/wt-commit.js +128 -0
package/src/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * 只负责两件事:init(安装命令模板)和 setup(安装 MCP 工具)。
7
7
  * 状态管理通过 sillyspec.db(SQLite)完成,使用 `sillyspec progress` 命令。
8
8
  */
9
- import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'fs';
9
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync, unlinkSync } from 'fs';
10
10
  import { writeAtomicSync } from './fs-atomic.js';
11
11
  import { basename, dirname, extname, join, resolve, isAbsolute, sep, relative } from 'path';
12
12
  import { safeGit, git } from './git-helper.js';
@@ -198,7 +198,7 @@ async function main() {
198
198
 
199
199
  // E22:重路径统一加载(轻路径 --version/help 已早退,未付此税)。
200
200
  const { ProgressManager, resolvePlatformSpecDir, resolvePlatformOpts } = await import('./progress.js');
201
- const { didYouMean, assertSafeChangeName, assertDatedChangeName, resolveSpecDir, detectWorktreeSpecDrift } = await import('./run/shared.js');
201
+ const { didYouMean, assertSafeChangeName, assertDatedChangeName, resolveSpecDir, detectWorktreeSpecDrift, detectCwdInsideWorktree } = await import('./run/shared.js');
202
202
 
203
203
  // 解析全局选项
204
204
  let json = false;
@@ -221,13 +221,13 @@ async function main() {
221
221
  json = true;
222
222
  } else if (args[i] === '--save') {
223
223
  saveWorkflowRunFlag = true;
224
- } else if (args[i] === '--dir' && args[i + 1]) {
224
+ } else if (args[i] === '--dir' && args[i + 1] && !args[i + 1].startsWith('--')) {
225
225
  targetDir = resolve(args[i + 1]);
226
226
  i++;
227
- } else if (args[i] === '--spec-dir' && args[i + 1]) {
227
+ } else if (args[i] === '--spec-dir' && args[i + 1] && !args[i + 1].startsWith('--')) {
228
228
  specDir = resolve(args[i + 1]);
229
229
  i++;
230
- } else if (args[i] === '--tool' && args[i + 1]) {
230
+ } else if (args[i] === '--tool' && args[i + 1] && !args[i + 1].startsWith('--')) {
231
231
  // 多值支持:逗号分隔(--tool claude,codex)与重复 flag(--tool a --tool b)都收集进数组
232
232
  for (const v of args[i + 1].split(',')) {
233
233
  const t = v.trim();
@@ -237,18 +237,21 @@ async function main() {
237
237
  } else if (args[i] === '--no-skills') {
238
238
  // init 专属:跳过 skills 复制段(吞进变量,不透传 filteredArgs)
239
239
  noSkills = true;
240
- } else if (args[i] === '--workspace-id' && args[i + 1]) {
240
+ } else if (args[i] === '--workspace-id' && args[i + 1] && !args[i + 1].startsWith('--')) {
241
241
  // 平台模式专属 flag(init 落平台指针用;--runtime-root/--scan-run-id 保持在
242
242
  // filteredArgs 由 runCommand 解析,init 侧只需要 workspaceId 作为平台信号 + runtimeRoot)
243
243
  platformWorkspaceId = args[i + 1];
244
244
  filteredArgs.push(args[i], args[i + 1]);
245
245
  i++;
246
- } else if (args[i] === '--runtime-root' && args[i + 1]) {
246
+ } else if (args[i] === '--runtime-root' && args[i + 1] && !args[i + 1].startsWith('--')) {
247
247
  platformRuntimeRoot = resolve(args[i + 1]);
248
248
  filteredArgs.push(args[i], args[i + 1]);
249
249
  i++;
250
250
  } else if (args[i] === '--interactive' || args[i] === '-i') {
251
251
  interactive = true;
252
+ } else if (args[i] === '--allow-worktree-cwd') {
253
+ // worktree cwd 硬拦逃生门:默认阻止在 .sillyspec/.runtime/worktrees/* 内跑 CLI,
254
+ // 此 flag 显式放行(坑 worktree-cwd-silent-split)
252
255
  } else if (args[i] === '--list' || args[i] === '-l') {
253
256
  filteredArgs.push('--list');
254
257
  } else {
@@ -311,6 +314,22 @@ async function main() {
311
314
  process.exit(1);
312
315
  }
313
316
 
317
+ // ── worktree cwd 硬拦(坑 worktree-cwd-silent-split,2026-08-29 用户实证)──
318
+ // Bash cwd 残留让 agent 两次误在 worktree 内跑 sillyspec:status 类完全静默、进度写进
319
+ // worktree 副本 .sillyspec 与主仓进度库分裂。守卫在命令分发前拦截,--dir 显式指向主仓
320
+ // 或 --allow-worktree-cwd 逃生门放行;wt-commit 命令豁免(子代理 workdir=worktree 内合法)。
321
+ if (!args.includes('--allow-worktree-cwd') && command !== 'wt-commit') {
322
+ const guardDir = args.includes('--dir') ? dir : process.cwd();
323
+ const wtInfo = detectCwdInsideWorktree(guardDir);
324
+ if (wtInfo) {
325
+ console.error(`\n❌ 当前在隔离 worktree 内(${wtInfo.changeName}):${guardDir}`);
326
+ console.error(` 主仓根:${wtInfo.mainRepoRoot}`);
327
+ console.error(' SillySpec 进度库只存在于主仓,worktree 内跑会产生分裂进度(坑 worktree-cwd-silent-split)。');
328
+ console.error(' 修复:cd 到主仓根后重跑,或用 --dir <主仓根>,或用 --allow-worktree-cwd 强制放行。');
329
+ process.exit(2);
330
+ }
331
+ }
332
+
314
333
  switch (command) {
315
334
  case 'init':
316
335
  await (await import('./init.js')).cmdInit(dir, { tool, tools: toolValues.length > 0 ? toolValues : null, interactive, specDir, noSkills, platformOpts: (platformWorkspaceId || platformRuntimeRoot) ? { workspaceId: platformWorkspaceId, runtimeRoot: platformRuntimeRoot } : null });
@@ -324,10 +343,10 @@ async function main() {
324
343
  const progDir = specDir ? dir : resolveEffectiveDir(dir);
325
344
  const subCommand = filteredArgs[1];
326
345
  const stageIdx = filteredArgs.indexOf('--stage');
327
- const stage = stageIdx >= 0 && filteredArgs[stageIdx + 1] ? filteredArgs[stageIdx + 1] : null;
346
+ const stage = stageIdx >= 0 && filteredArgs[stageIdx + 1] && !String(filteredArgs[stageIdx + 1]).startsWith("--") ? filteredArgs[stageIdx + 1] : null;
328
347
  // 解析 --change 参数
329
348
  const progChangeIdx = args.indexOf('--change');
330
- const progChangeName = progChangeIdx >= 0 && args[progChangeIdx + 1] ? args[progChangeIdx + 1] : null;
349
+ const progChangeName = progChangeIdx >= 0 && args[progChangeIdx + 1] && !String(args[progChangeIdx + 1]).startsWith("--") ? args[progChangeIdx + 1] : null;
331
350
  // 与 run 入口同源消毒(防路径穿越;progress 下游 completeStage 写 history 文件名拼 change 名)
332
351
  if (progChangeName) assertSafeChangeName(progChangeName, '--change 变更名');
333
352
 
@@ -469,7 +488,7 @@ async function main() {
469
488
  // 只读查询,不依赖 worktree、不写状态(D-002@v1)。
470
489
  const gateStage = filteredArgs[1];
471
490
  const gateChangeIdx = args.indexOf('--change');
472
- const gateChange = gateChangeIdx >= 0 && args[gateChangeIdx + 1] ? args[gateChangeIdx + 1] : null;
491
+ const gateChange = gateChangeIdx >= 0 && args[gateChangeIdx + 1] && !String(args[gateChangeIdx + 1]).startsWith("--") ? args[gateChangeIdx + 1] : null;
473
492
  if (!gateStage || gateStage.startsWith('-') || !gateChange) {
474
493
  console.error('用法: sillyspec gate <stage> --change <name> [--json]\n stage: brainstorm | plan | execute | verify | archive | ...');
475
494
  process.exit(2);
@@ -529,7 +548,7 @@ async function main() {
529
548
  // 单项事实核验(machine-interface v1):查询变更某一 facet 的真实状态,返回结构化 data。
530
549
  const facet = filteredArgs[1];
531
550
  const deriveChangeIdx = args.indexOf('--change');
532
- const deriveChange = deriveChangeIdx >= 0 && args[deriveChangeIdx + 1] ? args[deriveChangeIdx + 1] : null;
551
+ const deriveChange = deriveChangeIdx >= 0 && args[deriveChangeIdx + 1] && !String(args[deriveChangeIdx + 1]).startsWith("--") ? args[deriveChangeIdx + 1] : null;
533
552
  if (!facet || facet.startsWith('-') || !deriveChange) {
534
553
  console.error('用法: sillyspec derive <facet> --change <name> [--json]\n facet: execute-evidence | verify-test | task-reviews | artifacts');
535
554
  process.exit(2);
@@ -587,7 +606,7 @@ async function main() {
587
606
  }
588
607
  const rwVal = (flag) => {
589
608
  const i = filteredArgs.indexOf(flag);
590
- return i >= 0 && filteredArgs[i + 1] ? filteredArgs[i + 1] : null;
609
+ return i >= 0 && filteredArgs[i + 1] && !String(filteredArgs[i + 1]).startsWith("--") ? filteredArgs[i + 1] : null;
591
610
  };
592
611
  const rwChange = rwVal('--change');
593
612
  const rwTask = rwVal('--task');
@@ -645,6 +664,63 @@ async function main() {
645
664
  }
646
665
  break;
647
666
  }
667
+ case 'task': {
668
+ // task 级进行中状态标记(2026-09-12 驾驭第十四批②,用户实证「中断续跑时半成品无主,
669
+ // 主代理接管审查负担重」):开工/完工各一条命令落 .runtime/task-progress/<change>/<task-NN>.json
670
+ // (startedAt + note),list 带陈旧标红(>2h 视为中断遗留——续跑时主代理据此点名接管审查,
671
+ // 不再靠翻 git 工作区猜半成品)。轻量 marker:不进 DB、不设门禁(状态可见性工具,非流程卡点)。
672
+ const taskSub = filteredArgs[1];
673
+ if (!taskSub || ['help', '--help', '-h'].includes(taskSub)) {
674
+ console.log(`用法:
675
+ sillyspec task start --change <名> --task task-NN [--note "一句话在做什么"]
676
+ sillyspec task finish --change <名> --task task-NN
677
+ sillyspec task list --change <名>
678
+ task 进行中状态标记:开工/完工落 .runtime/task-progress/,list 标红 >2h 中断遗留(接管审查点名用)`);
679
+ break;
680
+ }
681
+ const tChangeIdx = filteredArgs.indexOf('--change');
682
+ const tChange = tChangeIdx >= 0 && filteredArgs[tChangeIdx + 1] && !String(filteredArgs[tChangeIdx + 1]).startsWith("--") ? filteredArgs[tChangeIdx + 1] : null;
683
+ const tTaskIdx = filteredArgs.indexOf('--task');
684
+ const tTask = tTaskIdx >= 0 && filteredArgs[tTaskIdx + 1] && !String(filteredArgs[tTaskIdx + 1]).startsWith("--") ? filteredArgs[tTaskIdx + 1] : null;
685
+ const tNoteIdx = filteredArgs.indexOf('--note');
686
+ const tNote = tNoteIdx >= 0 && filteredArgs[tNoteIdx + 1] && !String(filteredArgs[tNoteIdx + 1]).startsWith("--") ? filteredArgs[tNoteIdx + 1] : null;
687
+ if (!tChange) { console.error('❌ 缺 --change <变更名>'); process.exit(2); }
688
+ if (taskSub !== 'list') {
689
+ if (!tTask || !/^task-\d+$/.test(tTask)) { console.error('❌ --task 格式应为 task-NN'); process.exit(2); }
690
+ }
691
+ const tSpecBase = resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec');
692
+ const tRuntime = join(tSpecBase, '.runtime');
693
+ const tDir = join(tRuntime, 'task-progress', tChange);
694
+ if (taskSub === 'start') {
695
+ mkdirSync(tDir, { recursive: true });
696
+ writeFileSync(join(tDir, `${tTask}.json`), JSON.stringify({
697
+ task: tTask, change: tChange, note: tNote || '', startedAt: new Date().toISOString(),
698
+ }, null, 2) + '\n');
699
+ console.log(`📌 ${tTask} 已标记进行中${tNote ? '(' + tNote + ')' : ''}——完工时 sillyspec task finish --change ${tChange} --task ${tTask}`);
700
+ } else if (taskSub === 'finish') {
701
+ try { unlinkSync(join(tDir, `${tTask}.json`)); console.log(`✅ ${tTask} 进行中标记已清除(完工)`); }
702
+ catch { console.log(`ℹ️ ${tTask} 无进行中标记(已完工或从未 start)`); }
703
+ } else if (taskSub === 'list') {
704
+ if (!existsSync(tDir)) { console.log(`ℹ️ 无 ${tChange} 的进行中 task 标记`); break; }
705
+ const entries = readdirSync(tDir).filter(f => f.endsWith('.json'));
706
+ if (entries.length === 0) { console.log(`ℹ️ 无进行中标记(全部完工)`); break; }
707
+ console.log(`📋 ${tChange} 进行中 task(${entries.length} 个)——中断遗留接管审查点名用:`);
708
+ let stale = 0;
709
+ for (const f of entries.sort()) {
710
+ try {
711
+ const m = JSON.parse(readFileSync(join(tDir, f), 'utf8'));
712
+ const ageMin = Math.round((Date.now() - new Date(m.startedAt).getTime()) / 60000);
713
+ const isStale = ageMin > 120;
714
+ if (isStale) stale++;
715
+ console.log(` ${isStale ? '🔴' : '🟢'} ${m.task}: 开始于 ${m.startedAt}(${ageMin >= 60 ? Math.floor(ageMin / 60) + 'h' + ageMin % 60 + 'm' : ageMin + 'm'} 前)${m.note ? '——' + m.note : ''}${isStale ? ' ⚠️ 超过 2h,疑似中断遗留(接管者:对照 task 卡 + git 工作区半成品审查)' : ''}`);
716
+ } catch { console.log(` ⚠️ ${f} 标记损坏(忽略)`); }
717
+ }
718
+ if (stale > 0) console.log(` 共 ${stale} 个疑似中断遗留——主代理接管时逐一审查(半成品无主的痛点出口)`);
719
+ } else {
720
+ console.error(`❌ 未知子命令「${taskSub}」(start | finish | list)`); process.exit(2);
721
+ }
722
+ break;
723
+ }
648
724
  case 'backfill-reviews': {
649
725
  // 手动补 task(reopen execute / 直接实现)缺 review.json 的官方入口(坑 verify-archive-flow-pitfalls 坑2)。
650
726
  // 复用 generateTaskReviewDrafts(execute --done 同源兜底,幂等、fail-open):据 git diff base..head + working-tree
@@ -657,7 +733,7 @@ async function main() {
657
733
  // verdict——手算 hash、手抄 diff 清单是纯错误面,gate 校验的恰是这些字段。与草稿互补:
658
734
  // 草稿补「缺失」,adopt 修「已存在但 mechanics 错/缺」,verdict 原样保留。
659
735
  const brChangeIdx = args.indexOf('--change');
660
- const brChange = brChangeIdx >= 0 && args[brChangeIdx + 1] ? args[brChangeIdx + 1] : null;
736
+ const brChange = brChangeIdx >= 0 && args[brChangeIdx + 1] && !String(args[brChangeIdx + 1]).startsWith("--") ? args[brChangeIdx + 1] : null;
661
737
  const brAdopt = args.includes('--adopt');
662
738
  if (!brChange) {
663
739
  console.error('用法: sillyspec backfill-reviews --change <name> [--adopt] [--json] [--spec-dir <path>]\n 缺失 review.json → 生成 cannot_verify 草稿(解 archive 客观完成度阻断)\n --adopt → 已存在 review.json 的 base/head/changedFiles/diffPaths 等 mechanics 字段一键重算代填(verdict 保留;统一 commit 模式 diffPaths=task 卡 allowed_paths)');
@@ -739,7 +815,7 @@ async function main() {
739
815
  const wsArgs = filteredArgs.slice(2);
740
816
  const wsFlag = (name) => {
741
817
  const i = wsArgs.indexOf(name);
742
- return i >= 0 && wsArgs[i + 1] ? wsArgs[i + 1] : null;
818
+ return i >= 0 && wsArgs[i + 1] && !String(wsArgs[i + 1]).startsWith("--") ? wsArgs[i + 1] : null;
743
819
  };
744
820
  const { workspaceAdd, workspaceRemove, workspaceStatus } = await import('./workspace.js');
745
821
  if (wsSub === 'add') {
@@ -838,7 +914,9 @@ async function main() {
838
914
  const childArgs = m[1].trim().split(/\s+/);
839
915
  console.log(`🤖 next --apply 代跑:sillyspec ${childArgs.join(' ')}\n`);
840
916
  const { spawnSync } = await import('node:child_process');
841
- const r = spawnSync(process.execPath, [join(__dirname, '..', 'bin', 'sillyspec.js'), '--dir', dir, ...childArgs], { stdio: 'inherit' });
917
+ const { fileURLToPath } = await import('node:url');
918
+ // ESM 无 __dirname(ql-20260911-029:此处曾直接引用,--apply 常态路径必 ReferenceError)
919
+ const r = spawnSync(process.execPath, [fileURLToPath(new URL('../bin/sillyspec.js', import.meta.url)), '--dir', dir, ...childArgs], { stdio: 'inherit' });
842
920
  process.exit(r.status === null ? 1 : r.status);
843
921
  }
844
922
  if (json) {
@@ -868,7 +946,7 @@ async function main() {
868
946
  // 通用自动 git 在多会话仓是历史重灾区,故收窄到这一处。
869
947
  if (args.includes('--apply')) {
870
948
  const apChangeIdx = args.indexOf('--change');
871
- const apChange = apChangeIdx >= 0 && args[apChangeIdx + 1] ? args[apChangeIdx + 1] : null;
949
+ const apChange = apChangeIdx >= 0 && args[apChangeIdx + 1] && !String(args[apChangeIdx + 1]).startsWith("--") ? args[apChangeIdx + 1] : null;
872
950
  if (!apChange) {
873
951
  console.error('用法: sillyspec commit --apply --change <名>\n 只限归档语境(changes.status=archived 或 current_stage=archive)执行归档产物提交');
874
952
  process.exit(2);
@@ -947,7 +1025,7 @@ async function main() {
947
1025
  // --init 顺带生成 verify-result.md 七章节骨架(探针结果预填;结论留「待填」——gate 找
948
1026
  // 不到 PASS/FAIL 即判不过,骨架不能直接过门)。已存在不覆盖。
949
1027
  const vpChangeIdx = args.indexOf('--change');
950
- const vpChange = vpChangeIdx >= 0 && args[vpChangeIdx + 1] ? args[vpChangeIdx + 1] : null;
1028
+ const vpChange = vpChangeIdx >= 0 && args[vpChangeIdx + 1] && !String(args[vpChangeIdx + 1]).startsWith("--") ? args[vpChangeIdx + 1] : null;
951
1029
  const vpInit = args.includes('--init');
952
1030
  if (!vpChange) {
953
1031
  console.error('用法: sillyspec verify-probes --change <name> [--init] [--json] [--spec-dir <path>]\n 跑机械探针(TODO 标记/测试覆盖/API 对账/删除对账)输出 markdown;--init 生成 verify-result.md 骨架(探针预填,已存在不覆盖)');
@@ -1071,9 +1149,9 @@ async function main() {
1071
1149
  // stage-reviews 路径)、--kill=清在途记录+平台处置指引(处置权在人)。薄壳:全部逻辑在
1072
1150
  // src/review-dispatch.js(可测核心),此处只做 flag 解析与退出码。
1073
1151
  const rdChangeIdx = args.indexOf('--change');
1074
- const rdChange = rdChangeIdx >= 0 && args[rdChangeIdx + 1] ? args[rdChangeIdx + 1] : null;
1152
+ const rdChange = rdChangeIdx >= 0 && args[rdChangeIdx + 1] && !String(args[rdChangeIdx + 1]).startsWith("--") ? args[rdChangeIdx + 1] : null;
1075
1153
  const rdStageIdx = args.indexOf('--stage');
1076
- const rdStage = rdStageIdx >= 0 && args[rdStageIdx + 1] ? args[rdStageIdx + 1] : null;
1154
+ const rdStage = rdStageIdx >= 0 && args[rdStageIdx + 1] && !String(args[rdStageIdx + 1]).startsWith("--") ? args[rdStageIdx + 1] : null;
1077
1155
  const rdStatus = args.includes('--status');
1078
1156
  const rdKill = args.includes('--kill');
1079
1157
  const rdMode = rdKill ? 'kill' : rdStatus ? 'status' : 'create';
@@ -1124,9 +1202,9 @@ async function main() {
1124
1202
  // 模块文档 sidecar 同步命令化(2026-09-09 刀批):diff 归属模块 → sidecar 追加变更索引行 +
1125
1203
  // 卡 updated_at 戳——机械部分 CLI 代算,卡片正文语义更新仍归 agent/归档流程。幂等。
1126
1204
  const mdsChangeIdx = args.indexOf('--change');
1127
- const mdsChange = mdsChangeIdx >= 0 && args[mdsChangeIdx + 1] ? args[mdsChangeIdx + 1] : null;
1205
+ const mdsChange = mdsChangeIdx >= 0 && args[mdsChangeIdx + 1] && !String(args[mdsChangeIdx + 1]).startsWith("--") ? args[mdsChangeIdx + 1] : null;
1128
1206
  const mdsNoteIdx = args.indexOf('--note');
1129
- const mdsNote = mdsNoteIdx >= 0 && args[mdsNoteIdx + 1] ? args[mdsNoteIdx + 1] : null;
1207
+ const mdsNote = mdsNoteIdx >= 0 && args[mdsNoteIdx + 1] && !String(args[mdsNoteIdx + 1]).startsWith("--") ? args[mdsNoteIdx + 1] : null;
1130
1208
  if (!mdsChange) {
1131
1209
  console.error('用法: sillyspec module-docs-sync --change <name> [--note "一句话"] [--json]\n diff 归属模块 → sidecar 追加变更索引行 + 卡 updated_at 戳(幂等;卡片正文语义更新仍归 agent)');
1132
1210
  process.exit(2);
@@ -1151,7 +1229,7 @@ async function main() {
1151
1229
  // 手排 Wave 与拓扑比对不一致需手改;依赖方向硬拦(plan-postcheck)落地后给一条零手写出路。
1152
1230
  // depMap 与 postcheck 同源(collectTaskDepMap)→ topoSortWaves → 重写 Wave 段 + 同步任务总表 W 列。
1153
1231
  const pawChangeIdx = args.indexOf('--change');
1154
- const pawChange = pawChangeIdx >= 0 && args[pawChangeIdx + 1] ? args[pawChangeIdx + 1] : null;
1232
+ const pawChange = pawChangeIdx >= 0 && args[pawChangeIdx + 1] && !String(args[pawChangeIdx + 1]).startsWith("--") ? args[pawChangeIdx + 1] : null;
1155
1233
  const pawDryRun = args.includes('--dry-run');
1156
1234
  if (!pawChange) {
1157
1235
  console.error('用法: sillyspec plan-adopt-waves --change <name> [--dry-run] [--json] [--spec-dir <path>]\n 把 plan.md 的 Wave 段一键重排为 depends_on 拓扑分组(同步任务总表 W 列;--dry-run 只看不写)');
@@ -1196,7 +1274,7 @@ async function main() {
1196
1274
  // archive 模块影响矩阵骨架(2026-08-21 审计第三批 D3):文件×模块归属按 _module-map.yaml
1197
1275
  // paths 前缀匹配预填(机械),影响类型/review 标记留 <!--TODO-->(语义)。已存在不覆盖。
1198
1276
  const miChangeIdx = args.indexOf('--change');
1199
- const miChange = miChangeIdx >= 0 && args[miChangeIdx + 1] ? args[miChangeIdx + 1] : null;
1277
+ const miChange = miChangeIdx >= 0 && args[miChangeIdx + 1] && !String(args[miChangeIdx + 1]).startsWith("--") ? args[miChangeIdx + 1] : null;
1200
1278
  if (!miChange) {
1201
1279
  console.error('用法: sillyspec module-impact --change <name> [--json] [--spec-dir <path>]\n 生成 module-impact.md 骨架(模块影响矩阵按 module-map 预填 + 未匹配文件清单;已存在不覆盖)');
1202
1280
  process.exit(2);
@@ -1232,7 +1310,7 @@ async function main() {
1232
1310
  // 用法错 exit 2、assertSafeChangeName 消毒、动态 import、--json 结构化输出、运行错
1233
1311
  // fail-soft 出错误摘要 exit 1 不抛栈。
1234
1312
  const saChangeIdx = args.indexOf('--change');
1235
- const saChange = saChangeIdx >= 0 && args[saChangeIdx + 1] ? args[saChangeIdx + 1] : null;
1313
+ const saChange = saChangeIdx >= 0 && args[saChangeIdx + 1] && !String(args[saChangeIdx + 1]).startsWith("--") ? args[saChangeIdx + 1] : null;
1236
1314
  if (!saChange) {
1237
1315
  console.error('用法: sillyspec scope-audit --change <name> [--file <path>] [--json] [--spec-dir <path>]\n 变更范围对账:计划×实际三态全表 + 行数(quick 会话传 quick-<8hex> 出归属表;已归档变更可查——快照冻结记录态);--file <path> 看单文件变化内容(对账同源锚点的 git diff);advisory 只读展示,不构成门禁');
1238
1316
  process.exit(2);
@@ -1329,7 +1407,7 @@ async function main() {
1329
1407
  // 端点增删的 before 侧;扫描/落盘在 endpoint-baseline.js 纯函数,此处只做参数解析 +
1330
1408
  // worktree 主仓锚定 + 输出。
1331
1409
  const ebChangeIdx = args.indexOf('--change');
1332
- const ebChange = ebChangeIdx >= 0 && args[ebChangeIdx + 1] ? args[ebChangeIdx + 1] : null;
1410
+ const ebChange = ebChangeIdx >= 0 && args[ebChangeIdx + 1] && !String(args[ebChangeIdx + 1]).startsWith("--") ? args[ebChangeIdx + 1] : null;
1333
1411
  if (!ebChange) {
1334
1412
  console.error('用法: sillyspec endpoints baseline --change <name> [--spec-dir <path>] [--json]\n 拍变更前端点基线(全仓现扫,幂等已拍过不覆盖)→ .runtime/endpoint-baselines/<change>.json;归档 delta 端点增删的 before 侧。\n execute worktree 内跑:扫描根与落点自动锚定主仓(变更前代码在主仓;worktree 内是交付态且其 .runtime 随清理丢失)');
1335
1413
  process.exit(2);
@@ -1397,14 +1475,14 @@ async function main() {
1397
1475
  process.exit(2);
1398
1476
  }
1399
1477
  const epChangeIdx = args.indexOf('--change');
1400
- const epChange = epChangeIdx >= 0 && args[epChangeIdx + 1] ? args[epChangeIdx + 1] : null;
1478
+ const epChange = epChangeIdx >= 0 && args[epChangeIdx + 1] && !String(args[epChangeIdx + 1]).startsWith("--") ? args[epChangeIdx + 1] : null;
1401
1479
  const epTaskIdx = args.indexOf('--task');
1402
- const epTask = epTaskIdx >= 0 && args[epTaskIdx + 1] ? args[epTaskIdx + 1] : null;
1480
+ const epTask = epTaskIdx >= 0 && args[epTaskIdx + 1] && !String(args[epTaskIdx + 1]).startsWith("--") ? args[epTaskIdx + 1] : null;
1403
1481
  const epAllTasks = args.includes('--all-tasks');
1404
1482
  const epDirIdx = args.indexOf('--dir');
1405
- const epDir = epDirIdx >= 0 && args[epDirIdx + 1] ? args[epDirIdx + 1] : null;
1483
+ const epDir = epDirIdx >= 0 && args[epDirIdx + 1] && !String(args[epDirIdx + 1]).startsWith("--") ? args[epDirIdx + 1] : null;
1406
1484
  const epFilesIdx = args.indexOf('--files');
1407
- const epFilesRaw = epFilesIdx >= 0 && args[epFilesIdx + 1] ? args[epFilesIdx + 1] : null;
1485
+ const epFilesRaw = epFilesIdx >= 0 && args[epFilesIdx + 1] && !String(args[epFilesIdx + 1]).startsWith("--") ? args[epFilesIdx + 1] : null;
1408
1486
  if (!epChange) {
1409
1487
  console.error('❌ 缺 --change <变更名>');
1410
1488
  process.exit(2);
@@ -1552,7 +1630,7 @@ async function main() {
1552
1630
  // author/created_at 只 warning,agent 手改还常忘。本命令补齐(git user + 当前时间),
1553
1631
  // 已有两键的文件不动;有 frontmatter 就地插入,无则补一段。
1554
1632
  const sfhProjectIdx = args.indexOf('--project');
1555
- const sfhProject = sfhProjectIdx >= 0 && args[sfhProjectIdx + 1] ? args[sfhProjectIdx + 1] : null;
1633
+ const sfhProject = sfhProjectIdx >= 0 && args[sfhProjectIdx + 1] && !String(args[sfhProjectIdx + 1]).startsWith("--") ? args[sfhProjectIdx + 1] : null;
1556
1634
  const { fixScanDocHeaders } = await import('./scan-postcheck.js');
1557
1635
  const sfhResult = fixScanDocHeaders({ cwd: dir, specDir, project: sfhProject });
1558
1636
  if (json) {
@@ -1572,7 +1650,7 @@ async function main() {
1572
1650
  // plan.md)注册表生成逐 task <!--TODO--> 骨架;gate 拒绝未替换的占位(防骨架直接过门),
1573
1651
  // agent 只需逐行填结论。已存在不覆盖(幂等,agent 产出优先)。
1574
1652
  const siChangeIdx = args.indexOf('--change');
1575
- const siChange = siChangeIdx >= 0 && args[siChangeIdx + 1] ? args[siChangeIdx + 1] : null;
1653
+ const siChange = siChangeIdx >= 0 && args[siChangeIdx + 1] && !String(args[siChangeIdx + 1]).startsWith("--") ? args[siChangeIdx + 1] : null;
1576
1654
  if (!siChange) {
1577
1655
  console.error('用法: sillyspec symbol-impact --change <name> [--spec-dir <path>]\n 生成 symbol-impact.md 逐 task <!--TODO--> 骨架(已存在不覆盖);gate 拒绝未替换的占位行');
1578
1656
  process.exit(2);
@@ -1608,7 +1686,7 @@ async function main() {
1608
1686
  // 手写 proposal/requirements/decisions 的 frontmatter/章节骨架是格式返工根源——本命令
1609
1687
  // 逐文件生成骨架(已存在不覆盖),agent 只填语义。与 design-init 同族(幂等)。
1610
1688
  const fpChangeIdx = args.indexOf('--change');
1611
- const fpChange = fpChangeIdx >= 0 && args[fpChangeIdx + 1] ? args[fpChangeIdx + 1] : null;
1689
+ const fpChange = fpChangeIdx >= 0 && args[fpChangeIdx + 1] && !String(args[fpChangeIdx + 1]).startsWith("--") ? args[fpChangeIdx + 1] : null;
1612
1690
  if (!fpChange) {
1613
1691
  console.error('用法: sillyspec fourpiece-init --change <name> [--json] [--spec-dir <path>]\n 生成 proposal.md/requirements.md/decisions.md 骨架(frontmatter+章节标题+模板占位;已存在不覆盖;design.md 归 design-init)');
1614
1692
  process.exit(2);
@@ -1701,7 +1779,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
1701
1779
  // design-facts.js generateDesignSkeleton——本 case 只负责读盘/幂等保护/落盘)。已存在不覆盖
1702
1780
  // (幂等,agent 产出优先),--force 覆盖重生成。
1703
1781
  const diChangeIdx = args.indexOf('--change');
1704
- const diChange = diChangeIdx >= 0 && args[diChangeIdx + 1] ? args[diChangeIdx + 1] : null;
1782
+ const diChange = diChangeIdx >= 0 && args[diChangeIdx + 1] && !String(args[diChangeIdx + 1]).startsWith("--") ? args[diChangeIdx + 1] : null;
1705
1783
  const diForce = args.includes('--force');
1706
1784
  if (!diChange) {
1707
1785
  console.error('用法: sillyspec design-init --change <name> [--force] [--json] [--spec-dir <path>]\n 从变更 decisions.md 生成 design.md 十三章节骨架(决策追踪表按当前版本 D 条目预填;已存在不覆盖,--force 覆盖)');
@@ -1765,7 +1843,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
1765
1843
  // (重跑即刷新):与 design-init「agent 产出优先不覆盖」语义相反——delta 是源数据
1766
1844
  // (verify-runs/module-map)时点快照,旧报告天然过期,重新生成即对齐。
1767
1845
  const dlChangeIdx = args.indexOf('--change');
1768
- const dlChange = dlChangeIdx >= 0 && args[dlChangeIdx + 1] ? args[dlChangeIdx + 1] : null;
1846
+ const dlChange = dlChangeIdx >= 0 && args[dlChangeIdx + 1] && !String(args[dlChangeIdx + 1]).startsWith("--") ? args[dlChangeIdx + 1] : null;
1769
1847
  if (!dlChange) {
1770
1848
  console.error('用法: sillyspec delta --change <name> [--spec-dir <path>] [--json]\n 生成变更 delta.md(Before/Delta/After 三段式;四源 fail-soft,缺源逐段降级注记;幂等覆盖重跑即刷新)');
1771
1849
  process.exit(2);
@@ -1827,11 +1905,11 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
1827
1905
  // 调度者手动派独立子代理不写 marker 的死锁。docHash 由 CLI computeDocHash 算(部分实现 P6.1b defer)。
1828
1906
  // 纯新增,不改 gate 语义。与 task 级 backfill-reviews 对称(stage 级等价物)。
1829
1907
  const rsrChangeIdx = args.indexOf('--change');
1830
- const rsrChange = rsrChangeIdx >= 0 && args[rsrChangeIdx + 1] ? args[rsrChangeIdx + 1] : null;
1908
+ const rsrChange = rsrChangeIdx >= 0 && args[rsrChangeIdx + 1] && !String(args[rsrChangeIdx + 1]).startsWith("--") ? args[rsrChangeIdx + 1] : null;
1831
1909
  const rsrStageIdx = args.indexOf('--stage');
1832
- const rsrStage = rsrStageIdx >= 0 && args[rsrStageIdx + 1] ? args[rsrStageIdx + 1] : null;
1910
+ const rsrStage = rsrStageIdx >= 0 && args[rsrStageIdx + 1] && !String(args[rsrStageIdx + 1]).startsWith("--") ? args[rsrStageIdx + 1] : null;
1833
1911
  const rsrFromIdx = args.indexOf('--from');
1834
- const rsrFrom = rsrFromIdx >= 0 && args[rsrFromIdx + 1] ? args[rsrFromIdx + 1] : null;
1912
+ const rsrFrom = rsrFromIdx >= 0 && args[rsrFromIdx + 1] && !String(args[rsrFromIdx + 1]).startsWith("--") ? args[rsrFromIdx + 1] : null;
1835
1913
  // docHash 联动(坑 stage-review-dochash-manual-resync,2026-08-21 实证):改一版 design 要
1836
1914
  // 重算 2-3 个 stage review 的 docHash,手工易漏、gate 报错才补。--refresh-hash 就地刷新单个
1837
1915
  // 既有 review 的 hash(保留结论);--all 一次处理三个 stage(有 review 刷 hash,无 review
@@ -1940,13 +2018,14 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
1940
2018
  // --dry-run 列独立存在(「报告修复预览 + exit 1」),即 dryRun 置位 = fix 语义自动生效
1941
2019
  // 但零写盘(本口径记录于此,防歧义)。
1942
2020
  const BARE_FLAGS = ['--fix', '--dry-run', '--no-exempt'];
1943
- const PAIRED_FLAGS = ['--paths'];
2021
+ const PAIRED_FLAGS = ['--paths', '--against'];
1944
2022
  const rawDocsArgs = filteredArgs.slice(2);
1945
2023
  const docsCheckFlags = [];
1946
2024
  let cliPaths = null;
1947
2025
  let fix = false;
1948
2026
  let dryRun = false;
1949
2027
  let noExempt = false;
2028
+ let againstRef = null;
1950
2029
  for (let i = 0; i < rawDocsArgs.length; i++) {
1951
2030
  const a = rawDocsArgs[i];
1952
2031
  if (a === '--paths' && rawDocsArgs[i + 1] !== undefined) {
@@ -1955,6 +2034,13 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
1955
2034
  } else if (a === '--paths') {
1956
2035
  console.error('❌ docs check: --paths 缺值(逗号分隔 glob,如 --paths "docs/**/*.md")');
1957
2036
  process.exit(2);
2037
+ } else if (a === '--against' && rawDocsArgs[i + 1] !== undefined && !rawDocsArgs[i + 1].startsWith('--')) {
2038
+ // 坑 docs-gate-shared-worktree-parallel-block:按提交树校验,隔离并行在途编辑(移动靶)
2039
+ againstRef = rawDocsArgs[i + 1];
2040
+ i++;
2041
+ } else if (a === '--against') {
2042
+ console.error('❌ docs check: --against 缺值(如 --against HEAD)');
2043
+ process.exit(2);
1958
2044
  } else if (BARE_FLAGS.includes(a)) {
1959
2045
  if (a === '--fix') fix = true;
1960
2046
  else if (a === '--dry-run') dryRun = true;
@@ -1966,6 +2052,11 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
1966
2052
  docsCheckFlags.push(a);
1967
2053
  }
1968
2054
  }
2055
+ if ((fix || dryRun) && againstRef) {
2056
+ // --fix 操作工作区磁盘内容,against 语义与其矛盾(修的是未提交文本、校验的是提交树)
2057
+ console.error('❌ docs check: --fix/--dry-run 与 --against 互斥(fix 恒操作工作区,against 恒校验提交树)');
2058
+ process.exit(2);
2059
+ }
1969
2060
  const { runDocsCheck, readDocsCheckConfig, DocsCheckConfigError, applyFixes, runChangeNameAdvisory } = await import('./docs-check.js');
1970
2061
  try {
1971
2062
  // 配置优先级:--paths > local.yaml docs-check.paths > 缺省 docs/**/*.md
@@ -1978,6 +2069,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
1978
2069
  keywordAssert: cfg.keywordAssert,
1979
2070
  crossRepoRoots: cfg.crossRepoRoots,
1980
2071
  exempt: !noExempt,
2072
+ against: againstRef,
1981
2073
  });
1982
2074
  // task-03 修复链路(FR-04,platform-map-auto-anchors):fixActive 才构造 fixes——
1983
2075
  // 仅 fix.fixable===true 且 newLine 为整数的条目可重锚;newRef = ref 的行号部分替换为
@@ -2153,10 +2245,11 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
2153
2245
  // B9(2026-08-16):flag 白名单化(对齐 docs check 分支)——未知 --xxx 显式 exit 2
2154
2246
  // (interface-contract §1.3b 宣称),并接线 --paths 透传 runDocsGate.checkOpts(原被忽略)。
2155
2247
  const GATE_BARE_FLAGS = ['--init-baseline'];
2156
- const GATE_PAIRED_FLAGS = ['--paths'];
2248
+ const GATE_PAIRED_FLAGS = ['--paths', '--against'];
2157
2249
  const rawGateArgs = filteredArgs.slice(2);
2158
2250
  let initBaseline = false;
2159
2251
  let cliGatePaths = null;
2252
+ let gateAgainst = null;
2160
2253
  for (let i = 0; i < rawGateArgs.length; i++) {
2161
2254
  const a = rawGateArgs[i];
2162
2255
  if (a === '--paths' && rawGateArgs[i + 1] !== undefined) {
@@ -2165,13 +2258,21 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
2165
2258
  } else if (a === '--paths') {
2166
2259
  console.error('❌ docs gate: --paths 缺值(逗号分隔 glob,如 --paths "docs/**/*.md")');
2167
2260
  process.exit(2);
2261
+ } else if (a === '--against' && rawGateArgs[i + 1] !== undefined && !rawGateArgs[i + 1].startsWith('--')) {
2262
+ // 坑 docs-gate-shared-worktree-parallel-block:按提交树校验(pre-push 语义——推的
2263
+ // 是提交不是工作区),隔离并行会话在途编辑造成的锚点瞬时漂移(移动靶)
2264
+ gateAgainst = rawGateArgs[i + 1];
2265
+ i++;
2266
+ } else if (a === '--against') {
2267
+ console.error('❌ docs gate: --against 缺值(如 --against HEAD)');
2268
+ process.exit(2);
2168
2269
  } else if (GATE_BARE_FLAGS.includes(a)) {
2169
2270
  if (a === '--init-baseline') initBaseline = true;
2170
2271
  } else if (a.startsWith('--')) {
2171
2272
  console.error(`❌ docs gate: 未知 flag「${a}」。已知 flag:${[...GATE_BARE_FLAGS, ...GATE_PAIRED_FLAGS].join(' ')}(--json 为全局 flag)`);
2172
2273
  process.exit(2);
2173
2274
  } else {
2174
- console.error(`❌ docs gate: 多余位置参数「${a}」。用法:sillyspec docs gate [--init-baseline] [--paths <glob,...>] [--json]`);
2275
+ console.error(`❌ docs gate: 多余位置参数「${a}」。用法:sillyspec docs gate [--init-baseline] [--paths <glob,...>] [--against HEAD] [--json]`);
2175
2276
  process.exit(2);
2176
2277
  }
2177
2278
  }
@@ -2181,7 +2282,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
2181
2282
  const gateSpecBase = resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec');
2182
2283
  const g = await runDocsGate(
2183
2284
  { projectRoot: dir, specBase: gateSpecBase, initBaseline },
2184
- cliGatePaths ? { paths: cliGatePaths } : undefined
2285
+ { ...(cliGatePaths ? { paths: cliGatePaths } : {}), ...(gateAgainst ? { against: gateAgainst } : {}) }
2185
2286
  );
2186
2287
  if (json) {
2187
2288
  console.log(JSON.stringify(g, null, 2));
@@ -2237,7 +2338,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
2237
2338
  const dumpDbFlag = filteredArgs.includes('--dump-db');
2238
2339
  const doctorConfirm = filteredArgs.includes('--confirm');
2239
2340
  const pathIdx = filteredArgs.indexOf('--path');
2240
- const dbPath = pathIdx >= 0 && filteredArgs[pathIdx + 1] ? filteredArgs[pathIdx + 1] : null;
2341
+ const dbPath = pathIdx >= 0 && filteredArgs[pathIdx + 1] && !String(filteredArgs[pathIdx + 1]).startsWith("--") ? filteredArgs[pathIdx + 1] : null;
2241
2342
 
2242
2343
  // --align-execute-progress:基于 plan.md 声明对齐 execute 派生戳(仿 --cleanup-remnant 范式)
2243
2344
  // 默认 dry-run(只报告将补哪些 step),加 --confirm 才写。命中即 break,绝不 fall-through。
@@ -2245,7 +2346,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
2245
2346
  if (alignFlag) {
2246
2347
  // --change 解析:显式优先,缺省用单活跃变更自动兜底(与 run.js resolveChangeNameAuto 同逻辑)
2247
2348
  const alignChangeIdx = filteredArgs.indexOf('--change');
2248
- let alignChange = alignChangeIdx >= 0 && filteredArgs[alignChangeIdx + 1] ? filteredArgs[alignChangeIdx + 1] : null;
2349
+ let alignChange = alignChangeIdx >= 0 && filteredArgs[alignChangeIdx + 1] && !String(filteredArgs[alignChangeIdx + 1]).startsWith("--") ? filteredArgs[alignChangeIdx + 1] : null;
2249
2350
  if (!alignChange) {
2250
2351
  const changesDir = join(resolvePlatformSpecDir(doctorEffectiveDir, specDir) || join(doctorEffectiveDir, '.sillyspec'), 'changes');
2251
2352
  if (existsSync(changesDir)) {
@@ -2417,8 +2518,8 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
2417
2518
  const r = buildAndWriteScanFacts({
2418
2519
  cwd: factsEffectiveDir,
2419
2520
  specDir: resolvePlatformSpecDir(dir, specDir) || join(factsEffectiveDir, '.sillyspec'),
2420
- projectName: projIdx >= 0 && filteredArgs[projIdx + 1] ? filteredArgs[projIdx + 1] : basename(factsEffectiveDir),
2421
- projectPath: pathIdx >= 0 && filteredArgs[pathIdx + 1] ? filteredArgs[pathIdx + 1] : null,
2521
+ projectName: projIdx >= 0 && filteredArgs[projIdx + 1] && !String(filteredArgs[projIdx + 1]).startsWith("--") ? filteredArgs[projIdx + 1] : basename(factsEffectiveDir),
2522
+ projectPath: pathIdx >= 0 && filteredArgs[pathIdx + 1] && !String(filteredArgs[pathIdx + 1]).startsWith("--") ? filteredArgs[pathIdx + 1] : null,
2422
2523
  })
2423
2524
  const f = r.facts
2424
2525
  console.log(`scan facts 已写入: ${r.outPath}`)
@@ -2431,7 +2532,7 @@ ${generated.length} 个骨架已就绪——逐节把 <!--TODO--> 替换为语
2431
2532
  if (filteredArgs[1] === 'diff') {
2432
2533
  const { runScanDiff } = await import('./scan-diff.js')
2433
2534
  const diffBaseIdx = filteredArgs.indexOf('--base')
2434
- const diffBase = diffBaseIdx >= 0 && filteredArgs[diffBaseIdx + 1] ? filteredArgs[diffBaseIdx + 1] : null
2535
+ const diffBase = diffBaseIdx >= 0 && filteredArgs[diffBaseIdx + 1] && !String(filteredArgs[diffBaseIdx + 1]).startsWith("--") ? filteredArgs[diffBaseIdx + 1] : null
2435
2536
  // specBase/projectName 与 scan 主流程同口径(scan-profile.js:specBase=进度库根,projectName=仓库根 basename)。
2436
2537
  // A4 同族:平台指针 / 显式 --spec-dir / 本地 fallback 三合一,平台模式下 diff 不落本地孤儿库。
2437
2538
  const diffEffectiveDir = specDir ? dir : resolveEffectiveDir(dir)
@@ -2553,7 +2654,7 @@ SillySpec worktree — git worktree 隔离管理
2553
2654
  try { assertSafeChangeName(wtName, 'worktree 变更名'); }
2554
2655
  catch (e) { console.error(`❌ ${e.message}`); process.exit(2); }
2555
2656
  const baseIdx = args.indexOf('--base');
2556
- const base = baseIdx >= 0 && args[baseIdx + 1] ? args[baseIdx + 1] : undefined;
2657
+ const base = baseIdx >= 0 && args[baseIdx + 1] && !String(args[baseIdx + 1]).startsWith("--") ? args[baseIdx + 1] : undefined;
2557
2658
  const adoptBranch = args.includes('--adopt-branch'); // 收编既有同名分支为工作分支(坑 worktree-user-branch-conflict)
2558
2659
  try {
2559
2660
  const info = wm.create(wtName, { base, adoptBranch });
@@ -2907,7 +3008,7 @@ SillySpec worktree — git worktree 隔离管理
2907
3008
  const staleIdx = args.indexOf('--stale-hours');
2908
3009
  const staleHours = staleIdx !== -1 && args[staleIdx + 1] ? parseInt(args[staleIdx + 1], 10) : 24;
2909
3010
  const changeIdx = args.indexOf('--change');
2910
- const changeName = changeIdx !== -1 && args[changeIdx + 1] ? args[changeIdx + 1] : null;
3011
+ const changeName = changeIdx !== -1 && args[changeIdx + 1] && !String(args[changeIdx + 1]).startsWith("--") ? args[changeIdx + 1] : null;
2911
3012
  const diag = await wm.doctor({ fix: fixFlag, staleHours, changeName });
2912
3013
  if (diag.issues.length === 0) {
2913
3014
  console.log('✅ worktree 健康检查通过,无异常');
@@ -2990,7 +3091,7 @@ SillySpec dispatch — SillyHub 派发能力探测与策略生成(agent 调用
2990
3091
  if (dispatchSubCmd === 'hint') {
2991
3092
  // 解析 --contract <json>(仿 worktree 用 args.indexOf 取 flag 后一个值)
2992
3093
  const contractIdx = args.indexOf('--contract');
2993
- const contractRaw = contractIdx >= 0 && args[contractIdx + 1] ? args[contractIdx + 1] : null;
3094
+ const contractRaw = contractIdx >= 0 && args[contractIdx + 1] && !String(args[contractIdx + 1]).startsWith("--") ? args[contractIdx + 1] : null;
2994
3095
  if (!contractRaw) {
2995
3096
  console.error('用法: sillyspec dispatch hint --contract <json> [--json]\n 生成派发指令(DispatchContract:brief/worktreePath/branch/allowedPaths/readOnly/...)');
2996
3097
  process.exit(2);
@@ -3062,7 +3163,7 @@ SillySpec platform — SillyHub 平台同步
3062
3163
  // join(runtimeDir, `sync-conflict-${changeName}.json`) 可被 ../ 穿越出 .runtime
3063
3164
  {
3064
3165
  const pChangeIdx = args.indexOf('--change');
3065
- const pChangeVal = pChangeIdx >= 0 && args[pChangeIdx + 1] ? args[pChangeIdx + 1] : null;
3166
+ const pChangeVal = pChangeIdx >= 0 && args[pChangeIdx + 1] && !String(args[pChangeIdx + 1]).startsWith("--") ? args[pChangeIdx + 1] : null;
3066
3167
  if (pChangeVal) assertSafeChangeName(pChangeVal, 'platform --change 变更名');
3067
3168
  }
3068
3169
 
@@ -3130,7 +3231,7 @@ SillySpec platform — SillyHub 平台同步
3130
3231
  process.exit(1);
3131
3232
  }
3132
3233
  const tokenIdx = args.indexOf('--token');
3133
- const token = tokenIdx >= 0 && args[tokenIdx + 1] ? args[tokenIdx + 1] : undefined;
3234
+ const token = tokenIdx >= 0 && args[tokenIdx + 1] && !String(args[tokenIdx + 1]).startsWith("--") ? args[tokenIdx + 1] : undefined;
3134
3235
  if (!token) {
3135
3236
  // 缺 token 直接终止(体检 HUB-02):交互式输入尚未实现(task-11),此前
3136
3237
  // 继续执行会把 undefined 序列化成字符串 "undefined" 写入 local.yaml,
@@ -3142,17 +3243,19 @@ SillySpec platform — SillyHub 平台同步
3142
3243
  break;
3143
3244
  }
3144
3245
  case 'disconnect':
3145
- await syncModule.disconnect(dir);
3246
+ // --keep-pointer(坑 platform-sync-progress-rollback-and-db-corruption 坑1③):只清
3247
+ // local.yaml platform 段,保留指针/声明给 daemon 内嵌 CLI——回滚环恢复序列正规出口
3248
+ await syncModule.disconnect(dir, { keepPointer: filteredArgs.includes('--keep-pointer') });
3146
3249
  break;
3147
3250
  case 'sync': {
3148
3251
  const syncChangeIdx = args.indexOf('--change');
3149
- const syncChangeName = syncChangeIdx >= 0 && args[syncChangeIdx + 1] ? args[syncChangeIdx + 1] : null;
3252
+ const syncChangeName = syncChangeIdx >= 0 && args[syncChangeIdx + 1] && !String(args[syncChangeIdx + 1]).startsWith("--") ? args[syncChangeIdx + 1] : null;
3150
3253
  await syncModule.sync(syncChangeName, dir);
3151
3254
  break;
3152
3255
  }
3153
3256
  case 'sync-docs': {
3154
3257
  const syncDocsChangeIdx = args.indexOf('--change');
3155
- const syncDocsChangeName = syncDocsChangeIdx >= 0 && args[syncDocsChangeIdx + 1] ? args[syncDocsChangeIdx + 1] : null;
3258
+ const syncDocsChangeName = syncDocsChangeIdx >= 0 && args[syncDocsChangeIdx + 1] && !String(args[syncDocsChangeIdx + 1]).startsWith("--") ? args[syncDocsChangeIdx + 1] : null;
3156
3259
  await syncModule.syncDocuments(syncDocsChangeName, dir);
3157
3260
  break;
3158
3261
  }
@@ -3187,7 +3290,7 @@ SillySpec platform — SillyHub 平台同步
3187
3290
  // task-11 / D-006 / D-009 / FR-03:手动下行拉取平台进度到本地。
3188
3291
  // 与自动 triggerPull 共用 syncModule.pull(SyncManager.pull 实例方法),行为一致。
3189
3292
  const pullChangeIdx = args.indexOf('--change');
3190
- const pullChangeName = pullChangeIdx >= 0 && args[pullChangeIdx + 1] ? args[pullChangeIdx + 1] : null;
3293
+ const pullChangeName = pullChangeIdx >= 0 && args[pullChangeIdx + 1] && !String(args[pullChangeIdx + 1]).startsWith("--") ? args[pullChangeIdx + 1] : null;
3191
3294
  // 未连接明确提示不崩(acceptance:未连接平台输出明确提示)
3192
3295
  const smForCheck = new syncModule.SyncManager(dir);
3193
3296
  if (!smForCheck._getPlatform()) {
@@ -3231,7 +3334,7 @@ SillySpec platform — SillyHub 平台同步
3231
3334
  // 第一个非 flag 位置参数 → 唯一未决冲突自动选中。修复:旧实现盲取 platformArgs[0],
3232
3335
  // flag 放前面时(resolve --keep-local --change x)把 '--keep-local' 当变更名去读冲突文件。
3233
3336
  const changeIdx = args.indexOf('--change');
3234
- let resolveName = changeIdx >= 0 && args[changeIdx + 1] ? args[changeIdx + 1] : null;
3337
+ let resolveName = changeIdx >= 0 && args[changeIdx + 1] && !String(args[changeIdx + 1]).startsWith("--") ? args[changeIdx + 1] : null;
3235
3338
  if (!resolveName) {
3236
3339
  resolveName = platformArgs.find((a) => a && !a.startsWith('--')) || null;
3237
3340
  }
@@ -3297,7 +3400,7 @@ SillySpec platform — SillyHub 平台同步
3297
3400
  process.exit(1);
3298
3401
  }
3299
3402
  const reasonIdx = args.indexOf('--reason');
3300
- const reason = reasonIdx >= 0 && args[reasonIdx + 1] ? args[reasonIdx + 1] : undefined;
3403
+ const reason = reasonIdx >= 0 && args[reasonIdx + 1] && !String(args[reasonIdx + 1]).startsWith("--") ? args[reasonIdx + 1] : undefined;
3301
3404
  await syncModule.reject(rejectName, reason, dir);
3302
3405
  break;
3303
3406
  }
@@ -3321,7 +3424,7 @@ SillySpec platform — SillyHub 平台同步
3321
3424
  // task review / stage review / module-map)+ 轻形状检查(required-evidence / endpoints)
3322
3425
  // 为一把伞——「新解析器写之前先过 schema」的统一落点。skip = 未生成不算失败。
3323
3426
  const vaChangeIdx = args.indexOf('--change');
3324
- const vaChange = vaChangeIdx >= 0 && args[vaChangeIdx + 1] ? args[vaChangeIdx + 1] : null;
3427
+ const vaChange = vaChangeIdx >= 0 && args[vaChangeIdx + 1] && !String(args[vaChangeIdx + 1]).startsWith("--") ? args[vaChangeIdx + 1] : null;
3325
3428
  if (!vaChange) {
3326
3429
  console.error('用法: sillyspec validate --change <名> [--json]');
3327
3430
  console.error(' 校验该变更的全部结构化产物(verify-facts/required-evidence/task reviews/stage reviews/endpoints + module-map)');
@@ -3350,7 +3453,7 @@ SillySpec platform — SillyHub 平台同步
3350
3453
  // decisions list --change <c>——parseDecisions 只读视图(id/状态/类型/模块域/标题)。
3351
3454
  const decSub = filteredArgs[1];
3352
3455
  const decChangeIdx = args.indexOf('--change');
3353
- const decChange = decChangeIdx >= 0 && args[decChangeIdx + 1] ? args[decChangeIdx + 1] : null;
3456
+ const decChange = decChangeIdx >= 0 && args[decChangeIdx + 1] && !String(args[decChangeIdx + 1]).startsWith("--") ? args[decChangeIdx + 1] : null;
3354
3457
  if (!decChange || !['add', 'list'].includes(decSub)) {
3355
3458
  console.error('用法: sillyspec decisions add --change <名> --id D-001 --title "…" [--version N] [--status confirmed] [--type architecture] [--domain x(可重复)] [--question/--answer/--anchor/--reject-reason/--revisit-when/--supersedes/--impacts/--note]\n sillyspec decisions list --change <名>');
3356
3459
  process.exit(2);
@@ -3649,12 +3752,12 @@ SillySpec workflow — 工作流管理
3649
3752
  }
3650
3753
  // 解析 --project
3651
3754
  const projectIdx = filteredArgs.indexOf('--project');
3652
- const project = projectIdx !== -1 && filteredArgs[projectIdx + 1] ? filteredArgs[projectIdx + 1] : null;
3755
+ const project = projectIdx !== -1 && filteredArgs[projectIdx + 1] && !String(filteredArgs[projectIdx + 1]).startsWith("--") ? filteredArgs[projectIdx + 1] : null;
3653
3756
  // 解析 --json(已在顶层解析)
3654
3757
  const isJson = json;
3655
3758
  // 解析 --change
3656
3759
  const changeIdx = filteredArgs.indexOf('--change');
3657
- const changeName = changeIdx !== -1 && filteredArgs[changeIdx + 1] ? filteredArgs[changeIdx + 1] : null;
3760
+ const changeName = changeIdx !== -1 && filteredArgs[changeIdx + 1] && !String(filteredArgs[changeIdx + 1]).startsWith("--") ? filteredArgs[changeIdx + 1] : null;
3658
3761
 
3659
3762
  if (!project && wfName !== 'archive-impact') {
3660
3763
  console.error('❌ 请指定 --project,例如:--project sillyspec');
@@ -3757,7 +3860,7 @@ SillySpec modules — 模块文档管理
3757
3860
  // per-task 模块卡级联解析(token 成本优化 P0a):execute「加载上下文」步的
3758
3861
  // {MODULE_RESOLVE_TABLE} 注入同源;独立命令供 agent/人随时查(细卡优先,防整读大卡)
3759
3862
  const mrChangeIdx = args.indexOf('--change');
3760
- const mrChange = mrChangeIdx >= 0 && args[mrChangeIdx + 1] ? args[mrChangeIdx + 1] : null;
3863
+ const mrChange = mrChangeIdx >= 0 && args[mrChangeIdx + 1] && !String(args[mrChangeIdx + 1]).startsWith("--") ? args[mrChangeIdx + 1] : null;
3761
3864
  if (!mrChange) {
3762
3865
  console.error('用法: sillyspec modules resolve --change <名> [--spec-dir <path>] [--json]\n 输出 per-task 最优模块卡表(跨全部 _module-map.yaml 最长前缀匹配,细卡优先)');
3763
3866
  process.exit(2);
@@ -3765,7 +3868,8 @@ SillySpec modules — 模块文档管理
3765
3868
  assertSafeChangeName(mrChange, '--change 变更名');
3766
3869
  const mrSpecBase = resolvePlatformSpecDir(dir, specDir) || join(dir, '.sillyspec');
3767
3870
  const { resolveChangeModuleCards, renderModuleResolveTable } = await import('./module-resolve.js');
3768
- if (filteredArgs.includes('--json')) {
3871
+ if (json) {
3872
+ // 全局解析器把 --json 收进顶层 json 变量、不进 filteredArgs——查 filteredArgs 恒 false(ql-20260911-029)
3769
3873
  const r = resolveChangeModuleCards({ cwd: dir, specBase: mrSpecBase, changeName: mrChange });
3770
3874
  console.log(JSON.stringify(r, null, 2));
3771
3875
  } else {
@@ -3818,7 +3922,7 @@ SillySpec modules — 模块文档管理
3818
3922
  const { registerRepoInLocalYaml } = await import('./local-register.js');
3819
3923
  let rrResult;
3820
3924
  try {
3821
- rrResult = registerRepoInLocalYaml(target.path, rrKey, absRepo);
3925
+ rrResult = await registerRepoInLocalYaml(target.path, rrKey, absRepo);
3822
3926
  } catch (e) {
3823
3927
  console.error(`❌ 注册失败: ${e.message}`);
3824
3928
  process.exit(1);