sillyspec 3.33.0 → 3.33.2
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/.claude/skills/sillyspec-flow/SKILL.md +18 -10
- package/.github/workflows/ci.yml +11 -3
- package/AGENTS.md +3 -3
- package/README.md +2 -2
- package/assets/command-cards/flow.md +9 -5
- package/package.json +1 -1
- package/src/archive-delta.js +4 -3
- package/src/change-delete.js +6 -5
- package/src/contract-matrix.js +2 -1
- package/src/cross-placement.js +5 -1
- package/src/cross-repo-reconcile.js +4 -1
- package/src/db.js +8 -2
- package/src/decision-distill.js +22 -8
- package/src/decision-values.js +34 -0
- package/src/decisions-io.js +5 -2
- package/src/deleted-ledger.js +2 -1
- package/src/doctor-diagnostics.js +8 -3
- package/src/flow-draft.js +17 -10
- package/src/flow-parity.js +93 -5
- package/src/flow-review-pack.js +183 -0
- package/src/flow-review.js +45 -3
- package/src/flow.js +365 -109
- package/src/friction-ledger.js +2 -1
- package/src/friction-tally.js +8 -4
- package/src/handoff.js +2 -1
- package/src/hooks/worktree-guard.js +5 -5
- package/src/index.js +62 -21
- package/src/init.js +7 -30
- package/src/knowledge-hits.js +2 -1
- package/src/knowledge-vector.js +2 -1
- package/src/progress/consistency-doctor.js +5 -2
- package/src/progress/stage-machine.js +6 -2
- package/src/progress.js +19 -8
- package/src/quicklog.js +194 -9
- package/src/review-dispatch.js +4 -3
- package/src/review-tier.js +2 -1
- package/src/route-hindsight.js +4 -3
- package/src/run/bg-sync.js +10 -3
- package/src/run/command.js +14 -12
- package/src/run/complete-handlers.js +40 -37
- package/src/run/complete.js +12 -8
- package/src/run/gate-snapshot-ledger.js +2 -1
- package/src/run/gate-snapshot.js +102 -23
- package/src/run/gates.js +32 -8
- package/src/run/prompt.js +6 -5
- package/src/run/scan-profile.js +2 -1
- package/src/run/shared.js +109 -5
- package/src/run/stage.js +5 -4
- package/src/run/test-ledger.js +2 -1
- package/src/run/verify-quality-scan.js +11 -5
- package/src/runtime-paths.js +189 -0
- package/src/scan-refresh.js +4 -3
- package/src/scope-audit.js +13 -7
- package/src/sentinel-assertions.js +37 -0
- package/src/spec-sync.js +38 -22
- package/src/stage-contract.js +24 -2
- package/src/stage-review.js +3 -2
- package/src/stage-templates.js +3 -2
- package/src/stages/brainstorm.js +6 -5
- package/src/sync-noise.js +3 -2
- package/src/sync.js +71 -15
- package/src/task-done.js +2 -1
- package/src/task-review.js +7 -5
- package/src/task-tick.js +45 -1
- package/src/validate-artifacts.js +2 -1
- package/src/verify-draft.js +2 -1
- package/src/verify-postcheck.js +11 -10
- package/src/verify-probes.js +157 -52
- package/src/watcher.js +15 -9
- package/src/worktree-apply.js +2 -1
- package/src/write-audit.js +2 -1
- package/templates/agents-instruction.md +3 -3
package/src/flow.js
CHANGED
|
@@ -25,13 +25,14 @@
|
|
|
25
25
|
* 六子步完成标记/legacy_fallback/route_hint)——fs-atomic 原子写,缺文件=未参与 thin。
|
|
26
26
|
* 幂等循 task-done 先例:子步各查自身完成标记,中断半态重入断点续,中段失败精确报告。
|
|
27
27
|
*/
|
|
28
|
-
import { existsSync, readFileSync, writeFileSync, readdirSync, appendFileSync, renameSync, statSync } from 'node:fs'
|
|
28
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync, appendFileSync, renameSync, statSync, unlinkSync } from 'node:fs'
|
|
29
|
+
import { runtimeStatePath, runtimeTempPath } from './runtime-paths.js'
|
|
29
30
|
import { join, relative, dirname } from 'node:path'
|
|
30
31
|
import { DatabaseSync } from 'node:sqlite'
|
|
31
32
|
import yaml from 'js-yaml'
|
|
32
33
|
import { git, gitQuiet } from './git-helper.js'
|
|
33
34
|
import { writeAtomicSync } from './fs-atomic.js'
|
|
34
|
-
import { resolveRuntimeRoot, triggerSync, assertDatedChangeName } from './run/shared.js'
|
|
35
|
+
import { resolveRuntimeRoot, triggerSync, triggerSyncTerminalStrict, assertDatedChangeName } from './run/shared.js'
|
|
35
36
|
import { detectUiTouch, buildUiGuidanceLines, runUiVisualProbe, readUiVisualGate, UI_EVIDENCE_FILENAME } from './ui-visual.js'
|
|
36
37
|
import { runHunkAttributionGate, readHunkGate, renderHunkAttributionLines } from './hunk-attribution.js'
|
|
37
38
|
// 2026-10-13-thin-sync-stage:FLOW_STATE_FILE/readFlowState 单一源上移叶子模块 flow-state.js
|
|
@@ -673,7 +674,7 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
673
674
|
const r = redraftMissingArtifacts({ changeDir, change, input: null, runtimeRoot })
|
|
674
675
|
if (r.drafted.length > 0) console.log(`📌 收编补生成缺失机器稿:${r.drafted.join('、')}(brainstorm 未产的工件机器补齐,criteria 从 proposal 成功标准回提;已存在文件未动)`)
|
|
675
676
|
const b = ensureBindingSlots({ changeDir })
|
|
676
|
-
if (b.appended) console.log(`📌 收编追加测试绑定槽 ${b.slots} 枚(brainstorm requirements
|
|
677
|
+
if (b.appended) console.log(`📌 收编追加测试绑定槽 ${b.slots} 枚(brainstorm requirements 无绑定面——动码前作答,flow done 校验)`)
|
|
677
678
|
} catch (e) { console.warn(`⚠️ 收编补件失败(best-effort): ${(e && e.message) || e}`) }
|
|
678
679
|
// route-hindsight 首版快照(收编时点=redraftMissingArtifacts 首次落盘后):adopt 的
|
|
679
680
|
// design 是头脑风暴产物、机器只补缺件——快照取收编时点内容,此后 agent 改写计入指标。
|
|
@@ -686,18 +687,24 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
686
687
|
try {
|
|
687
688
|
const { spawnWatcher } = await import('./watcher.js')
|
|
688
689
|
const w = await spawnWatcher(cwd, change, { specBase })
|
|
689
|
-
if (w.status === 'spawned') console.log(`🔄 [watcher] 观测旁路已拉起:事件流 .sillyspec/.runtime/watcher-events-${change}.jsonl(恒带 provisional:true)`)
|
|
690
|
+
if (w.status === 'spawned') console.log(`🔄 [watcher] 观测旁路已拉起:事件流 .sillyspec/.runtime/state/watcher-events-${change}.jsonl(恒带 provisional:true)`)
|
|
690
691
|
} catch { /* 观测旁路 best-effort */ }
|
|
691
|
-
// adopt
|
|
692
|
-
//
|
|
693
|
-
//
|
|
692
|
+
// adopt 标题(2026-09-29;2026-10-16-short-change-title 提炼;2026-10-16-title-range-gate
|
|
693
|
+
// 硬区间 10-30):头脑风暴 proposal H1 人写语义标题(纯模板 H1 回退动机首句)→ --title
|
|
694
|
+
// 优先(合区间校验,超界 exit 2)→ --input 首行提炼 → 兜底变更名。收编是既成事实的
|
|
695
|
+
// 延续面,非 flag 的超界不硬断(收编半态不可恢复)打 ⚠️ 强提示。
|
|
694
696
|
try {
|
|
695
|
-
const { deriveTitleFromLinkedChange,
|
|
696
|
-
|
|
697
|
+
const { deriveTitleFromLinkedChange, deriveShortChangeTitle, isValidChangeTitle, shortTitleRejectMessage, shortTitleRangeWarnLine } = await import('./quicklog.js')
|
|
698
|
+
if (titleFlag && !isValidChangeTitle(titleFlag)) {
|
|
699
|
+
console.error(shortTitleRejectMessage(titleFlag))
|
|
700
|
+
process.exit(2)
|
|
701
|
+
}
|
|
702
|
+
const adoptTitle = titleFlag || deriveTitleFromLinkedChange(specBase, change) || deriveShortChangeTitle(input) || change
|
|
697
703
|
pm.initChange(cwd, change, { title: adoptTitle })
|
|
698
704
|
const _arow = pm._ensureDB(cwd).getDb().prepare('SELECT title FROM changes WHERE name = ?').get(change)
|
|
699
705
|
if (_arow && !_arow.title) pm.updateChangeMeta(cwd, change, { title: adoptTitle })
|
|
700
706
|
console.log(`🏷️ 变更标题:${adoptTitle}(收编自头脑风暴产物;重入 --title 可改)`)
|
|
707
|
+
if (!isValidChangeTitle(adoptTitle)) console.log(shortTitleRangeWarnLine(adoptTitle))
|
|
701
708
|
} catch { /* adopt 标题 best-effort */ }
|
|
702
709
|
const materials = materialPaths(specBase, change, changeDir)
|
|
703
710
|
const artifacts = changeArtifactPaths(changeDir)
|
|
@@ -716,10 +723,11 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
716
723
|
? `🖼️ 原型在场(${prototypePaths.length} 个 HTML):实现前必看,界面/交互/流程按原型对齐;有出入以 design.md 承诺为准并在回复中说明。`
|
|
717
724
|
: null,
|
|
718
725
|
`【你要做的】① spec 阶段先定稿任务面:把 tasks.md 改写为工作分解(每行=可独立完成并当场验证的一步:做什么+怎么验证;全 \`- [ ]\`——代码开动前工作队列先存在);`,
|
|
726
|
+
`🛑 断点①(机器门):spec 面(任务面+测试绑定槽)定稿后把摘要给用户看 → flow approve --change ${change}(或用户显式 --autopilot)——批准后才动码。design 四节槽收编豁免(以 brainstorm design 为准)。`,
|
|
719
727
|
`② 执行走任务循环:对 tasks.md 每个 pending 行——展示「Working on task N/M: <任务>」→ 做 → 测试绿后当场勾一格(\`- [ ]\`→\`- [x]\`:sillyspec task tick --change ${change} --task task-NN 或直接 Edit)→ 下一行;全勾后 flow done(收口硬门拒单拍多格勾选)。design/decisions 是本变更的承诺锚(flow done 豁免 design 四节槽,以其为准)。`,
|
|
720
|
-
`requirements 测试绑定槽(收编追加)每条 FR
|
|
728
|
+
`requirements 测试绑定槽(收编追加)每条 FR 至少一行作答;写码前作答(动码前,与任务面定稿同批)。`,
|
|
721
729
|
`✅ 任务面归你(工作分解契约):tasks.md 是你的工作队列(机器种子可增删改,保持 \`- [ ] task-NN:\` 行形态)——CLI 以 checkbox 为进度状态机(task tick/flow status 实时读它),TodoWrite 类工具不替代;`,
|
|
722
|
-
` 勾选是收口证据面(逐格核提交 token/review.json):做一件 → 勾一格(task tick 即时回显进度与下一任务)→
|
|
730
|
+
` 勾选是收口证据面(逐格核提交 token/review.json):做一件 → 勾一格(task tick 即时回显进度与下一任务)→ 继续下一条,勿攒一把勾;勾选修改与任务交付同一提交入库(pathspec 带上本变更 tasks.md——task tick 回显给现成命令);`,
|
|
723
731
|
` 任务拟法:收口/推送/发布类动作不单列 task(协议自身承担,成功标准照写 requirements);确需单列的,勾选前先有消息预埋 task-NN token 的交付提交(回执类证据写进提交消息)。`,
|
|
724
732
|
` flow status --change ${change} 为自愿查看/恢复面(非协议必需,D-007 中间零必需交互;恢复时给下一任务指针与进度)。`,
|
|
725
733
|
`⚠️ 交付纪律:收口前交付代码显式 pathspec 提交——冻结件范围=baseline..HEAD,未提交不进审计件。`,
|
|
@@ -780,17 +788,24 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
780
788
|
writeFlowState(changeDir, { autopilot: true })
|
|
781
789
|
console.log(`🤖 已声明 autopilot:spec 断点人审豁免(用户显式声明留痕)`)
|
|
782
790
|
}
|
|
783
|
-
//
|
|
784
|
-
//
|
|
785
|
-
//
|
|
791
|
+
// 变更标题补写(2026-09-29 title=中文概括;2026-10-16-short-change-title 提炼 ≤30;
|
|
792
|
+
// 2026-10-16-title-range-gate 硬区间 10-30)——存量在途变更 title 空/缺失时的回填通道:
|
|
793
|
+
// 显式 --title 恒生效(重入改标题)且必须合区间(超界 exit 2,参数面最易修正);提炼补写
|
|
794
|
+
// 仅当库内 title 为空才写(首写者胜),超界不阻断(resume 不可断)打 ⚠️ 强提示。
|
|
786
795
|
try {
|
|
787
|
-
const {
|
|
796
|
+
const { deriveShortChangeTitleEx, isValidChangeTitle, shortTitleRejectMessage, shortTitleRangeWarnLine, SHORT_TITLE_HARDCUT_HINT } = await import('./quicklog.js')
|
|
797
|
+
if (titleFlag && !isValidChangeTitle(titleFlag)) {
|
|
798
|
+
console.error(shortTitleRejectMessage(titleFlag))
|
|
799
|
+
process.exit(2)
|
|
800
|
+
}
|
|
788
801
|
const _row = pm._ensureDB(cwd).getDb().prepare('SELECT title FROM changes WHERE name = ?').get(change)
|
|
789
802
|
const _cur = _row ? (_row.title || '') : ''
|
|
790
|
-
const _derived = titleFlag
|
|
791
|
-
if (_derived && (titleFlag || !_cur)) {
|
|
792
|
-
pm.updateChangeMeta(cwd, change, { title: _derived })
|
|
793
|
-
console.log(`🏷️ 变更标题${titleFlag ? '已更新' : '已补写'}:${_derived}
|
|
803
|
+
const _derived = titleFlag ? { title: titleFlag, hardCut: false } : deriveShortChangeTitleEx(input)
|
|
804
|
+
if (_derived && _derived.title && (titleFlag || !_cur)) {
|
|
805
|
+
pm.updateChangeMeta(cwd, change, { title: _derived.title })
|
|
806
|
+
console.log(`🏷️ 变更标题${titleFlag ? '已更新' : '已补写'}:${_derived.title}(面板显示用;须 10-30 字、建议 ~20 字,重入 --title 可改)`)
|
|
807
|
+
if (_derived.hardCut) console.log(SHORT_TITLE_HARDCUT_HINT)
|
|
808
|
+
if (!titleFlag && !isValidChangeTitle(_derived.title)) console.log(shortTitleRangeWarnLine(_derived.title))
|
|
794
809
|
}
|
|
795
810
|
} catch { /* 标题补写 best-effort */ }
|
|
796
811
|
// 知识注入(2026-09-25-thin-fr-inject-parity):resume 路径域路由走基线以来文件面——
|
|
@@ -893,16 +908,29 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
893
908
|
// 形态信号:清晰度门管「需求说不清楚」(预段收编),升厚只留用户决策(--upgrade-thick 同意门)
|
|
894
909
|
// 与运行时证据(实测失败升档/edit_ratio/评审——风险面在收口时点按承诺词/diff 原语/盲维判定)。
|
|
895
910
|
|
|
896
|
-
//
|
|
897
|
-
//
|
|
898
|
-
//
|
|
899
|
-
|
|
911
|
+
// 变更标题硬区间门(2026-10-16-title-range-gate:10-30 字必须、建议 ~20、强制 agent 命名;
|
|
912
|
+
// 提炼口径见 2026-10-16-short-change-title):--title 显式 > --input 首行提炼 > 变更名兜底。
|
|
913
|
+
// agent 命名时刻强制:不在区间/无 --input 兜底 → 拒绝启动(exit 2,在任何状态写入前——
|
|
914
|
+
// 零半态);平台 writer 预建空目录(自动派发,无 agent 对话面)豁免硬门仅 ⚠️ 留痕。
|
|
915
|
+
let _st = null
|
|
916
|
+
let _rejectMsg = null
|
|
900
917
|
try {
|
|
901
|
-
const
|
|
902
|
-
|
|
903
|
-
|
|
918
|
+
const _q = await import('./quicklog.js')
|
|
919
|
+
_st = _q.resolveStartTitle({ titleFlag, input, change, enforce: !proceedFreshEmptyDir })
|
|
920
|
+
if (!_st.ok) _rejectMsg = _q.shortTitleRejectMessage(_st.kind === 'fallback' ? _st.rejectedTitle : _st.title)
|
|
921
|
+
} catch { /* quicklog 不可用(实践不可达):降级保持旧行为兜底 */ _st = { title: titleFlag || change, ok: true, kind: titleFlag ? 'flag' : 'fallback', hardCut: false } }
|
|
922
|
+
if (_rejectMsg) {
|
|
923
|
+
console.error(_rejectMsg)
|
|
924
|
+
process.exit(2)
|
|
925
|
+
}
|
|
926
|
+
const changeTitle = _st.title
|
|
904
927
|
pm.initChange(cwd, change, { title: changeTitle })
|
|
905
|
-
console.log(`🏷️ 变更标题:${changeTitle}
|
|
928
|
+
console.log(`🏷️ 变更标题:${changeTitle}(面板显示用;须 10-30 字、建议 ~20 字,重入 flow start --title 可改)`)
|
|
929
|
+
try {
|
|
930
|
+
const { isValidChangeTitle, shortTitleRangeWarnLine, SHORT_TITLE_HARDCUT_HINT } = await import('./quicklog.js')
|
|
931
|
+
if (_st.hardCut) console.log(SHORT_TITLE_HARDCUT_HINT)
|
|
932
|
+
if (proceedFreshEmptyDir && !isValidChangeTitle(changeTitle)) console.log(shortTitleRangeWarnLine(changeTitle))
|
|
933
|
+
} catch { /* 提示行 best-effort */ }
|
|
906
934
|
try {
|
|
907
935
|
const { resolveSessionIdentity } = await import('./progress.js')
|
|
908
936
|
const { session } = resolveSessionIdentity({ flagSession: null, cwd })
|
|
@@ -915,7 +943,7 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
915
943
|
try {
|
|
916
944
|
const { spawnWatcher } = await import('./watcher.js')
|
|
917
945
|
const r = await spawnWatcher(cwd, change, { specBase })
|
|
918
|
-
if (r.status === 'spawned') console.log(`🔄 [watcher] 观测旁路已拉起:事件流 .sillyspec/.runtime/watcher-events-${change}.jsonl(恒带 provisional:true)`)
|
|
946
|
+
if (r.status === 'spawned') console.log(`🔄 [watcher] 观测旁路已拉起:事件流 .sillyspec/.runtime/state/watcher-events-${change}.jsonl(恒带 provisional:true)`)
|
|
919
947
|
} catch { /* 观测旁路 best-effort,绝不阻断协议面 */ }
|
|
920
948
|
|
|
921
949
|
const head = gitQuiet(cwd, ['rev-parse', 'HEAD'])
|
|
@@ -1089,17 +1117,20 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
1089
1117
|
``,
|
|
1090
1118
|
`【协议调用 1/2(本次)】change 已建 + 基线锚定(baseline_commit=${baseline ? baseline.slice(0, 10) : '(无 git 历史)'})${withTasks ? ' + 任务卡模式(--with-tasks:中间自愿用 task done,收尾仍 flow done)' : ''}`,
|
|
1091
1119
|
``,
|
|
1092
|
-
|
|
1120
|
+
// 顺序正典(2026-10-19-thin-order-canon):行动句必须是顺序句——实证(artifact 时间戳
|
|
1121
|
+
// 还原+跨仓 14 例抽样)agent 跟从【你要做的】首义行动,旧句「直接干活」把动码提前、
|
|
1122
|
+
// 文档与批准沦为收口仪式;正典=先答 spec 面 → 批准 → 才动码(读代码探究不算动码)。
|
|
1123
|
+
`【你要做的】先答 spec 面,批准后才动码:① 答 spec——requirements FR 行为句+测试绑定行、design.md 四节作答(动码前;读代码探究不算动码);② 断点①——摘要给用户看 → flow approve --change ${change}(或用户显式 --autopilot);③ 才干活——写测试、改代码。治理工件骨架 CLI 已起草、收口机器做(协议记账单位=change 级)。`,
|
|
1093
1124
|
// 收口拒收红线前置(2026-10-11-flow-start-redline-frontload):钉死/空槽/绑定三面都是文档
|
|
1094
1125
|
// 先行校验,实证第一次最容易踩(四问文本被改写、区间 task-NN 写法零证据被拒)——横幅
|
|
1095
1126
|
// 头部先行告知,收口才暴露的规则前置到动笔前。
|
|
1096
|
-
`🚫
|
|
1127
|
+
`🚫 收口拒收红线(文档须先于动码——动笔前先读,第一次最容易踩的就是这三条):`,
|
|
1097
1128
|
` ① design 四问文本与 FR 标题锚逐字钉死勿改写——问题原文保持原样、答案写在问题下方,改写/删除即收口拒收(文档↔锚逐字对比);`,
|
|
1098
|
-
` ② design.md
|
|
1099
|
-
` ③ requirements 每条 FR 须有带强度词的行为句(必须/禁止/SHOULD)+
|
|
1129
|
+
` ② design.md 四节(做法/接口契约/边界并发四问/风险)每节至少一行——空节拒收(写「不适用:<理由>」也算答;动码前作答);`,
|
|
1130
|
+
` ③ requirements 每条 FR 须有带强度词的行为句(必须/禁止/SHOULD)+ 测试绑定行——缺任一即拒收(动码前作答)。`,
|
|
1100
1131
|
`书写面(纯 markdown 起草):requirements FR 正文/design 四节作答/测试绑定行都直接写正文——`,
|
|
1101
1132
|
` 文档内零指纹标记、零槽注释、零书写指导(规则以本横幅与命令卡为唯一源);成功标准漂移只出 advisory 不拒收。`,
|
|
1102
|
-
`⚠️ design.md
|
|
1133
|
+
`⚠️ design.md 四节动码前作答(它是断点① 批准的输入——后补属超期补写、留痕可审计);列改动文件用独立「## 文件变更清单」节+表格(| 操作 | 路径 | 说明 |)`,
|
|
1103
1134
|
` ——勿写在「接口契约」节内(收口按节名解析声明面)。`,
|
|
1104
1135
|
...crossRepoHintLines,
|
|
1105
1136
|
`📜 requirements 每条 FR = 标题锚 + 行为句 + 按需场景块(#### 场景:名 + Given/When/Then);`,
|
|
@@ -1112,30 +1143,31 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
|
|
|
1112
1143
|
` 也有 1/4 抽查采样。要强制/豁免可重启时带 --review / --no-review${reviewForce === true ? '(本变更已声明 --review)' : reviewForce === false ? '(本变更已声明 --no-review)' : ''}。`,
|
|
1113
1144
|
`⚠️ 交付纪律:收口前先把交付代码用显式 pathspec 提交(git add -- <文件> && git commit)——`,
|
|
1114
1145
|
` patch 冻结件范围=baseline..HEAD 提交面,未提交的代码不进审计件(R16 评审 P2 实证);`,
|
|
1115
|
-
` tasks.md 一并显式 pathspec 提交(勾选证据进 git 历史,勿 untracked 直至归档——R19
|
|
1146
|
+
` tasks.md 一并显式 pathspec 提交(勾选证据进 git 历史,勿 untracked 直至归档——R19 实证;task tick 回显会给现成命令)。`,
|
|
1116
1147
|
`✅ 任务面归你(工作分解契约,2026-10-07-thin-tasks-v3):tasks.md 是你的工作队列——机器种子按真实实现路径改写`,
|
|
1117
1148
|
` (每行=可独立完成并当场验证的一步:做什么+怎么验证;增删改随意,保持 \`- [ ] task-NN:\` 行形态);`,
|
|
1118
1149
|
` 执行循环:对每个未勾行 Working on task N/M → 做一件 → 测试绿后当场勾这一格 → 下一行——CLI 以 checkbox 为进度状态机`,
|
|
1119
1150
|
` (sillyspec task tick --change ${change} --task task-NN 即时回显进度 N/M 与下一任务指针,flow status 同源;或直接 Edit 翻格 \`- [ ]\`→\`- [x]\`),watcher 实时上平台。`,
|
|
1120
1151
|
` ⚠️ harness 的 TodoWrite 类工具是会话内便利面,不替代 tasks.md——平台进度/收口证据只读它(0/12 事故实证:todo 全 completed 而 tasks.md 0 勾);`,
|
|
1121
|
-
` 勾选是收口证据面:每格勾选要有对应提交(消息带 task-NN——逐个列出或 /、,连写如 task-01/02/03,「task-01..task-05
|
|
1152
|
+
` 勾选是收口证据面:每格勾选要有对应提交(消息带 task-NN——逐个列出或 /、,连写如 task-01/02/03,「task-01..task-05」区间写法不认;pathspec 带上本变更 tasks.md——勾选修改与任务交付同一提交入库,task tick 回显会给现成命令)或 review.json,全勾但零证据会被拒收;勿攒一把勾(收口硬门拒单拍多格);`,
|
|
1122
1153
|
// 任务拟法指引(2026-10-12-closeout-task-guidance,三次「哨兵抓收口型任务零证据」复盘):
|
|
1123
1154
|
// 成功标准≠工作分解——收口/推送/发布是协议自身承担的动作;单列则证据(token 提交)必须先于勾选存在。
|
|
1124
1155
|
` 任务拟法:收口/推送/发布类动作不单列 task(提交、归档、推送是 flow start→done 协议自身承担的动作——成功标准照写 requirements,勿逐字搬进任务清单);确需单列的收口/操作型任务,勾选前先做消息预埋 task-NN token 的交付提交——回执类证据(push/publish 回执、构建日志路径)写进提交消息,勿依赖勾选后补;`,
|
|
1125
1156
|
` flow status --change ${change} 为自愿查看/恢复面(恢复时给下一任务指针与进度)。`,
|
|
1157
|
+
`💡 执行期用户插入决策方向(有实现影响、约束后续变更的取舍/否决/定调)→ 当场 sillyspec decisions add --change ${change} --id D-001 --title "…" 落盘(只留会话=收口档案不可见、跨变更复潮无防);改变承诺面的同步 design/tasks 对应节。`,
|
|
1126
1158
|
``,
|
|
1127
|
-
`🛑
|
|
1159
|
+
`🛑 三断点纪律(顺序纪律:①未过不动码;②③为汇报点——用户没说「全跑完」就必须在每个断点向用户汇报并等确认):`,
|
|
1128
1160
|
` ① spec 断点【机器门】:填完 FR 区和 design 四节后,把摘要给用户看(FR 条目+盲维作答+方案概述),`,
|
|
1129
1161
|
` 用户确认后请其运行 sillyspec flow approve --change ${change}(批准留痕)再动手写代码——`,
|
|
1130
1162
|
` agent 代跑时如实声明通道:--user-confirmed(转述用户已确认)/ --self-approved(agent 自批),`,
|
|
1131
1163
|
` 无 flag 按 unattested 留痕(审计面无法区分用户确认与自批)——2026-10-12-approve-channel-attest。`,
|
|
1132
|
-
`
|
|
1164
|
+
` 方案错了返工最贵;批准是动码前置义务(未批准且未声明 --autopilot 时 flow done 亦拒收——2026-10-04-thin-docs-v2);`,
|
|
1133
1165
|
` 用户否掉方案 → flow reject --change ${change} --reason <理由>(拒后批准须显式通道再确认)。`,
|
|
1134
1166
|
` ② 执行断点:写完代码跑完测试后,把测试结果(过了几个/挂了什么)给用户看,`,
|
|
1135
|
-
`
|
|
1167
|
+
` 等用户确认再跑收口;汇报顺带对账执行期用户决策(decisions 条目列一下,无则说明无)。`,
|
|
1136
1168
|
` ③ 归档断点:flow done 跑完后(无论过/拒),把结果(归档成功/被什么拦了)给用户看。`,
|
|
1137
|
-
` 用户明确说「直接跑完/不用问我」→ 重跑 flow start --change ${change} --autopilot
|
|
1138
|
-
`
|
|
1169
|
+
` 用户明确说「直接跑完/不用问我」→ 重跑 flow start --change ${change} --autopilot 声明豁免(用户显式授权、留痕——非 agent 自选补救通道),`,
|
|
1170
|
+
` ①②③人审全免(agent 自主跑到底)。`,
|
|
1139
1171
|
` 随时可查进度:sillyspec flow status --change ${change}`,
|
|
1140
1172
|
...(detectUiTouch(input)
|
|
1141
1173
|
? [...buildUiGuidanceLines(specBase), '']
|
|
@@ -1177,18 +1209,21 @@ function printRecoveryBriefing({ cwd, specBase, change, changeDir, runtimeRoot,
|
|
|
1177
1209
|
? (gitQuiet(cwd, ['rev-list', '--count', `${st.baseline_commit}..HEAD`]) || '0').toString().trim()
|
|
1178
1210
|
: '?'
|
|
1179
1211
|
const dirty = changedFilesSinceBaseline(cwd, st.baseline_commit).length
|
|
1180
|
-
const hasLedger = existsSync(
|
|
1212
|
+
const hasLedger = existsSync(runtimeStatePath(runtimeRoot, `verify-quality-scan-${change}.json`))
|
|
1181
1213
|
console.log([
|
|
1182
1214
|
`🔁 flow start 恢复简报(重入): ${change}(tier=${st.tier}${st.legacy_fallback ? ',legacy_fallback=true 混跑回退中' : ''})`,
|
|
1183
1215
|
`══════════════════════════════════════════════════════`,
|
|
1184
1216
|
...(title ? [`- 标题:${title}`] : []),
|
|
1185
1217
|
`- 做到哪:任务勾选 ${checked}/${total};基线以来提交 ${commits} 个;变更/dirty 文件 ${dirty} 个;P2 质量扫描记录 ${hasLedger ? '在场' : '无'}。`,
|
|
1186
1218
|
`- 六子步标记:${done.length ? `已完成 ${done.join('/')}` : '(无)'}${left.length ? `;待办 ${left.join('/')}` : ';全部完成'}`,
|
|
1219
|
+
// 顺序正典(2026-10-19-thin-order-canon):恢复面带批准状态——重入时 agent 只看简报,
|
|
1220
|
+
// 未批准时「下一步」必须指向补批而非「继续干活」(防先码后批在恢复路径复发)。
|
|
1221
|
+
`- spec 批准:${st.spec_approved ? `✅ 已批准(channel=${approveChannelOf(st)})` : st.autopilot ? '🔑 autopilot 豁免(用户显式授权)' : '⬜ 未批准——动码前先过断点①(摘要给用户 → flow approve)'}`,
|
|
1187
1222
|
// 红线重申(2026-10-11-flow-start-redline-frontload):重入不出首进横幅——钉死规则与
|
|
1188
1223
|
// task-NN token 形态随简报补位,断点续跑生命周期内规则不丢。
|
|
1189
1224
|
`- 红线重申:design 四问文本/FR 标题锚逐字钉死勿改写(改写即拒收);勾选证据提交消息逐个带 task-NN 或 /、,连写(「task-01..task-05」区间写法不认)。`,
|
|
1190
1225
|
`- 剩什么:${left.length === 0 && dirty === 0 ? '活已干完' : dirty > 0 ? '活未干完(继续改代码)' : '收尾待裁决'}`,
|
|
1191
|
-
`- 下一步:${left.length === 0 ? `sillyspec flow done --change ${change}` : `继续干活;干完跑 flow done(断点续)`}`,
|
|
1226
|
+
`- 下一步:${left.length === 0 ? `sillyspec flow done --change ${change}` : !st.spec_approved && !st.autopilot ? `未批准先补批(摘要给用户 → sillyspec flow approve --change ${change});随后继续干活,干完跑 flow done(断点续)` : `继续干活;干完跑 flow done(断点续)`}`,
|
|
1192
1227
|
...(digestLines.length > 0 ? ['', ...digestLines] : []),
|
|
1193
1228
|
// 尾部原型锚(2026-10-14-guide-anchor-adopt-proto):收编/含原型变更重入时原型指针不隐身
|
|
1194
1229
|
//(首进 adopt 输出的 🖼️ 行在会话切换后已丢,简报尾部补可发现面;置于 digestLines 后=绝对
|
|
@@ -1265,7 +1300,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1265
1300
|
// 不许在失败面丢失;成功收口走函数末尾同款记录,失败面只多不少)
|
|
1266
1301
|
const appendTelemetry = (review) => {
|
|
1267
1302
|
try {
|
|
1268
|
-
appendFileSync(
|
|
1303
|
+
appendFileSync(runtimeTempPath(runtimeRoot, 'flow-telemetry.jsonl'), JSON.stringify({
|
|
1269
1304
|
ts: new Date().toISOString(), change, protocolCalls: 2,
|
|
1270
1305
|
draftAmendments: st.edit_ratio != null ? 1 : 0, editRatio: st.edit_ratio ?? null,
|
|
1271
1306
|
routeHint: st.route_hint ?? null, tier: st.tier, upgraded: st.upgrade_reason ?? null,
|
|
@@ -1305,10 +1340,10 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1305
1340
|
// spec 断点机器门(FR-06/FR-07,护栏#2 零 prompt 劝说——advisory 断点在 0/12 勾选类事故
|
|
1306
1341
|
// 实证下无牙;v2 起批准是收口硬前提,autopilot 是用户显式豁免通道)
|
|
1307
1342
|
if (!st.autopilot && !st.spec_approved) {
|
|
1308
|
-
console.error(`❌ spec
|
|
1309
|
-
console.error(`
|
|
1343
|
+
console.error(`❌ spec 断点未批准:批准是动码前置义务(不是收口补救动作)——本变更缺方案确认留痕`)
|
|
1344
|
+
console.error(` 补批通道(留痕可审计):把 FR 条目+design 四节摘要给用户看,用户确认后由用户运行:`)
|
|
1310
1345
|
console.error(` sillyspec flow approve --change ${change} [--user-confirmed|--self-approved]`)
|
|
1311
|
-
console.error(`
|
|
1346
|
+
console.error(` 或用户显式豁免(须用户授权,非 agent 自选):重跑 flow start --change ${change} --autopilot(声明留痕)`)
|
|
1312
1347
|
reportMidFail('artifacts')
|
|
1313
1348
|
process.exit(1)
|
|
1314
1349
|
}
|
|
@@ -1369,6 +1404,22 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1369
1404
|
let _sentinelNonMirrorTasks = null // 镜像豁免面外的任务数(null=哨兵未跑;0=纯镜像任务面——节奏 advisory 静默)
|
|
1370
1405
|
let _sentinelEvidenceComplete = false // 哨兵证据完备(status=complete——每格已勾任务均有 token/review.json):单拍降级判据(2026-10-13-batchtick-evidence-domain-hint)
|
|
1371
1406
|
let _autopilotTicked = 0 // 机器代勾格数(>0 时勾选节奏门降 advisory——代勾是单拍多格机械写,非 agent 纪律面)
|
|
1407
|
+
// 勾选观测共享采集(2026-10-17-tick-timing-source):watcher 事件流 + task-ticks.jsonl 侧车
|
|
1408
|
+
// 一次读取——时点判定与勾选节奏门同源消费(此前节奏门单独读,现两门一份数据;检测逻辑
|
|
1409
|
+
// 与优先序不变:事件流先判、空结果回退侧车突发)。侧车=CLI tick 逐格毫秒时戳事实源(随
|
|
1410
|
+
// 归档进 git、不受观测盲窗影响)。任一源缺失按空(fail-open),绝不阻断收口。
|
|
1411
|
+
const _tickObs = await (async () => {
|
|
1412
|
+
try {
|
|
1413
|
+
const { readWatcherEvents } = await import('./watcher.js')
|
|
1414
|
+
const { detectBatchCheckCadence, detectTickBurst } = await import('./sentinel-assertions.js')
|
|
1415
|
+
const stream = readWatcherEvents({ runtimeRoot, change })
|
|
1416
|
+
let ticks = []
|
|
1417
|
+
try { ticks = (await import('./timeline.js')).parseTickLog(readFileSync(join(changeDir, 'task-ticks.jsonl'), 'utf8')) } catch { /* 侧车缺失/不可读按空(fail-open) */ }
|
|
1418
|
+
let batch = stream.exists ? detectBatchCheckCadence(stream.events) : null
|
|
1419
|
+
if (!batch) batch = detectTickBurst(ticks)
|
|
1420
|
+
return { stream, ticks, batch }
|
|
1421
|
+
} catch { return { stream: { exists: false, events: [] }, ticks: [], batch: null } }
|
|
1422
|
+
})()
|
|
1372
1423
|
// 证据面=整条提交消息(2026-09-25-thin-done-gate-calibration 坑2:%s 只取标题行,正文里的
|
|
1373
1424
|
// token 被判零证据,与文案「提交带 task-NN」口径漂移)——%B%x1e 按提交切记录,advisory 的
|
|
1374
1425
|
// 提交计数不因多行正文失真。
|
|
@@ -1436,20 +1487,31 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1436
1487
|
if (sent.status === 'complete') {
|
|
1437
1488
|
_sentinelEvidenceComplete = true
|
|
1438
1489
|
console.log(`🛡️ 哨兵:全勾 ${sent.checked}/${sent.claimTotal} 证据齐(提交 token/review.json)`)
|
|
1439
|
-
// 勾选时点判定(2026-
|
|
1440
|
-
// ==
|
|
1441
|
-
//
|
|
1442
|
-
//
|
|
1443
|
-
//
|
|
1444
|
-
//
|
|
1490
|
+
// 勾选时点判定(2026-10-17-tick-timing-source 换源):判据从「tasks.md git 首次提交
|
|
1491
|
+
// == 收口最后提交」整体退役——git 历史形态被官方允许的 reset --soft 压扁无声摧毁
|
|
1492
|
+
// (tick-timing-squash-blindspot 坑:并行会话压扁归档把边干边勾的渐进历史抹平,警告
|
|
1493
|
+
// 照出,结构性误报;且 tasks.md 不进交付 pathspec 的纪律下「untracked 直至归档」恒
|
|
1494
|
+
// 复现)。换源=task-ticks.jsonl 侧车墙钟时戳(detectTickTiming 纯函数:跨度超 120s
|
|
1495
|
+
// =边干边勾确认;挤 ≤120s 短窗且变更存活 ≥10×跨度=末尾集中勾选)——墙钟时戳是文件
|
|
1496
|
+
// 事实,压扁/rebase 不可改写,与「审计真相锚 change.patch sha256、不依赖 git 形态」
|
|
1497
|
+
// 口径对齐。节奏门已检出单拍跳/突发时让位(防双重警告——其判定覆盖同一批格子);
|
|
1498
|
+
// 无侧车观测输出一行 task tick 教学(不再作结构性警告);advisory 不阻断语义不变。
|
|
1445
1499
|
try {
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1500
|
+
if (_tickObs.batch) {
|
|
1501
|
+
/* 节奏门将报单拍多格/突发——时点判定让位 */
|
|
1502
|
+
} else {
|
|
1503
|
+
const _baseRaw = st && st.baseline_commit ? gitQuiet(cwd, ['show', '-s', '--format=%ct', String(st.baseline_commit)]) : null
|
|
1504
|
+
const _baseTs = Number(String(_baseRaw ?? '').trim())
|
|
1505
|
+
const { detectTickTiming } = await import('./sentinel-assertions.js')
|
|
1506
|
+
const t = detectTickTiming({ ticks: _tickObs.ticks, lifespanMs: Number.isFinite(_baseTs) && _baseTs > 0 ? Date.now() - _baseTs * 1000 : NaN })
|
|
1507
|
+
if (t.status === 'concentrated') {
|
|
1508
|
+
console.warn(`⚠️ 勾选时点:${t.count} 格勾选时戳集中在 ${Math.max(1, Math.round(t.spanMs / 1000))}s 短窗内(末尾集中勾选形态)——token 证据齐放行,但边干边勾是进度锚,下次完成单元即勾(task tick 回显含现成提交命令)`)
|
|
1509
|
+
} else if (t.status === 'spread') {
|
|
1510
|
+
console.log(`✅ 勾选时点:${t.count} 格勾选时戳跨度 ${Math.max(1, Math.round(t.spanMs / 60000))} 分钟——边干边勾确认(判据=task-ticks 侧车墙钟时戳,不受 git 压扁影响)`)
|
|
1511
|
+
} else if (t.status === 'no-ticks') {
|
|
1512
|
+
console.log(`ℹ️ 勾选时点:无 tick 时戳观测(未走 task tick)——下次勾选建议 sillyspec task tick --change ${change} --task task-NN(即时回显进度与现成提交命令)`)
|
|
1513
|
+
}
|
|
1514
|
+
/* 'single'/'no-anchor' 静默:单格无节奏可言;缺存活锚 fail-open 不判 */
|
|
1453
1515
|
}
|
|
1454
1516
|
} catch { /* 时点判定 fail-soft */ }
|
|
1455
1517
|
}
|
|
@@ -1459,7 +1521,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1459
1521
|
// 任务面与实际不符可先改写 tasks.md(工作分解归 agent)再逐格勾。token 代勾(governance-
|
|
1460
1522
|
// autopilot)已在上方先跑:有证据未勾的格子已代勾,此处拒的纯属零证据未完成面。
|
|
1461
1523
|
if (sent.claimTotal > 0 && sent.checked < sent.claimTotal) {
|
|
1462
|
-
console.error(`🚫 任务未全勾(${sent.checked}/${sent.claimTotal})——tasks.md 是完成状态机:完成一件勾一格(每格勾选需对应提交带 task-NN token 或 review.json
|
|
1524
|
+
console.error(`🚫 任务未全勾(${sent.checked}/${sent.claimTotal})——tasks.md 是完成状态机:完成一件勾一格(每格勾选需对应提交带 task-NN token 或 review.json;提交 pathspec 带上本变更 tasks.md——勾选修改与任务交付同一提交入库),全勾后重跑 flow done`)
|
|
1463
1525
|
console.error(' 任务面与实际实现路径不符时,先改写 tasks.md(工作分解归你,增删改随意)再逐格勾;查下一任务:sillyspec task tick --change ' + `${change}` + ' --task task-NN(回显进度与指针)')
|
|
1464
1526
|
appendTelemetry({ sentinel: 'incomplete-tasks', checked: sent.checked, total: sent.claimTotal })
|
|
1465
1527
|
reportMidFail('ledger')
|
|
@@ -1477,19 +1539,12 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1477
1539
|
// 侧车突发回退(2026-10-14-tick-burst-sidecar-gate):CLI 收口端快连点对事件流不可见(单格
|
|
1478
1540
|
// 精确跳),空结果时回退 task-ticks.jsonl 毫秒时戳判突发——CLI 与直接 Edit 两条路径同判。
|
|
1479
1541
|
try {
|
|
1480
|
-
const {
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
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
|
-
}
|
|
1542
|
+
const { resolveBatchTickAction } = await import('./sentinel-assertions.js')
|
|
1543
|
+
// 观测消费共享采集(2026-10-17-tick-timing-source):事件流/侧车/突发判定已在前文哨兵段
|
|
1544
|
+
// 前置采集为 _tickObs(时点判定与节奏门同源一份数据;读取仅前移,检测逻辑与优先序不变
|
|
1545
|
+
// ——事件流先判、空结果回退侧车突发,侧车缺失按空 fail-open)。
|
|
1546
|
+
const stream = _tickObs.stream
|
|
1547
|
+
const batch = _tickObs.batch
|
|
1493
1548
|
if (batch) {
|
|
1494
1549
|
// 代勾计数取「本拍实时代勾 ∨ 持久化代勾」较大者(二轮 P2-B:重入漂移防护);决策纯函数
|
|
1495
1550
|
// 单源(resolveBatchTickAction)四态与豁免优先序见其 JSDoc——代勾须解释整跳才降级
|
|
@@ -1665,6 +1720,9 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1665
1720
|
// untracked 自拼 hunk),基=baseline,时点=done 冻结。fail-soft:失败只告警不阻断归档、
|
|
1666
1721
|
// 不标 done(重入 done 重试)。
|
|
1667
1722
|
let driftRefreeze = false
|
|
1723
|
+
// JSON-only 冻结形态(无主仓 change.patch,跨仓正文在 repos[])——收口指引文案按它
|
|
1724
|
+
// 切换审计锚表述(2026-10-19-thin-empty-face-cross-repo-freeze)。
|
|
1725
|
+
let jsonOnlyFreeze = false
|
|
1668
1726
|
if (st.substeps?.patch === 'done') {
|
|
1669
1727
|
// 处置漂移检测(2026-10-05-disposition-refreeze-drift,主清单 P1):冻结后又有本变更交付
|
|
1670
1728
|
// 提交时,幂等跳过会把 change.patch/review.json 停在处置前时点(归档审计件缺处置面,
|
|
@@ -1678,11 +1736,11 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1678
1736
|
// 当前 HEAD);review 处置沿用漂移面同一隔离规则——缺基面无法判 governance-only,
|
|
1679
1737
|
// 未锚定一律隔离(fail-safe 宁可多评)。
|
|
1680
1738
|
const patchJsonPath = join(changeDir, 'change-patch.json')
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1739
|
+
// 空面跨仓冻结(2026-10-19-thin-empty-face-cross-repo-freeze):JSON-only 留档
|
|
1740
|
+
// (无 change.patch、repos[] 承载跨仓正文)是完整冻结,存在性复查不得判缺件。
|
|
1741
|
+
// 完整性以探针的 complete 为准(坏 JSON 即使 change.patch 在场也不可信)。
|
|
1742
|
+
const { missingFreezeLabels } = await import('./flow-parity.js')
|
|
1743
|
+
const missingFreeze = missingFreezeLabels(changeDir)
|
|
1686
1744
|
let drifted = false
|
|
1687
1745
|
let drift = null
|
|
1688
1746
|
if (missingFreeze.length > 0) {
|
|
@@ -1973,11 +2031,119 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
1973
2031
|
}
|
|
1974
2032
|
console.warn(`⚠️ --accept-empty-face:显式签收空冻结面与声明面矛盾(${_declared.length} 条声明交付不进冻结面,留痕 flow-state accept_empty_face)`)
|
|
1975
2033
|
}
|
|
1976
|
-
//
|
|
1977
|
-
|
|
1978
|
-
|
|
2034
|
+
// ── 未标注跨仓交付窗口探测(2026-10-19-undeclared-cross-repo-probe)──
|
|
2035
|
+
// 生产实证(消防工作区 4e9eb05b,2026-10-12..10-19 共 14 例):design 无「文件
|
|
2036
|
+
// 变更清单」段、或清单裸写仓目录前缀时,主仓冻结面为空或只剩附件/治理模板,
|
|
2037
|
+
// 真实代码在注册跨仓窗口内却零告警。既有空面门只拦「清单有未标注条目」形态;
|
|
2038
|
+
// 本探测以真实跨仓 diff 为证据补位:无 repo 标注 + 注册仓非空 → 逐仓看
|
|
2039
|
+
// cross_baselines..工作树窗口,有交付改动才点名(advisory 不阻断)。纯文档变更/
|
|
2040
|
+
// 单仓工作区/窗口零改动 → 零输出。未提交面可能含并行会话改动,故文案用「疑似」。
|
|
2041
|
+
if (!planEntries.some((e) => e && e.repo)) {
|
|
2042
|
+
try {
|
|
2043
|
+
const { loadRegisteredRepoKeys } = await import('./scope-audit.js')
|
|
2044
|
+
const { collectRepoActual } = await import('./cross-repo-reconcile.js')
|
|
2045
|
+
const { filterDeliverableFiles } = await import('./worktree-apply.js')
|
|
2046
|
+
const { isToolGovernancePath } = await import('./flow-parity.js')
|
|
2047
|
+
const registeredKeys = loadRegisteredRepoKeys(specBase)
|
|
2048
|
+
const probeHits = []
|
|
2049
|
+
for (const repoKey of registeredKeys) {
|
|
2050
|
+
try {
|
|
2051
|
+
const actual = collectRepoActual({
|
|
2052
|
+
repoKey, specBase, cwd,
|
|
2053
|
+
runtimeRoot: resolveRuntimeRoot(runtimeRootOpt ? { runtimeRoot: runtimeRootOpt } : {}, specBase),
|
|
2054
|
+
changeName: change,
|
|
2055
|
+
crossBaselines: (st && st.cross_baselines && typeof st.cross_baselines === 'object') ? st.cross_baselines : null,
|
|
2056
|
+
})
|
|
2057
|
+
if (!actual || actual.degradedReason) continue
|
|
2058
|
+
const files = filterDeliverableFiles(actual.files || [])
|
|
2059
|
+
.filter((f) => !isToolGovernancePath(f))
|
|
2060
|
+
if (files.length > 0) probeHits.push({ key: repoKey, count: files.length })
|
|
2061
|
+
} catch { /* 单仓探测失败不阻断(fail-soft) */ }
|
|
2062
|
+
}
|
|
2063
|
+
if (probeHits.length > 0) {
|
|
2064
|
+
console.warn(`⚠️ 注册跨仓窗口内有交付改动,但 design 清单未按规范标注(主仓冻结面为空/仅治理件时归档 patch 将缺失这些交付面):${probeHits.map((h) => `${h.key}(${h.count} 文件)`).join('、')}`)
|
|
2065
|
+
console.warn(' 疑似跨仓交付(未提交面可能含并行会话改动,请人工确认归属)。正确写法三选一:① 段头「## <注册key> 仓变更」段内写仓根相对路径;② 子段「### <注册key>」;③ 条目前缀 cross-repo:<注册key>:<仓根相对路径>——key 以 local.yaml repos 注册为准')
|
|
2066
|
+
}
|
|
2067
|
+
} catch { /* 探测整体失败不阻断(fail-soft 不新造阻断面) */ }
|
|
2068
|
+
}
|
|
2069
|
+
// 空面跨仓冻结(2026-10-19-thin-empty-face-cross-repo-freeze):主仓冻结面为空且
|
|
2070
|
+
// 声明清单含规范跨仓标注时,跨仓交付面仍可留档——JSON 路线只写 change-patch.json,
|
|
2071
|
+
// scopeAudit.repos[] 承载各仓 patch 正文/sha256,不写 change.patch(主仓面确为空,
|
|
2072
|
+
// 混入跨仓正文会让按主仓 cwd 解析 hunk 的读方错位)。各仓窗口内无可归属交付时
|
|
2073
|
+
// 维持空面跳过(无清单段形态同此路,留给第 2 步的窗口探测点名)。
|
|
2074
|
+
let emptyFaceCrossRepos = []
|
|
2075
|
+
let emptyFaceCrossRows = undefined
|
|
2076
|
+
if (ownFiles.length === 0 && planEntries.some((e) => e && e.repo)) {
|
|
2077
|
+
try {
|
|
2078
|
+
const { reconcileCrossRepoPlan } = await import('./scope-audit.js')
|
|
2079
|
+
const recon = reconcileCrossRepoPlan({
|
|
2080
|
+
cwd, specBase,
|
|
2081
|
+
runtimeRoot: resolveRuntimeRoot(runtimeRootOpt ? { runtimeRoot: runtimeRootOpt } : {}, specBase),
|
|
2082
|
+
changeName: change,
|
|
2083
|
+
planEntries,
|
|
2084
|
+
collectPatch: true,
|
|
2085
|
+
crossBaselines: (st && st.cross_baselines && typeof st.cross_baselines === 'object') ? st.cross_baselines : null,
|
|
2086
|
+
})
|
|
2087
|
+
emptyFaceCrossRows = recon.rows
|
|
2088
|
+
// 「有交付」判定按对账三态行,不按 totals.files——后者含声明未动的 untouched
|
|
2089
|
+
// 占位行(空仓也会得到 files>0,导致空面误落 JSON-only 留档)。判定只决定
|
|
2090
|
+
// 落不落件;落件时 repos[] 原样携带全部仓(含降级仓与 degradedReason——
|
|
2091
|
+
// 与非空主仓分支同口径,读侧可感知采集失败面)。
|
|
2092
|
+
const deliveredRepoKeys = new Set((Array.isArray(recon.rows) ? recon.rows : [])
|
|
2093
|
+
.filter((r) => r && r.crossRepo && r.verdict !== 'untouched')
|
|
2094
|
+
.map((r) => r.crossRepo))
|
|
2095
|
+
const hasDelivered = deliveredRepoKeys.size > 0
|
|
2096
|
+
|| (recon.repos || []).some((r) => r && typeof r.patch === 'string' && r.patch.length > 0)
|
|
2097
|
+
emptyFaceCrossRepos = hasDelivered ? (recon.repos || []) : []
|
|
2098
|
+
for (const n of recon.notes) console.log(` 📎 ${n}`)
|
|
2099
|
+
} catch { /* 跨仓对账失败 → 退回空面跳过(fail-soft 不新造阻断面) */ }
|
|
1979
2100
|
}
|
|
1980
|
-
if (ownFiles.length === 0) {
|
|
2101
|
+
if (ownFiles.length === 0 && emptyFaceCrossRepos.length > 0) {
|
|
2102
|
+
const { buildThinSnapshotRows, writeCloseTraceArtifacts } = await import('./flow-parity.js')
|
|
2103
|
+
const { rows: snapRows, totals: snapTotals } = buildThinSnapshotRows({ ownFiles: [], stats: new Map(), planEntries, crossRepoRows: emptyFaceCrossRows })
|
|
2104
|
+
const headCommit = String(gitQuiet(cwd, ['rev-parse', 'HEAD']) || '').trim() || null
|
|
2105
|
+
// 先清旧 change.patch 再写 JSON:JSON-only 语义下残留旧正文与新 JSON(无顶级
|
|
2106
|
+
// patchSha256)互相矛盾。清理失败直接抛出(外层 catch → patch 子步不标 done,
|
|
2107
|
+
// 重入重试),不留「双件并存且复查判完整」的自愈盲区。
|
|
2108
|
+
const stalePatchPath = join(changeDir, 'change.patch')
|
|
2109
|
+
if (existsSync(stalePatchPath)) {
|
|
2110
|
+
try { unlinkSync(stalePatchPath) } catch (e) {
|
|
2111
|
+
throw new Error(`旧 change.patch 清理失败(${e.message})——保持半态可重入,修复文件权限后重跑`)
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
const closeTrace = writeCloseTraceArtifacts({
|
|
2115
|
+
changeDir,
|
|
2116
|
+
change,
|
|
2117
|
+
baseline: st.baseline_commit,
|
|
2118
|
+
head: headCommit,
|
|
2119
|
+
files: [],
|
|
2120
|
+
metaTotals: { files: 0, additions: 0, deletions: 0 },
|
|
2121
|
+
patchText: null,
|
|
2122
|
+
savedAt: new Date().toISOString(),
|
|
2123
|
+
patchStatus: 'ok',
|
|
2124
|
+
snapObj: {
|
|
2125
|
+
mode: 'full-flow', ok: true, degradedReason: null,
|
|
2126
|
+
baseAnchor: st.baseline_commit || null,
|
|
2127
|
+
totals: snapTotals,
|
|
2128
|
+
rows: snapRows,
|
|
2129
|
+
excluded: { foreignDeclared: [] },
|
|
2130
|
+
note: 'flow done 时点冻结(主仓冻结面为空;跨仓交付面见 repos[])',
|
|
2131
|
+
closedBy: 'flow done',
|
|
2132
|
+
repos: emptyFaceCrossRepos,
|
|
2133
|
+
},
|
|
2134
|
+
meta: {
|
|
2135
|
+
note: 'flow done 时点冻结(主仓冻结面为空;跨仓交付面见 scopeAudit.repos[]——2026-10-19-thin-empty-face-cross-repo-freeze)',
|
|
2136
|
+
modules: [], uncoveredDirs: [], moduleMaps: [],
|
|
2137
|
+
},
|
|
2138
|
+
})
|
|
2139
|
+
jsonOnlyFreeze = true
|
|
2140
|
+
const frozenRepos = emptyFaceCrossRepos.filter((r) => r && typeof r.patch === 'string' && r.patch.length > 0).length
|
|
2141
|
+
console.log(`📦 空面跨仓 patch 留档:change-patch.json(主仓面为空,跨仓 ${emptyFaceCrossRepos.length} 仓、正文 ${frozenRepos}——diff 见 scopeAudit.repos[])`)
|
|
2142
|
+
patchOk = closeTrace.patchStatus === 'ok'
|
|
2143
|
+
} else if (ownFiles.length === 0) {
|
|
2144
|
+
if (_declared.length === 0 && planEntries.some((e) => e && e.repo)) {
|
|
2145
|
+
console.warn('⚠️ 声明清单全部为跨仓规范标注且主仓冻结面为空——跨仓窗口内没有可归属交付,按空面跳过 patch 留档(如实现应在此,先提交后重跑)')
|
|
2146
|
+
}
|
|
1981
2147
|
console.log('📦 patch 留档跳过:本变更可归属文件面为空(无交付面改动——治理工件不入冻结面;若实现代码应在此,先提交后重跑)')
|
|
1982
2148
|
patchOk = true
|
|
1983
2149
|
} else {
|
|
@@ -2131,6 +2297,13 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
2131
2297
|
}
|
|
2132
2298
|
if (patchOk) mark('patch')
|
|
2133
2299
|
}
|
|
2300
|
+
// 冻结形态复核(含重入 skip 路径——上轮评审 P2:断点续跑回合不执行 patch 分支,
|
|
2301
|
+
// jsonOnlyFreeze 会停在 false,收口指引误报 change.patch)。直接按在场冻结件判定。
|
|
2302
|
+
try {
|
|
2303
|
+
const { inspectFreezeArtifacts: inspectFinal } = await import('./flow-parity.js')
|
|
2304
|
+
const fsFinal = inspectFinal(changeDir)
|
|
2305
|
+
jsonOnlyFreeze = fsFinal.jsonOk && !fsFinal.hasText && fsFinal.repoPatchCount > 0
|
|
2306
|
+
} catch { /* best-effort */ }
|
|
2134
2307
|
|
|
2135
2308
|
// ②c review:危险证据定档的独立评审(2026-09-25-thin-review-slice)。定档=证据累积制
|
|
2136
2309
|
// (承诺词一票/盲维实质作答/diff 原语/决策密度/声明一票),文件数出局;缺省要评审、豁免要
|
|
@@ -2162,6 +2335,20 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
2162
2335
|
const { classifyReviewNeed, renderReviewerTaskbook, validateReviewJson } = await import('./flow-review.js')
|
|
2163
2336
|
let patchText = null
|
|
2164
2337
|
try { patchText = readFileSync(join(changeDir, 'change.patch'), 'utf8') } catch { /* patch 子步 skip/失败面 */ }
|
|
2338
|
+
// JSON-only 跨仓冻结(2026-10-19-thin-empty-face-cross-repo-freeze):无主仓
|
|
2339
|
+
// change.patch 时交付 diff 证据在 change-patch.json 的 scopeAudit.repos[].patch——
|
|
2340
|
+
// 不接上的话 classifyReviewNeed 恒判「交付 diff 不可得」,评审定档失真。
|
|
2341
|
+
if (patchText === null) {
|
|
2342
|
+
try {
|
|
2343
|
+
const meta = JSON.parse(readFileSync(join(changeDir, 'change-patch.json'), 'utf8'))
|
|
2344
|
+
const repos = meta && meta.scopeAudit && Array.isArray(meta.scopeAudit.repos) ? meta.scopeAudit.repos : []
|
|
2345
|
+
const repoDiff = repos
|
|
2346
|
+
.filter((r) => r && typeof r.patch === 'string' && r.patch.length > 0)
|
|
2347
|
+
.map((r) => r.patch)
|
|
2348
|
+
.join('\n')
|
|
2349
|
+
if (repoDiff) patchText = repoDiff
|
|
2350
|
+
} catch { /* 无/坏 JSON → 保持 null(既有判定) */ }
|
|
2351
|
+
}
|
|
2165
2352
|
const tier = classifyReviewNeed({ changeDir, input: null, patchText, editRatio: st.edit_ratio ?? null, reviewForce: st.review_force ?? null, change })
|
|
2166
2353
|
if (!tier.required) {
|
|
2167
2354
|
console.log(`⚖️ 评审豁免(低风险证据齐全):${tier.exemptEvidence.join(';')}`)
|
|
@@ -2173,7 +2360,9 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
2173
2360
|
// 不再被漂移重冻结误隔离(实测竞态修复)。rev-parse 失败传 null(任务书退引导形态)。
|
|
2174
2361
|
let taskbookHead = null
|
|
2175
2362
|
try { const h = gitQuiet(cwd, ['rev-parse', 'HEAD']); if (h && /^[0-9a-f]{7,40}$/i.test(String(h).trim())) taskbookHead = String(h).trim() } catch { /* best-effort */ }
|
|
2176
|
-
|
|
2363
|
+
// 派发纪律(2026-10-19-thin-review-dispatch-verbatim):横幅是主会话派发时最先读到的通道,
|
|
2364
|
+
// 明确「原样复制」——实测手写缩编会丢材料包(厚道 stage-review.js:803 同款指令的薄道对齐)。
|
|
2365
|
+
console.log(`⚖️ 本变更需要独立评审(${tier.reasons.join(';')})——评审任务书如下,任务书原样复制给子代理(勿自行改写增删)执行后重跑本命令:\n`)
|
|
2177
2366
|
// 项目评审探针装配(2026-10-12-review-probe-injection):local.yaml 数据驱动的项目特有
|
|
2178
2367
|
// 必查面注入任务书(未配置零注入;装载告警/帽留痕 best-effort 提示,不阻断渲染)。
|
|
2179
2368
|
let _probeLines = null
|
|
@@ -2183,7 +2372,34 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
2183
2372
|
for (const _w of _assembled.warnings) console.warn(`⚠️ review_probes:${_w}`)
|
|
2184
2373
|
if (_assembled.lines.length > 0) _probeLines = _assembled.lines
|
|
2185
2374
|
} catch { /* 探针面 fail-soft:装配失败零注入 */ }
|
|
2186
|
-
|
|
2375
|
+
// 预消化材料包装配(2026-10-19-thin-review-material-pack):机械抽取 FR↔测试绑定表 +
|
|
2376
|
+
// 冻结 patch 文件级摘要注入任务书——砍零上下文评审员的定位类读与大 patch 整吞 prefill;
|
|
2377
|
+
// 摘要只从冻结件本身算(不走活 git,防与评审员手里冻结面漂移);fail-soft 零注入。
|
|
2378
|
+
// 归属切分(2026-10-19-thin-review-dispatch-verbatim):三单源——design 变更清单
|
|
2379
|
+
// (parseFileChangeListDetailed)、他侧归因(splitOwnVsForeignDiffFiles,与 patch 子步
|
|
2380
|
+
// 同源)、冻结面文件集(材料包同源解析)——砍评审员逐文件 git 考古(实测 22 次调用)。
|
|
2381
|
+
// 声明清单缺位/冻结面为空 → attribution=null(该子段零注入,FR 表/摘要不受影响)。
|
|
2382
|
+
let _attribution = null
|
|
2383
|
+
try {
|
|
2384
|
+
const { parseFrozenPatchSummary, buildFreezeAttribution } = await import('./flow-review-pack.js')
|
|
2385
|
+
const _freezeFiles = parseFrozenPatchSummary(patchText).map((f) => f.path)
|
|
2386
|
+
if (_freezeFiles.length > 0) {
|
|
2387
|
+
const { parseFileChangeListDetailed } = await import('./change-list.js')
|
|
2388
|
+
const _declared = parseFileChangeListDetailed(join(changeDir, 'design.md')).map((e) => e.path)
|
|
2389
|
+
if (_declared.length > 0) {
|
|
2390
|
+
const { splitOwnVsForeignDiffFiles } = await import('./foreign-declared.js')
|
|
2391
|
+
const _split = splitOwnVsForeignDiffFiles(cwd, change, _freezeFiles, { specBase })
|
|
2392
|
+
_attribution = buildFreezeAttribution({ declaredFiles: _declared, freezeFiles: _freezeFiles, foreign: _split && _split.foreign })
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
} catch { /* 归属面 fail-soft:装配失败零注入该子段 */ }
|
|
2396
|
+
let _packLines = null
|
|
2397
|
+
try {
|
|
2398
|
+
const { assembleReviewPack } = await import('./flow-review-pack.js')
|
|
2399
|
+
const _pack = assembleReviewPack({ changeDir, patchText, attribution: _attribution })
|
|
2400
|
+
if (_pack && Array.isArray(_pack.lines) && _pack.lines.length > 0) _packLines = _pack.lines
|
|
2401
|
+
} catch { /* 材料包面 fail-soft:装配失败零注入 */ }
|
|
2402
|
+
console.log(renderReviewerTaskbook({ change, changeDir, head: taskbookHead, probeLines: _probeLines, packLines: _packLines }))
|
|
2187
2403
|
reviewOutcome = { required: true, sampled: tier.sampled, verdict: 'missing' }
|
|
2188
2404
|
appendTelemetry(reviewOutcome) // 修评审 P2①:失败面先落账再 exit(校准信号不丢)
|
|
2189
2405
|
reportMidFail('review')
|
|
@@ -2315,7 +2531,8 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
2315
2531
|
}
|
|
2316
2532
|
} catch { /* 回读失败 → 空面(回写零命中静默) */ }
|
|
2317
2533
|
}
|
|
2318
|
-
// ④ distill:决策提炼(
|
|
2534
|
+
// ④ distill:决策提炼(needsWait 异态 → 升厚留人工裁决,不静默吞——判定 isDistillAbnormal
|
|
2535
|
+
// 单一源在 decision-distill.js,2026-10-20-distill-abnormal-upgrade-fix 修复幽灵触发器)+ FR 索引提炼
|
|
2319
2536
|
// (2026-09-22-thin-fr-distill-sync:轻量变更此前只蒸馏 decisions 不调 indexRequirements——
|
|
2320
2537
|
// requirements 永不进 knowledge/fr,知识复利在新默认道断流;轻量变更无 design.md,域路由
|
|
2321
2538
|
// 以基线以来交付 diff 供 deliverableFiles,伪域回退同口径)
|
|
@@ -2345,13 +2562,13 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
2345
2562
|
}
|
|
2346
2563
|
} catch (e) { console.warn(`⚠️ 变更索引回写失败(fail-soft 不阻断归档):${(e && e.message) || e}`) }
|
|
2347
2564
|
try {
|
|
2348
|
-
const { distillIntoKnowledge } = await import('./decision-distill.js')
|
|
2565
|
+
const { distillIntoKnowledge, isDistillAbnormal } = await import('./decision-distill.js')
|
|
2349
2566
|
const knowledgeRoot = join(specBase, 'knowledge')
|
|
2350
2567
|
const head = gitQuiet(cwd, ['rev-parse', 'HEAD'])
|
|
2351
2568
|
const res = distillIntoKnowledge(changeDir, knowledgeRoot, typeof head === 'string' ? head.trim() : '', null)
|
|
2352
|
-
const abnormal =
|
|
2569
|
+
const abnormal = isDistillAbnormal(res)
|
|
2353
2570
|
if (abnormal) {
|
|
2354
|
-
writeFlowState(changeDir, { tier: 'thick', upgrade_reason: `distill 异态(
|
|
2571
|
+
writeFlowState(changeDir, { tier: 'thick', upgrade_reason: `distill 异态(needsWait=${res.needsWait})——升厚留人工裁决` })
|
|
2355
2572
|
resetThinStageRowOnUpgrade(cwd, change, specBase)
|
|
2356
2573
|
console.warn(`⚠️ distill 异态 → 已升厚档(tier=thick)。剩余流程按厚档走 run <stage> 完整仪式;归档子步仍将执行(厚档语义不跳过 plan.md 校验)。`)
|
|
2357
2574
|
}
|
|
@@ -2449,7 +2666,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
2449
2666
|
// ⑥ events:事件收口(watcher 观测旁路 best-effort 读回,非真相源)
|
|
2450
2667
|
if (st.substeps?.events === 'done') { skip('events') } else {
|
|
2451
2668
|
try {
|
|
2452
|
-
const eventsPath =
|
|
2669
|
+
const eventsPath = runtimeStatePath(runtimeRoot, `watcher-events-${change}.jsonl`)
|
|
2453
2670
|
if (existsSync(eventsPath)) {
|
|
2454
2671
|
const n = readFileSync(eventsPath, 'utf8').split('\n').filter(Boolean).length
|
|
2455
2672
|
console.log(`📊 watcher 事件收口:${n} 条 provisional 事件在案(.runtime 留档,平台展示面)`)
|
|
@@ -2479,12 +2696,14 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
|
|
|
2479
2696
|
const finalHead = gitQuiet(cwd, ['rev-parse', 'HEAD'])
|
|
2480
2697
|
if (st.baseline_commit && typeof finalHead === 'string' && finalHead.trim() && finalHead.trim() !== st.baseline_commit) {
|
|
2481
2698
|
console.log(`🧹 Git 历史可自由整理:多轮中间提交可压扁为单提交——git reset --soft ${st.baseline_commit.slice(0, 10)} && git commit -m "<最终提交信息>"`)
|
|
2482
|
-
console.log(` 审计真相已冻结在归档件 change.patch(sha256
|
|
2699
|
+
console.log(` 审计真相已冻结在归档件 ${jsonOnlyFreeze ? 'change-patch.json(跨仓 repos[] 正文+sha256 锚定)' : 'change.patch(sha256 锚定)'} + verify-result + review.json,不依赖 git 历史;`)
|
|
2483
2700
|
console.log(` 注:knowledge「最近确认」hash 为溯源快照,压扁后成孤儿引用属预期(非活引用)。`)
|
|
2484
2701
|
}
|
|
2485
2702
|
} catch { /* 指引 best-effort */ }
|
|
2486
|
-
// 平台同步(同 flow start 尾部接线——归档后的 docs/knowledge/FR
|
|
2487
|
-
|
|
2703
|
+
// 平台同步(同 flow start 尾部接线——归档后的 docs/knowledge/FR 面随本轮回推平台)。
|
|
2704
|
+
// 终态强校验(2026-10-17-archive-half-state-location-stuck-active):flow done 到此 =
|
|
2705
|
+
// 变更已归档,文件树推送失败不再静默半态(🚨 告警 + 自救命令 + exit 1)
|
|
2706
|
+
try { await triggerSyncTerminalStrict(cwd, change) } catch { /* 同步绝不阻断协议面 */ }
|
|
2488
2707
|
return { change, substeps: doneList }
|
|
2489
2708
|
}
|
|
2490
2709
|
|
|
@@ -2519,6 +2738,39 @@ async function cmdFlowApprove({ change, cwd, specBase, by = null, channelFlag =
|
|
|
2519
2738
|
console.error(` (拒绝后派生授权须显式再确认——2026-10-12-approve-channel-attest)`)
|
|
2520
2739
|
process.exit(2)
|
|
2521
2740
|
}
|
|
2741
|
+
// 批准前文档完整性校验(2026-10-19-approve-gate-tick-nudge FR-01/02):批准=有内容的机器
|
|
2742
|
+
// 事实(批时文档必齐)。复用 done 门同源校验(verifyFlowDrafts 双轨);repair:false——批准
|
|
2743
|
+
// 面只读,不做 design 问题行自动回填(写盘修复属 done 面)。诚实边界:本门证明「答在批前」,
|
|
2744
|
+
// 不证明「答在码前」——时序证据路线已实证不可行(批量收口下 git 时间戳说谎,thin-order-canon
|
|
2745
|
+
// 四路调查),不做假装能拦的检查。
|
|
2746
|
+
{
|
|
2747
|
+
const runtimeRoot = resolveRuntimeRoot({}, specBase)
|
|
2748
|
+
const { verifyFlowDrafts, verifyDesignRecordFilled } = await import('./flow-draft.js')
|
|
2749
|
+
const adoptedDesign = st.adopted_from === 'brainstorm' && existsSync(join(changeDir, 'design.md'))
|
|
2750
|
+
const r = verifyFlowDrafts({ changeDir, change, runtimeRoot, skipDesign: adoptedDesign, repair: false })
|
|
2751
|
+
for (const a of r.advisories || []) console.warn(`⚠️ [门柱漂移 advisory] ${a}(不阻断批准)`)
|
|
2752
|
+
if (r.applicable && r.violations.length > 0) {
|
|
2753
|
+
console.error(`❌ spec 断点未批准——文档未齐(批准是动码前置,先答 spec 面再批):`)
|
|
2754
|
+
for (const v of r.violations) console.error(` - ${v}`)
|
|
2755
|
+
console.error(` 作答齐备后重跑:sillyspec flow approve --change ${change} [--user-confirmed|--self-approved]`)
|
|
2756
|
+
process.exit(1)
|
|
2757
|
+
}
|
|
2758
|
+
if (!r.applicable) {
|
|
2759
|
+
// draft-ledger 机本位缺失(跨机批准/存量变更):fail-open 降级——仅 v1(AGENT 槽形态)跑
|
|
2760
|
+
// 空槽兜底;v2 纯 markdown 无 ledger 无法对锚校验,如实降级放行(不假装能查)。
|
|
2761
|
+
let v1Text = null
|
|
2762
|
+
try { v1Text = readFileSync(join(changeDir, 'design.md'), 'utf8') } catch { /* 无 design */ }
|
|
2763
|
+
if (v1Text && /<!--AGENT:槽\d+/.test(v1Text)) {
|
|
2764
|
+
const dr = verifyDesignRecordFilled({ changeDir })
|
|
2765
|
+
if (dr.applicable && dr.emptySlots.length > 0) {
|
|
2766
|
+
console.error(`❌ spec 断点未批准——design.md 有 ${dr.emptySlots.length} 个空 AGENT 槽(${dr.emptySlots.join('、')}):每节至少一行(「不适用:<理由>」也算答)`)
|
|
2767
|
+
console.error(` 作答齐备后重跑:sillyspec flow approve --change ${change} [--user-confirmed|--self-approved]`)
|
|
2768
|
+
process.exit(1)
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
console.log(`ℹ️ 批准校验降级放行:draft-ledger 不在此机(.runtime 机本位/存量变更)——空槽兜底校验通过`)
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2522
2774
|
let approvedBy = by
|
|
2523
2775
|
if (!approvedBy) {
|
|
2524
2776
|
try {
|
|
@@ -2548,7 +2800,7 @@ async function cmdFlowApprove({ change, cwd, specBase, by = null, channelFlag =
|
|
|
2548
2800
|
console.warn(`⚠️ agent 自批留痕:如用户实际未确认,请撤销——sillyspec flow reject --change ${change} --reason <理由>`)
|
|
2549
2801
|
}
|
|
2550
2802
|
if (patch.spec_rejected_superseded_at) console.log(` 已翻越既有拒绝记录(superseded_at=${patch.spec_rejected_superseded_at},拒绝理由保留于 spec_rejected_reason)`)
|
|
2551
|
-
console.log(` flow done
|
|
2803
|
+
console.log(` 批准是动码前置义务:若尚未动码,现在可以开始写码;若已动码,本次属超期补批(留痕可审计)。flow done 断点门已满足。撤销:sillyspec flow reject --change ${change} --reason <理由>`)
|
|
2552
2804
|
}
|
|
2553
2805
|
|
|
2554
2806
|
/**
|
|
@@ -2663,7 +2915,7 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
|
|
|
2663
2915
|
console.error(`❌ ${e.message}`)
|
|
2664
2916
|
// 可照抄实例(2026-10-05-input-teach-copyable):引号内换行合法,行级 trim 容忍缩进
|
|
2665
2917
|
console.error(' 重试(--input 引号内换行合法,可照抄):')
|
|
2666
|
-
console.error(' sillyspec flow start --change <YYYY-MM-DD-简短描述> --input "
|
|
2918
|
+
console.error(' sillyspec flow start --change <YYYY-MM-DD-简短描述> --input "<一句 10-30 字中文概括(首行即变更标题,硬区间 10-30 必须、建议 ~20 字);随后动机与背景')
|
|
2667
2919
|
console.error('')
|
|
2668
2920
|
console.error(' 成功标准:')
|
|
2669
2921
|
console.error(' - <可验证标准>"')
|
|
@@ -2759,17 +3011,10 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
|
|
|
2759
3011
|
let designFilled = false, frFilled = false, bindingsFilled = 0, bindingsTotal = 0
|
|
2760
3012
|
try {
|
|
2761
3013
|
const dText = readFileSync(join(changeDir, 'design.md'), 'utf8')
|
|
2762
|
-
//
|
|
2763
|
-
//
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
} else {
|
|
2767
|
-
try {
|
|
2768
|
-
const { readV2SectionAnswer } = await import('./flow-review.js')
|
|
2769
|
-
const { DESIGN_QUESTIONS } = await import('./flow-draft.js')
|
|
2770
|
-
designFilled = DESIGN_QUESTIONS.sections.filter((s) => readV2SectionAnswer(dText, s.heading, DESIGN_QUESTIONS)).length >= 3
|
|
2771
|
-
} catch { designFilled = false }
|
|
2772
|
-
}
|
|
3014
|
+
// 双格式判定单一源(2026-10-19-approve-gate-tick-nudge FR-03 上移 flow-review.js;
|
|
3015
|
+
// 判据与原内联逻辑逐字一致——status/tick 同源,行为零变化)
|
|
3016
|
+
const { designSectionsFilled } = await import('./flow-review.js')
|
|
3017
|
+
designFilled = designSectionsFilled(dText)
|
|
2773
3018
|
} catch { /* 无 design */ }
|
|
2774
3019
|
try {
|
|
2775
3020
|
const rText = readFileSync(join(changeDir, 'requirements.md'), 'utf8')
|
|
@@ -2784,9 +3029,18 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
|
|
|
2784
3029
|
bindingsFilled = _rows.filter((m) => m[2].trim() && !m[2].trim().startsWith('(待填')).length
|
|
2785
3030
|
}
|
|
2786
3031
|
} catch { /* 无 requirements */ }
|
|
2787
|
-
//
|
|
3032
|
+
// 阶段推断(2026-10-19-thin-order-canon:相位看批准——docs 齐但未批准仍停 ① spec 待批准,
|
|
3033
|
+
// 防 status 中途查询面诱导「先执行后卡点」;autopilot 是用户显式豁免、进执行段带标注)
|
|
3034
|
+
const specApproved = st.spec_approved === true
|
|
3035
|
+
const autopilotOn = st.autopilot === true
|
|
2788
3036
|
let phase = '① spec(填 FR + design 槽)'
|
|
2789
|
-
if (designFilled && frFilled && bindingsFilled >= bindingsTotal && bindingsTotal > 0)
|
|
3037
|
+
if (designFilled && frFilled && bindingsFilled >= bindingsTotal && bindingsTotal > 0) {
|
|
3038
|
+
phase = specApproved
|
|
3039
|
+
? '② 执行(已批准·写代码跑测试)'
|
|
3040
|
+
: autopilotOn
|
|
3041
|
+
? '② 执行(autopilot 豁免·写代码跑测试)'
|
|
3042
|
+
: '① spec(→ 待用户批准:摘要给用户后 flow approve)'
|
|
3043
|
+
}
|
|
2790
3044
|
if (subDone.includes('ledger')) phase = '③ 归档(flow done 收口)'
|
|
2791
3045
|
// 任务心跳(2026-09-29-flow-task-heartbeat + d007-incontext 纠偏):②执行阶段把「下一个任务」
|
|
2792
3046
|
// 交给机器指——当场重读 tasks.md(唯一进度源)取第一个未勾行 + 进度。进度推进本身零 CLI
|
|
@@ -2827,6 +3081,7 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
|
|
|
2827
3081
|
designFilled, frFilled, bindingsFilled, bindingsTotal,
|
|
2828
3082
|
tasksChecked, tasksTotal,
|
|
2829
3083
|
substeps: subDone, substepsTotal: SUBSTEPS.length,
|
|
3084
|
+
specApproved, specApprovedChannel: specApproved ? approveChannelOf(st) : null,
|
|
2830
3085
|
...(st.legacy_fallback ? { legacyFallback: true } : {}),
|
|
2831
3086
|
}))
|
|
2832
3087
|
return
|
|
@@ -2835,6 +3090,7 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
|
|
|
2835
3090
|
`📋 ${change}`,
|
|
2836
3091
|
...(changeTitle ? [` 标题:${changeTitle}`] : []),
|
|
2837
3092
|
` 阶段:${phase}`,
|
|
3093
|
+
` spec 批准:${specApproved ? '✅ 已批准' : autopilotOn ? '🔑 autopilot 豁免(用户显式授权)' : '⬜ 未批准(动码前先过断点①)'}`,
|
|
2838
3094
|
` design 槽:${designFilled ? '✅ 已填' : '⬜ 未填'}|FR 区:${frFilled ? '✅ 已填' : '⬜ 未填'}|绑定槽:${bindingsFilled}/${bindingsTotal}`,
|
|
2839
3095
|
hasTasks ? ` 任务勾选:${tasksChecked}/${tasksTotal}` : null,
|
|
2840
3096
|
...(heartbeat || []),
|
|
@@ -2892,9 +3148,9 @@ export async function cmdFlow(args, cwd, specDir = null, opts = {}) {
|
|
|
2892
3148
|
console.log(`✅ flow amend-draft 留痕重锚:${r.reanchored.join('、')}——ledger amendment 审计在案;editRatio=${maxRatio}(段级最大;基准=首版原文,AGENT 槽不计入)`)
|
|
2893
3149
|
return r
|
|
2894
3150
|
}
|
|
2895
|
-
console.error('用法: sillyspec flow start --change <名> --input "
|
|
3151
|
+
console.error('用法: sillyspec flow start --change <名> --input "<一句 10-30 字中文概括(首行即变更标题,硬区间 10-30 必须、建议 ~20 字)+动机与背景详述+独立一行『成功标准:』+每行一条『- 可验证标准』>" [--thick|--with-tasks] [--no-prestage(hindsight 标记在场时声明需求已含决策,留痕放行)] | sillyspec flow done --change <名> | sillyspec flow status --change <名> [--json] | sillyspec flow diff --change <名> [--json](只读文件统计)')
|
|
2896
3152
|
console.error(' --input 可照抄形态(引号内换行合法):')
|
|
2897
|
-
console.error(' sillyspec flow start --change <名> --input "
|
|
3153
|
+
console.error(' sillyspec flow start --change <名> --input "<一句 10-30 字中文概括(首行即变更标题,硬区间 10-30 必须、建议 ~20 字);随后动机与背景')
|
|
2898
3154
|
console.error('')
|
|
2899
3155
|
console.error(' 成功标准:')
|
|
2900
3156
|
console.error(' - <可验证标准>"')
|