sillyspec 3.32.8 → 3.32.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -3
- package/assets/command-cards/flow.md +8 -3
- package/package.json +1 -1
- package/src/change-list.js +13 -0
- package/src/commit-guard.js +70 -2
- package/src/cross-repo-reconcile.js +54 -1
- package/src/deleted-ledger.js +75 -0
- package/src/design-facts.js +5 -3
- package/src/dispatch/backends/local-agent.js +1 -1
- package/src/dispatch/backends/sillyhub-mcp.js +1 -1
- package/src/flow-draft.js +24 -2
- package/src/flow-parity.js +14 -0
- package/src/flow.js +353 -58
- package/src/index.js +15 -5
- package/src/progress/change-registry.js +7 -0
- package/src/progress.js +67 -0
- package/src/redomain.js +27 -0
- package/src/route-hindsight.js +34 -2
- package/src/run/command.js +37 -41
- package/src/run/complete.js +16 -19
- package/src/run/gates.js +1 -1
- package/src/run/prompt.js +10 -0
- package/src/run/shared.js +19 -0
- package/src/scope-audit.js +2 -2
- package/src/stages/brainstorm.js +19 -11
- package/src/stages/execute.js +1 -1
- package/src/stages/explore.js +3 -5
- package/src/sync.js +70 -0
- package/src/verify-postcheck.js +34 -5
- package/templates/agents-instruction.md +4 -3
- package/templates/prompts/testcase-design.md +5 -0
- 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'
|
|
@@ -442,7 +443,136 @@ function changeArtifactPaths(changeDir) {
|
|
|
442
443
|
* flow start —— 第 1 次协议调用(建卡+下发)。已存在 change → 恢复简报(不新建不重置)。
|
|
443
444
|
* @param {{change:string, input?:string, thick?:boolean, withTasks?:boolean, cwd:string, specBase:string, json?:boolean}} p
|
|
444
445
|
*/
|
|
445
|
-
|
|
446
|
+
/**
|
|
447
|
+
* flow diff —— change 粒度文件统计只读查询(2026-10-13-flow-diff-cmd)。
|
|
448
|
+
* 双态同构:归档态读 change-patch.json scopeAudit(冻结时点真相,含 verdict);活跃态实时算
|
|
449
|
+
* baseline→工作树 累计口径(与 patch 子步冻结面同规则:filterCommittedFace 剔 ownPrefix 治理件
|
|
450
|
+
* 保 .sillyspec/docs/;行数 git numstat,untracked 按文件行数、二进制 ∅)。只读零副作用:
|
|
451
|
+
* 不 pull(无 triggerPullActiveChange)、不写盘、不动状态机——scan diff 同纪律。
|
|
452
|
+
* rows[].state:committed(工作树==HEAD)/ dirty(有未提交改动)/ untracked / archived。
|
|
453
|
+
*/
|
|
454
|
+
async function cmdFlowDiff({ change, cwd, specBase, json = false }) {
|
|
455
|
+
const toPosix = (p) => String(p).replace(/\\/g, '/')
|
|
456
|
+
const activeDir = join(specBase, 'changes', change)
|
|
457
|
+
const archiveJson = join(specBase, 'changes', 'archive', change, 'change-patch.json')
|
|
458
|
+
const rows = []
|
|
459
|
+
let mode = null
|
|
460
|
+
let baseline = null
|
|
461
|
+
if (existsSync(archiveJson)) {
|
|
462
|
+
mode = 'archived'
|
|
463
|
+
try {
|
|
464
|
+
const meta = JSON.parse(readFileSync(archiveJson, 'utf8'))
|
|
465
|
+
const sa = meta.scopeAudit || {}
|
|
466
|
+
for (const r of (sa.rows || [])) {
|
|
467
|
+
if (!r || !r.path) continue
|
|
468
|
+
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 })
|
|
469
|
+
}
|
|
470
|
+
const t = (sa.totals && sa.totals.files != null) ? sa.totals : meta.totals
|
|
471
|
+
var archivedTotals = (t && typeof t === 'object') ? { files: t.files ?? rows.length, additions: t.additions ?? null, deletions: t.deletions ?? null } : null
|
|
472
|
+
} catch { /* 损坏件 → 按活跃态兜底(下方 activeDir 分支不命中则报不存在) */
|
|
473
|
+
mode = null
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (mode === null && existsSync(join(activeDir, 'flow-state.yaml'))) {
|
|
477
|
+
mode = 'active'
|
|
478
|
+
const st = readFlowState(activeDir)
|
|
479
|
+
baseline = st?.baseline_commit || null
|
|
480
|
+
if (baseline) {
|
|
481
|
+
const ownPrefix = `.sillyspec/changes/${change}/`
|
|
482
|
+
const committedRaw = String(gitQuiet(cwd, ['diff', '--name-only', `${baseline}..HEAD`]) || '').split('\n').map((s) => s.trim().replace(/\\/g, '/')).filter(Boolean)
|
|
483
|
+
const { filterCommittedFace } = await import('./flow-parity.js')
|
|
484
|
+
const committed = filterCommittedFace(committedRaw, ownPrefix)
|
|
485
|
+
// dirty 面(含 untracked):porcelain 全列,交付规则同冻结面(.sillyspec/ 剔、.sillyspec/docs/ 保、ownPrefix 剔)
|
|
486
|
+
// ⚠ 行不可 trim——porcelain 前导状态码含空格(' M x');git() 默认整段 trim 会吃掉首行
|
|
487
|
+
// 前导空格(实测 ' M work.txt' → 'M work.txt' → slice(3) 错位吃路径首字符),须 trim:false
|
|
488
|
+
const por = String(gitQuiet(cwd, ['status', '--porcelain', '-uall'], { trim: false }) || '').split('\n').filter((s) => s.length >= 4)
|
|
489
|
+
const dirtyState = new Map() // path -> 'dirty' | 'untracked'
|
|
490
|
+
for (const line of por) {
|
|
491
|
+
const xy = line.slice(0, 2)
|
|
492
|
+
const raw = line.slice(3).trim()
|
|
493
|
+
const path = toPosix(raw.includes(' -> ') ? raw.split(' -> ').pop() : raw)
|
|
494
|
+
if (!path || path.startsWith(ownPrefix)) continue
|
|
495
|
+
if (path.startsWith('.sillyspec/') && !path.startsWith('.sillyspec/docs/')) continue
|
|
496
|
+
dirtyState.set(path, xy === '??' ? 'untracked' : 'dirty')
|
|
497
|
+
}
|
|
498
|
+
const face = [...new Set([...committed, ...dirtyState.keys()])]
|
|
499
|
+
// 累计口径 numstat:baseline → 工作树(无 HEAD 上限,含未提交;untracked 不入 git diff 单独算)
|
|
500
|
+
const tracked = face.filter((f) => dirtyState.get(f) !== 'untracked')
|
|
501
|
+
const nsOut = String(gitQuiet(cwd, ['diff', '--numstat', baseline, '--', ...tracked]) || '')
|
|
502
|
+
const ns = new Map()
|
|
503
|
+
for (const l of nsOut.split('\n').filter(Boolean)) {
|
|
504
|
+
const [a, d, ...rest] = l.split('\t')
|
|
505
|
+
if (rest.length === 0) continue
|
|
506
|
+
ns.set(toPosix(rest.join('\t')), { additions: /^\d+$/.test(a) ? Number(a) : null, deletions: /^\d+$/.test(d) ? Number(d) : null })
|
|
507
|
+
}
|
|
508
|
+
for (const f of face) {
|
|
509
|
+
const st3 = dirtyState.get(f)
|
|
510
|
+
if (st3 === 'untracked') {
|
|
511
|
+
try {
|
|
512
|
+
const buf = readFileSync(join(cwd, f))
|
|
513
|
+
const text = buf.toString('utf8')
|
|
514
|
+
const lines = text.split('\n')
|
|
515
|
+
if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop()
|
|
516
|
+
rows.push({ path: f, additions: buf.includes(0) ? null : lines.length, deletions: null, state: 'untracked' })
|
|
517
|
+
} catch { rows.push({ path: f, additions: null, deletions: null, state: 'untracked' }) }
|
|
518
|
+
} else {
|
|
519
|
+
const s = ns.get(f)
|
|
520
|
+
rows.push({ path: f, additions: s ? s.additions : null, deletions: s ? s.deletions : null, state: st3 === 'dirty' ? 'dirty' : 'committed' })
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (mode === null) {
|
|
526
|
+
console.error(`❌ 变更「${change}」不存在(活跃 flow-state 与归档 change-patch.json 均未找到)`)
|
|
527
|
+
console.error(' 修复:sillyspec status 查变更列表核对拼写/日期前缀;归档变更名与归档目录名逐字一致')
|
|
528
|
+
process.exit(1)
|
|
529
|
+
}
|
|
530
|
+
const totals = mode === 'archived' && archivedTotals
|
|
531
|
+
? archivedTotals
|
|
532
|
+
: { files: rows.length, additions: rows.reduce((s, r) => s + (r.additions || 0), 0), deletions: rows.reduce((s, r) => s + (r.deletions || 0), 0) }
|
|
533
|
+
if (json) {
|
|
534
|
+
console.log(JSON.stringify({ change, mode, baseline, rows, totals }))
|
|
535
|
+
return
|
|
536
|
+
}
|
|
537
|
+
if (mode === 'active' && !baseline) {
|
|
538
|
+
console.log(`📦 flow diff — ${change}(active):无 git 基线(无历史仓)——无可对账面`)
|
|
539
|
+
return
|
|
540
|
+
}
|
|
541
|
+
console.log(`📐 flow diff — ${change}(${mode}${mode === 'active' ? `|baseline ${String(baseline).slice(0, 8)}→工作树` : '|flow done 冻结件'})`)
|
|
542
|
+
for (const r of rows) {
|
|
543
|
+
const a = r.additions == null ? ' ∅' : `+${String(r.additions).padStart(4)}`
|
|
544
|
+
const d = r.deletions == null ? ' ∅' : `-${String(r.deletions).padStart(4)}`
|
|
545
|
+
const tag = r.state === 'dirty' ? '(未提交)' : r.state === 'untracked' ? '(未跟踪·新文件)' : r.state === 'archived' && r.verdict ? `|${r.verdict}` : ''
|
|
546
|
+
console.log(` ${a} ${d} ${r.path}${tag}`)
|
|
547
|
+
}
|
|
548
|
+
console.log(`合计:${totals.files} 文件 +${totals.additions ?? '?'} -${totals.deletions ?? '?'}${mode === 'active' ? '(baseline→工作树累计口径,含未提交)' : ''}`)
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* 升厚行值重置(坑 thin-upgrade-stale-stage-window,2026-10-13):升厚只写 flow-state
|
|
553
|
+
* 不碰 DB 行——行值残留 'thin'(平台 pull 往返透传形态)时,serializeForSync 的 thin 投影
|
|
554
|
+
* 在首次 run <stage> 前持续误标平台「轻量」出身(短窗展示面失真)。升厚三写点统一调本
|
|
555
|
+
* helper:行 current_stage 'thin' → 'brainstorm'(厚档起点,与 run 阶段机首写同值),
|
|
556
|
+
* 仅 thin 值才动(其余行值不越权重置)。best-effort 不阻断升厚链。
|
|
557
|
+
*/
|
|
558
|
+
function resetThinStageRowOnUpgrade(cwd, change, specBase = null) {
|
|
559
|
+
try {
|
|
560
|
+
// cmdFlowDone 作用域无共享 ProgressManager——helper 直走底层数据库文件(node:sqlite)
|
|
561
|
+
// 零依赖;specBase 优先(与 flow 侧锚定同口径,平台模式 specRoot 异位不迷路)
|
|
562
|
+
const db = new DatabaseSync(join(specBase || join(cwd, '.sillyspec'), '.runtime', 'sillyspec.db'))
|
|
563
|
+
try {
|
|
564
|
+
const row = db.prepare('SELECT current_stage FROM changes WHERE name = ?').get(change)
|
|
565
|
+
if (row && row.current_stage === 'thin') {
|
|
566
|
+
db.prepare('UPDATE changes SET current_stage = ? WHERE name = ?').run('brainstorm', change)
|
|
567
|
+
console.log(' ↺ 升厚重置 DB 行 current_stage: thin → brainstorm(防平台短窗误标轻量出身)')
|
|
568
|
+
}
|
|
569
|
+
} finally {
|
|
570
|
+
try { db.close() } catch { /* 关闭失败不放大 */ }
|
|
571
|
+
}
|
|
572
|
+
} catch { /* 行值重置 best-effort,不阻断升厚 */ }
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
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
576
|
// local.yaml 缺席 fail-fast(R22 实证:缺 local.yaml 时 flow done 测试门静默兜底裸
|
|
447
577
|
// python -m pytest → aiobotocore 假红/全量 860s 撞 600s 帽 → 11 轮重试 135min)。
|
|
448
578
|
// local.yaml 是 init 的职责面——flow start 在此拒绝,不让 agent 在错误的测试配置上走完全程
|
|
@@ -477,9 +607,18 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
477
607
|
// 保留,机器只补缺件与绑定面。无产物 = 真 legacy 既有变更,维持原拒收。
|
|
478
608
|
const hasBsArtifacts = existsSync(join(changeDir, 'proposal.md')) || existsSync(join(changeDir, 'design.md'))
|
|
479
609
|
if (hasBsArtifacts) {
|
|
610
|
+
// 预段反馈闭环(2026-10-14-prestage-consent-gate):走了头脑风暴预段=选道正确,
|
|
611
|
+
// 消费 route-hindsight 标记(同意门的一次性豁免在这里用掉,无声明 flag 也放行)
|
|
612
|
+
try {
|
|
613
|
+
const { clearHindsightMark } = await import('./route-hindsight.js')
|
|
614
|
+
if (clearHindsightMark({ specBase })) console.log('🕰️ 预段同意门:头脑风暴收编即选道正确,route-hindsight 标记已消费')
|
|
615
|
+
} catch { /* 消费 best-effort */ }
|
|
480
616
|
const head = gitQuiet(cwd, ['rev-parse', 'HEAD'])
|
|
481
617
|
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: {}
|
|
618
|
+
writeFlowState(changeDir, { tier: 'thin', born_face: 'thin', adopted_from: 'brainstorm', review_force: reviewForce, baseline_commit: baseline, substeps: {},
|
|
619
|
+
// 极速模式留痕(2026-10-14-thin-turbo-mode D-005):--turbo 用户显式触发声明性落盘;
|
|
620
|
+
// flow done 读态以 turbo && tier!=='thick' 判定(D-007 升厚即失效)
|
|
621
|
+
...(turbo ? { turbo: true, turbo_since: new Date().toISOString() } : {}) })
|
|
483
622
|
try {
|
|
484
623
|
const { redraftMissingArtifacts, ensureBindingSlots } = await import('./flow-draft.js')
|
|
485
624
|
const r = redraftMissingArtifacts({ changeDir, change, input: null, runtimeRoot })
|
|
@@ -519,6 +658,8 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
519
658
|
try { digestLines = (await flowKnowledgeDigest({ specBase, change, changeDir, input: null })).lines } catch { /* 注入 best-effort */ }
|
|
520
659
|
console.log([
|
|
521
660
|
`🧲 头脑风暴产物已收编进轻量跑道: ${change}(adopted_from=brainstorm,baseline=${baseline ? baseline.slice(0, 10) : '(无 git 历史)'})`,
|
|
661
|
+
// 极速指引行(2026-10-14-thin-turbo-mode FR-01):--turbo 在场才输出,缺省路径逐字不变
|
|
662
|
+
...(turbo ? [`⚡ 极速模式(用户显式触发):只产 spec+代码,收口自动跳过实测与重仪式门(必留面照常);本次收口不含测试客观核验`] : []),
|
|
522
663
|
`══════════════════════════════════════`,
|
|
523
664
|
`📖 必读(头脑风暴产出——方案的承诺锚,先读完再动手):`,
|
|
524
665
|
...artifacts.map((p) => ` - ${p}`),
|
|
@@ -540,6 +681,10 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
540
681
|
...materials.map((p) => ` - ${p}`),
|
|
541
682
|
``,
|
|
542
683
|
...digestLines,
|
|
684
|
+
...(prototypePaths.length > 0 ? [
|
|
685
|
+
`变更原型(头脑风暴产出,UI 实现前必看——中部 🖼️ 行的绝对尾部重列,防长输出截断丢指针):`,
|
|
686
|
+
...prototypePaths.map((p) => ` - ${p}`),
|
|
687
|
+
] : []),
|
|
543
688
|
].filter(Boolean).join('\n'))
|
|
544
689
|
try { await triggerSync(cwd, change) } catch { /* 同步绝不阻断协议面 */ }
|
|
545
690
|
return { adopted: true, change, baseline }
|
|
@@ -663,6 +808,37 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
663
808
|
}
|
|
664
809
|
}
|
|
665
810
|
|
|
811
|
+
// 预段选道同意门(2026-10-14-prestage-consent-gate):route-hindsight 标记从「下次 start 点名
|
|
812
|
+
// 提示」升级为声明即过的同意门——上个轻量变更被标「疑似该走预段未走」时,本次全新 start 须
|
|
813
|
+
// 显式选道一次(--no-prestage 声明留痕 / 先走 brainstorm 预段)。边界:门不判本次需求清不清晰
|
|
814
|
+
// (判定归 agent/用户,D-005 弱信号),只把上次的过程形态信号摆上台面要求一次显式消费——同
|
|
815
|
+
// --autopilot/--upgrade-thick 的同意门模式(「可无视」升级为「可无视但需留痕声明」)。
|
|
816
|
+
// 豁免面:resume(上方分支早退)天然不受影响;平台 writer 预建空目录(proceedFreshEmptyDir)
|
|
817
|
+
// 豁免——平台自动派发无选道对话面,标记留给人工会话消化;adopt(头脑风暴收编)= 选道正确,
|
|
818
|
+
// 在 adopt 分支消费标记。读取 fail-open(读不到=无门),消费失败不阻断(下次再声明一次)。
|
|
819
|
+
if (!proceedFreshEmptyDir) {
|
|
820
|
+
let prestageMark = null
|
|
821
|
+
try {
|
|
822
|
+
const { readHindsightMark } = await import('./route-hindsight.js')
|
|
823
|
+
prestageMark = readHindsightMark({ specBase })
|
|
824
|
+
} catch { /* 标记读取 fail-open */ }
|
|
825
|
+
if (prestageMark) {
|
|
826
|
+
if (!prestageAck) {
|
|
827
|
+
const _why = Array.isArray(prestageMark.reasons) && prestageMark.reasons.length > 0
|
|
828
|
+
? prestageMark.reasons.join(';') : '过程形态超阈'
|
|
829
|
+
console.error(`❓ 预段选道同意门:上个轻量变更「${prestageMark.change}」被 route-hindsight 标记「疑似该走头脑风暴预段未走」(${_why})。本次全新 start 须显式选道(声明即过——标记是过程形态信号非定罪):`)
|
|
830
|
+
console.error(` ① 确认需求已含决策(成功标准来自用户原话或既有文档转写,而非你的推断):sillyspec flow start --change ${change} --input "<原输入>" --no-prestage(声明留痕 flow-state.prestage_ack,标记消费)`)
|
|
831
|
+
console.error(` ② 需求不清晰或需方案探索:sillyspec run brainstorm --change ${change}(完成后回来 flow start 收编,标记自动消费)`)
|
|
832
|
+
process.exit(2)
|
|
833
|
+
}
|
|
834
|
+
try {
|
|
835
|
+
const { clearHindsightMark } = await import('./route-hindsight.js')
|
|
836
|
+
clearHindsightMark({ specBase })
|
|
837
|
+
console.log(`🕰️ 预段同意门:已按 --no-prestage 声明放行(本次需求已含决策,留痕 flow-state.prestage_ack;上个标记「${prestageMark.change}」已消费)`)
|
|
838
|
+
} catch { /* 标记消费 best-effort:失败不阻断(下次 start 再声明一次) */ }
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
666
842
|
// 升厚预判已删除(2026-09-25-thin-precheck-removal):技术面关键词(数据库/迁移等)测错轴——
|
|
667
843
|
// R16 实证碰迁移的变更轻量变更带评审最优收口,预判反诱发误升厚(臂 A 129M)与误报打断。选道只留
|
|
668
844
|
// 形态信号:清晰度门管「需求说不清楚」(预段收编),升厚只留用户决策(--upgrade-thick 同意门)
|
|
@@ -708,10 +884,44 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
708
884
|
// autopilot(2026-10-04-thin-docs-v2 FR-06):用户显式声明跳过 spec 断点人审(--autopilot);
|
|
709
885
|
// 缺省 false——flow done 要求 flow approve 批准证据(spec 断点机器化,护栏#2:零 prompt 劝说)
|
|
710
886
|
autopilot: Boolean(autopilot),
|
|
887
|
+
// 预段同意门声明留痕(2026-10-14-prestage-consent-gate):hindsight 标记在场时全新 start
|
|
888
|
+
// 以 --no-prestage 显式声明「本次需求已含决策」放行——审计面可区分「确认过」与「没被问过」
|
|
889
|
+
prestage_ack: Boolean(prestageAck),
|
|
890
|
+
// 极速模式留痕(2026-10-14-thin-turbo-mode D-005):--turbo 用户显式触发声明性落盘;
|
|
891
|
+
// flow done 读态以 turbo && tier!=='thick' 判定(D-007 升厚即失效)
|
|
892
|
+
...(turbo ? { turbo: true, turbo_since: new Date().toISOString() } : {}),
|
|
711
893
|
baseline_commit: baseline,
|
|
712
894
|
substeps: {},
|
|
713
895
|
})
|
|
714
896
|
|
|
897
|
+
// 跨仓基线锚(2026-10-13-cross-repo-baseline-anchor):thin 手工交付的跨仓对账此前无锚
|
|
898
|
+
// (B 档 HEAD~1 单笔窗口,多提交交付漏文件+交错时序抓错面——fire-ledger 两笔交付实证)。
|
|
899
|
+
// start 时点记各注册仓 HEAD(cross_baselines),收口窗口=基线..工作树(与主仓 baseline_commit
|
|
900
|
+
// 同构)。首写者胜(对齐 baseline_sha256 模式:重入不刷新,防跨仓已有并行提交后重锚漂移窗口)。
|
|
901
|
+
// 纯读 fail-soft:注册不可达/非 git 仓/rev-parse 失败 → 跳过该仓,绝不阻断 start。
|
|
902
|
+
try {
|
|
903
|
+
if (!(readFlowState(changeDir) || {}).cross_baselines) {
|
|
904
|
+
const _yamlText = existsSync(join(specBase, 'local.yaml')) ? readFileSync(join(specBase, 'local.yaml'), 'utf8') : ''
|
|
905
|
+
if (_yamlText) {
|
|
906
|
+
const { parseRepoRegistry } = await import('./stages/plan-postcheck.js')
|
|
907
|
+
const { isAbsolute: _isAbs, resolve: _res } = await import('node:path')
|
|
908
|
+
const _repos = parseRepoRegistry(_yamlText)
|
|
909
|
+
const _bl = {}
|
|
910
|
+
for (const [k, rawPath] of _repos || []) {
|
|
911
|
+
const rp = _isAbs(rawPath) ? rawPath : _res(cwd, rawPath)
|
|
912
|
+
if (!rp || !existsSync(rp)) continue
|
|
913
|
+
const sha = gitQuiet(rp, ['rev-parse', 'HEAD'], { timeout: 10_000 })
|
|
914
|
+
const s = sha === null ? '' : String(sha).trim()
|
|
915
|
+
if (/^[0-9a-f]{7,40}$/i.test(s)) _bl[k] = s
|
|
916
|
+
}
|
|
917
|
+
if (Object.keys(_bl).length > 0) {
|
|
918
|
+
writeFlowState(changeDir, { cross_baselines: _bl })
|
|
919
|
+
console.log(`🌐 跨仓基线锚:${Object.keys(_bl).length} 个注册仓 HEAD 已记入 flow-state(cross_baselines 首写者胜)——收口跨仓对账窗口=基线..工作树`)
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
} catch { /* 跨仓基线锚 best-effort:失败不阻断 start(收口回退既有档位) */ }
|
|
924
|
+
|
|
715
925
|
// 全件机器起草(R7 切片三 / FR-07:工件回填轮=0——治理工件 CLI 写,agent 只裁例外)。
|
|
716
926
|
// 任务卡分岔(用户裁定#3):默认 thin+直写零任务卡;--thick/--with-tasks 才生成任务卡。
|
|
717
927
|
let drafted = []
|
|
@@ -818,6 +1028,11 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
818
1028
|
const lines = [
|
|
819
1029
|
`🏃 flow start(${thick ? 'thick 厚档(--thick 显式声明,人声明不做启发式)' : 'thin 轻量跑道'}): ${change}`,
|
|
820
1030
|
`══════════════════════════════════════`,
|
|
1031
|
+
// 极速指引行(2026-10-14-thin-turbo-mode FR-01;2026-10-14-turbo-guide-tier-copy 评审 P3
|
|
1032
|
+
// 清偿):承诺文案随档位收窄——thick 起步时 done 读态 turbo 不生效(D-007),文案如实告知
|
|
1033
|
+
// 全量仪式,防「提示说 A 实现是 B」漂移(cli-copy-selfsufficient②)。缺省路径逐字不变。
|
|
1034
|
+
...(turbo && !thick ? [`⚡ 极速模式(用户显式触发):只产 spec+代码,收口自动跳过实测与重仪式门(必留面照常);本次收口不含测试客观核验`]
|
|
1035
|
+
: turbo && thick ? [`⚡ 极速留痕已记(--turbo),但 --thick 厚档起步——收口走全量仪式,实测与仪式门照跑不跳过(升厚即失效)`] : []),
|
|
821
1036
|
...(hindsightHint ? [hindsightHint, ''] : []),
|
|
822
1037
|
`🔎 选道自检——对本需求,你还有没有必须问用户才能动手的问题?`,
|
|
823
1038
|
` 有 → sillyspec run brainstorm --change ${change}(它就是结构化问询协议,产物随后 flow start 自动收编续跑);`,
|
|
@@ -926,6 +1141,15 @@ function printRecoveryBriefing({ cwd, specBase, change, changeDir, runtimeRoot,
|
|
|
926
1141
|
`- 剩什么:${left.length === 0 && dirty === 0 ? '活已干完' : dirty > 0 ? '活未干完(继续改代码)' : '收尾待裁决'}`,
|
|
927
1142
|
`- 下一步:${left.length === 0 ? `sillyspec flow done --change ${change}` : `继续干活;干完跑 flow done(断点续)`}`,
|
|
928
1143
|
...(digestLines.length > 0 ? ['', ...digestLines] : []),
|
|
1144
|
+
// 尾部原型锚(2026-10-14-guide-anchor-adopt-proto):收编/含原型变更重入时原型指针不隐身
|
|
1145
|
+
//(首进 adopt 输出的 🖼️ 行在会话切换后已丢,简报尾部补可发现面;置于 digestLines 后=绝对
|
|
1146
|
+
// 尾部,长知识注入段也顶不掉——评审 P3-2 清偿)。无原型零输出。
|
|
1147
|
+
...(() => {
|
|
1148
|
+
const protos = changeArtifactPaths(changeDir).filter((p) => /\.html$/i.test(p))
|
|
1149
|
+
return protos.length > 0
|
|
1150
|
+
? [`- 变更原型在场(UI 实现前必看,界面/交互/流程按原型对齐):`, ...protos.map((p) => ` - ${p}`)]
|
|
1151
|
+
: []
|
|
1152
|
+
})(),
|
|
929
1153
|
].join('\n'))
|
|
930
1154
|
}
|
|
931
1155
|
|
|
@@ -958,6 +1182,9 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
958
1182
|
console.error('❌ 本 change 已混跑回退 legacy(跑过 run <stage>)——剩余流程按厚档走 run <stage>,flow done 不再裁决')
|
|
959
1183
|
process.exit(2)
|
|
960
1184
|
}
|
|
1185
|
+
// 极速模式读态(2026-10-14-thin-turbo-mode FR-02/D-007):turbo && tier!=='thick' 双条件——
|
|
1186
|
+
// 升厚(distill 异态/verify 失败/--thick 起步)后 turbo 残留不短路厚档仪式,重入即恢复全量校验
|
|
1187
|
+
const turbo = st.turbo === true && st.tier !== 'thick'
|
|
961
1188
|
const runtimeRoot = resolveRuntimeRoot(runtimeRootOpt ? { runtimeRoot: runtimeRootOpt } : {}, specBase)
|
|
962
1189
|
// 归属收窄清单(2026-09-25-thin-dogfood-fixes 修复②):baseline..HEAD 不分作者——并行会话在
|
|
963
1190
|
// start..done 之间的提交会混入本变更实测面与 FR 域路由。复用 verify 对账同款切分器:他侧显式
|
|
@@ -1235,7 +1462,14 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1235
1462
|
}
|
|
1236
1463
|
}
|
|
1237
1464
|
} catch { /* 节奏门 best-effort:读流失败静默(fail-open——观测缺席不阻断) */ }
|
|
1238
|
-
|
|
1465
|
+
// 极速模式实测门跳过(2026-10-14-thin-turbo-mode FR-02/D-002):turbo 在场不调
|
|
1466
|
+
// runQuickTestLintGate,合成 gate 同形状 skipped 对象(带 status:'skipped' 完整形态——
|
|
1467
|
+
// 下方 fmt 对 null 显示 '—',带形态才输出「skipped — 跳过原因」,审计可见;
|
|
1468
|
+
// 对齐 quick-audit env 逃生门 skip 形态先例 src/run/quick-audit.js:482)。
|
|
1469
|
+
// 失败升厚分支(下方 action==='fail')turbo 下天然不触达——升厚语义不变。
|
|
1470
|
+
const gate = turbo
|
|
1471
|
+
? { 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 } }
|
|
1472
|
+
: await runQuickTestLintGate({ cwd, specBase, changedFiles, changeName: change, skipSentinel: true /* flow 侧哨兵带 baseline 区间,quick 侧区间不可靠——单判不双判 */ })
|
|
1239
1473
|
if (gate && gate.action === 'fail') {
|
|
1240
1474
|
console.error(`❌ 测试门 FAIL(整单 FAIL——实测失败/超时=失败,不继续 distill/归档):`)
|
|
1241
1475
|
console.error(` ${gate.reason || gate.message || JSON.stringify(gate)}`)
|
|
@@ -1279,6 +1513,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1279
1513
|
} catch { /* lint 件套 best-effort */ }
|
|
1280
1514
|
// 失败触发升级(R7 切片四 / FR-10 / 护栏#4:不依赖 agent 主动)——剩余流程按厚档走
|
|
1281
1515
|
writeFlowState(changeDir, { tier: 'thick', upgrade_reason: `verify 实测失败(${gate.reason || 'test fail'})——失败自动升厚` })
|
|
1516
|
+
resetThinStageRowOnUpgrade(cwd, change, specBase)
|
|
1282
1517
|
console.error(' ⬆️ 已自动升厚档(tier=thick):重入修复后剩余流程按厚档语义(归档不跳过 plan.md 校验)')
|
|
1283
1518
|
reportMidFail('ledger')
|
|
1284
1519
|
process.exit(1)
|
|
@@ -1290,7 +1525,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1290
1525
|
const fmt = (r) => r
|
|
1291
1526
|
? `${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
1527
|
: '—'
|
|
1293
|
-
gateSummaryText =
|
|
1528
|
+
gateSummaryText = `${turbo ? '⚡极速模式|' : ''}test: ${fmt(gate && gate.test)}|lint: ${fmt(gate && gate.lint)}|门文件 ${Array.isArray(changedFiles) ? changedFiles.length : '?'} 个`
|
|
1294
1529
|
console.log(`🧾 实测面对账 — ${gateSummaryText}`)
|
|
1295
1530
|
try { writeFlowState(changeDir, { gate_summary: gateSummaryText }) } catch { /* 存档 best-effort */ }
|
|
1296
1531
|
// FR 腐烂 suspect(2026-09-25-thin-fr-inject-parity):quick-done 钩子迁轻量道(quick 退役后
|
|
@@ -1374,62 +1609,87 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1374
1609
|
// 提交时,幂等跳过会把 change.patch/review.json 停在处置前时点(归档审计件缺处置面,
|
|
1375
1610
|
// 2026-10-05-flowdone-lintfail-output 收编活体复现)。漂移 → 自动重冻结(等价 --refreeze)
|
|
1376
1611
|
// + review 已有结论则隔离旧 review.json(结论对着旧冻结面不作数)并重置标记强制重评。
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1612
|
+
//
|
|
1613
|
+
// 冻结件存在性复查(2026-10-14-patch-refreeze-missing-artifact):子步标记 done 而冻结件
|
|
1614
|
+
// 缺席(手工误删/上次写盘中断)时,旧逻辑 readFileSync 抛错被下方 catch 吞掉 → 幂等跳过
|
|
1615
|
+
// → 归档无审计冻结件(multi-agent-platform 2026-10-14 实证:verify-result 记「无冻结件」,
|
|
1616
|
+
// 只能按 git 手工重建)。存在性复查优先于子步状态:缺件按漂移同款自动重冻结(重冻面=
|
|
1617
|
+
// 当前 HEAD);review 处置沿用漂移面同一隔离规则——缺基面无法判 governance-only,
|
|
1618
|
+
// 未锚定一律隔离(fail-safe 宁可多评)。
|
|
1619
|
+
const patchJsonPath = join(changeDir, 'change-patch.json')
|
|
1620
|
+
const patchTextPath = join(changeDir, 'change.patch')
|
|
1621
|
+
const missingFreeze = [
|
|
1622
|
+
!existsSync(patchJsonPath) ? 'change-patch.json' : null,
|
|
1623
|
+
!existsSync(patchTextPath) ? 'change.patch' : null,
|
|
1624
|
+
].filter(Boolean)
|
|
1625
|
+
let drifted = false
|
|
1626
|
+
let drift = null
|
|
1627
|
+
if (missingFreeze.length > 0) {
|
|
1628
|
+
drifted = true
|
|
1629
|
+
console.warn(`⚠️ 冻结件缺失:patch 子步标记 done 但 ${missingFreeze.join('、')} 不在——存在性复查优先于子步状态,按漂移同款自动重冻结(2026-10-14-patch-refreeze-missing-artifact)`)
|
|
1630
|
+
} else {
|
|
1631
|
+
try {
|
|
1632
|
+
const patchMeta = JSON.parse(readFileSync(patchJsonPath, 'utf8'))
|
|
1633
|
+
const { detectPatchDrift } = await import('./flow-parity.js')
|
|
1634
|
+
drift = detectPatchDrift({ cwd, change, freezeHead: patchMeta.head })
|
|
1635
|
+
if (drift.drifted) {
|
|
1636
|
+
drifted = true
|
|
1637
|
+
console.warn(`⚠️ 审计时点漂移:patch 冻结(${String(patchMeta.head || '').slice(0, 8)})后另有 ${drift.ownCommits.length} 个本变更交付提交(${drift.ownCommits.slice(0, 3).join(';')}${drift.ownCommits.length > 3 ? ' 等' : ''})——幂等跳过会把审计件停在处置前时点`)
|
|
1638
|
+
}
|
|
1639
|
+
} catch { /* 漂移检测失败按现状幂等跳过(fail-safe 不新造阻断面) */ }
|
|
1640
|
+
}
|
|
1641
|
+
if (drifted) {
|
|
1642
|
+
// 隔离先行(评审处置 P3:时间戳槽位防跨代覆盖——单槽 .superseded 在 Windows renameSync
|
|
1643
|
+
// 同名碰撞会抛错;隔离失败则整体退回现状幂等跳过(fail-safe:漂移处理要么完整交付要么
|
|
1644
|
+
// 不动,不留半套状态),指引人工处置后重跑)
|
|
1645
|
+
const reviewPath = join(changeDir, 'review.json')
|
|
1646
|
+
let quarantineOk = true
|
|
1647
|
+
if (existsSync(reviewPath)) {
|
|
1648
|
+
// 重冻结目标 HEAD:漂移面取 detectPatchDrift.head;缺件重冻面取当前 HEAD(重冻结将锚定该面)。
|
|
1649
|
+
// 锚定保留判定(2026-10-06-review-anchor-and-negation 实测竞态:复审对着当前 HEAD
|
|
1650
|
+
// 做的 PASS 结论落盘 26 秒后被「重冻结时刻在场=过期」口径误隔离):reviewedAgainst
|
|
1651
|
+
// 命中重冻结目标 HEAD = 评审对象即最新交付面,结论有效——保留不隔离、review 标记不重置。
|
|
1652
|
+
// 缺省/读失败/格式非法按未锚定处理,隔离行为不变(fail-safe 宁可多评)。
|
|
1653
|
+
const refreezeHead = drift ? drift.head : String(gitQuiet(cwd, ['rev-parse', 'HEAD']) || '').trim()
|
|
1654
|
+
let anchoredToHead = false
|
|
1655
|
+
try {
|
|
1656
|
+
const { reviewAnchoredToHead } = await import('./flow-review.js')
|
|
1657
|
+
anchoredToHead = reviewAnchoredToHead(reviewPath, refreezeHead)
|
|
1658
|
+
} catch { /* 锚定判定失败按未锚定处理 */ }
|
|
1659
|
+
if (anchoredToHead) {
|
|
1660
|
+
console.log(` review.json 已锚定当前 HEAD(reviewedAgainst 命中 ${String(refreezeHead || '').slice(0, 8)})——评审对象即最新交付面,结论保留不隔离`)
|
|
1661
|
+
} else if (drift && drift.governanceOnly && !drift.touchedPromiseFace) {
|
|
1662
|
+
// 治理面等价保留(2026-10-10-drift-review-governance-keep,坑 drift-review-governance-loop
|
|
1663
|
+
// 用户实证):评审结论的对象是交付面——窗口内本变更提交只碰 .sillyspec/** 治理文件且
|
|
1664
|
+
// 未触本变更 requirements/design(承诺面也是评审对象)时,结论对最新交付面逐字节等价,
|
|
1665
|
+
// 保留不隔离不重置标记;change.patch 仍走下方 driftRefreeze 自动重冻结(FR-01 后治理
|
|
1666
|
+
// 工件不入冻结面——治理修复的留档真相=归档目录本体文件,重冻结保交付面时点对齐)
|
|
1667
|
+
// (审计件覆盖最新提交面不变量不破)。此前任何后缀提交一律隔离重评——评审后修一个
|
|
1668
|
+
// P2 文档措辞也付整轮子代理评审费(循环,被迫手工重排执行顺序自赎)。
|
|
1669
|
+
console.log(` review.json 保留不隔离——窗口内本变更提交仅触及治理文件(${drift.ownFiles.length} 个,未触 requirements/design),评审对象(交付面)未变,结论对最新面等价;change.patch 自动重冻结(治理工件不入冻结面,治理修复随归档目录本体留档)`)
|
|
1670
|
+
} else {
|
|
1671
|
+
const ts = new Date().toISOString().replace(/[-:]/g, '').replace(/\..+$/, '')
|
|
1394
1672
|
try {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
} catch {
|
|
1398
|
-
|
|
1399
|
-
console.
|
|
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
|
-
}
|
|
1673
|
+
renameSync(reviewPath, `${reviewPath}.superseded-${ts}`)
|
|
1674
|
+
console.warn(` 旧 review.json 已隔离为 review.json.superseded-${ts}(评审结论对着旧冻结面)——重新执行评审任务书后再收口`)
|
|
1675
|
+
} catch (e) {
|
|
1676
|
+
quarantineOk = false
|
|
1677
|
+
console.error(` ⚠️ 旧 review.json 隔离失败(${e.message})——本次保持现状幂等跳过,手动删除/改名 ${reviewPath} 后重跑(将自动重冻结+重评)`)
|
|
1418
1678
|
}
|
|
1419
1679
|
}
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1680
|
+
}
|
|
1681
|
+
if (quarantineOk) {
|
|
1682
|
+
driftRefreeze = true
|
|
1683
|
+
if (st.substeps?.review === 'done' && !existsSync(reviewPath)) {
|
|
1684
|
+
// 双故障边界(评审 P3 处置披露):writeFlowState 盘写失败时内存重置保本运行正确;
|
|
1685
|
+
// 极端双故障(隔离成功+盘写失败)下轮残留 done 且 review.json 缺席 → review 子步
|
|
1686
|
+
// backfill 按 exempt 误读——该窄路径未测(需 I/O 故障注入,design 边界披露)
|
|
1687
|
+
try { writeFlowState(changeDir, { substeps: { review: null } }) } catch { /* 见上边界注释 */ }
|
|
1688
|
+
if (st.substeps) st.substeps.review = null
|
|
1689
|
+
if (!existsSync(reviewPath)) console.warn(' review 豁免口径对着旧冻结面——review 子步标记已重置,本次重新定档')
|
|
1430
1690
|
}
|
|
1431
1691
|
}
|
|
1432
|
-
}
|
|
1692
|
+
}
|
|
1433
1693
|
if (!driftRefreeze) skip('patch')
|
|
1434
1694
|
}
|
|
1435
1695
|
if (st.substeps?.patch !== 'done' || driftRefreeze) {
|
|
@@ -1532,7 +1792,12 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1532
1792
|
// ——旧行为警告后继续归档,autopilot 单跑到底把三选一变成不可达选项(归档即拒收重入)。
|
|
1533
1793
|
// 缺口在场且未显式处置 → 停在 patch 子步标记之前(半态可重入,与 review 中断同款
|
|
1534
1794
|
// exit 语义):处置后重跑,patch 全量重建自动并入已提交 src。
|
|
1535
|
-
|
|
1795
|
+
// 极速模式(2026-10-14-thin-turbo-mode FR-02/D-002):缺口三选一阻断降 advisory——
|
|
1796
|
+
// 警告+缺口数留痕后放行,不 exit;非 turbo 路径逐字不变。
|
|
1797
|
+
if (turbo && !freezeDirty && !acceptDirtyGap) {
|
|
1798
|
+
console.warn(`⚡ 极速模式:未提交交付缺口未显式处置——降 advisory 放行(缺口数 ${freeze.dirtyWarned.length} 随收口输出留痕,审计自负)`)
|
|
1799
|
+
}
|
|
1800
|
+
if (!freezeDirty && !acceptDirtyGap && !turbo) {
|
|
1536
1801
|
console.error('⛔ 未提交交付缺口未显式处置——收口阻断(不归档;处置后重跑同一条命令从断点续):')
|
|
1537
1802
|
console.error(` ① git commit 这些文件(信息尾缀带 (${change}) 或 (${change}),重跑本命令——冻结面自动并入`)
|
|
1538
1803
|
console.error(` ② 全部归属本变更:flow done --change ${change} --freeze-dirty`)
|
|
@@ -1731,6 +1996,9 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1731
1996
|
changeName: change,
|
|
1732
1997
|
planEntries,
|
|
1733
1998
|
collectPatch: true,
|
|
1999
|
+
// 跨仓基线锚透传(2026-10-13-cross-repo-baseline-anchor):flow start 记录的
|
|
2000
|
+
// cross_baselines → collectRepoActual 3c 档(基线..工作树窗口,token 归因主面)
|
|
2001
|
+
crossBaselines: (st && st.cross_baselines && typeof st.cross_baselines === 'object') ? st.cross_baselines : null,
|
|
1734
2002
|
})
|
|
1735
2003
|
crossRepoRows = recon.rows
|
|
1736
2004
|
crossRepos = recon.repos
|
|
@@ -1822,6 +2090,13 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1822
2090
|
reviewOutcome = { required: false, sampled: false, verdict: 'exempt', backfilled: true }
|
|
1823
2091
|
}
|
|
1824
2092
|
} catch { reviewOutcome = { required: null, verdict: 'done', backfilled: true } }
|
|
2093
|
+
} else if (turbo) {
|
|
2094
|
+
// 极速模式评审豁免(2026-10-14-thin-turbo-mode FR-02/D-001/D-002):用户显式极速声明=
|
|
2095
|
+
// 风险自担留痕,跳过 classifyReviewNeed 强制豁免(telemetry src/flow.js 整对象 JSON
|
|
2096
|
+
// 序列化,turboExempt 字段无破坏——Grill X-003 探针已核)。
|
|
2097
|
+
console.log('⚡ 极速模式:评审子步豁免(用户显式极速声明=风险自担留痕)')
|
|
2098
|
+
reviewOutcome = { required: false, sampled: false, verdict: 'exempt', turboExempt: true }
|
|
2099
|
+
mark('review')
|
|
1825
2100
|
} else {
|
|
1826
2101
|
const { classifyReviewNeed, renderReviewerTaskbook, validateReviewJson } = await import('./flow-review.js')
|
|
1827
2102
|
let patchText = null
|
|
@@ -1912,6 +2187,10 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1912
2187
|
// 视觉降级无用户裁决留痕恒拦;ui_visual_gate=error 时缺 visual-evidence.md 亦拦;
|
|
1913
2188
|
// 默认 warn 档只警告不拦。与 run/gates.js verify 收尾同一检测单点,不二算)。
|
|
1914
2189
|
try {
|
|
2190
|
+
if (turbo) {
|
|
2191
|
+
// 极速模式(2026-10-14-thin-turbo-mode FR-02/D-002):ui 视觉探针跳过留痕
|
|
2192
|
+
console.log('⚡ 极速模式:UI 视觉证据探针跳过(审计留痕)')
|
|
2193
|
+
} else {
|
|
1915
2194
|
const uiProbe = runUiVisualProbe({ changeDir, gate: readUiVisualGate(specBase) })
|
|
1916
2195
|
if (uiProbe.level === 'error') {
|
|
1917
2196
|
console.error('\n❌ UI 视觉证据门(探针 12 error 档)阻断收口:')
|
|
@@ -1922,6 +2201,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1922
2201
|
} else if (uiProbe.level === 'warn') {
|
|
1923
2202
|
console.warn(`⚠️ UI 视觉证据(探针 12 warn 档):${uiProbe.notes.join(';')}`)
|
|
1924
2203
|
}
|
|
2204
|
+
}
|
|
1925
2205
|
} catch (e) {
|
|
1926
2206
|
console.warn(`⚠️ UI 视觉证据探针异常(fail-soft 降级放行):${(e && e.message) || e}`)
|
|
1927
2207
|
}
|
|
@@ -1930,7 +2210,9 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1930
2210
|
// 竞争、在途残留;hunk_gate warn 默认(警告)/error(阻断)/off。与「提交面夹带嫌疑
|
|
1931
2211
|
// advisory」互补:advisory 在冻结面采集处看文件名,本门在 probes 执法看 hunk 与竞争面。
|
|
1932
2212
|
try {
|
|
1933
|
-
|
|
2213
|
+
// 极速模式(2026-10-14-thin-turbo-mode FR-02/D-002):hunk 门强制降 warn 档——
|
|
2214
|
+
// 判定链路照跑(三类信号照常渲染),仅 error 阻断档解除(降 advisory 留痕不阻断)。
|
|
2215
|
+
const gate = turbo ? 'warn' : readHunkGate(specBase)
|
|
1934
2216
|
let committedForGate = []
|
|
1935
2217
|
try {
|
|
1936
2218
|
committedForGate = String(gitQuiet(cwd, ['diff', '--name-only', `${st.baseline_commit}..HEAD`]) || '')
|
|
@@ -2007,6 +2289,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
2007
2289
|
const abnormal = res && (res.rejected > 0 || res.needsWait > 0)
|
|
2008
2290
|
if (abnormal) {
|
|
2009
2291
|
writeFlowState(changeDir, { tier: 'thick', upgrade_reason: `distill 异态(rejected=${res.rejected} needsWait=${res.needsWait})——升厚留人工裁决` })
|
|
2292
|
+
resetThinStageRowOnUpgrade(cwd, change, specBase)
|
|
2010
2293
|
console.warn(`⚠️ distill 异态 → 已升厚档(tier=thick)。剩余流程按厚档走 run <stage> 完整仪式;归档子步仍将执行(厚档语义不跳过 plan.md 校验)。`)
|
|
2011
2294
|
}
|
|
2012
2295
|
} catch (e) {
|
|
@@ -2333,6 +2616,8 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
|
|
|
2333
2616
|
withTasks: hasFlag('--with-tasks'),
|
|
2334
2617
|
reviewForce: hasFlag('--review') ? true : hasFlag('--no-review') ? false : null,
|
|
2335
2618
|
autopilot: hasFlag('--autopilot'),
|
|
2619
|
+
prestageAck: hasFlag('--no-prestage'),
|
|
2620
|
+
turbo: hasFlag('--turbo'),
|
|
2336
2621
|
cwd, specBase,
|
|
2337
2622
|
json: hasFlag('--json') || jsonGlobal,
|
|
2338
2623
|
})
|
|
@@ -2358,6 +2643,15 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
|
|
|
2358
2643
|
await reportAgentLog(change, sub)
|
|
2359
2644
|
return cmdFlowReject({ change, cwd, specBase, reason: getFlag('--reason') })
|
|
2360
2645
|
}
|
|
2646
|
+
if (sub === 'diff') {
|
|
2647
|
+
// 只读查询面(2026-10-13-flow-diff-cmd):change 粒度文件统计双态口——与 status 同级自愿面
|
|
2648
|
+
const change = getFlag('--change')
|
|
2649
|
+
if (!change) { console.error('❌ flow diff 需 --change <名>'); process.exit(2) }
|
|
2650
|
+
validateChangeName(change)
|
|
2651
|
+
await reportAgentLog(change, sub)
|
|
2652
|
+
await cmdFlowDiff({ change, cwd, specBase, json: jsonGlobal || hasFlag('--json') })
|
|
2653
|
+
return
|
|
2654
|
+
}
|
|
2361
2655
|
if (sub === 'status') {
|
|
2362
2656
|
// 三断点配套(2026-09-25-flow-checkpoints):随时可查当前变更阶段/槽位/子步进度
|
|
2363
2657
|
const change = getFlag('--change')
|
|
@@ -2535,12 +2829,13 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
|
|
|
2535
2829
|
console.log(`✅ flow amend-draft 留痕重锚:${r.reanchored.join('、')}——ledger amendment 审计在案;editRatio=${maxRatio}(段级最大;基准=首版原文,AGENT 槽不计入)`)
|
|
2536
2830
|
return r
|
|
2537
2831
|
}
|
|
2538
|
-
console.error('用法: sillyspec flow start --change <名> --input "<动机与背景+独立一行『成功标准:』+每行一条『- 可验证标准』>" [--thick|--with-tasks] | sillyspec flow done --change <名> | sillyspec flow status --change <名> [--json]')
|
|
2832
|
+
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
2833
|
console.error(' --input 可照抄形态(引号内换行合法):')
|
|
2540
2834
|
console.error(' sillyspec flow start --change <名> --input "<动机与背景>')
|
|
2541
2835
|
console.error('')
|
|
2542
2836
|
console.error(' 成功标准:')
|
|
2543
2837
|
console.error(' - <可验证标准>"')
|
|
2838
|
+
console.error(' 预段同意门被拦时(上个轻量变更被 route-hindsight 标记「疑似该走预段未走」):确认需求已含决策加 --no-prestage;不清晰则先 run brainstorm --change <名>,完成后回来 flow start 收编(标记自动消费)')
|
|
2544
2839
|
process.exit(2)
|
|
2545
2840
|
}
|
|
2546
2841
|
|