sillyspec 3.28.2 → 3.28.4

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 (67) hide show
  1. package/.claude/skills/sillyspec-auto/SKILL.md +16 -55
  2. package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -1
  3. package/.claude/skills/sillyspec-continue/SKILL.md +8 -38
  4. package/.claude/skills/sillyspec-execute/SKILL.md +10 -2
  5. package/.claude/skills/sillyspec-knowledge/SKILL.md +19 -249
  6. package/.claude/skills/sillyspec-plan/SKILL.md +1 -1
  7. package/.claude/skills/sillyspec-propose/SKILL.md +6 -77
  8. package/.claude/skills/sillyspec-resume/SKILL.md +8 -57
  9. package/.claude/skills/sillyspec-state/SKILL.md +7 -46
  10. package/.claude/skills/sillyspec-workspace/SKILL.md +14 -156
  11. package/AGENTS.md +24 -0
  12. package/INSTRUCTIONS.md +12 -0
  13. package/package.json +1 -1
  14. package/src/archive-delta.js +127 -2
  15. package/src/change-delete.js +2 -1
  16. package/src/change-list.js +7 -2
  17. package/src/change-risk-profile.js +77 -6
  18. package/src/config-cat.js +12 -1
  19. package/src/constants.js +9 -1
  20. package/src/design-facts.js +64 -0
  21. package/src/dispatch/backends/sillyhub-mcp.js +17 -5
  22. package/src/docs-check.js +153 -27
  23. package/src/docs-migrate.js +124 -0
  24. package/src/doctor-diagnostics.js +441 -5
  25. package/src/foreign-declared.js +53 -2
  26. package/src/index.js +353 -28
  27. package/src/module-impact.js +77 -12
  28. package/src/plan-adopt-waves.js +13 -5
  29. package/src/progress/change-registry.js +35 -0
  30. package/src/progress.js +64 -1
  31. package/src/quick-recommend.js +6 -1
  32. package/src/quicklog.js +10 -2
  33. package/src/run/command.js +159 -13
  34. package/src/run/complete-handlers.js +162 -9
  35. package/src/run/complete.js +104 -5
  36. package/src/run/gates.js +116 -8
  37. package/src/run/progress-confirm.js +66 -0
  38. package/src/run/prompt.js +235 -36
  39. package/src/run/quick-audit.js +12 -5
  40. package/src/run/scan-profile.js +20 -2
  41. package/src/run/shared.js +84 -11
  42. package/src/run/stage.js +42 -0
  43. package/src/runtime-hygiene.js +84 -0
  44. package/src/scan-postcheck.js +5 -1
  45. package/src/spec-sync.js +63 -8
  46. package/src/stage-contract.js +83 -3
  47. package/src/stage-review.js +3 -0
  48. package/src/stages/archive.js +4 -4
  49. package/src/stages/brainstorm-auto.js +2 -1
  50. package/src/stages/brainstorm.js +11 -25
  51. package/src/stages/doctor.js +41 -515
  52. package/src/stages/execute.js +10 -14
  53. package/src/stages/plan-postcheck.js +191 -7
  54. package/src/stages/plan.js +7 -27
  55. package/src/stages/quick.js +15 -13
  56. package/src/stages/verify.js +23 -21
  57. package/src/sync-noise.js +158 -0
  58. package/src/sync.js +42 -17
  59. package/src/task-review.js +225 -1
  60. package/src/verify-facts-schema.js +141 -0
  61. package/src/verify-postcheck.js +421 -49
  62. package/src/verify-probes.js +142 -13
  63. package/src/workflow.js +6 -0
  64. package/src/worktree-apply.js +51 -13
  65. package/src/worktree.js +5 -1
  66. package/templates/agents-instruction.md +1 -1
  67. package/.claude/skills/verify-per-user/SKILL.md +0 -6
package/src/run/gates.js CHANGED
@@ -552,7 +552,33 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
552
552
  // runtimeRoot 进 context(坑 verify-receipt-runtime-split):verify 回执写入方
553
553
  // reapVerifyServices 落 resolveRuntimeRoot(platformOpts, specBase),校验器读侧不透传时
554
554
  // 平台/漂移模式恒读不到回执 → 证据门退回纯字面匹配(回执机制正是为消灭它而生)
555
- const contractResult = runValidators(stageName, cwd, changeName, { projectName, specRoot: platformOpts?.specRoot, runtimeRoot: resolveRuntimeRoot(platformOpts, specBase) })
555
+ //
556
+ // ── verify 收尾前置:facts slot-backfill + verifyStartAt(2026-09-08-ir-verify-facts FR-03)──
557
+ // 执行次序(design Phase 3):backfill(结论/证据/回执槽固化)先行 → runValidators(结论门/
558
+ // 集成证据门读得到固化值与 context.verifyStartAt)→ verify 专属块(test 实测 → tests 段二次
559
+ // 回填 → cannot_verify 硬门)。verifyStartAt = DB execute 行 completed_at(只读;缺省 null →
560
+ // 证据核验走 R-05 design created_at fallback)。
561
+ let verifyStartAtIso = null
562
+ if (stageName === 'verify') {
563
+ try {
564
+ verifyStartAtIso = pm.getStageCompletedAt(cwd, changeName, 'execute')
565
+ } catch { verifyStartAtIso = null }
566
+ try {
567
+ const verifyMdPath = join(specBase, 'changes', changeName, 'verify-result.md')
568
+ if (existsSync(verifyMdPath)) {
569
+ const verifyMd = readFileSync(verifyMdPath, 'utf8')
570
+ const { backfillFactsFromMdAndTests } = await import('../verify-probes.js')
571
+ const { extractVerifyConclusionSlot } = await import('../stage-contract.js')
572
+ backfillFactsFromMdAndTests(join(specBase, 'changes', changeName, 'verify-facts.json'), {
573
+ verifyMd,
574
+ conclusion: extractVerifyConclusionSlot(verifyMd),
575
+ })
576
+ }
577
+ } catch (e) {
578
+ console.warn(`⚠️ facts slot-backfill 失败(fail-soft,不阻断;门禁仍读 md 槽): ${e && e.message ? e.message : e}`)
579
+ }
580
+ }
581
+ const contractResult = runValidators(stageName, cwd, changeName, { projectName, specRoot: platformOpts?.specRoot, runtimeRoot: resolveRuntimeRoot(platformOpts, specBase), ...(verifyStartAtIso ? { verifyStartAt: verifyStartAtIso } : {}) })
556
582
  if (contractResult.errors.length > 0) {
557
583
  console.error(`\n❌ 阶段 ${stageName} 校验失败:`)
558
584
  for (const err of contractResult.errors) {
@@ -584,7 +610,9 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
584
610
  try {
585
611
  const { resolveApplyAllowSet } = await import('../worktree-apply.js')
586
612
  const { warnFacadeCandidateFiles } = await import('../facade-hint.js')
587
- const allowMap = resolveApplyAllowSet(cwd, changeName)
613
+ // specBase 显式传(2026-09-08 平台模式修复):changes/ 实体在 specRoot,本地目录常空——
614
+ // 旧硬编码在平台模式 allow 集恒空,facade 候选预检对空集全量误报
615
+ const allowMap = resolveApplyAllowSet(cwd, changeName, { specBase })
588
616
  warnFacadeCandidateFiles({ cwd, changeName, allowSet: allowMap.get('main') })
589
617
  } catch { /* 预检失败不阻断 plan 完成 */ }
590
618
  }
@@ -598,6 +626,21 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
598
626
  console.log(`\n⏳ Verify 测试对账:CLI 亲自执行 local.yaml 的 commands.test(同步,耗时可能较长,请等待…)`)
599
627
  const testCheck = runVerifyTestCheck({ cwd, specBase, changeName, ctx })
600
628
  printVerifyTestCheck(testCheck)
629
+ // tests 段二次回填(2026-09-08-ir-verify-facts FR-03 次序:实测在 runValidators 之后,
630
+ // tests 快照此刻才可得;不参与门禁——供 P3d 追溯)
631
+ try {
632
+ const verifyMdPath2 = join(specBase, 'changes', changeName, 'verify-result.md')
633
+ if (existsSync(verifyMdPath2)) {
634
+ const { backfillFactsFromMdAndTests } = await import('../verify-probes.js')
635
+ const { extractVerifyConclusionSlot } = await import('../stage-contract.js')
636
+ const verifyMd2 = readFileSync(verifyMdPath2, 'utf8')
637
+ backfillFactsFromMdAndTests(join(specBase, 'changes', changeName, 'verify-facts.json'), {
638
+ verifyMd: verifyMd2,
639
+ conclusion: extractVerifyConclusionSlot(verifyMd2),
640
+ testCheckResult: testCheck,
641
+ })
642
+ }
643
+ } catch { /* fail-soft:回填失败不影响门禁 */ }
601
644
  if (testCheck.status === 'failed') {
602
645
  console.error('\n❌ verify 阶段被阻断:verify-result.md 自报告通过,但 CLI 实测测试失败。')
603
646
  // 并行 WIP 归因鉴别(坑 verify-reconcile-foreign-wip):实测跑在主仓共享工作区,并行会话
@@ -650,6 +693,19 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
650
693
  console.log(`\n⏳ Verify lint 对账:CLI 亲自执行 local.yaml 的 commands.lint…`)
651
694
  }
652
695
  printVerifyLintCheck(lintCheck)
696
+ // lint 硬门(2026-09-09 升硬:观察期 14 次 5 败全真阳性,pre-push 本就硬拦——fail-fast 前移;
697
+ // 逃生 SILLYSPEC_VERIFY_LINT_GATE=advisory)
698
+ const { shouldBlockVerifyLint } = await import('../verify-postcheck.js')
699
+ if (shouldBlockVerifyLint(lintCheck)) {
700
+ console.error('\n❌ verify 阶段被阻断:CLI 亲自实测 lint 失败(agent 的 lint 自报告与实测不符时以实测为准)。')
701
+ if (lintCheck.outputTail) {
702
+ const tail = lintCheck.outputTail.split('\n').slice(-8).join('\n')
703
+ console.error(' 输出(末尾):')
704
+ for (const line of tail.split('\n')) console.error(` | ${line}`)
705
+ }
706
+ console.error(' 修复后重跑 --done(进度不丢);确认要跳过实测请设 SILLYSPEC_VERIFY_LINT_GATE=advisory(审计留痕)。')
707
+ return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts)
708
+ }
653
709
  // 契约 parity 对账:扫前端 API 调用 vs execute 提取的 provider endpoint artifact。
654
710
  // 接线自 contract-matrix pipeline(verifyApiParity 的 CLI 入口)。
655
711
  const { runVerifyParityCheck, printVerifyParityCheck } = await import('../verify-postcheck.js')
@@ -660,12 +716,26 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
660
716
  const { runVerifyDeletionCheck, printVerifyDeletionCheck } = await import('../verify-postcheck.js')
661
717
  const deletionCheck = runVerifyDeletionCheck({ cwd, specBase, changeName })
662
718
  printVerifyDeletionCheck(deletionCheck)
663
- // ── required-evidence 对账(advisory,不阻断):闭合 execute→verify evidence 死链 ──
664
- // execute Task Review Gate 把 cannot_verify 任务的 evidence 落盘 verify-required-evidence.json,
665
- // 本探针查每个 cannot_verify 任务是否在 verify-result.md 体现(CLI 只查提及,满足度 agent 自报告)。
719
+ // ── required-evidence 对账(2026-09-08-ir-verify-facts FR-03:v2 起 cannot_verify 不闭环阻断)──
720
+ // execute Task Review Gate 把 cannot_verify 任务的 evidence 落盘 verify-required-evidence.json。
721
+ // v2 槽优先分类核验(FR-02):blocked = missing 无豁免或 satisfied 核验不过 → 阻断 verify 完成
722
+ // (此前 advisory 只查「提及」——死链不闭环);无槽存量 md 降级 legacy 子串对账(warning 不阻断)。
666
723
  const { runVerifyRequiredEvidenceCheck, printVerifyRequiredEvidenceCheck } = await import('../verify-postcheck.js')
667
- const evidenceCheck = runVerifyRequiredEvidenceCheck({ cwd, specBase, changeName })
724
+ const evidenceCheck = runVerifyRequiredEvidenceCheck({ cwd, specBase, changeName, verifyStartAt: verifyStartAtIso })
668
725
  printVerifyRequiredEvidenceCheck(evidenceCheck)
726
+ if (evidenceCheck.status === 'blocked') {
727
+ console.error(`\n❌ verify 阶段被阻断:cannot_verify 证据账未闭环(${evidenceCheck.summary})。`)
728
+ for (const d of (evidenceCheck.detailed || [])) {
729
+ console.error(` - ${d.task} [${d.status}${d.exempt ? '/豁免' : ''}] ${d.reason}`)
730
+ for (const v of (d.verification || [])) {
731
+ if (v && v.reason) console.error(` · ${v.path || '(无路径)'} [${v.pathClass || '?'}] ${v.reason}(filesExist=${v.filesExist} mtimeOk=${v.mtimeOk} diffHit=${v.diffHit})`)
732
+ }
733
+ }
734
+ console.error(` 修复:在 verify-result.md「## 证据账(cannot_verify 任务)」槽段逐 task 一行——`)
735
+ console.error(` - task-NN: satisfied | verifiedFiles: <精确路径>(CLI 核验:代码类 存在×mtime×diff 交集;日志/文档类豁免 diff)`)
736
+ console.error(` - 确实无法验证 → 填 missing 并加(豁免:<一句话理由>)后缀;部分满足 → partial + 已核验路径`)
737
+ return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts)
738
+ }
669
739
  // ── target_files 声明 ↔ 实际改动 对账(task-05 / ir-stage-p3a:②ERROR 阻断 / ③WARNING 放行)──
670
740
  // 「计划落空」(task 卡声明没做)此前全盲区:review.json changedFiles 是 agent 手写不能当
671
741
  // 事实源,execute 的 scope creep 只有 symbol-impact 试图抓。reconcileTargetFiles 用 git 三源
@@ -674,13 +744,15 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
674
744
  // 声明=scope creep)启发式噪音面大(并行 WIP 剔除后仍可能有工具产物),WARNING 放行留审计;
675
745
  // skipped/degraded(无 task 卡 / 全无声明 / git 不可用)降级不误红(存量变更零红门禁)。
676
746
  // 阻断形态照 runVerifyTestCheck 先例:逐条列 task + path + 修复提示 + rollbackCompletionAndReturn。
677
- const { reconcileTargetFiles } = await import('../verify-postcheck.js')
747
+ const { reconcileTargetFiles, isStrictChange } = await import('../verify-postcheck.js')
678
748
  const reconcileRuntimeRoot = resolveRuntimeRoot(platformOpts, specBase)
679
749
  const reconcileCheck = reconcileTargetFiles({
680
750
  cwd, specBase, changeName,
681
751
  // runtimeRoot 口径与上方 parity 对账同源(B3 apply-pathspec 兜底源在此根下;平台模式下
682
752
  // specBase 与 .runtime 分离,不传会读不到兜底清单 → 假降级)
683
753
  runtimeRoot: reconcileRuntimeRoot,
754
+ // IR 严格档(2026-09-07-ir-hardening D-003@v1):主仓卡全零声明 → strictViolation → ERROR
755
+ strictMode: isStrictChange({ pm, cwd, changeName }),
684
756
  })
685
757
  const reconcileEnvelope = buildReconcileTargetFilesEnvelope(reconcileCheck)
686
758
  const reconcileBlocked = printReconcileTargetFilesCheck(reconcileCheck, reconcileEnvelope)
@@ -705,6 +777,9 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
705
777
  // 的锚点产物实际都在 changeDir,此参为统一传参签名对齐预留,口径一处定义不二算
706
778
  cwd, specBase, changeName,
707
779
  runtimeRoot: reconcileRuntimeRoot,
780
+ // IR 严格档(2026-09-07-ir-hardening D-002@v1):created_at ≥ IR_STRICT_SINCE 的变更
781
+ // 探针子节全缺 = ERROR(存量 skip 语义零变化);fail-open(读不到 → false 落豁免)
782
+ strictMode: isStrictChange({ pm, cwd, changeName }),
708
783
  })
709
784
  const probeEnvelope = buildProbeConsistencyEnvelope(probeCheck)
710
785
  const probeBlocked = printProbeConsistencyCheck(probeCheck, probeEnvelope)
@@ -716,6 +791,24 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
716
791
  if (probeBlocked) {
717
792
  return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts)
718
793
  }
794
+ // ── 行号漂移自动重锚(2026-09-09 §7-6:quick --done 已接,verify/archive 收尾补接)──
795
+ // 本变更 diff 中的 .md 文档经 autoReanchorDocRefs 定点重锚(fixable 唯一/优选命中才改 +
796
+ // 同口径回执),消灭「src 改动平移行号 → 下次 docs gate 拦」的人工往返。挂 verify 收尾即
797
+ // 覆盖 archive 面(漂移源是 src 改动,archive 的文档同步步不产生新 src 平移)。fail-open。
798
+ try {
799
+ const { resolveVerifyChangedFiles } = await import('../verify-postcheck.js')
800
+ const mdChangedVa = (resolveVerifyChangedFiles(cwd, changeName, null, { includeWorkingTree: true, specBase }) || [])
801
+ .filter(f => f.endsWith('.md') && !f.startsWith('.sillyspec/changes/'))
802
+ if (mdChangedVa.length > 0) {
803
+ const { autoReanchorDocRefs, readDocsCheckConfig } = await import('../docs-check.js')
804
+ let fixCfgVa = {}
805
+ try { fixCfgVa = readDocsCheckConfig(cwd) || {} } catch { fixCfgVa = {} }
806
+ const rVa = autoReanchorDocRefs(cwd, mdChangedVa, fixCfgVa)
807
+ if (rVa.applied > 0) {
808
+ console.log(`🔧 文档行号漂移自动重锚(${stageName} 收尾):${rVa.applied} 处已改写(${rVa.invalidBefore} → ${rVa.invalidAfter};剩余 ${rVa.remaining} 处需人工 sillyspec docs check)`)
809
+ }
810
+ }
811
+ } catch { /* 自动重锚失败不影响收尾(docs gate 兜底) */ }
719
812
  // ── module-impact 死信探针(blocking,债单 D-1/D-5):更新结果表 pending/待办行 → 阻断 verify ──
720
813
  // 与 archive 移动前校验(extractPendingDocSyncRows)同一口径,把死信号从 archive 提前到 verify:
721
814
  // agent 在 verify 阶段就须完成文档同步并回填 done/skipped,而非拖到归档被拦(修复 perf-remediation
@@ -1357,6 +1450,14 @@ export function printReconcileTargetFilesCheck(r, envelope = buildReconcileTarge
1357
1450
  console.warn(` 对账口径 actual=${(r.sources || []).join(' + ') || 'n/a'}(③类误报先核对 sources 与并行会话剔除提示)。`)
1358
1451
  return false
1359
1452
  }
1453
+ // IR 严格档(2026-09-07-ir-hardening D-003):strictViolation → ERROR 阻断(与②类同处置)
1454
+ if (r.status === 'skipped' && r.strictViolation) {
1455
+ console.error(`
1456
+ ❌ target_files 对账阻断:${r.skipReason}`)
1457
+ console.error(` ${r.strictViolation.message}`)
1458
+ console.error(` 信封 code: ${r.strictViolation.code}`)
1459
+ return true
1460
+ }
1360
1461
  // 降级(skipped/degraded):WARNING 放行,skipReason 一句话(存量卡/git 不可用,不误红)
1361
1462
  if (r.status === 'skipped' || r.status === 'degraded') {
1362
1463
  console.warn(`\n⚠️ target_files 对账${r.status === 'degraded' ? '降级' : '跳过'}(不阻断):${r.skipReason}`)
@@ -1385,6 +1486,12 @@ export function printReconcileTargetFilesCheck(r, envelope = buildReconcileTarge
1385
1486
  export function buildProbeConsistencyEnvelope(r) {
1386
1487
  const blocked = r.status === 'mismatch' && r.severity === 'error'
1387
1488
  const drifted = r.status === 'mismatch' && !blocked
1489
+ // IR 严格档独立 code(2026-09-07-ir-hardening D-002):首条 mismatch 携带
1490
+ // probe_prefill_missing_strict 时路由专用 code(平台/上游按 code 分支可区分
1491
+ // 「篡改预填段」与「从未生成预填段」两类 ERROR)
1492
+ const strictMissing = blocked
1493
+ && Array.isArray(r.mismatches) && r.mismatches.length > 0
1494
+ && r.mismatches[0] && r.mismatches[0].code === 'probe_prefill_missing_strict'
1388
1495
  const supportedFixes = []
1389
1496
  if (blocked) {
1390
1497
  supportedFixes.push('如实重生成:跑 `sillyspec verify-probes --change <变更名> --init` 重新落盘机械预填段,再基于新预填段如实补写正文结论(删改预填段过门是 Step7 防篡改纪律红线)')
@@ -1396,7 +1503,8 @@ export function buildProbeConsistencyEnvelope(r) {
1396
1503
  }
1397
1504
  return {
1398
1505
  // code 机器路由键(task-02 JSDoc):四状态→四 code,供平台/上游按 code 分支
1399
- code: blocked ? 'probe_consistency_mismatch'
1506
+ code: strictMissing ? 'probe_prefill_missing_strict'
1507
+ : blocked ? 'probe_consistency_mismatch'
1400
1508
  : drifted ? 'probe_consistency_drift'
1401
1509
  : (r.status === 'ok' ? 'probe_consistency_ok' : 'probe_consistency_skipped'),
1402
1510
  name: 'probe_consistency',
@@ -0,0 +1,66 @@
1
+ /**
2
+ * progressConfirm — 主流程 Step1「进度确认」的 noAI 动作(brainstorm / execute / verify)
3
+ * (2026-09-07 noAI 主流程首批;机制先例:scan 系 noAI 步 + planPostcheck,见 run/stage.js noAI 分支)
4
+ *
5
+ * noAI 化依据(step1 职责在历次加固后已全部机械化):
6
+ * - 阶段路由:`run <stage>` 显式调用即写 currentStage(run/stage.js D-003@v1),
7
+ * 阶段顺序违规由 checkTransition 拦截——原 prompt 的「阶段不符则停止」检查已残留化
8
+ * ({PROGRESS_SNAPSHOT} 注入文案本就写着「CLI 已路由到本阶段」);
9
+ * - 进度摘要:快照三行(变更/阶段/步骤位置)由 CLI 注入,agent 的「输出当前进度摘要」
10
+ * 只是把注入内容复述一遍再 --done——整轮往返零判断增量;
11
+ * - brainstorm 变更名检查:自动名在创建时 CLI 已打印 rename 提示(run/command.js),
12
+ * 此处机械复检一次模式命中;命中只打印 ⚠️ 建议(noAI 步的 console 输出仍在 CLI 工具
13
+ * 结果里,agent 可见、可在后续步骤与用户交互改名)——不 throw、不阻塞推进。
14
+ * - execute 执行范围:$ARGUMENTS 是会话层信息,CLI 不掺入;本步只补 tasks 勾选计数
15
+ * (readPlanCheckboxStatus 既有机器事实),范围以用户在会话中给出的为准。
16
+ * - verify 测试提示:原 prompt 首段的「CLI 在 --done 统一跑测试」要点改为 console 提示。
17
+ *
18
+ * 输出纪律:只 console 打印(noAI 步不写 step output,与 scan 系动作一致);
19
+ * 纯确认步无失败语义,不 throw。
20
+ */
21
+ import { join } from 'node:path'
22
+
23
+ /** brainstorm 自动生成变更名的模式(run/command.js:1083 `${date}-new-change-${hex}`) */
24
+ const AUTO_CHANGE_NAME_RE = /^\d{4}-\d{2}-\d{2}-new-change-[a-f0-9]+$/
25
+
26
+ /**
27
+ * 执行「进度确认」noAI 动作:打印进度快照 + 阶段专属机械提示。
28
+ *
29
+ * @param {object} opts
30
+ * @param {string} opts.stageName - brainstorm / execute / verify
31
+ * @param {string} opts.cwd
32
+ * @param {object} opts.stageData - progress.stages[stageName](steps 数组)
33
+ * @param {string|null} opts.changeName
34
+ * @param {object|null} [opts.pm] - ProgressManager(execute 档 tasks 勾选计数用;缺省跳过该段)
35
+ */
36
+ export function executeProgressConfirm({ stageName, cwd, stageData, changeName, pm = null }) {
37
+ const steps = Array.isArray(stageData?.steps) ? stageData.steps : []
38
+ const done = steps.filter(s => s.status === 'completed').length
39
+ console.log('📊 进度快照(CLI 注入,无需跑 progress show):')
40
+ console.log(` - 变更:${changeName || '(未知)'}`)
41
+ console.log(` - 阶段:${stageName}(CLI 已路由到本阶段)`)
42
+ console.log(` - 步骤:${done}/${steps.length} 已完成;本步已由 CLI 自动确认`)
43
+
44
+ if (stageName === 'brainstorm' && changeName && AUTO_CHANGE_NAME_RE.test(changeName)) {
45
+ console.log('')
46
+ console.log(`⚠️ 变更名「${changeName}」疑似自动生成——建议与用户确认语义化名称后执行:`)
47
+ console.log(` sillyspec change-rename ${changeName} <新名称>(勿用 mv,会漏改进度库引用)`)
48
+ }
49
+
50
+ if (stageName === 'execute' && pm && changeName) {
51
+ try {
52
+ const changeDir = join(pm._getSpecDir(cwd), 'changes', changeName)
53
+ const plan = pm.readPlanCheckboxStatus(changeDir)
54
+ if (plan && plan.total > 0) {
55
+ console.log(` - 任务勾选:tasks ${plan.checked}/${plan.total}(执行范围以用户会话指定为准,未指定则全部 Wave)`)
56
+ }
57
+ } catch { /* 勾选计数 fail-soft:plan.md 缺失/解析失败不阻塞确认步 */ }
58
+ }
59
+
60
+ if (stageName === 'verify') {
61
+ console.log('')
62
+ console.log('💡 `run verify` 只下发指令不跑测试——全量测试由 CLI 在最后 --done 统一执行')
63
+ console.log(' (local.yaml 的 commands.test,同步对账可能耗时较长);后续「运行测试和质量扫描」')
64
+ console.log(' 步骤无需手动跑全量,只做 lint/静态检查 + 可选快速冒烟。')
65
+ }
66
+ }