sillyspec 3.28.7 → 3.28.9

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 (79) hide show
  1. package/.claude/skills/sillyspec-quick/SKILL.md +2 -0
  2. package/.husky/pre-push +3 -1
  3. package/AGENTS.md +3 -2
  4. package/README.md +35 -11
  5. package/SKILL.md +24 -10
  6. package/SillySpec-/350/203/275/345/212/233/344/272/256/347/202/271/345/205/250/346/231/257-2026-09-14.pptx +0 -0
  7. package/package.json +1 -1
  8. package/src/agent-session-log.js +446 -35
  9. package/src/change-risk-profile.js +20 -0
  10. package/src/config-cat.js +6 -3
  11. package/src/config-schema.js +32 -0
  12. package/src/constants.js +3 -0
  13. package/src/db.js +23 -6
  14. package/src/decision-distill.js +15 -3
  15. package/src/design-facts.js +33 -3
  16. package/src/docs-check.js +137 -12
  17. package/src/docs-gate.js +108 -6
  18. package/src/doctor-diagnostics.js +243 -9
  19. package/src/friction-ledger.js +171 -0
  20. package/src/friction-tally.js +7 -2
  21. package/src/git-helper.js +6 -4
  22. package/src/hooks/worktree-guard.js +72 -30
  23. package/src/index.js +356 -81
  24. package/src/knowledge-classify.js +363 -0
  25. package/src/knowledge-hits.js +74 -0
  26. package/src/knowledge-stats.js +180 -0
  27. package/src/local-detect.js +30 -15
  28. package/src/local-register.js +13 -4
  29. package/src/mcp-server.js +38 -8
  30. package/src/progress/change-registry.js +224 -43
  31. package/src/progress/consistency-doctor.js +11 -4
  32. package/src/progress/shared.js +3 -2
  33. package/src/progress/stage-machine.js +3 -1
  34. package/src/progress/step-store.js +30 -17
  35. package/src/progress.js +123 -8
  36. package/src/quick-gate-profile.js +251 -0
  37. package/src/quicklog.js +199 -32
  38. package/src/review-dispatch.js +2 -1
  39. package/src/run/command.js +124 -15
  40. package/src/run/complete-handlers.js +514 -24
  41. package/src/run/complete.js +82 -25
  42. package/src/run/concurrent-detect.js +99 -1
  43. package/src/run/gate-snapshot.js +115 -5
  44. package/src/run/gates.js +85 -14
  45. package/src/run/prompt.js +143 -6
  46. package/src/run/quick-audit.js +136 -15
  47. package/src/run/shared.js +174 -20
  48. package/src/run/stage.js +6 -0
  49. package/src/run/verify-quality-scan.js +14 -3
  50. package/src/scan-diff.js +61 -30
  51. package/src/scan-postcheck.js +74 -0
  52. package/src/scan-refresh.js +477 -0
  53. package/src/scan-staleness.js +25 -13
  54. package/src/scope-audit.js +365 -35
  55. package/src/spec-sync.js +19 -14
  56. package/src/stage-contract-spec.js +4 -3
  57. package/src/stage-contract.js +185 -2
  58. package/src/stage-review.js +29 -12
  59. package/src/stages/brainstorm.js +7 -1
  60. package/src/stages/execute.js +91 -4
  61. package/src/stages/knowledge.js +52 -3
  62. package/src/stages/plan-postcheck.js +57 -12
  63. package/src/stages/quick.js +1 -1
  64. package/src/stages/verify.js +1 -0
  65. package/src/sync-noise.js +114 -6
  66. package/src/sync.js +201 -11
  67. package/src/task-review.js +333 -27
  68. package/src/verify-facts-schema.js +10 -3
  69. package/src/verify-postcheck.js +41 -6
  70. package/src/verify-probes.js +348 -7
  71. package/src/worktree-apply.js +594 -62
  72. package/src/worktree-cross.js +12 -18
  73. package/src/worktree-deps.js +100 -2
  74. package/src/worktree.js +255 -30
  75. package/src/wt-commit.js +128 -0
  76. package/templates/agents-instruction.md +2 -2
  77. package/templates/prompts/testcase-design.md +2 -1
  78. package/templates/prompts/verify-probes.md +4 -1
  79. package/sa-rename-probe/src/new.js +0 -4
@@ -22,6 +22,7 @@
22
22
  import { basename, join, resolve, dirname } from 'node:path'
23
23
  import { existsSync, readdirSync, mkdirSync, writeFileSync, readFileSync, rmSync, unlinkSync } from 'node:fs'
24
24
  import { randomBytes, randomUUID } from 'node:crypto'
25
+ import { fileURLToPath } from 'node:url'
25
26
  import { writeAtomicSync } from '../fs-atomic.js'
26
27
  import { resolveSpecDir, countAncestorSpecDirs, ancestorSpecDirs, resolveAncestorCeiling, resolveChangeDir, triggerSync, getStageSteps, formatWaitOptions, checkApproval, warnApprovalUnknown, didYouMean, assertSafeChangeName, assertDatedChangeName, detectQuickSessionDrift, detectWorktreeSpecDrift, resolveRuntimeRoot, resolveQuickSessionsDir, writePlatformPointer, checkPlatformManaged, isSelfReferentialSpecRoot, isTempResidueSpecRoot, PLATFORM_MANAGED_FILENAME, warnSelfRefPointerOnce } from './shared.js'
27
28
  import { resolveQuickLinkedChanges } from './quick-audit.js'
@@ -29,7 +30,7 @@ import { outputStep, collectStageWaitHistory } from './prompt.js'
29
30
  import { completeStep, skipStep, waitStep, continueStep, synthesizeStepOutput } from './complete.js'
30
31
  import { runStage } from './stage.js'
31
32
  import { sanitizeDesc } from '../quicklog.js'
32
- import { ProgressManager } from '../progress.js'
33
+ import { ProgressManager, resolveSessionIdentity } from '../progress.js'
33
34
  import { validateChangeExists, checkTransition } from '../stage-contract.js'
34
35
  import { READONLY_AUXILIARY_STAGES } from '../constants.js'
35
36
  import { stageRegistry, auxiliaryStages } from '../stages/index.js'
@@ -47,6 +48,7 @@ const VALUE_FLAGS = new Set([
47
48
  '--req', '--cause', '--solution', '--result', // quick 末步四字段参数(合成 --output,见 outputText 解析段)
48
49
  '--ql', // quick --cancel 显式指定 qlId(缺省读会话 guard.json)
49
50
  '--base', // scan diff 基线 commit(吃值;只在 run scan --diff 转发路径消费)
51
+ '--session', // 显式会话标识(吃值;2026-09-14-change-ownership-guards task-02 所有权三级解析最高优先级层,启动 claim 消费)
50
52
  ])
51
53
 
52
54
  /**
@@ -621,6 +623,9 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
621
623
  // 历史写法,语义与「指定变更名」冲突,新用法建议改用 --linked-changes)
622
624
  let changeName = null
623
625
  let linkedChanges = []
626
+ // 机器自动关联溯源(坑 quick-single-change-auto-link):resolveQuickLinkedChanges 单候选
627
+ // 信号门控命中时标记,guard 落 linkedChangesAuto,--done 归档闸对这类变更不触发轻量归档
628
+ let linkedAuto = []
624
629
  const changeValue = getFlagValue('--change')
625
630
  if (changeValue !== null) {
626
631
  changeName = changeValue
@@ -691,6 +696,11 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
691
696
  if (!quickSessionId) {
692
697
  quickSessionId = 'quick-' + randomUUID().slice(0, 8)
693
698
  quickSidFresh = true
699
+ // 坑 quick-sync-block-filenotes-and-quicklog-mixed-commit 坑1③:新会话 ID 生成即打印
700
+ // 第一行——后续 agent-log push / spec-sync 等 best-effort 网络动作(平台慢时分钟级)
701
+ // 若把命令拖过 exec 超时,调用方仍能从输出头部拿到 sessionId 续用(--status / guard.json
702
+ // 核对),不会误开空壳新会话。
703
+ console.log(`📌 quick 会话已建立: ${quickSessionId}(后续若超时/中断,用 --change ${quickSessionId} 续用)`)
694
704
  }
695
705
  changeName = quickSessionId
696
706
  } else {
@@ -798,17 +808,29 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
798
808
  const isAllowDelete = flags.includes('--allow-delete')
799
809
  const isForceBaseline = flags.includes('--force-baseline')
800
810
  const isForceRescan = flags.includes('--force-rescan')
811
+ // --no-docs(2026-09-14-quick-exit-tiered-gates task-02,no-docs-flag):L2 门禁文档认领的显式
812
+ // 豁免布尔 flag(先例上方同款 includes 解析)——只影响 gate 画像 docClaim(exempt-no-docs)
813
+ // 与 [gate] 落账留痕,不改 status/exit code(D-003 advisory),审计链经 completeStep options 透传。
814
+ const isNoDocs = flags.includes('--no-docs')
815
+ // --skip-apply(2026-09-14-change-ownership-guards task-03,D-002@v1):归档收口门(archive
816
+ // step3 归档移动前 applyWorktree checkOnly 探测未 apply 交付面)的显式跳过布尔 flag——
817
+ // 语义出口在 completeStep → handleArchiveConfirmStep → archiveChangeDirectory(task-02 只
818
+ // 注册白名单透传,本行补 includes 解析进 completeStep options,isNoDocs 同款先例)。
819
+ const isSkipApply = flags.includes('--skip-apply')
801
820
 
802
821
  // F10b(ql-20260818-010):语义别名定向提示。did-you-mean 按编辑距离猜形近 flag,猜中的常是
803
822
  // 形近但语义错的(--title → --files,ql-20260818-003 负面③实证)。常见「语义别名」在此登记
804
823
  // 定向指引:命中时替代 did-you-mean 打印,引导到真正承载该语义的 flag/机制。
824
+ // 2026-09-14-change-ownership-guards task-02:原 '--session' 条目(quick 会话名提示)随
825
+ // --session 升格为真实 flag(所有权会话标识)而移除——提示只对未知 flag 生效,已知 flag
826
+ // 挂提示是死代码;'--session-id' 是最常见形近拼法,改挂新语义指引(所有权三级标识)。
805
827
  const FLAG_SEMANTIC_HINTS = {
806
828
  '--title': 'QUICKLOG 条目标题无独立参数——从 --output 的「需求:」字段自动提取(写成一句语义化短标题即可)',
807
829
  '--message': '结果摘要用 --output(quick 末步须含 需求:/根因:/方案:/结果: 四字段)',
808
830
  '--summary': '结果摘要用 --output(quick 末步须含 需求:/根因:/方案:/结果: 四字段)',
809
831
  '--result': '结果摘要用 --output(quick 末步须含 需求:/根因:/方案:/结果: 四字段)',
810
832
  '--name': 'quick 会话名由 CLI 自动分配(quick-<hash>),恢复会话用 --change <quick-session-id>;关联变更用 --linked-changes',
811
- '--session': 'quick 会话名由 CLI 自动分配(quick-<hash>),恢复会话用 --change <quick-session-id>',
833
+ '--session-id': '会话标识用 --session <id>(所有权三级解析最高优先级层;缺省 env SILLYSPEC_SESSION_ID,再缺省 anon@<host> 机器级降级)',
812
834
  '--note': '文件括注用 --file-notes "path::注 || path::注";启动时任务描述用 --input',
813
835
  '--notes': '文件括注用 --file-notes "path::注 || path::注"',
814
836
  '--desc': '启动时任务描述用 --input;QUICKLOG 标题从 --output「需求:」自动提取',
@@ -819,17 +841,22 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
819
841
  const knownFlags = new Set([
820
842
  '--done', '--skip', '--status', '--reset', '--confirm', '--skip-approval', '--cancel', '--ql',
821
843
  '--wait', '--continue', '--non-interactive', '--interactive',
844
+ '--wait-interactive', // auto 模式 TTY 直通(带值形态 `--wait-interactive true`,autoFlagValue 消费;ql-20260911-029 补注册——此前漏登记,进命令前即被未知参数 exit(2) 拦死,FR-03 整条死路)
822
845
  '--reason', '--options', '--answer', '--confirm-mode',
823
846
  '--output', '--input', '--change', '--linked-changes',
824
847
  '--req', '--cause', '--solution', '--result',
825
848
  '--spec-dir', '--spec-root', '--runtime-root', '--workspace-id', '--scan-run-id',
826
849
  '--files', '--file-notes', '--allow-new', '--allow-delete', '--force-baseline', '--force-rescan',
850
+ '--no-docs', // L2 门禁文档认领显式豁免(task-02:布尔 flag 不吃值,audit 链 docClaim → exempt-no-docs)
827
851
  '--json', '--dir', '--help',
828
852
  '--reopen', '--from-step', '--mode',
829
853
  '--deep', '--quick', '--standard', // scan profile 三档显式选择(scan-profile.js 从 argv 读;互斥见下方 PROFILE_FLAGS 检测)
830
854
  '--adopt-branch', // execute 显式收编既有 sillyspec/<change> 分支为 worktree 工作分支(坑 worktree-user-branch-conflict)
831
855
  '--diff', '--base', '--full', '--report', // scan diff(D-001:command.js 只补 flag,裸 token 解析归 index.js 子命令拦截)
832
856
  '--meta', // P1-3:渲染 SS-META 机器块(布尔 flag,不吃值)
857
+ '--session', // 显式会话标识(吃值,VALUE_FLAGS 同步登记;2026-09-14-change-ownership-guards task-02:启动 claim 消费,所有权三级解析最高优先级层)
858
+ '--takeover', // 所有权护栏显式强制接管(task-02 / FR-01:本层只注册透传,实际消费在 index.js apply/cleanup 子命令层——run 侧带上不报未知参数,语义出口在 worktree 命令)
859
+ '--skip-apply', // 归档收口跳过 apply 校验(task-02 只注册透传不改行为,消费归 task-03 archive 接线)
833
860
  '-h',
834
861
  ])
835
862
  for (let i = 0; i < flags.length; i++) {
@@ -892,6 +919,7 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
892
919
  // D-002:guard 按 session 存(.runtime/quick-sessions/<sessionId>/guard.json)。
893
920
  // sessionId == changeName == quick-<uuid8>(上面参数解析已确定)。回退读旧单文件 quick-guard.json(task-03 前兼容)。
894
921
  let persistedLinked = null
922
+ let persistedAuto = null
895
923
  try {
896
924
  const sessionGuardFile = join(specRoot, '.runtime', 'quick-sessions', changeName, 'guard.json')
897
925
  const legacyGuardFile = join(specRoot, '.runtime', 'quick-guard.json')
@@ -899,15 +927,20 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
899
927
  ? JSON.parse(readFileSync(sessionGuardFile, 'utf8'))
900
928
  : (existsSync(legacyGuardFile) ? JSON.parse(readFileSync(legacyGuardFile, 'utf8')) : null)
901
929
  if (g && Array.isArray(g.linkedChanges)) persistedLinked = g.linkedChanges
930
+ if (g && Array.isArray(g.linkedChangesAuto)) persistedAuto = g.linkedChangesAuto
902
931
  } catch {}
903
932
  if (persistedLinked) {
904
933
  linkedChanges = persistedLinked
934
+ // autoLinked 溯源同步恢复(--done 复用启动 guard 的关联时,归档闸同样要看到「机器猜的」标记)
935
+ if (persistedAuto) linkedAuto = persistedAuto
905
936
  } else {
906
- linkedChanges = await resolveQuickLinkedChanges({
937
+ const resolved = await resolveQuickLinkedChanges({
907
938
  pm, cwd, specDir: specRoot, quickFiles,
908
939
  taskDescription: inputText || '',
909
940
  nonInteractive: isNonInteractive,
910
941
  })
942
+ linkedChanges = resolved.changes
943
+ linkedAuto = resolved.autoLinked
911
944
  }
912
945
  }
913
946
 
@@ -1009,12 +1042,27 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
1009
1042
  }
1010
1043
 
1011
1044
  if (!progress) {
1045
+ // ── 辅助阶段 default 行亲和(坑 default-empty-dir-materialized 配套,2026-09-14 用户反馈①)──
1046
+ // default 行是辅助阶段(explore 等)不带 --change 启动时的进度容器(下方 isAuxiliary
1047
+ // 兜底 'default')。此前「续跑也锚回 default」靠的是 changes/default/ 目录在目录计数
1048
+ // 解析里的存在感——目录停建后(initChange 对系统 key 不再物化)该隐式锚定失效,
1049
+ // 「default 在途 + 恰好一个其他变更目录」会把辅助阶段挂到别人的变更行。这里显式化:
1050
+ // 无 --change 且 default 行有在途(非 completed)的本阶段 steps → 锚回 default。
1051
+ // 显式 --change 恒优先;非辅助阶段(brainstorm/execute 等)从不落 default,不适用。
1052
+ if (!changeName && isAuxiliary) {
1053
+ const affinity = resolveAuxiliaryDefaultAffinity(pm, cwd, stageName)
1054
+ if (affinity) {
1055
+ changeName = affinity
1056
+ progress = pm.read(cwd, affinity)
1057
+ }
1058
+ }
1012
1059
  // ── done-like 存在性守卫(坑 done-phantom-change-silent-create,2026-09-04 事故元凶①)──
1013
1060
  // --done/--skip/--wait/--continue/--reset/--reopen 都预设「目标 change 已存在于当前进度库」。
1014
1061
  // 此前 !progress 时无条件 initChange 静默新建——接管指针切库/换库后 --change 指向旧库的
1015
1062
  // 变更名,新库里凭空建幻影变更再把动作记上去。目标已物化(changes/ 目录或 quick 会话
1016
1063
  // guard 存在)仍放行走下方 initChange 自愈(DB 重建/迁移后从目录物化是合法恢复路径)。
1017
- if (isDone || isSkip || isWait || isContinue || isReset || isReopen) {
1064
+ // !progress 前置:上方亲和已锚到 default 行(DB 在途)时目标已确证存在,不走磁盘物化判据。
1065
+ if (!progress && (isDone || isSkip || isWait || isContinue || isReset || isReopen)) {
1018
1066
  const actionFlag = isDone ? '--done' : isSkip ? '--skip' : isWait ? '--wait'
1019
1067
  : isContinue ? '--continue' : isReset ? '--reset' : '--reopen'
1020
1068
  const guardTarget = changeName || resolveChangeNameAuto(cwd, specRoot)
@@ -1040,6 +1088,13 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
1040
1088
  }
1041
1089
  if (stageName === 'quick' || /^quick-[0-9a-f]{8}$/.test(guardTarget || '')) {
1042
1090
  console.error(` ③ quick 会话跨进程恢复须带 --change <quick-session-id>;本库无该会话 guard——会话可能属于旧库`)
1091
+ } else if (stageName === 'explore' && activeList.includes('default')) {
1092
+ // 坑 explore-done-change-default-hint(2026-09-14 用户反馈①):explore 不带 --change 启动
1093
+ // 时进度按约定挂 default 变更行(isAuxiliary 兜底),多活跃变更库(多 agent 并行常态)
1094
+ // 目标解析失败被拒时,③ 只指 brainstorm 新建路径——对「进度就在 default 行」的 explore
1095
+ // 会话是误导(用户实证要靠 --status 才摸出来)。default 在活跃列表 = 有辅助阶段在途,
1096
+ // 补指向自身的 --change default 出口。
1097
+ console.error(` ③ explore 阶段进度默认挂在 default 变更行——本库 default 在活跃列表,重试加 --change default`)
1043
1098
  } else {
1044
1099
  console.error(` ③ 确要新建变更:先 sillyspec run brainstorm --change <名>(不带 ${actionFlag})`)
1045
1100
  }
@@ -1188,6 +1243,28 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
1188
1243
  // 注册变更到全局活跃列表(如果尚未注册)
1189
1244
  if (effectiveChange) {
1190
1245
  pm.registerChange(cwd, effectiveChange)
1246
+ // ── 所有权首建 claim(2026-09-14-change-ownership-guards task-02 / FR-01 / D-001@v1)──
1247
+ // run <stage> 启动即认领 owner(claimChangeOwner 守卫语义:已有值不覆盖——他人持有不抢
1248
+ // 也不拒;拒绝语义只锁接管类操作,见 index.js apply/cleanup/assess 接线)。会话标识三级:
1249
+ // quick 会话=changeName 本身(quick-<8hex>,既有 sessionId 机制——guard 按 change 名落盘
1250
+ // 天然跨进程,selfSession==changeName 恒 self);full-flow=--session > env
1251
+ // SILLYSPEC_SESSION_ID > anon@host 机器级降级(降级时一次性教学 warning)。
1252
+ // default 容器行不认领:辅助阶段共享容器非真实变更,挂伪所有权没有消费者还添噪声。
1253
+ // fail-open:claim 失败不阻断启动(护栏判定退回 no-owner 放行)。
1254
+ try {
1255
+ if (effectiveChange !== 'default') {
1256
+ const ident = resolveSessionIdentity({
1257
+ flagSession: getFlagValue('--session'),
1258
+ quickChangeName: stageName === 'quick' ? effectiveChange : null,
1259
+ // anon 降级教学 marker 落盘根用 dirname(specRoot)(锚定后的主仓根)而非裸 cwd:
1260
+ // worktree 副本 cwd(execute 在 .sillyspec/.runtime/worktrees/<c>/ 内跑)会把
1261
+ // marker mkdir 进副本 .runtime(worktree-execute-spec-drift AC-A6 副本零写入实证),
1262
+ // 且副本随 cleanup 删除、marker 永不跨会话去重——落到持久主仓根才是教学降频语义。
1263
+ cwd: dirname(specRoot),
1264
+ })
1265
+ pm.claimChangeOwner(cwd, effectiveChange, ident.session)
1266
+ }
1267
+ } catch { /* claim 失败不阻断启动 */ }
1191
1268
  }
1192
1269
 
1193
1270
  // --reset
@@ -1286,11 +1363,14 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
1286
1363
  console.error('用法: sillyspec run quick --cancel --change <quick-会话ID> [--ql <ql-xxx>]\n 取消误启动的 quick 会话:QUICKLOG 条目翻「已取消」+ tasks.md 挂载行移除 + 会话目录清理 + db 行注销。\n 已完成/已勾选的会话拒绝取消(真实工作记录)')
1287
1364
  process.exit(2)
1288
1365
  }
1289
- const specBase = resolveSpecDir(cwd, { specDir })
1366
+ // 平台模式对齐(坑 quick-cancel-platform-specbase,2026-09-12 审查批 B-④):此处原用
1367
+ // resolveSpecDir 本地重建 specBase,遮蔽外层平台感知 specBase(:476 specRoot 优先)——
1368
+ // 平台模式 guard 恒读不到、QUICKLOG 翻态落错库。guard 查找同其他收敛点走
1369
+ // resolveQuickSessionsDir(runtimeRoot 可与 specBase/.runtime 异位,multi-agent-review Q4)。
1290
1370
  // qlId 优先 --ql 显式;否则读会话 guard.json(allocate 时 CLI 写入);再退 current marker 旁的会话
1291
1371
  let qlId = qlFlag
1292
1372
  if (!qlId) {
1293
- const guardPath = join(specBase, '.runtime', 'quick-sessions', cancelSessionRaw, 'guard.json')
1373
+ const guardPath = join(resolveQuickSessionsDir(platformOpts, specBase), cancelSessionRaw, 'guard.json')
1294
1374
  try {
1295
1375
  if (existsSync(guardPath)) {
1296
1376
  // 字段名 bug 修复(2026-09-09 §7-5,会话实证:guard 在场仍报「缺失/损坏」):
@@ -1386,18 +1466,20 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
1386
1466
  }
1387
1467
  }
1388
1468
 
1389
- // --file-notes 非末步静默忽略前置 warn(坑 quick-file-notes-nonfinal-ignored,2026-08-22 实证:
1390
- // step2 --done 传 --file-notes 被静默丢——CLI 短进程,注入随进程结束即丢,白传一轮)。
1391
- // 放此处:ensureStageSteps 之后 progress 已就绪可判步骤状态;仅 quick + 带了 --file-notes +
1392
- // 非「唯一 pending 的末步 --done」时提示。末步 --done 是消费点,不提示。
1469
+ // --file-notes 非末步硬拒绝(坑 quick-file-notes-nonfinal-ignored → quick-sync-block
1470
+ // 坑2 升级,2026-09-13 实证:warn 版仍「先收下再丢弃再告知」——9 条括注静默丢失需凭记忆
1471
+ // 重传)。改为 exit 2 直接拒绝:调用方立刻知道参数传错了时机,不做静默丢弃。末步 --done
1472
+ //(唯一 pending)是消费点,正常消费。
1393
1473
  if (quickFileNotes !== '' && stageName === 'quick' && !isStatus) {
1394
1474
  try {
1395
1475
  const qSteps = progress?.stages?.quick?.steps
1396
1476
  const pendingCount = Array.isArray(qSteps) ? qSteps.filter(st => ['pending', 'in-progress'].includes(st?.status)).length : 0
1397
1477
  if (!isDone || pendingCount > 1) {
1398
- console.warn(`⚠️ --file-notes 本次不会生效:它只在 quick 末步(暂存和更新记录)--done 时随收尾消费——CLI 是短进程,本次注入随进程结束即丢。请在末步 --done 时连同 --output 一起传。`)
1478
+ console.error(`❌ --file-notes 此时不生效(拒绝执行而非静默丢弃):它只在 quick 末步 --done 时随收尾消费——CLI 是短进程,非末步注入随进程结束即丢。`)
1479
+ console.error(` 请去掉 --file-notes 重跑本条命令;括注内容留到末步 --done 时连同 --output 一起传。`)
1480
+ process.exit(2)
1399
1481
  }
1400
- } catch { /* 判定失败不提示(fail-open) */ }
1482
+ } catch { /* 判定失败不阻断(fail-open 维持旧行为) */ }
1401
1483
  }
1402
1484
 
1403
1485
  // --status
@@ -1442,11 +1524,29 @@ export async function runCommand(args, cwd, specDir = null, opts = {}) {
1442
1524
  }
1443
1525
  }
1444
1526
  const doneAnswer = getFlagValue('--answer')
1445
- return await completeStep(pm, progress, stageName, cwd, outputText, inputText, { confirm: isConfirm, changeName: effectiveChange, nonInteractive: isNonInteractive && !isInteractive, platformOpts, doneAnswer, isForceBaseline, isAllowNew, isAllowDelete, quickFiles })
1527
+ // isSkipApply/sessionFlag(task-03):--skip-apply 归档收口跳过 + --session 所有权会话标识,
1528
+ // 随 completeStep options 透传(isNoDocs 同款链路)——消费点 archive/quick 收尾 handler。
1529
+ return await completeStep(pm, progress, stageName, cwd, outputText, inputText, { confirm: isConfirm, changeName: effectiveChange, nonInteractive: isNonInteractive && !isInteractive, platformOpts, doneAnswer, isForceBaseline, isAllowNew, isAllowDelete, isNoDocs, isSkipApply, sessionFlag: getFlagValue('--session'), quickFiles })
1446
1530
  }
1447
1531
 
1448
1532
  // 默认:输出当前步骤
1449
- return await runStage(pm, progress, stageName, cwd, effectiveChange, isSkipApproval, platformOpts, { quickFiles, isAllowNew, isAllowDelete, isForceBaseline, isForceRescan, linkedChanges, taskDescription: inputText, adoptBranch: stageName === 'execute' && flags.includes('--adopt-branch') })
1533
+ return await runStage(pm, progress, stageName, cwd, effectiveChange, isSkipApproval, platformOpts, { quickFiles, isAllowNew, isAllowDelete, isForceBaseline, isForceRescan, linkedChanges, linkedChangesAuto: linkedAuto, taskDescription: inputText, adoptBranch: stageName === 'execute' && flags.includes('--adopt-branch') })
1534
+ }
1535
+
1536
+ /**
1537
+ * 辅助阶段 default 行亲和:default 行有在途(非 completed、steps 已物化)的本阶段时返回 'default'。
1538
+ * steps>0 门槛排除「从未跑过」的空壳行(initChange 播的 pending 空表)——那种行不是在途会话,
1539
+ * 不锚(防凭空把 --done 记上去)。只读不抛(无行/读失败 → null)。目录停建后辅助阶段续跑的
1540
+ * 显式锚定(见 !progress 块内注释)。completed 永不锚(辅助阶段收尾会 reset 回 pending 可重跑,
1541
+ * 见 gates.js auxiliary 重置——reset 后 pending+steps>0 照样锚,即「下一轮 explore 续用 default 行」)。
1542
+ */
1543
+ function resolveAuxiliaryDefaultAffinity(pm, cwd, stageName) {
1544
+ try {
1545
+ const p = pm.read(cwd, 'default')
1546
+ const st = p && p.stages && p.stages[stageName]
1547
+ if (st && st.status && st.status !== 'completed' && Array.isArray(st.steps) && st.steps.length > 0) return 'default'
1548
+ } catch { /* 读失败不猜(fail-closed,走原解析链) */ }
1549
+ return null
1450
1550
  }
1451
1551
 
1452
1552
  /**
@@ -1896,6 +1996,15 @@ async function runAutoMode(pm, progress, cwd, flags, changeName, platformOpts =
1896
1996
  * 无新状态);任一不满足/异常 → 静默或提示回三段式(fail-open,Grill P2-①时序)。
1897
1997
  * 仅 runAutoMode 的首渲染点消费(--done 推进路径不重收)。
1898
1998
  */
1999
+ /**
2000
+ * 本 CLI 入口(bin/sillyspec.js)的绝对路径——wait 直通子进程自调用。
2001
+ * fileURLToPath 解 URL(ql-20260911-029:旧 pathname.replace 手解百分号编码,含空格/中文
2002
+ * 路径 ENOENT;且旧相对层级 ../bin 少一级——src/run/ 到仓根 bin 应为 ../../bin)。导出供单测锁层级。
2003
+ */
2004
+ export function resolveBinSelfPath() {
2005
+ return fileURLToPath(new URL('../../bin/sillyspec.js', import.meta.url))
2006
+ }
2007
+
1899
2008
  export async function maybeWaitInteractive(stepDef, changeName, stageName, { rlFactory = null } = {}) {
1900
2009
  try {
1901
2010
  if (!globalThis.__ssWaitInteractive) return
@@ -1920,7 +2029,7 @@ export async function maybeWaitInteractive(stepDef, changeName, stageName, { rlF
1920
2029
  console.log(`📩 已接收,自动续行:sillyspec run auto --continue --answer "${answer.trim().slice(0, 60)}${answer.trim().length > 60 ? '…' : ''}" ...`)
1921
2030
  // 复用既有 continue 路径:与 runCommand 的 --continue 同源(子进程自调避免递归锁)
1922
2031
  const { execFileSync } = await import('node:child_process')
1923
- const binSelf = new URL('../bin/sillyspec.js', import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1')
2032
+ const binSelf = resolveBinSelfPath()
1924
2033
  try {
1925
2034
  execFileSync(process.execPath, [binSelf, 'run', 'auto', '--continue', '--answer', answer.trim(), '--change', changeName], { stdio: 'inherit', cwd: process.cwd() })
1926
2035
  } catch { /* continue 失败(非 waiting 态等)静默——回三段式由 agent 走 */ }