sillyspec 3.29.3 → 3.29.5

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 (82) hide show
  1. package/asset-audit-2026-09-20/README.md +42 -0
  2. package/asset-audit-2026-09-20/asset-audit.cjs +253 -0
  3. package/asset-audit-2026-09-20/extract-candidates.cjs +37 -0
  4. package/asset-audit-2026-09-20/platform-archive.csv +294 -0
  5. package/asset-audit-2026-09-20/platform-fr.csv +12 -0
  6. package/asset-audit-2026-09-20/platform-gotchas.csv +6 -0
  7. package/asset-audit-2026-09-20/platform-knownissues.csv +23 -0
  8. package/asset-audit-2026-09-20/platform-modules.csv +222 -0
  9. package/asset-audit-2026-09-20/platform-quicklog.csv +1076 -0
  10. package/asset-audit-2026-09-20/platform-troubleshooting-candidates.csv +88 -0
  11. package/asset-audit-2026-09-20/platform-troubleshooting.csv +1 -0
  12. package/asset-audit-2026-09-20/sillyspec-archive.csv +108 -0
  13. package/asset-audit-2026-09-20/sillyspec-fr.csv +51 -0
  14. package/asset-audit-2026-09-20/sillyspec-gotchas.csv +4 -0
  15. package/asset-audit-2026-09-20/sillyspec-knownissues.csv +34 -0
  16. package/asset-audit-2026-09-20/sillyspec-modules.csv +30 -0
  17. package/asset-audit-2026-09-20/sillyspec-quicklog.csv +460 -0
  18. package/asset-audit-2026-09-20/sillyspec-troubleshooting.csv +69 -0
  19. package/asset-audit-2026-09-20/summary.json +241 -0
  20. package/assets/command-cards/run-archive.md +26 -0
  21. package/assets/command-cards/run-brainstorm.md +33 -0
  22. package/assets/command-cards/run-execute.md +29 -0
  23. package/assets/command-cards/run-plan.md +27 -0
  24. package/assets/command-cards/run-quick.md +34 -0
  25. package/assets/command-cards/run-verify.md +29 -0
  26. package/assets/command-cards/status.md +27 -0
  27. package/package.json +1 -1
  28. package/round4/account.mjs +85 -0
  29. package/round4/brief-autocompact.md +34 -0
  30. package/round4/brief-session-replay.md +50 -0
  31. package/round4/deep-read-synthesis.md +38 -0
  32. package/round4/extract-full.mjs +38 -0
  33. package/round4/extract-timeline.mjs +43 -0
  34. package/round4/find-sessions.mjs +13 -0
  35. package/round4/full-r4-l.md +4707 -0
  36. package/round4/full-r4-o-l.md +4300 -0
  37. package/round4/full-r4-os.md +1681 -0
  38. package/round4/full-r4-sf.md +4255 -0
  39. package/round4/full-r4-sq.md +1734 -0
  40. package/round4/gate-audit-relay-receipt.md +30 -0
  41. package/round4/gate-value-audit.md +52 -0
  42. package/round4/prereg.md +67 -0
  43. package/round4/prompt-R4-L.md +63 -0
  44. package/round4/prompt-R4-O-L.md +59 -0
  45. package/round4/prompt-R4-O-S.md +56 -0
  46. package/round4/prompt-R4-S-F.md +60 -0
  47. package/round4/prompt-R4-S-Q.md +66 -0
  48. package/round4/r4-os-timeline.txt +297 -0
  49. package/round4/r4-sf-timeline.txt +738 -0
  50. package/round4/r4-sq-timeline.txt +322 -0
  51. package/round4/r4l-timeline.txt +793 -0
  52. package/round4/r4ol-timeline.txt +729 -0
  53. package/round4/r4sf-timeline.txt +852 -0
  54. package/round4/result-S.md +171 -0
  55. package/round4/result-final.md +46 -0
  56. package/round4/result-quality.md +63 -0
  57. package/src/command-cards.js +73 -0
  58. package/src/contract-matrix.js +26 -5
  59. package/src/cross-repo-reconcile.js +232 -46
  60. package/src/diagnostic-codes.js +2 -1
  61. package/src/fr-index.js +58 -2
  62. package/src/index.js +35 -8
  63. package/src/init.js +25 -8
  64. package/src/machine-interface.js +88 -3
  65. package/src/module-impact.js +51 -22
  66. package/src/run/bg-sync.js +231 -0
  67. package/src/run/command.js +4 -0
  68. package/src/run/complete-handlers.js +244 -6
  69. package/src/run/complete.js +6 -3
  70. package/src/run/gate-snapshot.js +10 -4
  71. package/src/run/gates.js +124 -42
  72. package/src/run/prompt.js +1 -1
  73. package/src/run/quick-audit.js +28 -2
  74. package/src/run/shared.js +35 -1
  75. package/src/scope-audit.js +303 -20
  76. package/src/stages/execute.js +2 -2
  77. package/src/stages/plan-postcheck.js +33 -19
  78. package/src/stages/quick.js +2 -0
  79. package/src/sync.js +12 -0
  80. package/src/taskcard-frontmatter.js +57 -0
  81. package/src/verify-postcheck.js +68 -6
  82. package/src/verify-probes.js +45 -23
package/src/run/gates.js CHANGED
@@ -861,6 +861,47 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
861
861
  } catch { /* advisory 失败不阻断完成(fail-soft,facade 预检先例同款) */ }
862
862
  }
863
863
 
864
+ // ── fail-fast 前置:纯文档 blocking 检查先于 test/lint 实测门(R4-S-F 实证,2026-09-21)──
865
+ // module-impact 死信与预填注清零 error 门只读文档零外部执行,原先排在 9 分钟级 test+lint
866
+ // 实测门之后——文档未清时每轮 --done 都先白烧一轮门禁再被拦(R4-S-F 166min 的主要形状)。
867
+ // 前置到实测门之前:秒级失败秒级返工,贵的门只在文档面已清后才跑。
868
+ if (stageName === 'verify' && changeName) {
869
+ // module-impact 死信探针(blocking,债单 D-1/D-5):更新结果表 pending/待办行 → 阻断 verify。
870
+ // 与 archive 移动前校验(extractPendingDocSyncRows)同一口径,把死信号从 archive 提前到 verify:
871
+ // agent 在 verify 阶段就须完成文档同步并回填 done/skipped,而非拖到归档被拦。
872
+ const verifyChangeDir = resolveChangeDir(cwd, progress, platformOpts?.specRoot)
873
+ if (verifyChangeDir) {
874
+ const impactPath = join(verifyChangeDir, 'module-impact.md')
875
+ if (existsSync(impactPath)) {
876
+ const { extractPendingDocSyncRows } = await import('./complete-handlers.js')
877
+ const pendingRows = extractPendingDocSyncRows(readFileSync(impactPath, 'utf8'))
878
+ if (pendingRows.length > 0) {
879
+ console.error(`\n❌ verify 阶段被阻断:module-impact.md「更新结果」表存在 ${pendingRows.length} 个未清 pending/待办项(死信)`)
880
+ for (const row of pendingRows) console.error(` - ${row}`)
881
+ console.error(' 文档同步是 verify 的收尾义务:请完成模块文档同步并回填状态为 done/skipped(说明原因),再重新完成 verify。')
882
+ return rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-contract' })
883
+ }
884
+ }
885
+ }
886
+ // 预填注清零 error 门(归档前最后一道;2026-09-18-artifact-prefill task-03 / FR-03 / D-003@v1
887
+ // 门禁梯度·error 档):白名单槽宿主文件(design.md + tasks/task-*.md)仍含未删预填注 →
888
+ // 阻断 verify 完成。brainstorm/plan --done 的 advisory 在此收口为硬门,注在场=预填未确认。
889
+ // 检测单点 = runProbe10PrefillNoteClearance(不二算)。已知误报面:散文引用注字面量会命中
890
+ // ——修法是核对后删注/改写散文措辞,不是绕门。
891
+ try {
892
+ const { runProbe10PrefillNoteClearance } = await import('../verify-probes.js')
893
+ const noteClearance = runProbe10PrefillNoteClearance({ specBase, changeName })
894
+ if (noteClearance.unclearedFiles.length > 0) {
895
+ console.error(`\n❌ verify 阶段被阻断:预填注清零校验未过(${noteClearance.unclearedFiles.length} 个文件的白名单槽仍含未删预填注——预填≠结论,注在场=未确认):`)
896
+ for (const f of noteClearance.unclearedFiles) console.error(` - ${f}`)
897
+ console.error(' 修复:逐槽核对预填值后删除行尾「(预填:核对后删本注)」注(删注=确认动作);散文含注字面量的命中改写该处措辞。清零后重新完成 verify(进度不丢)。')
898
+ return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-contract' })
899
+ }
900
+ } catch (e) {
901
+ console.warn(`⚠️ 预填注清零校验异常(降级放行,fail-soft——异常不是未清零,ceremony 双跑同款处置): ${(e && e.message) || e}`)
902
+ }
903
+ }
904
+
864
905
  // verify 产物校验通过 + 结论非 FAIL(否则上面已阻断)。
865
906
  // 再由 CLI 亲自执行 local.yaml 的测试命令,与 verify-result.md 的自报告对账:
866
907
  // 自报告 PASS 但实测失败 → 阻断(防止"文案通过"绕过验证)。
@@ -937,6 +978,20 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
937
978
  }
938
979
  } catch { /* fail-soft:回填失败不影响门禁 */ }
939
980
  if (testCheck.status === 'failed') {
981
+ // ── 纯超时降档(R4 门禁价值考古 2026-09-21:227 跑 106 败里 36 次 600s 帽杀纯超时假拦
982
+ // =失败桶最大单一来源)。超时=未完成非测试挂——所有失败单元 reason 均含超时 → advisory
983
+ // 放行带处置指引;任一单元真实挂测/退出码 → 维持原硬拦(真拦 12 次的防线不动)。──
984
+ let timeoutOnly = false
985
+ try {
986
+ const { isTimeoutOnlyTestFailure } = await import('../verify-postcheck.js')
987
+ timeoutOnly = isTimeoutOnlyTestFailure(testCheck)
988
+ } catch { /* 判定链异常维持硬拦(保守) */ }
989
+ if (timeoutOnly) {
990
+ if (verifyGateSnap) { try { verifyGateSnap.cleanup() } catch {} ; verifyGateSnap = null }
991
+ console.warn('\n⚠️ Verify 实测门未在超时帽内完成(纯超时,无任何挂掉的测试)——降档 advisory 不阻断:')
992
+ console.warn(` ${testCheck.reason || ''}`)
993
+ console.warn(' 处置:① local.yaml 配置 modules: 块(name: { path, test })启用模块子集实测——触碰模块全套测试,时长与超时同步收窄;② 定向复跑触碰模块自证;③ 确需长跑设 SILLYSPEC_TEST_TIMEOUT_MS。')
994
+ } else {
940
995
  if (verifyGateSnap) {
941
996
  try { const { printSnapshotFailureHint } = await import('./gate-snapshot.js')
942
997
  printSnapshotFailureHint(verifyGateSnap) } catch { /* 提示失败不影响阻断 */ }
@@ -953,6 +1008,7 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
953
1008
  } catch { /* 归因提示失败不影响阻断语义 */ }
954
1009
  console.error(' 请修复失败的测试并更新 verify-result.md 后重新完成此步骤。')
955
1010
  return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-test' })
1011
+ } // else(纯超时降档分支)结束——真实测试失败分支的 rollback 语义不变
956
1012
  }
957
1013
  // lint 对账(2026-08-21 审查 CLI-1):test 侧"自报告 PASS 但实测失败→阻断"已闭环,
958
1014
  // lint 侧此前纯口头——CLI 亲自执行 commands.lint,advisory 起步(失败打印不阻断,观察期后升级)
@@ -983,6 +1039,22 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
983
1039
  // 逃生 SILLYSPEC_VERIFY_LINT_GATE=advisory)
984
1040
  const { shouldBlockVerifyLint } = await import('../verify-postcheck.js')
985
1041
  if (shouldBlockVerifyLint(lintCheck)) {
1042
+ // ── lint 归属鉴定降档(R4-S-F 实证:verify 侧 lint 硬门 7 轮全是 HEAD 存量债——隔离
1043
+ // 快照含基线债文件时恒败,agent 被逼范围外清偿或逃生口)。失败输出提及文件 × 本变更
1044
+ // 文件集零交集 → 存量债 advisory 降档放行(债非本变更引入,硬拦只是制造范围外功);
1045
+ // 有交集 / 输出无可识别路径(unattributable,纯配置错误类)→ 维持硬拦(保守)。──
1046
+ let lintOwnership = null
1047
+ try {
1048
+ const { triageLintOwnership, resolveVerifyChangedFiles } = await import('../verify-postcheck.js')
1049
+ const lintScopeFiles = (resolveVerifyChangedFiles(cwd, changeName, null, { includeWorkingTree: true, specBase }) || [])
1050
+ .map(f => String(f).replace(/\\/g, '/')).filter(f => !f.startsWith('.sillyspec/'))
1051
+ lintOwnership = triageLintOwnership({ failureFiles: lintCheck.failureFiles || [], changeFiles: lintScopeFiles })
1052
+ } catch { /* 鉴定链异常维持硬拦(保守) */ }
1053
+ if (lintOwnership && lintOwnership.verdict === 'pre-existing') {
1054
+ console.warn('\n⚠️ Verify lint 实测失败,但归属鉴定为 HEAD 存量债(失败提及文件与本变更文件集零交集)——降档 advisory 不阻断 verify 完成:')
1055
+ console.warn(` 存量债文件:${(lintCheck.failureFiles || []).join('、') || '(见上方输出)'}`)
1056
+ console.warn(' 处置建议:单独 quick 机械清偿(顺手清债解锁全仓 lint 门),或本变更确认无关时设 SILLYSPEC_VERIFY_LINT_GATE=advisory(审计留痕)。债随基线存在,与本变更交付无关。')
1057
+ } else {
986
1058
  console.error('\n❌ verify 阶段被阻断:CLI 亲自实测 lint 失败(agent 的 lint 自报告与实测不符时以实测为准)。')
987
1059
  if (usedSnapInfo) {
988
1060
  try { const { printSnapshotFailureHint } = await import('./gate-snapshot.js')
@@ -1006,6 +1078,7 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
1006
1078
  }
1007
1079
  console.error(' 修复后重跑 --done(进度不丢);确认要跳过实测请设 SILLYSPEC_VERIFY_LINT_GATE=advisory(审计留痕)。')
1008
1080
  return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-lint' })
1081
+ } // else(pre-existing 降档分支)结束——维持硬拦分支的 rollback 语义不变
1009
1082
  }
1010
1083
  // 契约 parity 对账:扫前端 API 调用 vs execute 提取的 provider endpoint artifact。
1011
1084
  // 接线自 contract-matrix pipeline(verifyApiParity 的 CLI 入口)。
@@ -1076,6 +1149,7 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
1076
1149
  // 放行态),fail-soft 不影响 gate 判定;②类阻断回执同样留档供平台/审计消费
1077
1150
  writeReconcileRunResult({ runtimeRoot: reconcileRuntimeRoot, changeName, envelope: reconcileEnvelope, result: reconcileCheck })
1078
1151
  if (reconcileBlocked) {
1152
+ writeVerifyGatePointer({ runtimeRoot: reconcileRuntimeRoot, changeName, blocked: true, note: 'target_files ②类(missing_declared)阻断' })
1079
1153
  return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-contract' })
1080
1154
  }
1081
1155
  // ── verify-result.md 探针预填段一致性抽查(task-03 / ir-stage-p3b D-002@v1 方案A)──
@@ -1122,6 +1196,7 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
1122
1196
  // 两检查间耦合;各写各的回执、按文件名订阅更干净。fail-soft 不影响 gate 判定
1123
1197
  writeProbeConsistencyRunResult({ runtimeRoot: reconcileRuntimeRoot, changeName, envelope: probeEnvelope, result: probeCheck })
1124
1198
  if (probeBlocked) {
1199
+ writeVerifyGatePointer({ runtimeRoot: reconcileRuntimeRoot, changeName, blocked: true, note: '探针一致性抽查 error 阻断' })
1125
1200
  return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-contract' })
1126
1201
  }
1127
1202
  // ── ceremony 双跑收口·第一出口(2026-09-18-ceremony-risk-pricing task-04 / FR-03 / D-003;
@@ -1164,43 +1239,9 @@ export async function runStageCompletionGates({ stageName, cwd, changeName, plat
1164
1239
  }
1165
1240
  }
1166
1241
  } catch { /* 自动重锚失败不影响收尾(docs gate 兜底) */ }
1167
- // ── module-impact 死信探针(blocking,债单 D-1/D-5):更新结果表 pending/待办行 → 阻断 verify ──
1168
- // 与 archive 移动前校验(extractPendingDocSyncRows)同一口径,把死信号从 archive 提前到 verify:
1169
- // agent 在 verify 阶段就须完成文档同步并回填 done/skipped,而非拖到归档被拦(修复 perf-remediation
1170
- // 类「verify PASS → archive 才发现 pending」的时序漏洞)。
1171
- const verifyChangeDir = resolveChangeDir(cwd, progress, platformOpts?.specRoot)
1172
- if (verifyChangeDir) {
1173
- const impactPath = join(verifyChangeDir, 'module-impact.md')
1174
- if (existsSync(impactPath)) {
1175
- const { extractPendingDocSyncRows } = await import('./complete-handlers.js')
1176
- const pendingRows = extractPendingDocSyncRows(readFileSync(impactPath, 'utf8'))
1177
- if (pendingRows.length > 0) {
1178
- console.error(`\n❌ verify 阶段被阻断:module-impact.md「更新结果」表存在 ${pendingRows.length} 个未清 pending/待办项(死信)`)
1179
- for (const row of pendingRows) console.error(` - ${row}`)
1180
- console.error(' 文档同步是 verify 的收尾义务:请完成模块文档同步并回填状态为 done/skipped(说明原因),再重新完成 verify。')
1181
- return rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-contract' })
1182
- }
1183
- }
1184
- }
1185
- // ── 预填注清零 error 门(归档前最后一道;2026-09-18-artifact-prefill task-03 / FR-03 /
1186
- // D-003@v1 门禁梯度·error 档):白名单槽宿主文件(design.md + tasks/task-*.md)仍含
1187
- // 未删预填注 → 阻断 verify 完成(verify 完成是 archive 前置——「归档前注清零」的落点;
1188
- // brainstorm/plan --done 的 advisory 在此收口为硬门,注在场=预填未确认)。检测单点 =
1189
- // verify-probes 探针面 runProbe10PrefillNoteClearance(探针 10 --init 预填与本门同一
1190
- // 实现,不二算)。已知误报面:散文引用注字面量会命中——修法是核对后删注/改写散文
1191
- // 措辞,不是绕门。──
1192
- try {
1193
- const { runProbe10PrefillNoteClearance } = await import('../verify-probes.js')
1194
- const noteClearance = runProbe10PrefillNoteClearance({ specBase, changeName })
1195
- if (noteClearance.unclearedFiles.length > 0) {
1196
- console.error(`\n❌ verify 阶段被阻断:预填注清零校验未过(${noteClearance.unclearedFiles.length} 个文件的白名单槽仍含未删预填注——预填≠结论,注在场=未确认):`)
1197
- for (const f of noteClearance.unclearedFiles) console.error(` - ${f}`)
1198
- console.error(' 修复:逐槽核对预填值后删除行尾「(预填:核对后删本注)」注(删注=确认动作);散文含注字面量的命中改写该处措辞。清零后重新完成 verify(进度不丢)。')
1199
- return await rollbackCompletionAndReturn(pm, progress, stageData, steps, currentIdx, cwd, changeName, platformOpts, { type: 'gate_rollback', detail: 'verify-contract' })
1200
- }
1201
- } catch (e) {
1202
- console.warn(`⚠️ 预填注清零校验异常(降级放行,fail-soft——异常不是未清零,ceremony 双跑同款处置): ${(e && e.message) || e}`)
1203
- }
1242
+ // ── module-impact 死信探针 + 预填注清零 error 门:已前移至 test/lint 实测门之前 ──
1243
+ // (fail-fast,R4-S-F 实证——纯文档 blocking 检查排在 9 分钟实测门后白烧门禁轮次;
1244
+ // 实现体见上方「fail-fast 前置」块,此处不再重复执行。)
1204
1245
  // ── verify 服务进程回收(坑 verify-service-process-leak;坑 verify-pids-cross-session-kill
1205
1246
  // 见 reapVerifyServices 注释)──
1206
1247
  try {
@@ -1890,6 +1931,42 @@ export function writeReconcileRunResult({ runtimeRoot, changeName, envelope, res
1890
1931
  }
1891
1932
  }
1892
1933
 
1934
+ /**
1935
+ * verify --done 阻断明细稳定锚点(quick-D,2026-09-20 报告问题 D):ts 目录(verify-runs/<ts>/
1936
+ * reconcile-result.json 等)的可发现性依赖 stdout——宿主把长命令转后台截断输出后,逐条打印的
1937
+ * ②类明细实际取不回(实证:target_files 阻断明细靠推断绕 3 轮才定位)。稳定路径指针(每次
1938
+ * 覆盖、不随 ts 变)指向本轮最新 verify-runs 目录,agent 无 stdout 也能直读。fail-soft:
1939
+ * 写失败只留痕不阻断 gate(对齐 writeReconcileRunResult 口径)。
1940
+ * @param {{ runtimeRoot: string, changeName: string|null, blocked?: boolean, note?: string }} opts
1941
+ * @returns {string|null} 成功返回指针路径;失败返回 null
1942
+ */
1943
+ export function writeVerifyGatePointer({ runtimeRoot, changeName, blocked = false, note = '' }) {
1944
+ try {
1945
+ const ts = new Date().toISOString().slice(0, 19).replace(/[-T:]/g, '')
1946
+ const runsDir = join(runtimeRoot, 'verify-runs')
1947
+ const pointerPath = join(runsDir, `gate-last-${changeName || 'change'}.json`)
1948
+ mkdirSync(runsDir, { recursive: true })
1949
+ writeFileSync(pointerPath, JSON.stringify({
1950
+ change: changeName,
1951
+ blocked,
1952
+ note,
1953
+ latest_run_dir: join(runsDir, ts),
1954
+ artifacts_hint: [
1955
+ `${ts}/reconcile-result.json(target_files ②类阻断明细在 missing 数组)`,
1956
+ `${ts}/probe-consistency-result.json(探针一致性抽查明细)`,
1957
+ ],
1958
+ written_at: new Date().toISOString(),
1959
+ }, null, 2) + '\n')
1960
+ if (blocked) {
1961
+ console.error(`\n📄 verify 门失败明细稳定锚点(stdout 被截断时直读此文件): ${pointerPath}\n 逐条明细在指针文件指向的 verify-runs/<ts>/ 目录(reconcile-result.json 的 missing 数组等)`)
1962
+ }
1963
+ return pointerPath
1964
+ } catch (e) {
1965
+ console.error(`⚠️ verify 门稳定锚点写入失败(不影响 gate): ${e.message}`)
1966
+ return null
1967
+ }
1968
+ }
1969
+
1893
1970
  /**
1894
1971
  * 打印 target_files 对账结果(task-05 接线配套的打印层)。
1895
1972
  *
@@ -1905,22 +1982,27 @@ export function writeReconcileRunResult({ runtimeRoot, changeName, envelope, res
1905
1982
  */
1906
1983
  /**
1907
1984
  * 打印跨仓 per-repo 对账结果(cross-repo-reconcile.js 产出的 advisory 明细,verify 块接线)。
1908
- * 恒不阻断(返回 void):跨仓 diff 锚点=各仓最近提交窗口,锚点脆弱期(已 commit 多笔/全在
1909
- * working-tree)会产②类假信号——明细供人工裁决,不翻主仓对账状态。
1985
+ * 恒不阻断(返回 void):锚点档逐行携带(内核分级:A reviews 锡点区间 > B 最近提交窗口 >
1986
+ * C 未提交窗口,anchor.label 透传),锚点脆弱期(已 commit 多笔/全在 working-tree)会产②类
1987
+ * 假信号——明细供人工裁决,不翻主仓对账状态。
1910
1988
  * @param {Array<{repo:string,repoPath:string|null,declaredCount:number,actualCount:number,
1911
1989
  * matched:string[],missing:Array<{task:string,path:string,isNew?:boolean}>,
1912
- * undeclared:string[],scaffoldCount:number,degradedReason:string|null}>|undefined} crossRepo
1990
+ * undeclared:string[],scaffoldCount:number,degradedReason:string|null,
1991
+ * anchor?:{source:string,base:string|null,head:string|null,label:string}}>|undefined} crossRepo
1913
1992
  */
1914
1993
  function printCrossRepoReconcile(crossRepo) {
1915
1994
  if (!Array.isArray(crossRepo) || crossRepo.length === 0) return
1916
- console.log(`\n🔀 跨仓 per-repo 对账(advisory 不阻断,锚点=各仓最近提交窗口):`)
1995
+ console.log(`\n🔀 跨仓 per-repo 对账(advisory 不阻断):`)
1917
1996
  for (const r of crossRepo) {
1918
1997
  if (r.degradedReason) {
1919
1998
  console.warn(` ⚠️ ${r.repo}:${r.degradedReason}`)
1920
1999
  continue
1921
2000
  }
1922
2001
  const flag = (r.missing.length > 0 || r.undeclared.length > 0) ? '⚠️ ' : '✅ '
1923
- console.log(` ${flag}${r.repo}(${r.repoPath}):声明 ${r.declaredCount} / 实测 ${r.actualCount} / 对上 ${r.matched.length} / ②缺 ${r.missing.length} / ③多 ${r.undeclared.length}${r.scaffoldCount > 0 ? ` / 脚手架聚合 ${r.scaffoldCount}` : ''}`)
2002
+ // 锚点档标签(task-03):有 anchor.label 时追加(A/B/C 档人类可读描述);无 anchor(旧形态)
2003
+ // 零输出——逐字兼容
2004
+ const anchorTag = r.anchor && typeof r.anchor.label === 'string' && r.anchor.label ? `|锚点 ${r.anchor.label}` : ''
2005
+ console.log(` ${flag}${r.repo}(${r.repoPath}):声明 ${r.declaredCount} / 实测 ${r.actualCount} / 对上 ${r.matched.length} / ②缺 ${r.missing.length} / ③多 ${r.undeclared.length}${r.scaffoldCount > 0 ? ` / 脚手架聚合 ${r.scaffoldCount}` : ''}${anchorTag}`)
1924
2006
  for (const m of r.missing.slice(0, 10)) {
1925
2007
  console.warn(` ②缺 ${m.task}: ${m.path}${m.isNew ? '(NEW: 声明新建,该仓未见)' : ''}——锚点窗口外已提交或多笔时可能假信号,到该仓 git log 核实`)
1926
2008
  }
package/src/run/prompt.js CHANGED
@@ -1151,7 +1151,7 @@ export async function outputStep(stageName, stepIndex, steps, cwd, changeName, d
1151
1151
  if (entries.length === 0) {
1152
1152
  promptText = promptText.replace(/\{FR_INDEX_DIGEST\}/g, '(触达域暂无 active FR 索引条目——本变更大概率是这些域的首批需求,照常写作)')
1153
1153
  } else {
1154
- const lines = entries.map((e) => `- ${e.id} ${e.title}(来源 ${e.change}${e.scenarios.length ? ';场景:' + e.scenarios.slice(0, 3).join(',') : ''}${(e.decisions || []).length ? ';依据:' + e.decisions.slice(0, 3).join('、') : ''})`)
1154
+ const lines = entries.map((e) => `- ${e.id} ${e.title}(来源 ${e.change}${e.scenarios.length ? ';场景:' + e.scenarios.slice(0, 3).join(',') : ''}${(e.decisions || []).length ? ';依据:' + e.decisions.slice(0, 3).join('、') : ''})${e.needsReview ? ` ⚠️ 待复核(${e.needsReview})——该 FR 覆盖的代码近期被 quick 触达,行为可能已变;本变更若触及同域先核对现状再决定承接/新写` : ''}`)
1155
1155
  lines.push('')
1156
1156
  lines.push('> 域解析自本变更 design.md 文件清单(漏域先核对清单)。改写/取代已有行为 → 对应 FR 块加承接行;新行为 → 新 FR 块。superseded 条目默认不列(历史回溯自行读 knowledge/fr/)。依据决策(L2)= 当年取舍锚——翻案须先读 knowledge/decisions/<域>.md 的否决理由,满足复潮条件走 D-xxx@vN+1,不得静默改行为。承接行可带退役理由:`承接: FR-<域>-NNN(退役理由:一句话)`——归档时写进被取代条目(理由内禁逗号);条目正文是截断摘要,全文锚(全文:<归档路径>#FR-NN)由 CLI 自动落。')
1157
1157
  promptText = promptText.replace(/\{FR_INDEX_DIGEST\}/g, lines.join('\n'))
@@ -472,8 +472,34 @@ export async function runQuickTestLintGate({ cwd, specBase, changedFiles = [], d
472
472
  lint = runVerifyLintCheck({ cwd, specBase })
473
473
  }
474
474
  const failed = []
475
- if (test.status === 'failed') failed.push('test')
476
- if (lint.status === 'failed') failed.push('lint')
475
+ // 纯超时降档(R4 门禁价值考古:36/106 失败是 600s 帽杀纯超时假拦——超时=未完成非测试挂)。
476
+ // 所有失败单元 reason 均含超时 → advisory 不计入 failed;任一单元真实挂测 → 维持硬拦。
477
+ if (test.status === 'failed') {
478
+ const { isTimeoutOnlyTestFailure } = await import('../verify-postcheck.js')
479
+ if (isTimeoutOnlyTestFailure(test)) {
480
+ console.warn(`\n⚠️ quick test 实测纯超时(无任何挂掉的测试)——不拦完成:${test.reason || ''}`)
481
+ console.warn(' 处置:定向复跑触碰模块自证;常发超时建议 local.yaml 配 modules: 块启用模块子集实测。')
482
+ } else {
483
+ failed.push('test')
484
+ }
485
+ }
486
+ // ── lint 归属鉴定降档(R4-S-F/R4-S-Q 同根因:隔离快照含 HEAD 存量债文件时 lint 恒败,
487
+ // agent 被逼范围外清偿才过门)。失败输出提及文件 × 本会话文件集零交集 → 存量债
488
+ // advisory 放行不拦完成;有交集 / 输出无可识别路径(unattributable)→ 维持硬拦。──
489
+ if (lint.status === 'failed') {
490
+ const { triageLintOwnership } = await import('../verify-postcheck.js')
491
+ const own = triageLintOwnership({
492
+ failureFiles: lint.failureFiles || [],
493
+ changeFiles: files.map(f => String(f).replace(/\\/g, '/')),
494
+ })
495
+ if (own.verdict === 'pre-existing') {
496
+ console.warn(`\n⚠️ quick lint 实测失败,但归属鉴定为 HEAD 存量债(失败提及文件与本会话文件集零交集)——不拦完成`)
497
+ console.warn(` 存量债文件:${(lint.failureFiles || []).join('、') || '(见上方输出)'}`)
498
+ console.warn(' 处置建议:单独 quick 机械清偿解锁全仓 lint 门(顺手清债);确与本会话无关可忽略本行。')
499
+ } else {
500
+ failed.push('lint')
501
+ }
502
+ }
477
503
  if (failed.length > 0 && snapshot) {
478
504
  try { const { printSnapshotFailureHint } = await import('./gate-snapshot.js')
479
505
  printSnapshotFailureHint({ snapshotRoot: snapshot.snapshotRoot, changeFileCount: snapshot.overlaid, sourceRoot: snapshot.sourceRoot || null }, { offEnv: 'SILLYSPEC_QUICK_GATE_SNAPSHOT_OFF' })
package/src/run/shared.js CHANGED
@@ -854,6 +854,28 @@ async function raceWithAbort(op, timeoutMs = resolveSyncTotalTimeoutMs()) {
854
854
  export const QUICK_SID_RE = /^quick-[0-9a-f]{8}$/
855
855
 
856
856
  export async function triggerSync(cwd, changeName, platformOpts = {}, opts = {}) {
857
+ // ── 后台异步化(2026-09-20 用户反馈:--done 被网络尾巴拖住分钟级)──
858
+ // 默认把同步整体挪进 detached 后台子进程:17 个调用点全部 fire-and-forget,但进程
859
+ // 退出要等事件循环排空,在飞 fetch 拖住命令返回。回落 inline 的三条件:
860
+ // opts.inline(bg 子进程自身回环调用/测试断言进程内行为)
861
+ // env SILLYSPEC_SYNC_BG=0(逃生阀,回退旧行为)
862
+ // env SILLYSPEC_BG_SYNC_CWD 在场(本进程已是后台子进程,防 fork 链)
863
+ // spawn 决策(未连接预判/单飞锁/rerunQueued 合并/日志)见 bg-sync.js;bg 机制任何
864
+ // 异常降级 inline,绝不因后台化阻断主流程。
865
+ if (!opts.inline && changeName
866
+ && process.env.SILLYSPEC_SYNC_BG !== '0'
867
+ && !process.env.SILLYSPEC_BG_SYNC_CWD) {
868
+ try {
869
+ const bg = await import('./bg-sync.js')
870
+ const r = await bg.spawnBackgroundSync(cwd, changeName, platformOpts, opts)
871
+ if (r.status === 'spawned') {
872
+ console.log(`🔄 [spec-sync] 平台同步转后台执行(不阻塞本命令返回;输出与结果见 ${r.logPath})`)
873
+ return
874
+ }
875
+ if (r.status === 'coalesced') return // 活后台轮在跑,本轮状态已并入其下一轮
876
+ // not-connected → 落回 inline(内部静默 no-op,与旧行为一致)
877
+ } catch { /* 后台化失败降级 inline(best-effort 契约不变) */ }
878
+ }
857
879
  // 平台模式不再整体跳过(2026-08-26 用户决策):上行回传(进度 + 四件套 + spec 树)
858
880
  // 在平台模式同样执行,凭据经 env SILLYHUB_PLATFORM_URL/TOKEN(预留通道,daemon 未
859
881
  // 实现注入——需外部显式 export,sync.js _getPlatform,链路 D 同款)或 local.yaml
@@ -1269,7 +1291,9 @@ async function matchQuickModules(srcChanged, specBase, projectName) {
1269
1291
  * @param {string} projectName 项目名(progress.project)
1270
1292
  * @returns {Promise<object|null>} 模块索引(扁平 { id: {...} });不可得 → null
1271
1293
  */
1272
- async function loadQuickModuleIndex(specBase, projectName) {
1294
+ // quick 资产尾②(changelog 边车)消费:complete-handlers 动态 import 解构取用——
1295
+ // 缺 export 曾致「loadQuickModuleIndex is not a function」fail-open 死路(R4-S-Q 实证)。
1296
+ export async function loadQuickModuleIndex(specBase, projectName) {
1273
1297
  if (!specBase || !projectName) return null
1274
1298
  try {
1275
1299
  const mapPath = join(specBase, 'docs', projectName, 'modules', '_module-map.yaml')
@@ -1815,6 +1839,16 @@ export async function auditQuickCompletion(cwd, guard, options = {}) {
1815
1839
  type: 'fr-rot-suspect', change: guard.changeName || null,
1816
1840
  domains: [...touchedDomains], count: frs.length, source: 'quick-done',
1817
1841
  })
1842
+ // 地图诚实升级(2026-09-20-quick-asset-tail FR-02/D-003):遥测之外给条目打
1843
+ // 待复核标记——下一次 brainstorm 注入行带 ⚠️,承接翻链时清除。fail-open 同块。
1844
+ const markRef = guard.changeName || 'recent-quick'
1845
+ try {
1846
+ const { markFrNeedsReview } = await import('../fr-index.js')
1847
+ const mr = markFrNeedsReview(join(specBase, 'knowledge'), frs.map(f => f.id), markRef)
1848
+ if (mr.marked > 0) {
1849
+ console.warn(`⚠️ [FR 待复核标记] ${mr.marked} 条 active FR 已标「待复核:${markRef}」——下次 brainstorm 注入可见,承接翻链时自动清除`)
1850
+ }
1851
+ } catch { /* 标记失败不影响遥测/审计 */ }
1818
1852
  console.warn(`⚠️ [FR 腐烂 suspect·advisory] 本次 quick 触达 ${[...touchedDomains].join('、')} 域的 ${frs.length} 条 active FR——quick 不走归档,若改动影响这些行为,索引不会自动 supersede(L3 声明义务;盲区:unmapped 文件/map 漂移不报警)`)
1819
1853
  }
1820
1854
  }