sillyspec 3.32.8 → 3.32.10

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 (63) hide show
  1. package/.claude/skills/sillyspec-brainstorm/SKILL.md +2 -2
  2. package/.claude/skills/sillyspec-execute/SKILL.md +2 -2
  3. package/.claude/skills/sillyspec-flow/SKILL.md +1 -1
  4. package/.claude/skills/sillyspec-plan/SKILL.md +2 -2
  5. package/AGENTS.md +5 -4
  6. package/assets/command-cards/flow.md +9 -4
  7. package/package.json +1 -1
  8. package/src/change-list.js +13 -0
  9. package/src/commit-guard.js +70 -2
  10. package/src/commit-suggest.js +45 -4
  11. package/src/config-schema.js +14 -0
  12. package/src/cross-repo-reconcile.js +54 -1
  13. package/src/decision-distill.js +77 -21
  14. package/src/decision-renumber.js +157 -0
  15. package/src/deleted-ledger.js +75 -0
  16. package/src/design-facts.js +5 -3
  17. package/src/dispatch/backends/local-agent.js +1 -1
  18. package/src/dispatch/backends/sillyhub-mcp.js +1 -1
  19. package/src/docs-check.js +30 -15
  20. package/src/docs-debt.js +3 -2
  21. package/src/doctor-diagnostics.js +3 -1
  22. package/src/flow-draft.js +24 -2
  23. package/src/flow-parity.js +32 -2
  24. package/src/flow.js +427 -69
  25. package/src/fr-index.js +15 -1
  26. package/src/hooks/worktree-guard.js +3 -3
  27. package/src/index.js +22 -10
  28. package/src/knowledge-digest.js +23 -6
  29. package/src/knowledge-graph.js +92 -14
  30. package/src/knowledge-match.js +34 -9
  31. package/src/knowledge-stats.js +12 -13
  32. package/src/knowledge-vector.js +98 -7
  33. package/src/machine-interface.js +26 -0
  34. package/src/progress/change-registry.js +7 -0
  35. package/src/progress.js +67 -0
  36. package/src/quicklog.js +6 -1
  37. package/src/redomain.js +27 -0
  38. package/src/route-hindsight.js +34 -2
  39. package/src/run/archive-distill.js +3 -0
  40. package/src/run/command.js +40 -44
  41. package/src/run/complete-handlers.js +8 -3
  42. package/src/run/complete.js +129 -63
  43. package/src/run/gates.js +94 -90
  44. package/src/run/prompt.js +13 -3
  45. package/src/run/shared.js +19 -0
  46. package/src/run/stage.js +1 -1
  47. package/src/scope-audit.js +2 -2
  48. package/src/sentinel-assertions.js +43 -1
  49. package/src/stage-contract.js +26 -3
  50. package/src/stage-review.js +44 -11
  51. package/src/stage-templates.js +2 -0
  52. package/src/stages/brainstorm.js +24 -12
  53. package/src/stages/execute.js +1 -1
  54. package/src/stages/explore.js +3 -5
  55. package/src/stages/knowledge.js +56 -5
  56. package/src/sync.js +98 -2
  57. package/src/task-tick.js +12 -0
  58. package/src/timeline.js +60 -19
  59. package/src/verify-postcheck.js +83 -6
  60. package/src/verify-probes.js +61 -18
  61. package/templates/agents-instruction.md +5 -4
  62. package/templates/prompts/testcase-design.md +5 -0
  63. package/src/stages/brainstorm-auto.js +0 -286
package/src/flow.js CHANGED
@@ -27,6 +27,7 @@
27
27
  */
28
28
  import { existsSync, readFileSync, writeFileSync, readdirSync, appendFileSync, renameSync, statSync } from 'node:fs'
29
29
  import { join, relative, dirname } from 'node:path'
30
+ import { DatabaseSync } from 'node:sqlite'
30
31
  import yaml from 'js-yaml'
31
32
  import { git, gitQuiet } from './git-helper.js'
32
33
  import { writeAtomicSync } from './fs-atomic.js'
@@ -223,6 +224,23 @@ export async function extractRoutingInputPaths(cwd, specBase, input) {
223
224
  })
224
225
  }
225
226
 
227
+ /**
228
+ * 轻量道知识注入查询串瘦身(2026-10-15-thin-query-slim):--input 的「成功标准:」段是验收
229
+ * 判据语料(文件路径/测试名/断言词——对知识匹配是纯噪音,且路径词会过触路由关键词把命中面
230
+ * 撑爆:本仓实证整段 input 匹配出 76 条目、58 个 fr 域文件全命中)。查询只取动机段——
231
+ * 「成功标准:」标记前正文;标记缺席=无标准段,全文照旧(兼容旧形态);标记前为空白则退全 input。
232
+ * @param {string} change 变更名(查询首行,既有口径)
233
+ * @param {string} input --input 全文
234
+ * @returns {string} 瘦身后的查询串
235
+ */
236
+ export function thinKnowledgeQuery(change, input) {
237
+ const raw = String(input || '')
238
+ const markerIdx = raw.search(/成功标准[::]/)
239
+ const motivation = markerIdx >= 0 ? raw.slice(0, markerIdx) : raw
240
+ const body = motivation.trim() || raw.trim()
241
+ return `${change || ''}\n${body}`
242
+ }
243
+
226
244
  /**
227
245
  * 轻量道知识注入段(2026-09-25-thin-fr-inject-parity):默认快道读取面对齐——
228
246
  * {FR_INDEX_DIGEST}/{DECISION_HITS}/execute 知识命中报告此前只在 run 族装配,轻量道 fresh
@@ -294,10 +312,11 @@ export async function flowKnowledgeDigest({ specBase, change, changeDir, input,
294
312
  }
295
313
  }
296
314
  const { matchKnowledgeHybrid } = await import('./knowledge-vector.js')
297
- const { deathPathNote } = await import('./knowledge-match.js')
315
+ const { deathPathNote, decisionChangeQualifier } = await import('./knowledge-match.js')
298
316
  // scope 遍历召回(2026-10-08-knowledge-graph,FR-04):touched=机器算的图查询键
299
317
  // (filesOverride ∪ design 交付表——上方既有变量),词面三层零命中时沿强边保底防复潮
300
- const km = await matchKnowledgeHybrid(knowledgeRoot, `${change}\n${input || ''}`, { cwd: dirname(specBase), scopeFiles: touched })
318
+ const kmQuery = thinKnowledgeQuery(change, input)
319
+ const km = await matchKnowledgeHybrid(knowledgeRoot, kmQuery, { cwd: dirname(specBase), scopeFiles: touched })
301
320
  // 防复潮面 = 死路注记 ∪ 有主题重叠的 rejected(2026-09-28-knowledge-inject-ranking 起 rejected∪死路;
302
321
  // 2026-09-28-knowledge-gate-denoise 收紧:score 零的 rejected 与查询零主题重叠——空标题条目靠状态
303
322
  // 蹭进回显是行为实测三例的噪音源,不再注入)
@@ -305,20 +324,51 @@ export async function flowKnowledgeDigest({ specBase, change, changeDir, input,
305
324
  summary.rejectedDecisions = rejected.length
306
325
  summary.knowledgeEntries = km.matched ? (km.entries || []).length : 0
307
326
  if (rejected.length > 0) {
308
- lines.push(` ⚠️ 否决决策/死路注记(历史已否决或已记死路,防复潮——除非复潮条件满足勿重提,复潮须在本变更 decisions.md 记新版本):`)
327
+ lines.push(` ⚠️ 否决决策/死路注记(历史已否决或已记死路,防复潮——除非复潮条件满足勿重提,复潮须在本变更 decisions.md 记新版本;跨变更引用带变更名 变更名#D-xxx@vN):`)
309
328
  for (const h of rejected.slice(0, 5)) {
310
329
  const note = h.status === 'rejected' ? (h.reason || '(未记录)') : `死路——${deathPathNote(h.reason)}`
311
- lines.push(` - ${h.id} ${h.title}(${h.file})${h.status === 'rejected' ? '否决理由' : '⚰️'}:${note}`)
330
+ lines.push(` - ${h.id} ${h.title}(${h.file}|${decisionChangeQualifier(h.change)})${h.status === 'rejected' ? '否决理由' : '⚰️'}:${note}`)
312
331
  }
313
332
  }
314
333
  if (km.matched && (km.entries || []).length > 0) {
315
334
  const src = km.entries.slice(0, 3).map((e) => (e.anchor ? `${e.file}#${e.anchor}` : e.file)).join(';')
316
335
  lines.push(` 📚 知识命中(INDEX 关键词,按需 Read):${src}`)
317
336
  }
337
+ // 遥测导线(2026-10-15-thin-inject-telemetry):轻量道注入此前不落 hits.jsonl——thin 常态化后
338
+ // inject 事件流断流(本仓 10-09 后归零实证),knowledge stats 从未命中判定被盲区污染。
339
+ // 分级字段(A2-lite):injectedFiles=正文实际注入(thin 面恒空——只出按需 Read 行)、
340
+ // mentionedFiles=显式展示给 agent 的锚点(📚 行 top-3)。既有 matchedFiles 口径不变
341
+ // (knowledge-stats 消费面零回归)。仅命中面非空才落盘;fail-soft(R-04)不阻断注入。
342
+ const thinMatchedFiles = km.matched ? (km.entries || []).map((e) => (e.anchor ? `${e.file}#${e.anchor}` : e.file)) : []
343
+ if (thinMatchedFiles.length > 0 || rejected.length > 0) {
344
+ try {
345
+ const { appendKnowledgeHit } = await import('./knowledge-hits.js')
346
+ appendKnowledgeHit(join(specBase, '.runtime'), {
347
+ type: 'inject',
348
+ change,
349
+ query: kmQuery,
350
+ matchedFiles: thinMatchedFiles,
351
+ injectedFiles: [],
352
+ mentionedFiles: thinMatchedFiles.slice(0, 3),
353
+ rejectedCount: rejected.length,
354
+ source: 'thin-start',
355
+ })
356
+ } catch { /* 遥测 fail-soft(R-04):hits 落盘失败不影响注入本体 */ }
357
+ }
318
358
  if (domains.length === 0 && rejected.length === 0 && !km.matched) {
319
359
  lines.length = 0
320
360
  lines.push(`🧠 知识注入(轻量道读取面):语料未命中知识库(无触达域依据、无否决命中、INDEX 无对齐条目)——可自行查 knowledge/INDEX.md。`)
321
361
  }
362
+ // 向量开关发现性提示(2026-10-15-vector-switch-discoverability):零命中=正需要语义召回
363
+ // 而未获得的时刻——平台已连接但开关未开(缺省关)时给一行可执行提示;有命中/未连接/
364
+ // 已开启零新增(vectorSwitchHint 单源,同款提示也挂 knowledge search hint 键)。
365
+ if (!km.matched && rejected.length === 0) {
366
+ try {
367
+ const { vectorSwitchHint } = await import('./knowledge-vector.js')
368
+ const hint = vectorSwitchHint(dirname(specBase))
369
+ if (hint) lines.push(` ${hint}`)
370
+ } catch { /* 提示 fail-open */ }
371
+ }
322
372
  // 模块卡欠账提示前移(2026-10-12-thin-module-gate-and-feed 三档):触碰命中模块 behind>0
323
373
  // 才出事实行——动手前看到「你要动的卡已落后」比收口时警告有效;computeDocsDebt(厚道
324
374
  // execute {DOCS_DEBT})同源,空债/无图零输出,异常走外层 catch fail-open 不阻断 start。
@@ -442,7 +492,136 @@ function changeArtifactPaths(changeDir) {
442
492
  * flow start —— 第 1 次协议调用(建卡+下发)。已存在 change → 恢复简报(不新建不重置)。
443
493
  * @param {{change:string, input?:string, thick?:boolean, withTasks?:boolean, cwd:string, specBase:string, json?:boolean}} p
444
494
  */
445
- export async function cmdFlowStart({ change, input, title: titleFlag = null, thick = false, withTasks = false, reviewForce = null, autopilot = false, cwd, specBase, runtimeRootOpt = null, json = false }) {
495
+ /**
496
+ * flow diff —— change 粒度文件统计只读查询(2026-10-13-flow-diff-cmd)。
497
+ * 双态同构:归档态读 change-patch.json scopeAudit(冻结时点真相,含 verdict);活跃态实时算
498
+ * baseline→工作树 累计口径(与 patch 子步冻结面同规则:filterCommittedFace 剔 ownPrefix 治理件
499
+ * 保 .sillyspec/docs/;行数 git numstat,untracked 按文件行数、二进制 ∅)。只读零副作用:
500
+ * 不 pull(无 triggerPullActiveChange)、不写盘、不动状态机——scan diff 同纪律。
501
+ * rows[].state:committed(工作树==HEAD)/ dirty(有未提交改动)/ untracked / archived。
502
+ */
503
+ async function cmdFlowDiff({ change, cwd, specBase, json = false }) {
504
+ const toPosix = (p) => String(p).replace(/\\/g, '/')
505
+ const activeDir = join(specBase, 'changes', change)
506
+ const archiveJson = join(specBase, 'changes', 'archive', change, 'change-patch.json')
507
+ const rows = []
508
+ let mode = null
509
+ let baseline = null
510
+ if (existsSync(archiveJson)) {
511
+ mode = 'archived'
512
+ try {
513
+ const meta = JSON.parse(readFileSync(archiveJson, 'utf8'))
514
+ const sa = meta.scopeAudit || {}
515
+ for (const r of (sa.rows || [])) {
516
+ if (!r || !r.path) continue
517
+ rows.push({ path: toPosix(r.path), additions: Number.isFinite(r.additions) ? r.additions : null, deletions: Number.isFinite(r.deletions) ? r.deletions : null, state: 'archived', verdict: r.verdict || null })
518
+ }
519
+ const t = (sa.totals && sa.totals.files != null) ? sa.totals : meta.totals
520
+ var archivedTotals = (t && typeof t === 'object') ? { files: t.files ?? rows.length, additions: t.additions ?? null, deletions: t.deletions ?? null } : null
521
+ } catch { /* 损坏件 → 按活跃态兜底(下方 activeDir 分支不命中则报不存在) */
522
+ mode = null
523
+ }
524
+ }
525
+ if (mode === null && existsSync(join(activeDir, 'flow-state.yaml'))) {
526
+ mode = 'active'
527
+ const st = readFlowState(activeDir)
528
+ baseline = st?.baseline_commit || null
529
+ if (baseline) {
530
+ const ownPrefix = `.sillyspec/changes/${change}/`
531
+ const committedRaw = String(gitQuiet(cwd, ['diff', '--name-only', `${baseline}..HEAD`]) || '').split('\n').map((s) => s.trim().replace(/\\/g, '/')).filter(Boolean)
532
+ const { filterCommittedFace } = await import('./flow-parity.js')
533
+ const committed = filterCommittedFace(committedRaw, ownPrefix)
534
+ // dirty 面(含 untracked):porcelain 全列,交付规则同冻结面(.sillyspec/ 剔、.sillyspec/docs/ 保、ownPrefix 剔)
535
+ // ⚠ 行不可 trim——porcelain 前导状态码含空格(' M x');git() 默认整段 trim 会吃掉首行
536
+ // 前导空格(实测 ' M work.txt' → 'M work.txt' → slice(3) 错位吃路径首字符),须 trim:false
537
+ const por = String(gitQuiet(cwd, ['status', '--porcelain', '-uall'], { trim: false }) || '').split('\n').filter((s) => s.length >= 4)
538
+ const dirtyState = new Map() // path -> 'dirty' | 'untracked'
539
+ for (const line of por) {
540
+ const xy = line.slice(0, 2)
541
+ const raw = line.slice(3).trim()
542
+ const path = toPosix(raw.includes(' -> ') ? raw.split(' -> ').pop() : raw)
543
+ if (!path || path.startsWith(ownPrefix)) continue
544
+ if (path.startsWith('.sillyspec/') && !path.startsWith('.sillyspec/docs/')) continue
545
+ dirtyState.set(path, xy === '??' ? 'untracked' : 'dirty')
546
+ }
547
+ const face = [...new Set([...committed, ...dirtyState.keys()])]
548
+ // 累计口径 numstat:baseline → 工作树(无 HEAD 上限,含未提交;untracked 不入 git diff 单独算)
549
+ const tracked = face.filter((f) => dirtyState.get(f) !== 'untracked')
550
+ const nsOut = String(gitQuiet(cwd, ['diff', '--numstat', baseline, '--', ...tracked]) || '')
551
+ const ns = new Map()
552
+ for (const l of nsOut.split('\n').filter(Boolean)) {
553
+ const [a, d, ...rest] = l.split('\t')
554
+ if (rest.length === 0) continue
555
+ ns.set(toPosix(rest.join('\t')), { additions: /^\d+$/.test(a) ? Number(a) : null, deletions: /^\d+$/.test(d) ? Number(d) : null })
556
+ }
557
+ for (const f of face) {
558
+ const st3 = dirtyState.get(f)
559
+ if (st3 === 'untracked') {
560
+ try {
561
+ const buf = readFileSync(join(cwd, f))
562
+ const text = buf.toString('utf8')
563
+ const lines = text.split('\n')
564
+ if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop()
565
+ rows.push({ path: f, additions: buf.includes(0) ? null : lines.length, deletions: null, state: 'untracked' })
566
+ } catch { rows.push({ path: f, additions: null, deletions: null, state: 'untracked' }) }
567
+ } else {
568
+ const s = ns.get(f)
569
+ rows.push({ path: f, additions: s ? s.additions : null, deletions: s ? s.deletions : null, state: st3 === 'dirty' ? 'dirty' : 'committed' })
570
+ }
571
+ }
572
+ }
573
+ }
574
+ if (mode === null) {
575
+ console.error(`❌ 变更「${change}」不存在(活跃 flow-state 与归档 change-patch.json 均未找到)`)
576
+ console.error(' 修复:sillyspec status 查变更列表核对拼写/日期前缀;归档变更名与归档目录名逐字一致')
577
+ process.exit(1)
578
+ }
579
+ const totals = mode === 'archived' && archivedTotals
580
+ ? archivedTotals
581
+ : { files: rows.length, additions: rows.reduce((s, r) => s + (r.additions || 0), 0), deletions: rows.reduce((s, r) => s + (r.deletions || 0), 0) }
582
+ if (json) {
583
+ console.log(JSON.stringify({ change, mode, baseline, rows, totals }))
584
+ return
585
+ }
586
+ if (mode === 'active' && !baseline) {
587
+ console.log(`📦 flow diff — ${change}(active):无 git 基线(无历史仓)——无可对账面`)
588
+ return
589
+ }
590
+ console.log(`📐 flow diff — ${change}(${mode}${mode === 'active' ? `|baseline ${String(baseline).slice(0, 8)}→工作树` : '|flow done 冻结件'})`)
591
+ for (const r of rows) {
592
+ const a = r.additions == null ? ' ∅' : `+${String(r.additions).padStart(4)}`
593
+ const d = r.deletions == null ? ' ∅' : `-${String(r.deletions).padStart(4)}`
594
+ const tag = r.state === 'dirty' ? '(未提交)' : r.state === 'untracked' ? '(未跟踪·新文件)' : r.state === 'archived' && r.verdict ? `|${r.verdict}` : ''
595
+ console.log(` ${a} ${d} ${r.path}${tag}`)
596
+ }
597
+ console.log(`合计:${totals.files} 文件 +${totals.additions ?? '?'} -${totals.deletions ?? '?'}${mode === 'active' ? '(baseline→工作树累计口径,含未提交)' : ''}`)
598
+ }
599
+
600
+ /**
601
+ * 升厚行值重置(坑 thin-upgrade-stale-stage-window,2026-10-13):升厚只写 flow-state
602
+ * 不碰 DB 行——行值残留 'thin'(平台 pull 往返透传形态)时,serializeForSync 的 thin 投影
603
+ * 在首次 run <stage> 前持续误标平台「轻量」出身(短窗展示面失真)。升厚三写点统一调本
604
+ * helper:行 current_stage 'thin' → 'brainstorm'(厚档起点,与 run 阶段机首写同值),
605
+ * 仅 thin 值才动(其余行值不越权重置)。best-effort 不阻断升厚链。
606
+ */
607
+ function resetThinStageRowOnUpgrade(cwd, change, specBase = null) {
608
+ try {
609
+ // cmdFlowDone 作用域无共享 ProgressManager——helper 直走底层数据库文件(node:sqlite)
610
+ // 零依赖;specBase 优先(与 flow 侧锚定同口径,平台模式 specRoot 异位不迷路)
611
+ const db = new DatabaseSync(join(specBase || join(cwd, '.sillyspec'), '.runtime', 'sillyspec.db'))
612
+ try {
613
+ const row = db.prepare('SELECT current_stage FROM changes WHERE name = ?').get(change)
614
+ if (row && row.current_stage === 'thin') {
615
+ db.prepare('UPDATE changes SET current_stage = ? WHERE name = ?').run('brainstorm', change)
616
+ console.log(' ↺ 升厚重置 DB 行 current_stage: thin → brainstorm(防平台短窗误标轻量出身)')
617
+ }
618
+ } finally {
619
+ try { db.close() } catch { /* 关闭失败不放大 */ }
620
+ }
621
+ } catch { /* 行值重置 best-effort,不阻断升厚 */ }
622
+ }
623
+
624
+ export async function cmdFlowStart({ change, input, title: titleFlag = null, thick = false, withTasks = false, reviewForce = null, autopilot = false, prestageAck = false, turbo = false, cwd, specBase, runtimeRootOpt = null, json = false }) {
446
625
  // local.yaml 缺席 fail-fast(R22 实证:缺 local.yaml 时 flow done 测试门静默兜底裸
447
626
  // python -m pytest → aiobotocore 假红/全量 860s 撞 600s 帽 → 11 轮重试 135min)。
448
627
  // local.yaml 是 init 的职责面——flow start 在此拒绝,不让 agent 在错误的测试配置上走完全程
@@ -477,9 +656,18 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
477
656
  // 保留,机器只补缺件与绑定面。无产物 = 真 legacy 既有变更,维持原拒收。
478
657
  const hasBsArtifacts = existsSync(join(changeDir, 'proposal.md')) || existsSync(join(changeDir, 'design.md'))
479
658
  if (hasBsArtifacts) {
659
+ // 预段反馈闭环(2026-10-14-prestage-consent-gate):走了头脑风暴预段=选道正确,
660
+ // 消费 route-hindsight 标记(同意门的一次性豁免在这里用掉,无声明 flag 也放行)
661
+ try {
662
+ const { clearHindsightMark } = await import('./route-hindsight.js')
663
+ if (clearHindsightMark({ specBase })) console.log('🕰️ 预段同意门:头脑风暴收编即选道正确,route-hindsight 标记已消费')
664
+ } catch { /* 消费 best-effort */ }
480
665
  const head = gitQuiet(cwd, ['rev-parse', 'HEAD'])
481
666
  const baseline = typeof head === 'string' && head.trim() ? head.trim() : null
482
- writeFlowState(changeDir, { tier: 'thin', born_face: 'thin', adopted_from: 'brainstorm', review_force: reviewForce, baseline_commit: baseline, substeps: {} })
667
+ writeFlowState(changeDir, { tier: 'thin', born_face: 'thin', adopted_from: 'brainstorm', review_force: reviewForce, baseline_commit: baseline, substeps: {},
668
+ // 极速模式留痕(2026-10-14-thin-turbo-mode D-005):--turbo 用户显式触发声明性落盘;
669
+ // flow done 读态以 turbo && tier!=='thick' 判定(D-007 升厚即失效)
670
+ ...(turbo ? { turbo: true, turbo_since: new Date().toISOString() } : {}) })
483
671
  try {
484
672
  const { redraftMissingArtifacts, ensureBindingSlots } = await import('./flow-draft.js')
485
673
  const r = redraftMissingArtifacts({ changeDir, change, input: null, runtimeRoot })
@@ -519,6 +707,8 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
519
707
  try { digestLines = (await flowKnowledgeDigest({ specBase, change, changeDir, input: null })).lines } catch { /* 注入 best-effort */ }
520
708
  console.log([
521
709
  `🧲 头脑风暴产物已收编进轻量跑道: ${change}(adopted_from=brainstorm,baseline=${baseline ? baseline.slice(0, 10) : '(无 git 历史)'})`,
710
+ // 极速指引行(2026-10-14-thin-turbo-mode FR-01):--turbo 在场才输出,缺省路径逐字不变
711
+ ...(turbo ? [`⚡ 极速模式(用户显式触发):只产 spec+代码,收口自动跳过实测与重仪式门(必留面照常);本次收口不含测试客观核验`] : []),
522
712
  `══════════════════════════════════════`,
523
713
  `📖 必读(头脑风暴产出——方案的承诺锚,先读完再动手):`,
524
714
  ...artifacts.map((p) => ` - ${p}`),
@@ -540,6 +730,10 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
540
730
  ...materials.map((p) => ` - ${p}`),
541
731
  ``,
542
732
  ...digestLines,
733
+ ...(prototypePaths.length > 0 ? [
734
+ `变更原型(头脑风暴产出,UI 实现前必看——中部 🖼️ 行的绝对尾部重列,防长输出截断丢指针):`,
735
+ ...prototypePaths.map((p) => ` - ${p}`),
736
+ ] : []),
543
737
  ].filter(Boolean).join('\n'))
544
738
  try { await triggerSync(cwd, change) } catch { /* 同步绝不阻断协议面 */ }
545
739
  return { adopted: true, change, baseline }
@@ -663,6 +857,37 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
663
857
  }
664
858
  }
665
859
 
860
+ // 预段选道同意门(2026-10-14-prestage-consent-gate):route-hindsight 标记从「下次 start 点名
861
+ // 提示」升级为声明即过的同意门——上个轻量变更被标「疑似该走预段未走」时,本次全新 start 须
862
+ // 显式选道一次(--no-prestage 声明留痕 / 先走 brainstorm 预段)。边界:门不判本次需求清不清晰
863
+ // (判定归 agent/用户,D-005 弱信号),只把上次的过程形态信号摆上台面要求一次显式消费——同
864
+ // --autopilot/--upgrade-thick 的同意门模式(「可无视」升级为「可无视但需留痕声明」)。
865
+ // 豁免面:resume(上方分支早退)天然不受影响;平台 writer 预建空目录(proceedFreshEmptyDir)
866
+ // 豁免——平台自动派发无选道对话面,标记留给人工会话消化;adopt(头脑风暴收编)= 选道正确,
867
+ // 在 adopt 分支消费标记。读取 fail-open(读不到=无门),消费失败不阻断(下次再声明一次)。
868
+ if (!proceedFreshEmptyDir) {
869
+ let prestageMark = null
870
+ try {
871
+ const { readHindsightMark } = await import('./route-hindsight.js')
872
+ prestageMark = readHindsightMark({ specBase })
873
+ } catch { /* 标记读取 fail-open */ }
874
+ if (prestageMark) {
875
+ if (!prestageAck) {
876
+ const _why = Array.isArray(prestageMark.reasons) && prestageMark.reasons.length > 0
877
+ ? prestageMark.reasons.join(';') : '过程形态超阈'
878
+ console.error(`❓ 预段选道同意门:上个轻量变更「${prestageMark.change}」被 route-hindsight 标记「疑似该走头脑风暴预段未走」(${_why})。本次全新 start 须显式选道(声明即过——标记是过程形态信号非定罪):`)
879
+ console.error(` ① 确认需求已含决策(成功标准来自用户原话或既有文档转写,而非你的推断):sillyspec flow start --change ${change} --input "<原输入>" --no-prestage(声明留痕 flow-state.prestage_ack,标记消费)`)
880
+ console.error(` ② 需求不清晰或需方案探索:sillyspec run brainstorm --change ${change}(完成后回来 flow start 收编,标记自动消费)`)
881
+ process.exit(2)
882
+ }
883
+ try {
884
+ const { clearHindsightMark } = await import('./route-hindsight.js')
885
+ clearHindsightMark({ specBase })
886
+ console.log(`🕰️ 预段同意门:已按 --no-prestage 声明放行(本次需求已含决策,留痕 flow-state.prestage_ack;上个标记「${prestageMark.change}」已消费)`)
887
+ } catch { /* 标记消费 best-effort:失败不阻断(下次 start 再声明一次) */ }
888
+ }
889
+ }
890
+
666
891
  // 升厚预判已删除(2026-09-25-thin-precheck-removal):技术面关键词(数据库/迁移等)测错轴——
667
892
  // R16 实证碰迁移的变更轻量变更带评审最优收口,预判反诱发误升厚(臂 A 129M)与误报打断。选道只留
668
893
  // 形态信号:清晰度门管「需求说不清楚」(预段收编),升厚只留用户决策(--upgrade-thick 同意门)
@@ -708,10 +933,44 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
708
933
  // autopilot(2026-10-04-thin-docs-v2 FR-06):用户显式声明跳过 spec 断点人审(--autopilot);
709
934
  // 缺省 false——flow done 要求 flow approve 批准证据(spec 断点机器化,护栏#2:零 prompt 劝说)
710
935
  autopilot: Boolean(autopilot),
936
+ // 预段同意门声明留痕(2026-10-14-prestage-consent-gate):hindsight 标记在场时全新 start
937
+ // 以 --no-prestage 显式声明「本次需求已含决策」放行——审计面可区分「确认过」与「没被问过」
938
+ prestage_ack: Boolean(prestageAck),
939
+ // 极速模式留痕(2026-10-14-thin-turbo-mode D-005):--turbo 用户显式触发声明性落盘;
940
+ // flow done 读态以 turbo && tier!=='thick' 判定(D-007 升厚即失效)
941
+ ...(turbo ? { turbo: true, turbo_since: new Date().toISOString() } : {}),
711
942
  baseline_commit: baseline,
712
943
  substeps: {},
713
944
  })
714
945
 
946
+ // 跨仓基线锚(2026-10-13-cross-repo-baseline-anchor):thin 手工交付的跨仓对账此前无锚
947
+ // (B 档 HEAD~1 单笔窗口,多提交交付漏文件+交错时序抓错面——fire-ledger 两笔交付实证)。
948
+ // start 时点记各注册仓 HEAD(cross_baselines),收口窗口=基线..工作树(与主仓 baseline_commit
949
+ // 同构)。首写者胜(对齐 baseline_sha256 模式:重入不刷新,防跨仓已有并行提交后重锚漂移窗口)。
950
+ // 纯读 fail-soft:注册不可达/非 git 仓/rev-parse 失败 → 跳过该仓,绝不阻断 start。
951
+ try {
952
+ if (!(readFlowState(changeDir) || {}).cross_baselines) {
953
+ const _yamlText = existsSync(join(specBase, 'local.yaml')) ? readFileSync(join(specBase, 'local.yaml'), 'utf8') : ''
954
+ if (_yamlText) {
955
+ const { parseRepoRegistry } = await import('./stages/plan-postcheck.js')
956
+ const { isAbsolute: _isAbs, resolve: _res } = await import('node:path')
957
+ const _repos = parseRepoRegistry(_yamlText)
958
+ const _bl = {}
959
+ for (const [k, rawPath] of _repos || []) {
960
+ const rp = _isAbs(rawPath) ? rawPath : _res(cwd, rawPath)
961
+ if (!rp || !existsSync(rp)) continue
962
+ const sha = gitQuiet(rp, ['rev-parse', 'HEAD'], { timeout: 10_000 })
963
+ const s = sha === null ? '' : String(sha).trim()
964
+ if (/^[0-9a-f]{7,40}$/i.test(s)) _bl[k] = s
965
+ }
966
+ if (Object.keys(_bl).length > 0) {
967
+ writeFlowState(changeDir, { cross_baselines: _bl })
968
+ console.log(`🌐 跨仓基线锚:${Object.keys(_bl).length} 个注册仓 HEAD 已记入 flow-state(cross_baselines 首写者胜)——收口跨仓对账窗口=基线..工作树`)
969
+ }
970
+ }
971
+ }
972
+ } catch { /* 跨仓基线锚 best-effort:失败不阻断 start(收口回退既有档位) */ }
973
+
715
974
  // 全件机器起草(R7 切片三 / FR-07:工件回填轮=0——治理工件 CLI 写,agent 只裁例外)。
716
975
  // 任务卡分岔(用户裁定#3):默认 thin+直写零任务卡;--thick/--with-tasks 才生成任务卡。
717
976
  let drafted = []
@@ -818,6 +1077,11 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
818
1077
  const lines = [
819
1078
  `🏃 flow start(${thick ? 'thick 厚档(--thick 显式声明,人声明不做启发式)' : 'thin 轻量跑道'}): ${change}`,
820
1079
  `══════════════════════════════════════`,
1080
+ // 极速指引行(2026-10-14-thin-turbo-mode FR-01;2026-10-14-turbo-guide-tier-copy 评审 P3
1081
+ // 清偿):承诺文案随档位收窄——thick 起步时 done 读态 turbo 不生效(D-007),文案如实告知
1082
+ // 全量仪式,防「提示说 A 实现是 B」漂移(cli-copy-selfsufficient②)。缺省路径逐字不变。
1083
+ ...(turbo && !thick ? [`⚡ 极速模式(用户显式触发):只产 spec+代码,收口自动跳过实测与重仪式门(必留面照常);本次收口不含测试客观核验`]
1084
+ : turbo && thick ? [`⚡ 极速留痕已记(--turbo),但 --thick 厚档起步——收口走全量仪式,实测与仪式门照跑不跳过(升厚即失效)`] : []),
821
1085
  ...(hindsightHint ? [hindsightHint, ''] : []),
822
1086
  `🔎 选道自检——对本需求,你还有没有必须问用户才能动手的问题?`,
823
1087
  ` 有 → sillyspec run brainstorm --change ${change}(它就是结构化问询协议,产物随后 flow start 自动收编续跑);`,
@@ -926,6 +1190,15 @@ function printRecoveryBriefing({ cwd, specBase, change, changeDir, runtimeRoot,
926
1190
  `- 剩什么:${left.length === 0 && dirty === 0 ? '活已干完' : dirty > 0 ? '活未干完(继续改代码)' : '收尾待裁决'}`,
927
1191
  `- 下一步:${left.length === 0 ? `sillyspec flow done --change ${change}` : `继续干活;干完跑 flow done(断点续)`}`,
928
1192
  ...(digestLines.length > 0 ? ['', ...digestLines] : []),
1193
+ // 尾部原型锚(2026-10-14-guide-anchor-adopt-proto):收编/含原型变更重入时原型指针不隐身
1194
+ //(首进 adopt 输出的 🖼️ 行在会话切换后已丢,简报尾部补可发现面;置于 digestLines 后=绝对
1195
+ // 尾部,长知识注入段也顶不掉——评审 P3-2 清偿)。无原型零输出。
1196
+ ...(() => {
1197
+ const protos = changeArtifactPaths(changeDir).filter((p) => /\.html$/i.test(p))
1198
+ return protos.length > 0
1199
+ ? [`- 变更原型在场(UI 实现前必看,界面/交互/流程按原型对齐):`, ...protos.map((p) => ` - ${p}`)]
1200
+ : []
1201
+ })(),
929
1202
  ].join('\n'))
930
1203
  }
931
1204
 
@@ -958,6 +1231,9 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
958
1231
  console.error('❌ 本 change 已混跑回退 legacy(跑过 run <stage>)——剩余流程按厚档走 run <stage>,flow done 不再裁决')
959
1232
  process.exit(2)
960
1233
  }
1234
+ // 极速模式读态(2026-10-14-thin-turbo-mode FR-02/D-007):turbo && tier!=='thick' 双条件——
1235
+ // 升厚(distill 异态/verify 失败/--thick 起步)后 turbo 残留不短路厚档仪式,重入即恢复全量校验
1236
+ const turbo = st.turbo === true && st.tier !== 'thick'
961
1237
  const runtimeRoot = resolveRuntimeRoot(runtimeRootOpt ? { runtimeRoot: runtimeRootOpt } : {}, specBase)
962
1238
  // 归属收窄清单(2026-09-25-thin-dogfood-fixes 修复②):baseline..HEAD 不分作者——并行会话在
963
1239
  // start..done 之间的提交会混入本变更实测面与 FR 域路由。复用 verify 对账同款切分器:他侧显式
@@ -1198,11 +1474,22 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1198
1474
  // 采样去重(2026-10-07-thin-tasks-v3):task tick 直写的精确事件(source:'task-tick')为
1199
1475
  // 权威口径,watcher 3s 采样合并跳被去重——CLI 快速连续 tick 不再误伤;观测旁路缺席
1200
1476
  // (无流/读失败)→ 跳过(fail-open,watcher 非真相源);--allow-batch-tick 显式旁路留痕。
1477
+ // 侧车突发回退(2026-10-14-tick-burst-sidecar-gate):CLI 收口端快连点对事件流不可见(单格
1478
+ // 精确跳),空结果时回退 task-ticks.jsonl 毫秒时戳判突发——CLI 与直接 Edit 两条路径同判。
1201
1479
  try {
1202
1480
  const { readWatcherEvents } = await import('./watcher.js')
1203
- const { detectBatchCheckCadence, resolveBatchTickAction } = await import('./sentinel-assertions.js')
1481
+ const { detectBatchCheckCadence, detectTickBurst, resolveBatchTickAction } = await import('./sentinel-assertions.js')
1204
1482
  const stream = readWatcherEvents({ runtimeRoot, change })
1205
- const batch = stream.exists ? detectBatchCheckCadence(stream.events) : null
1483
+ let batch = stream.exists ? detectBatchCheckCadence(stream.events) : null
1484
+ if (!batch) {
1485
+ // 侧车突发回退(2026-10-14-tick-burst-sidecar-gate):事件流空结果时读 change 目录
1486
+ // task-ticks.jsonl(CLI tick 毫秒时戳事实源,随归档进 git、不受观测盲窗影响)判快连点;
1487
+ // 事件流结果优先不双重报告;侧车缺失/坏文件按无证据静默(fail-open)。
1488
+ try {
1489
+ const { parseTickLog } = await import('./timeline.js')
1490
+ batch = detectTickBurst(parseTickLog(readFileSync(join(changeDir, 'task-ticks.jsonl'), 'utf8')))
1491
+ } catch { /* 侧车缺失/不可读 → 无侧车证据(fail-open) */ }
1492
+ }
1206
1493
  if (batch) {
1207
1494
  // 代勾计数取「本拍实时代勾 ∨ 持久化代勾」较大者(二轮 P2-B:重入漂移防护);决策纯函数
1208
1495
  // 单源(resolveBatchTickAction)四态与豁免优先序见其 JSDoc——代勾须解释整跳才降级
@@ -1227,7 +1514,8 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1227
1514
  console.warn(`⚠️ 勾选节奏:单拍多格勾选(${batch.detail},${at})——${why},降级提醒不拒`)
1228
1515
  }
1229
1516
  } else if (v.action === 'reject') {
1230
- console.error(`🚫 单拍勾选拒收:事件流观测到一拍勾选 ${batch.detail}(${at})——勾选纪律要求逐格(做一件→勾一格→下一个),勾选任务 ${_sentinelNonMirrorTasks} 格`)
1517
+ const _via = batch.source === 'tick-burst' ? '侧车时戳观测到' : '事件流观测到'
1518
+ console.error(`🚫 单拍勾选拒收:${_via}一拍勾选 ${batch.detail}(${at})——勾选纪律要求逐格(做一件→勾一格→下一个),勾选任务 ${_sentinelNonMirrorTasks} 格`)
1231
1519
  console.error(' 出口:①节奏违例已既成——认知后重跑带 --allow-batch-tick 显式留痕过门(平台时间线可见旁路);②疑观测误判→sillyspec doctor 核对事件流')
1232
1520
  appendTelemetry({ sentinel: 'batch-tick', jump: batch.detail })
1233
1521
  reportMidFail('ledger')
@@ -1235,7 +1523,14 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1235
1523
  }
1236
1524
  }
1237
1525
  } catch { /* 节奏门 best-effort:读流失败静默(fail-open——观测缺席不阻断) */ }
1238
- const gate = await runQuickTestLintGate({ cwd, specBase, changedFiles, changeName: change, skipSentinel: true /* flow 侧哨兵带 baseline 区间,quick 侧区间不可靠——单判不双判 */ })
1526
+ // 极速模式实测门跳过(2026-10-14-thin-turbo-mode FR-02/D-002):turbo 在场不调
1527
+ // runQuickTestLintGate,合成 gate 同形状 skipped 对象(带 status:'skipped' 完整形态——
1528
+ // 下方 fmt 对 null 显示 '—',带形态才输出「skipped — 跳过原因」,审计可见;
1529
+ // 对齐 quick-audit env 逃生门 skip 形态先例 src/run/quick-audit.js:482)。
1530
+ // 失败升厚分支(下方 action==='fail')turbo 下天然不触达——升厚语义不变。
1531
+ const gate = turbo
1532
+ ? { action: 'skip', failed: [], reason: `⚡ 极速模式(用户显式触发 ${st.turbo_since || '时点未记'})——test+lint 实测跳过(审计留痕;本次收口不含测试客观核验)`, test: { status: 'skipped', command: null, exitCode: null, durationMs: null, outputTail: null, reason: `⚡ 极速模式(用户显式触发 ${st.turbo_since || '时点未记'})——本次收口不含测试客观核验(审计留痕)`, resultPath: null }, lint: { status: 'skipped', command: null, exitCode: null, durationMs: null, outputTail: null, reason: '⚡ 极速模式——lint 实测跳过(审计留痕)', resultPath: null } }
1533
+ : await runQuickTestLintGate({ cwd, specBase, changedFiles, changeName: change, skipSentinel: true /* flow 侧哨兵带 baseline 区间,quick 侧区间不可靠——单判不双判 */ })
1239
1534
  if (gate && gate.action === 'fail') {
1240
1535
  console.error(`❌ 测试门 FAIL(整单 FAIL——实测失败/超时=失败,不继续 distill/归档):`)
1241
1536
  console.error(` ${gate.reason || gate.message || JSON.stringify(gate)}`)
@@ -1279,6 +1574,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1279
1574
  } catch { /* lint 件套 best-effort */ }
1280
1575
  // 失败触发升级(R7 切片四 / FR-10 / 护栏#4:不依赖 agent 主动)——剩余流程按厚档走
1281
1576
  writeFlowState(changeDir, { tier: 'thick', upgrade_reason: `verify 实测失败(${gate.reason || 'test fail'})——失败自动升厚` })
1577
+ resetThinStageRowOnUpgrade(cwd, change, specBase)
1282
1578
  console.error(' ⬆️ 已自动升厚档(tier=thick):重入修复后剩余流程按厚档语义(归档不跳过 plan.md 校验)')
1283
1579
  reportMidFail('ledger')
1284
1580
  process.exit(1)
@@ -1290,7 +1586,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1290
1586
  const fmt = (r) => r
1291
1587
  ? `${r.status}${r.command ? ` ← ${r.command}` : ''}${typeof r.durationMs === 'number' ? `(${(r.durationMs / 1000).toFixed(1)}s)` : ''}${r.resultPath ? ` 结果:${r.resultPath}` : ''}${r.status === 'skipped' && r.reason ? ` — 跳过原因:${String(r.reason).split('。')[0]}。` : ''}`
1292
1588
  : '—'
1293
- gateSummaryText = `test: ${fmt(gate && gate.test)}|lint: ${fmt(gate && gate.lint)}|门文件 ${Array.isArray(changedFiles) ? changedFiles.length : '?'} 个`
1589
+ gateSummaryText = `${turbo ? '⚡极速模式|' : ''}test: ${fmt(gate && gate.test)}|lint: ${fmt(gate && gate.lint)}|门文件 ${Array.isArray(changedFiles) ? changedFiles.length : '?'} 个`
1294
1590
  console.log(`🧾 实测面对账 — ${gateSummaryText}`)
1295
1591
  try { writeFlowState(changeDir, { gate_summary: gateSummaryText }) } catch { /* 存档 best-effort */ }
1296
1592
  // FR 腐烂 suspect(2026-09-25-thin-fr-inject-parity):quick-done 钩子迁轻量道(quick 退役后
@@ -1374,62 +1670,87 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1374
1670
  // 提交时,幂等跳过会把 change.patch/review.json 停在处置前时点(归档审计件缺处置面,
1375
1671
  // 2026-10-05-flowdone-lintfail-output 收编活体复现)。漂移 → 自动重冻结(等价 --refreeze)
1376
1672
  // + review 已有结论则隔离旧 review.json(结论对着旧冻结面不作数)并重置标记强制重评。
1377
- try {
1378
- const patchMeta = JSON.parse(readFileSync(join(changeDir, 'change-patch.json'), 'utf8'))
1379
- const { detectPatchDrift } = await import('./flow-parity.js')
1380
- const drift = detectPatchDrift({ cwd, change, freezeHead: patchMeta.head })
1381
- if (drift.drifted) {
1382
- console.warn(`⚠️ 审计时点漂移:patch 冻结(${String(patchMeta.head || '').slice(0, 8)})后另有 ${drift.ownCommits.length} 个本变更交付提交(${drift.ownCommits.slice(0, 3).join(';')}${drift.ownCommits.length > 3 ? ' 等' : ''})——幂等跳过会把审计件停在处置前时点`)
1383
- // 隔离先行(评审处置 P3:时间戳槽位防跨代覆盖——单槽 .superseded 在 Windows renameSync
1384
- // 同名碰撞会抛错;隔离失败则整体退回现状幂等跳过(fail-safe:漂移处理要么完整交付要么
1385
- // 不动,不留半套状态),指引人工处置后重跑)
1386
- const reviewPath = join(changeDir, 'review.json')
1387
- let quarantineOk = true
1388
- if (existsSync(reviewPath)) {
1389
- // 锚定保留判定(2026-10-06-review-anchor-and-negation 实测竞态:复审对着当前 HEAD
1390
- // 做的 PASS 结论落盘 26 秒后被「重冻结时刻在场=过期」口径误隔离):reviewedAgainst
1391
- // 命中当前 HEAD = 评审对象即最新交付面,结论有效——保留不隔离、review 标记不重置。
1392
- // 缺省/读失败/格式非法按未锚定处理,隔离行为不变(fail-safe 宁可多评)。
1393
- let anchoredToHead = false
1673
+ //
1674
+ // 冻结件存在性复查(2026-10-14-patch-refreeze-missing-artifact):子步标记 done 而冻结件
1675
+ // 缺席(手工误删/上次写盘中断)时,旧逻辑 readFileSync 抛错被下方 catch 吞掉 → 幂等跳过
1676
+ // → 归档无审计冻结件(multi-agent-platform 2026-10-14 实证:verify-result 记「无冻结件」,
1677
+ // 只能按 git 手工重建)。存在性复查优先于子步状态:缺件按漂移同款自动重冻结(重冻面=
1678
+ // 当前 HEAD);review 处置沿用漂移面同一隔离规则——缺基面无法判 governance-only,
1679
+ // 未锚定一律隔离(fail-safe 宁可多评)。
1680
+ const patchJsonPath = join(changeDir, 'change-patch.json')
1681
+ const patchTextPath = join(changeDir, 'change.patch')
1682
+ const missingFreeze = [
1683
+ !existsSync(patchJsonPath) ? 'change-patch.json' : null,
1684
+ !existsSync(patchTextPath) ? 'change.patch' : null,
1685
+ ].filter(Boolean)
1686
+ let drifted = false
1687
+ let drift = null
1688
+ if (missingFreeze.length > 0) {
1689
+ drifted = true
1690
+ console.warn(`⚠️ 冻结件缺失:patch 子步标记 done 但 ${missingFreeze.join('、')} 不在——存在性复查优先于子步状态,按漂移同款自动重冻结(2026-10-14-patch-refreeze-missing-artifact)`)
1691
+ } else {
1692
+ try {
1693
+ const patchMeta = JSON.parse(readFileSync(patchJsonPath, 'utf8'))
1694
+ const { detectPatchDrift } = await import('./flow-parity.js')
1695
+ drift = detectPatchDrift({ cwd, change, freezeHead: patchMeta.head })
1696
+ if (drift.drifted) {
1697
+ drifted = true
1698
+ console.warn(`⚠️ 审计时点漂移:patch 冻结(${String(patchMeta.head || '').slice(0, 8)})后另有 ${drift.ownCommits.length} 个本变更交付提交(${drift.ownCommits.slice(0, 3).join(';')}${drift.ownCommits.length > 3 ? ' 等' : ''})——幂等跳过会把审计件停在处置前时点`)
1699
+ }
1700
+ } catch { /* 漂移检测失败按现状幂等跳过(fail-safe 不新造阻断面) */ }
1701
+ }
1702
+ if (drifted) {
1703
+ // 隔离先行(评审处置 P3:时间戳槽位防跨代覆盖——单槽 .superseded 在 Windows renameSync
1704
+ // 同名碰撞会抛错;隔离失败则整体退回现状幂等跳过(fail-safe:漂移处理要么完整交付要么
1705
+ // 不动,不留半套状态),指引人工处置后重跑)
1706
+ const reviewPath = join(changeDir, 'review.json')
1707
+ let quarantineOk = true
1708
+ if (existsSync(reviewPath)) {
1709
+ // 重冻结目标 HEAD:漂移面取 detectPatchDrift.head;缺件重冻面取当前 HEAD(重冻结将锚定该面)。
1710
+ // 锚定保留判定(2026-10-06-review-anchor-and-negation 实测竞态:复审对着当前 HEAD
1711
+ // 做的 PASS 结论落盘 26 秒后被「重冻结时刻在场=过期」口径误隔离):reviewedAgainst
1712
+ // 命中重冻结目标 HEAD = 评审对象即最新交付面,结论有效——保留不隔离、review 标记不重置。
1713
+ // 缺省/读失败/格式非法按未锚定处理,隔离行为不变(fail-safe 宁可多评)。
1714
+ const refreezeHead = drift ? drift.head : String(gitQuiet(cwd, ['rev-parse', 'HEAD']) || '').trim()
1715
+ let anchoredToHead = false
1716
+ try {
1717
+ const { reviewAnchoredToHead } = await import('./flow-review.js')
1718
+ anchoredToHead = reviewAnchoredToHead(reviewPath, refreezeHead)
1719
+ } catch { /* 锚定判定失败按未锚定处理 */ }
1720
+ if (anchoredToHead) {
1721
+ console.log(` review.json 已锚定当前 HEAD(reviewedAgainst 命中 ${String(refreezeHead || '').slice(0, 8)})——评审对象即最新交付面,结论保留不隔离`)
1722
+ } else if (drift && drift.governanceOnly && !drift.touchedPromiseFace) {
1723
+ // 治理面等价保留(2026-10-10-drift-review-governance-keep,坑 drift-review-governance-loop
1724
+ // 用户实证):评审结论的对象是交付面——窗口内本变更提交只碰 .sillyspec/** 治理文件且
1725
+ // 未触本变更 requirements/design(承诺面也是评审对象)时,结论对最新交付面逐字节等价,
1726
+ // 保留不隔离不重置标记;change.patch 仍走下方 driftRefreeze 自动重冻结(FR-01 后治理
1727
+ // 工件不入冻结面——治理修复的留档真相=归档目录本体文件,重冻结保交付面时点对齐)
1728
+ // (审计件覆盖最新提交面不变量不破)。此前任何后缀提交一律隔离重评——评审后修一个
1729
+ // P2 文档措辞也付整轮子代理评审费(循环,被迫手工重排执行顺序自赎)。
1730
+ console.log(` review.json 保留不隔离——窗口内本变更提交仅触及治理文件(${drift.ownFiles.length} 个,未触 requirements/design),评审对象(交付面)未变,结论对最新面等价;change.patch 自动重冻结(治理工件不入冻结面,治理修复随归档目录本体留档)`)
1731
+ } else {
1732
+ const ts = new Date().toISOString().replace(/[-:]/g, '').replace(/\..+$/, '')
1394
1733
  try {
1395
- const { reviewAnchoredToHead } = await import('./flow-review.js')
1396
- anchoredToHead = reviewAnchoredToHead(reviewPath, drift.head)
1397
- } catch { /* 锚定判定失败按未锚定处理 */ }
1398
- if (anchoredToHead) {
1399
- console.log(` review.json 已锚定当前 HEAD(reviewedAgainst 命中 ${String(drift.head || '').slice(0, 8)})——评审对象即最新交付面,结论保留不隔离`)
1400
- } else if (drift.governanceOnly && !drift.touchedPromiseFace) {
1401
- // 治理面等价保留(2026-10-10-drift-review-governance-keep,坑 drift-review-governance-loop
1402
- // 用户实证):评审结论的对象是交付面——窗口内本变更提交只碰 .sillyspec/** 治理文件且
1403
- // 未触本变更 requirements/design(承诺面也是评审对象)时,结论对最新交付面逐字节等价,
1404
- // 保留不隔离不重置标记;change.patch 仍走下方 driftRefreeze 自动重冻结(FR-01 后治理
1405
- // 工件不入冻结面——治理修复的留档真相=归档目录本体文件,重冻结保交付面时点对齐)
1406
- // (审计件覆盖最新提交面不变量不破)。此前任何后缀提交一律隔离重评——评审后修一个
1407
- // P2 文档措辞也付整轮子代理评审费(循环,被迫手工重排执行顺序自赎)。
1408
- console.log(` review.json 保留不隔离——窗口内本变更提交仅触及治理文件(${drift.ownFiles.length} 个,未触 requirements/design),评审对象(交付面)未变,结论对最新面等价;change.patch 自动重冻结(治理工件不入冻结面,治理修复随归档目录本体留档)`)
1409
- } else {
1410
- const ts = new Date().toISOString().replace(/[-:]/g, '').replace(/\..+$/, '')
1411
- try {
1412
- renameSync(reviewPath, `${reviewPath}.superseded-${ts}`)
1413
- console.warn(` 旧 review.json 已隔离为 review.json.superseded-${ts}(评审结论对着旧冻结面)——重新执行评审任务书后再收口`)
1414
- } catch (e) {
1415
- quarantineOk = false
1416
- console.error(` ⚠️ 旧 review.json 隔离失败(${e.message})——本次保持现状幂等跳过,手动删除/改名 ${reviewPath} 后重跑(将自动重冻结+重评)`)
1417
- }
1734
+ renameSync(reviewPath, `${reviewPath}.superseded-${ts}`)
1735
+ console.warn(` 旧 review.json 已隔离为 review.json.superseded-${ts}(评审结论对着旧冻结面)——重新执行评审任务书后再收口`)
1736
+ } catch (e) {
1737
+ quarantineOk = false
1738
+ console.error(` ⚠️ 旧 review.json 隔离失败(${e.message})——本次保持现状幂等跳过,手动删除/改名 ${reviewPath} 后重跑(将自动重冻结+重评)`)
1418
1739
  }
1419
1740
  }
1420
- if (quarantineOk) {
1421
- driftRefreeze = true
1422
- if (st.substeps?.review === 'done' && !existsSync(reviewPath)) {
1423
- // 双故障边界(评审 P3 处置披露):writeFlowState 盘写失败时内存重置保本运行正确;
1424
- // 极端双故障(隔离成功+盘写失败)下轮残留 done 且 review.json 缺席 → review 子步
1425
- // backfill 按 exempt 误读——该窄路径未测(需 I/O 故障注入,design 边界披露)
1426
- try { writeFlowState(changeDir, { substeps: { review: null } }) } catch { /* 见上边界注释 */ }
1427
- if (st.substeps) st.substeps.review = null
1428
- if (!existsSync(reviewPath)) console.warn(' review 豁免口径对着旧冻结面——review 子步标记已重置,本次重新定档')
1429
- }
1741
+ }
1742
+ if (quarantineOk) {
1743
+ driftRefreeze = true
1744
+ if (st.substeps?.review === 'done' && !existsSync(reviewPath)) {
1745
+ // 双故障边界(评审 P3 处置披露):writeFlowState 盘写失败时内存重置保本运行正确;
1746
+ // 极端双故障(隔离成功+盘写失败)下轮残留 done 且 review.json 缺席 → review 子步
1747
+ // backfill 按 exempt 误读——该窄路径未测(需 I/O 故障注入,design 边界披露)
1748
+ try { writeFlowState(changeDir, { substeps: { review: null } }) } catch { /* 见上边界注释 */ }
1749
+ if (st.substeps) st.substeps.review = null
1750
+ if (!existsSync(reviewPath)) console.warn(' review 豁免口径对着旧冻结面——review 子步标记已重置,本次重新定档')
1430
1751
  }
1431
1752
  }
1432
- } catch { /* 漂移检测失败按现状幂等跳过(fail-safe 不新造阻断面) */ }
1753
+ }
1433
1754
  if (!driftRefreeze) skip('patch')
1434
1755
  }
1435
1756
  if (st.substeps?.patch !== 'done' || driftRefreeze) {
@@ -1532,7 +1853,12 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1532
1853
  // ——旧行为警告后继续归档,autopilot 单跑到底把三选一变成不可达选项(归档即拒收重入)。
1533
1854
  // 缺口在场且未显式处置 → 停在 patch 子步标记之前(半态可重入,与 review 中断同款
1534
1855
  // exit 语义):处置后重跑,patch 全量重建自动并入已提交 src。
1535
- if (!freezeDirty && !acceptDirtyGap) {
1856
+ // 极速模式(2026-10-14-thin-turbo-mode FR-02/D-002):缺口三选一阻断降 advisory——
1857
+ // 警告+缺口数留痕后放行,不 exit;非 turbo 路径逐字不变。
1858
+ if (turbo && !freezeDirty && !acceptDirtyGap) {
1859
+ console.warn(`⚡ 极速模式:未提交交付缺口未显式处置——降 advisory 放行(缺口数 ${freeze.dirtyWarned.length} 随收口输出留痕,审计自负)`)
1860
+ }
1861
+ if (!freezeDirty && !acceptDirtyGap && !turbo) {
1536
1862
  console.error('⛔ 未提交交付缺口未显式处置——收口阻断(不归档;处置后重跑同一条命令从断点续):')
1537
1863
  console.error(` ① git commit 这些文件(信息尾缀带 (${change}) 或 (${change}),重跑本命令——冻结面自动并入`)
1538
1864
  console.error(` ② 全部归属本变更:flow done --change ${change} --freeze-dirty`)
@@ -1731,6 +2057,9 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1731
2057
  changeName: change,
1732
2058
  planEntries,
1733
2059
  collectPatch: true,
2060
+ // 跨仓基线锚透传(2026-10-13-cross-repo-baseline-anchor):flow start 记录的
2061
+ // cross_baselines → collectRepoActual 3c 档(基线..工作树窗口,token 归因主面)
2062
+ crossBaselines: (st && st.cross_baselines && typeof st.cross_baselines === 'object') ? st.cross_baselines : null,
1734
2063
  })
1735
2064
  crossRepoRows = recon.rows
1736
2065
  crossRepos = recon.repos
@@ -1822,6 +2151,13 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1822
2151
  reviewOutcome = { required: false, sampled: false, verdict: 'exempt', backfilled: true }
1823
2152
  }
1824
2153
  } catch { reviewOutcome = { required: null, verdict: 'done', backfilled: true } }
2154
+ } else if (turbo) {
2155
+ // 极速模式评审豁免(2026-10-14-thin-turbo-mode FR-02/D-001/D-002):用户显式极速声明=
2156
+ // 风险自担留痕,跳过 classifyReviewNeed 强制豁免(telemetry src/flow.js 整对象 JSON
2157
+ // 序列化,turboExempt 字段无破坏——Grill X-003 探针已核)。
2158
+ console.log('⚡ 极速模式:评审子步豁免(用户显式极速声明=风险自担留痕)')
2159
+ reviewOutcome = { required: false, sampled: false, verdict: 'exempt', turboExempt: true }
2160
+ mark('review')
1825
2161
  } else {
1826
2162
  const { classifyReviewNeed, renderReviewerTaskbook, validateReviewJson } = await import('./flow-review.js')
1827
2163
  let patchText = null
@@ -1912,6 +2248,10 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1912
2248
  // 视觉降级无用户裁决留痕恒拦;ui_visual_gate=error 时缺 visual-evidence.md 亦拦;
1913
2249
  // 默认 warn 档只警告不拦。与 run/gates.js verify 收尾同一检测单点,不二算)。
1914
2250
  try {
2251
+ if (turbo) {
2252
+ // 极速模式(2026-10-14-thin-turbo-mode FR-02/D-002):ui 视觉探针跳过留痕
2253
+ console.log('⚡ 极速模式:UI 视觉证据探针跳过(审计留痕)')
2254
+ } else {
1915
2255
  const uiProbe = runUiVisualProbe({ changeDir, gate: readUiVisualGate(specBase) })
1916
2256
  if (uiProbe.level === 'error') {
1917
2257
  console.error('\n❌ UI 视觉证据门(探针 12 error 档)阻断收口:')
@@ -1922,6 +2262,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1922
2262
  } else if (uiProbe.level === 'warn') {
1923
2263
  console.warn(`⚠️ UI 视觉证据(探针 12 warn 档):${uiProbe.notes.join(';')}`)
1924
2264
  }
2265
+ }
1925
2266
  } catch (e) {
1926
2267
  console.warn(`⚠️ UI 视觉证据探针异常(fail-soft 降级放行):${(e && e.message) || e}`)
1927
2268
  }
@@ -1930,7 +2271,9 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1930
2271
  // 竞争、在途残留;hunk_gate warn 默认(警告)/error(阻断)/off。与「提交面夹带嫌疑
1931
2272
  // advisory」互补:advisory 在冻结面采集处看文件名,本门在 probes 执法看 hunk 与竞争面。
1932
2273
  try {
1933
- const gate = readHunkGate(specBase)
2274
+ // 极速模式(2026-10-14-thin-turbo-mode FR-02/D-002):hunk 门强制降 warn 档——
2275
+ // 判定链路照跑(三类信号照常渲染),仅 error 阻断档解除(降 advisory 留痕不阻断)。
2276
+ const gate = turbo ? 'warn' : readHunkGate(specBase)
1934
2277
  let committedForGate = []
1935
2278
  try {
1936
2279
  committedForGate = String(gitQuiet(cwd, ['diff', '--name-only', `${st.baseline_commit}..HEAD`]) || '')
@@ -1990,8 +2333,10 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1990
2333
  // 回读块保证在场(patch 本轮算出或 change-patch.json 回读)。
1991
2334
  try {
1992
2335
  if (moduleScope && Array.isArray(moduleScope.modules) && moduleScope.modules.length > 0) {
1993
- const { appendChangeIndexEntries } = await import('./flow-parity.js')
1994
- const motive = String(st.input || '').split('\n').map((s) => s.trim()).find(Boolean) || ''
2336
+ const { appendChangeIndexEntries, motiveFromInput } = await import('./flow-parity.js')
2337
+ // 摘要与变更标题同源(2026-10-15-title-label-skip):段头行(动机与背景:等)跳过取次行,
2338
+ // 空提取回退变更名——此前裸取首行致「动机与背景:」整行入摘要(stages.changelog 实证)。
2339
+ const motive = motiveFromInput(st.input, change)
1995
2340
  const wr = appendChangeIndexEntries({ specBase, modules: moduleScope.modules, change, summary: motive, date: new Date().toISOString().slice(0, 10) })
1996
2341
  changelogAppended = wr.appended
1997
2342
  if (wr.appended.length > 0) console.log(`📖 模块变更索引回写:${wr.appended.length} 个目标(${wr.appended.join('、')})——sidecar 优先/卡内节次之,随归档窄化入暂存`)
@@ -2007,6 +2352,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
2007
2352
  const abnormal = res && (res.rejected > 0 || res.needsWait > 0)
2008
2353
  if (abnormal) {
2009
2354
  writeFlowState(changeDir, { tier: 'thick', upgrade_reason: `distill 异态(rejected=${res.rejected} needsWait=${res.needsWait})——升厚留人工裁决` })
2355
+ resetThinStageRowOnUpgrade(cwd, change, specBase)
2010
2356
  console.warn(`⚠️ distill 异态 → 已升厚档(tier=thick)。剩余流程按厚档走 run <stage> 完整仪式;归档子步仍将执行(厚档语义不跳过 plan.md 校验)。`)
2011
2357
  }
2012
2358
  } catch (e) {
@@ -2317,7 +2663,7 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
2317
2663
  console.error(`❌ ${e.message}`)
2318
2664
  // 可照抄实例(2026-10-05-input-teach-copyable):引号内换行合法,行级 trim 容忍缩进
2319
2665
  console.error(' 重试(--input 引号内换行合法,可照抄):')
2320
- console.error(' sillyspec flow start --change <YYYY-MM-DD-简短描述> --input "<动机与背景>')
2666
+ console.error(' sillyspec flow start --change <YYYY-MM-DD-简短描述> --input "<一句中文概括(首行即变更标题);随后动机与背景')
2321
2667
  console.error('')
2322
2668
  console.error(' 成功标准:')
2323
2669
  console.error(' - <可验证标准>"')
@@ -2333,6 +2679,8 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
2333
2679
  withTasks: hasFlag('--with-tasks'),
2334
2680
  reviewForce: hasFlag('--review') ? true : hasFlag('--no-review') ? false : null,
2335
2681
  autopilot: hasFlag('--autopilot'),
2682
+ prestageAck: hasFlag('--no-prestage'),
2683
+ turbo: hasFlag('--turbo'),
2336
2684
  cwd, specBase,
2337
2685
  json: hasFlag('--json') || jsonGlobal,
2338
2686
  })
@@ -2358,6 +2706,15 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
2358
2706
  await reportAgentLog(change, sub)
2359
2707
  return cmdFlowReject({ change, cwd, specBase, reason: getFlag('--reason') })
2360
2708
  }
2709
+ if (sub === 'diff') {
2710
+ // 只读查询面(2026-10-13-flow-diff-cmd):change 粒度文件统计双态口——与 status 同级自愿面
2711
+ const change = getFlag('--change')
2712
+ if (!change) { console.error('❌ flow diff 需 --change <名>'); process.exit(2) }
2713
+ validateChangeName(change)
2714
+ await reportAgentLog(change, sub)
2715
+ await cmdFlowDiff({ change, cwd, specBase, json: jsonGlobal || hasFlag('--json') })
2716
+ return
2717
+ }
2361
2718
  if (sub === 'status') {
2362
2719
  // 三断点配套(2026-09-25-flow-checkpoints):随时可查当前变更阶段/槽位/子步进度
2363
2720
  const change = getFlag('--change')
@@ -2535,12 +2892,13 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
2535
2892
  console.log(`✅ flow amend-draft 留痕重锚:${r.reanchored.join('、')}——ledger amendment 审计在案;editRatio=${maxRatio}(段级最大;基准=首版原文,AGENT 槽不计入)`)
2536
2893
  return r
2537
2894
  }
2538
- console.error('用法: sillyspec flow start --change <名> --input "<动机与背景+独立一行『成功标准:』+每行一条『- 可验证标准』>" [--thick|--with-tasks] | sillyspec flow done --change <名> | sillyspec flow status --change <名> [--json]')
2895
+ console.error('用法: sillyspec flow start --change <名> --input "<一句中文概括(首行即变更标题)+动机与背景详述+独立一行『成功标准:』+每行一条『- 可验证标准』>" [--thick|--with-tasks] [--no-prestage(hindsight 标记在场时声明需求已含决策,留痕放行)] | sillyspec flow done --change <名> | sillyspec flow status --change <名> [--json] | sillyspec flow diff --change <名> [--json](只读文件统计)')
2539
2896
  console.error(' --input 可照抄形态(引号内换行合法):')
2540
- console.error(' sillyspec flow start --change <名> --input "<动机与背景>')
2897
+ console.error(' sillyspec flow start --change <名> --input "<一句中文概括(首行即变更标题);随后动机与背景')
2541
2898
  console.error('')
2542
2899
  console.error(' 成功标准:')
2543
2900
  console.error(' - <可验证标准>"')
2901
+ console.error(' 预段同意门被拦时(上个轻量变更被 route-hindsight 标记「疑似该走预段未走」):确认需求已含决策加 --no-prestage;不清晰则先 run brainstorm --change <名>,完成后回来 flow start 收编(标记自动消费)')
2544
2902
  process.exit(2)
2545
2903
  }
2546
2904