sillyspec 3.33.5 → 3.33.6

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.
@@ -41,6 +41,28 @@ description: 用于 SillySpec local.yaml 配置引导。适合用户说"配置
41
41
  - 凭据段报缺键=两键要成对(或整段删除回退)。
42
42
  - **禁止**在 validate 未通过时向用户宣称配置完成。
43
43
 
44
+ ## 知识注入作用域(非 local.yaml——写在知识文件标记行,不改配置)
45
+
46
+ > 这是「配置」之外唯一需要写的声明:让某条知识每次变更都被注入(规约/仓级约束),而不是等关键词命中。
47
+
48
+ 在 `knowledge/<文件>.md` 某个 section 的 heading 下方写一行 HTML 注释(无标记=关键词命中现状):
49
+
50
+ ```markdown
51
+ ## 工具中立性
52
+ <!-- scope: always -->
53
+ 不锁死开发语言/框架……
54
+
55
+ ## 本仓发布纪律
56
+ <!-- scope: repo -->
57
+ 发布前必须跑锚套件……
58
+
59
+ ## 模块路由规则
60
+ <!-- scope: module:cli-entry,core-engine -->
61
+ ```
62
+
63
+ - `always`=每次变更恒注入(跨仓通用规约);`repo`=本仓恒注入;`module:<域>[,<域>]`=触达该知识域时注入(域 id 与 FR 域同源);无标记=关键词档不恒注入。
64
+ - 查看现状/语法自查:`sillyspec knowledge scope`(纯读;空态会提示「仅关键词档在用」);flow start 注入块以 📌 行呈现。
65
+
44
66
  ## 边界
45
67
 
46
68
  - 禁止写 `.sillyspec/` 之外的 local.yaml(hook 拦截);项目根没有这个文件。
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: sillyspec:push
3
+ description: 推送前压扁本地未推送提交并快进推送。适合用户说“推送 / push / 推到远端 / 发布 / 同步 origin / 把这堆中间提交整理一下”。默认按连续变更段压扁(by-change),可选 --mode one 合并为一笔;只改写本地未推送段,禁止改写已推送历史、禁止 force-push、禁止直接 git push。
4
+ argument-hint: "[--mode by-change|one|none] [--change <名>] [--dry-run] [--no-push] [--undo] [--remote <名>] [--branch <名>] [--json]"
5
+ ---
6
+
7
+ ## 交互规范
8
+ 当需要用户从多个选项中做出选择时,必须使用 Claude Code 内置的 AskUserQuestion 工具,将选项以参数传入。真实推送前必须先展示 `--dry-run` 计划并取得用户确认。
9
+
10
+ ## 何时使用
11
+ - `flow done` / `archive` 收口、提交完成后,准备推送到远端。
12
+ - 用户说“推送 / push / 推到 origin / 发布 / 同步远端 / 把中间提交压扁”。
13
+ - 本地有多个 `task-NN`、wip、fix、归档提交,需要整理成可审阅的历史。
14
+ - 不适用:改写已推送历史、他人分支未授权改写、平台进度同步(那是 `sillyspec platform sync`,不是 git push)。
15
+
16
+ ## 用法
17
+ - `sillyspec push` — 默认 `--mode by-change`:连续且归属同一变更的提交压成一笔,其余原样保留。
18
+ - `sillyspec push --mode one --change <名>` — 所有未推送提交合并为一笔(有活跃变更时拒绝)。
19
+ - `sillyspec push --dry-run` — 只输出计划,不改写 ref/index/工作区/远端。
20
+ - `sillyspec push --no-push` — 只压扁,不推送。
21
+ - `sillyspec push --undo` — 推送前恢复最近一次压扁;远端已更新时拒绝。
22
+ - `sillyspec push --remote origin --branch <分支> --json` — 显式远端/分支/机器输出。
23
+
24
+ ## 流程
25
+ ### 1. 预检(只读)
26
+ ```bash
27
+ git status -sb
28
+ git log --oneline --decorate @{u}..HEAD
29
+ sillyspec push --dry-run
30
+ ```
31
+ - 无 upstream、detached HEAD、merge commit、staged 改动、cwd 在 worktree 内 → 先停下说明原因。
32
+ - 展示:目标远端/分支、base、每个分段(变更名、旧范围、新 subject)、保留的无关提交、是否快进、pre-push 会跑什么。
33
+
34
+ ### 2. 用户确认
35
+ AskUserQuestion:确认执行 / 改为 `--mode one --change <名>` / 取消。
36
+ 用户未明确同意,不执行真实推送。
37
+
38
+ ### 3. 执行
39
+ ```bash
40
+ sillyspec push
41
+ # 或
42
+ sillyspec push --mode one --change <名>
43
+ ```
44
+ - CLI 自动:文件锁、pre/post 备份 ref、`commit-tree`、树等价校验、CAS `update-ref`、普通快进 push。
45
+ - pre-push hook 失败:修复 lint/docs gate 后重跑;禁止 `--no-verify`。
46
+
47
+ ### 4. 汇报
48
+ - 旧 → 新 SHA、远端 ref、备份 ref、压扁段数、`--undo` 命令。
49
+ - 若 push 失败:本地压扁保留,按提示重试或 `--undo`;不要手工 `reset`。
50
+
51
+ ## 模式与守卫
52
+ - `by-change`:只合并连续同归属段;subject 必须保留 `(<变更名>)`,body 保留全部 `task-NN` 证据。
53
+ - `one`:必须 `--change`;区间含其他活跃变更归属时拒绝。
54
+ - 拒绝:base 非 HEAD 祖先、区间含已推送提交、merge commit、staged 改动、active worktree、CAS 并发失败、远端分叉。
55
+ - 活锚点:scan `source_commit`、活跃 `baseline_commit`、change-patch、worktree meta 会自动抬升 effectiveBase;不要手工越过它们。
56
+
57
+ ## 绝对规则
58
+ - ❌ 不要直接 `git push` / `git push --force` / `git push --force-with-lease`。
59
+ - ❌ 不要手工 `git reset --soft` + `git commit` 压扁(会卷入共享暂存区)。
60
+ - ❌ 不要改写已推送历史;base 非祖先一律停下让用户先 pull/rebase。
61
+ - ❌ 不要 `--no-verify`,不要跳过 pre-push hook。
62
+ - ❌ 不要在 staged 改动未处置时运行;不要动其他会话的未提交文件。
63
+ - ❌ 不要在 SillySpec worktree cwd 内压扁;先 `worktree apply` 回主仓再推送。
64
+ - ✅ 不确定时先 `--dry-run`;失败用 `--undo` 恢复,不手改 ref。
65
+
66
+ ## 多会话与并发
67
+ - 在主仓根运行;每条命令带 `SILLYSPEC_SESSION_ID=<agent名+任务名>`。
68
+ - CLI 用 `<git-common-dir>/sillyspec-push.lock` 串行化,分支用 CAS 更新;锁只约束走 `sillyspec push` 的进程。
69
+ - 共享暂存区可能有其他会话的 staged 文件:检测到即拒绝,不要替别人提交或清理。
70
+
71
+ ## 故障排查
72
+ - 无 upstream → 先 `git push -u origin <branch>` 一次,再重跑。
73
+ - base 非祖先 / 远端领先 → 人工 fetch + rebase/merge,不自动 force。
74
+ - pre-push hook 失败 → 修 lint/docs gate,不跳过。
75
+ - CAS/锁失败 → 重跑 `--dry-run` 重新计划;确认无并发 push 后再执行。
76
+ - 已推送后想整理 → 不在本命令范围;需要团队明确同意后人工 force-with-lease。
77
+
78
+ ## 用户指令
79
+ $ARGUMENTS
package/AGENTS.md CHANGED
@@ -43,5 +43,6 @@ sillyspec flow start --change <YYYY-MM-DD-名> --input "<一句 10-30 字中文
43
43
  11. **禁止目录级 git add / git add -A**(多会话共享仓):目录级暂存会夹带并行会话的进行中文件甚至误删已提交文件。提交一律用显式 pathspec,且 **add 与 commit 用同一份清单**:`git commit -m "..." -- 文件1 文件2`——带 pathspec 的 commit 只提交这些路径,他侧已 staged 的条目既不被带走、也原样留给对方;裸 `git commit` 提交的是整个共享暂存区,他会话 add 过的文件会被一并扫入。核对暂存面固定用 `git diff --cached --name-only` 全量读取,禁用过滤式核对(过滤正是致盲原因);核对与提交分开两条命令执行,不得链在同一命令里(链行没有拦截点)。安装了 commit-guard hook 的仓会对超声明面的 staged 文件警告(只警告不阻断)。
44
44
  12. **不奉承用户**,禁止"你说得对"类话术,直接给结论、依据、方案。
45
45
  13. **边干边勾(任务进度唯一源=变更的 tasks.md)**:完成一个任务单元(实现到位 + 相关测试跑绿)当场勾对应格——`sillyspec task tick --change <名> --task task-NN`(即时回显进度与下一任务)或直接 Edit 翻格 `- [ ]`→`- [x]`,勿攒到收口一把勾。harness 自带的 TodoWrite 类工具是会话内便利面,不替代 tasks.md——平台进度投影、收口哨兵、断点恢复只读 tasks.md。
46
+ 14. **推送前压扁**:本地未推送提交统一走 `sillyspec push`(默认 by-change;`--mode one` 需明确归属),禁止直接 `git push`、`git push --force` 或手工 `git reset --soft` 压扁;已推送历史不改写,并行会话的 staged 文件/Dirty 文件不替人提交或清理。
46
47
 
47
48
  00. **不要枚举定义开放世界 以及 不要限制死 开发语言和框架**
package/README.md CHANGED
@@ -116,6 +116,7 @@ SillySpec 进度库使用 SQLite 持久化(基于 Node.js 内置的 `node:sqli
116
116
  | `/sillyspec:resume` | 恢复工作(支持大模块阶段进度) |
117
117
  | `/sillyspec:doctor` | 项目自检与状态修复 |
118
118
  | `/sillyspec:commit` | 智能提交 |
119
+ | `/sillyspec:push` | 推送前压扁本地未推送提交并快进推送 |
119
120
  | `/sillyspec:workspace` | 多项目工作区管理 |
120
121
  | `/sillyspec:export` | 导出成功方案为可复用模板 |
121
122
 
@@ -126,6 +127,7 @@ sillyspec run <stage> 执行阶段(auto/brainstorm/plan/execute/ver
126
127
  sillyspec run <stage> --done 完成当前步骤并推进到下一步
127
128
  sillyspec run <stage> --status 查看阶段进度
128
129
  sillyspec progress show 显示当前项目状态
130
+ sillyspec push 推送前压扁本地未推送提交并快进推送(默认 by-change)
129
131
  sillyspec init 初始化(零交互,自动检测工具)
130
132
  sillyspec setup 安装推荐 MCP 工具(交互式)
131
133
  sillyspec setup --list 查看已安装 MCP 状态
package/SKILL.md CHANGED
@@ -54,6 +54,7 @@ Claude Code / Cursor / Codex / OpenCode / OpenClaw / Gemini 通用。
54
54
  | `/sillyspec:resume` | 恢复工作 |
55
55
  | `/sillyspec:doctor` | 项目自检 |
56
56
  | `/sillyspec:commit` | 智能提交 |
57
+ | `/sillyspec:push` | 推送前压扁本地未推送提交并快进推送 |
57
58
  | `/sillyspec:knowledge` | 知识库检索 / 沉淀 |
58
59
  | `/sillyspec:export` | 导出成功方案为可复用模板 |
59
60
  | `/sillyspec:workspace` | 多项目工作区管理 |
@@ -69,6 +70,7 @@ sillyspec run execute 执行开发阶段(子代理并行 + worktree
69
70
  sillyspec run verify 执行验证阶段
70
71
  sillyspec run archive 执行归档阶段
71
72
  sillyspec flow start/done/status 轻量变更(默认快道,2 次调用收口)
73
+ sillyspec push 推送前压扁本地未推送提交并快进推送(默认 by-change)
72
74
  sillyspec run explore 自由探索
73
75
  sillyspec progress show 显示当前项目状态
74
76
  sillyspec setup 安装推荐 MCP 工具
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: sillyspec:push
3
+ description: 推送前压扁本地未推送提交并快进推送(默认 by-change;拒绝改写已推送历史与 force-push)
4
+ ---
5
+ # 何时用
6
+
7
+ - `flow done` / `archive` 收口、提交完成后准备推送到远端。
8
+ - 用户说“推送 / push / 推到 origin / 发布 / 整理中间提交”。
9
+ - 不适用:已推送历史改写、他人分支未授权改写、平台进度同步(用 `platform sync`)。
10
+
11
+ # 生命周期速查
12
+
13
+ ```bash
14
+ export SILLYSPEC_SESSION_ID=<agent名+任务名> # 多会话所有权判定依赖
15
+ sillyspec push --dry-run # 先看计划,零写盘
16
+ sillyspec push # 默认 by-change:连续同变更段压扁 + 快进推送
17
+ sillyspec push --mode one --change <名> # 全部未推送提交合并一笔(守卫通过才可)
18
+ sillyspec push --no-push # 只压扁不推送
19
+ sillyspec push --undo # 推送前恢复最近一次压扁
20
+ # pre-push hook 会跑 lint/docs gate;失败必须修复,禁止 --no-verify
21
+ ```
22
+
23
+ # 防坑清单
24
+
25
+ - 不要直接 `git push`、`git push --force`,也不要手工 `git reset --soft` 压扁。
26
+ - base 非 HEAD 祖先 / upstream 领先 → 拒绝;先人工 fetch/rebase,本命令不 force。
27
+ - index 有 staged 改动 → 拒绝;不要替其他会话提交或清理暂存区。
28
+ - 在 SillySpec worktree 内 → 拒绝;先 `worktree apply` 回主仓再 push。
29
+ - 区间含 merge commit / 已推送提交 → 默认拒绝;不要试图展平他人已推送历史。
30
+ - by-change 只合并连续段;裸提交与其他变更提交原样保留,不跨段重排。
31
+ - one 模式必须 `--change`;有活跃变更或其他归属提交时拒绝。
32
+ - 活锚点(scan `source_commit` / 活跃 baseline / change-patch / worktree meta)自动抬升 base;不要手工越过。
33
+ - 备份 ref 不自动清理;`--undo` 只允许推送前,已推送后拒绝。
34
+
35
+ # 边界声明
36
+
37
+ 本卡不含实现细节——计划、分段、锚点抬升、CAS 与推送由 `sillyspec push` 按当前仓状态实时计算。
38
+ 本卡不替代 commit:先用 `sillyspec commit` 或显式 pathspec 提交,再 push。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sillyspec",
3
- "version": "3.33.5",
3
+ "version": "3.33.6",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
@@ -24,7 +24,7 @@
24
24
  ],
25
25
  "license": "MIT",
26
26
  "scripts": {
27
- "test:core": "node --test test/preview-migration.test.mjs test/preview-progress.test.mjs test/preview-outlet.test.mjs test/preview-gc.test.mjs test/preview-gate-isolation.test.mjs test/db-engine.test.mjs test/db-concurrency.test.mjs test/db-atomic-write.test.mjs test/platform-sync-schema.test.mjs test/platform-sync-serialization.test.mjs test/change-ownership-guards.test.mjs test/watcher.test.mjs test/watcher-alerts.test.mjs test/watcher-timeline.test.mjs test/sentinel-rules.test.mjs test/handoff.test.mjs test/execution-mode-render.test.mjs test/knowledge-inbox.test.mjs test/knowledge-baseline.test.mjs test/quick-laststep-fourfields-preview.test.mjs test/doctor-align-execute-progress.test.mjs test/test-ledger.test.mjs test/test-bindings.test.mjs test/flow-draft-binding-extract.test.mjs test/gate-snapshot-lifecycle.test.mjs test/gate-snapshot-cleanup.test.mjs test/verify-trace-residual.test.mjs test/platform-creds-fallback.test.mjs test/archive-timeline-bake.test.mjs test/doc-ref-check.test.mjs test/flow-status-json.test.mjs test/flow-status-title.test.mjs test/flow-resume-title.test.mjs test/datetime-timeago.test.mjs test/resume-domain-flip.test.mjs test/fr-regress-cap-drop.test.mjs test/fr-priority-overlap.test.mjs test/archive-commit-suggest-race.test.mjs test/flow-done-fail-brief.test.mjs test/verify-probes-refresh-backup.test.mjs test/taskcard-yaml-triage-validate.test.mjs test/gate-last-reader.test.mjs test/status-multi-active-list.test.mjs test/gate-docs-only.test.mjs test/probe7-fr-prefill.test.mjs test/plan-fill-batch-config.test.mjs test/step-guide-prune.test.mjs test/gate-stage-review-hint.test.mjs test/design-ref-suggest.test.mjs test/flow-friction-batch3.test.mjs test/wave-auto-adopt.test.mjs test/test-incremental-rerun.test.mjs test/knowledge-graph.test.mjs test/explore-graph-guidance.test.mjs",
27
+ "test:core": "node --test test/preview-migration.test.mjs test/preview-progress.test.mjs test/preview-outlet.test.mjs test/preview-gc.test.mjs test/preview-gate-isolation.test.mjs test/db-engine.test.mjs test/db-concurrency.test.mjs test/db-atomic-write.test.mjs test/platform-sync-schema.test.mjs test/platform-sync-serialization.test.mjs test/change-ownership-guards.test.mjs test/watcher.test.mjs test/watcher-alerts.test.mjs test/watcher-timeline.test.mjs test/sentinel-rules.test.mjs test/handoff.test.mjs test/execution-mode-render.test.mjs test/knowledge-inbox.test.mjs test/knowledge-baseline.test.mjs test/quick-laststep-fourfields-preview.test.mjs test/doctor-align-execute-progress.test.mjs test/test-ledger.test.mjs test/test-bindings.test.mjs test/flow-draft-binding-extract.test.mjs test/gate-snapshot-lifecycle.test.mjs test/gate-snapshot-cleanup.test.mjs test/verify-trace-residual.test.mjs test/platform-creds-fallback.test.mjs test/archive-timeline-bake.test.mjs test/doc-ref-check.test.mjs test/flow-status-json.test.mjs test/flow-status-title.test.mjs test/flow-resume-title.test.mjs test/datetime-timeago.test.mjs test/resume-domain-flip.test.mjs test/fr-regress-cap-drop.test.mjs test/fr-priority-overlap.test.mjs test/archive-commit-suggest-race.test.mjs test/flow-done-fail-brief.test.mjs test/verify-probes-refresh-backup.test.mjs test/taskcard-yaml-triage-validate.test.mjs test/gate-last-reader.test.mjs test/status-multi-active-list.test.mjs test/gate-docs-only.test.mjs test/probe7-fr-prefill.test.mjs test/plan-fill-batch-config.test.mjs test/step-guide-prune.test.mjs test/gate-stage-review-hint.test.mjs test/design-ref-suggest.test.mjs test/flow-friction-batch3.test.mjs test/wave-auto-adopt.test.mjs test/test-incremental-rerun.test.mjs test/knowledge-graph.test.mjs test/explore-graph-guidance.test.mjs test/git-helper-input.test.mjs test/push-plan.test.mjs test/push-cli.test.mjs test/push-skill.test.mjs test/flow-guidance.test.mjs",
28
28
  "test": "node test/run-tests.mjs",
29
29
  "lint": "node test/check-syntax.mjs",
30
30
  "eval:dry": "node eval/run.mjs --dry-run",
@@ -10,7 +10,7 @@ import { writeAtomicSync } from './fs-atomic.js'
10
10
 
11
11
  export const COMMAND_CARD_TARGETS = { zcode: '.zcode/commands/sillyspec', claude: '.claude/commands/sillyspec' }
12
12
  // flow=轻量变更默认道入口卡(2026-09-25-quick-channel-retire 配套:quick 退役后快道首次有卡)
13
- export const COMMAND_CARD_NAMES = ['flow', 'run-brainstorm', 'run-plan', 'run-execute', 'run-verify', 'run-archive', 'status']
13
+ export const COMMAND_CARD_NAMES = ['flow', 'run-brainstorm', 'run-plan', 'run-execute', 'run-verify', 'run-archive', 'status', 'push']
14
14
 
15
15
  const ANCHOR_TAIL_RE = /\n?<!-- sillyspec-card: v(\S+) sha256=([0-9a-f]{64}) -->\s*$/
16
16
  const sha256 = (s) => createHash('sha256').update(s, 'utf8').digest('hex')
@@ -75,6 +75,7 @@ export const LOCAL_YAML_SCHEMA = {
75
75
  { path: 'commands.smoke', type: 'string', optional: true, status: 'live', readers: ['executeVerifyQualityScan (src/run/verify-quality-scan.js)'], desc: '接口冒烟命令——verify 阶段 CLI 亲跑(脚本自理服务生命周期:后台起服+轮询就绪+finally 杀),300s 超时帽,失败/超时只记失败态不阻断本步、触发 PASS 封顶(D-002);指纹含此键,未变不重跑。', example: 'node scripts/smoke.mjs' },
76
76
  { path: 'commands.install', type: 'string', optional: true, status: 'live', readers: ['extractInstallCommand (src/worktree-deps.js)'], desc: '依赖安装命令(worktree 依赖 provisioning 用)。', example: 'npm install' },
77
77
  { path: 'commands.knowledge-gate', type: 'boolean', optional: true, status: 'live', readers: ['completeStep (src/run/complete.js)'], desc: '【2026-09-28-unclear-req-to-brainstorm FR-03/D-004】brainstorm 方案步(提出 2-3 种方案)--done 知识检索命中回显开关——缺省 true=开(对 --output 与 decisions.md 条目跑既有检索匹配器,命中 warn 回显「须 evidence 回应或说明不复潮」不阻断);false(或 off)关回显,无命中时输出与现状一致。', example: 'false' },
78
+ { path: 'commands.scan:check', type: 'string', optional: true, status: 'live', readers: ['scan 漂移检查(scripts/scan-drift-check.py 经 scan 流程调用)'], desc: '【2026-10-23-schema-registration-debt 平台仓 dogfood 补登】scan 产物漂移自检命令。', example: 'python scripts/scan-drift-check.py' },
78
79
  ],
79
80
  },
80
81
  {
@@ -122,6 +123,7 @@ export const LOCAL_YAML_SCHEMA = {
122
123
  keys: [
123
124
  { path: 'modules.<name>.path', type: 'string', optional: true, status: 'live', readers: ['extractModules (src/verify-postcheck.js)', 'parseLocalYamlModules (src/plan-postcheck.js)', 'extractModulePaths (src/worktree-deps.js)'], desc: '子模块目录路径(相对仓库根)。', example: 'frontend/' },
124
125
  { path: 'modules.<name>.test', type: 'string', optional: true, status: 'deprecated', readers: ['extractModules (src/verify-postcheck.js,退役提示用)'], desc: '【2026-09-26-dynamic-test-inference 退役,不再消费】测试面按变更动态推断、runner 自项目结构(pyproject/uv.lock/package.json 就近祖先)推断。模块级 flaky 处置迁 known_failures。块在场时 CLI 打印退役指引。', example: 'cd frontend && pnpm test' },
126
+ { path: 'module_paths', type: 'object', optional: true, status: 'live', readers: ['module_paths 旧字段(部分工具仍用——平台仓 dogfood 2026-10-23 核实在用)'], desc: '【旧字段】模块名 → 目录路径映射(如 backend: backend/)。部分工具仍消费;新配置勿再写(模块归属走 docs/<project>/modules/_module-map.yaml)。', example: '{ backend: backend/, frontend: frontend/ }' },
125
127
  { path: 'fr_unmapped_baseline', type: 'integer', optional: true, status: 'live', readers: ['unmapped 大池消音(src/fr-index.js)'], desc: 'unmapped 域 FR 条数消音基线(跨仓历史堆积已清算时设当前值;增量 >10 才警告,缺省 0=无基线恒警告行为)。', example: '720' },
126
128
  ],
127
129
  },
@@ -140,6 +142,9 @@ export const LOCAL_YAML_SCHEMA = {
140
142
  keys: [
141
143
  { path: 'verify.test_rerun', type: 'enum', values: ['incremental', 'full'], optional: true, status: 'live', readers: ['readTestRerunConfig (src/verify-postcheck.js — dynamic-subset 分支增量档)'], desc: '实测门失败后重跑档:incremental(缺省)= 前轮失败后,下轮只跑「失败批测试文件 ∪ 自失败基线以来变更文件」的三源推断面(未触碰绿面复用,返工轮分钟级);full = 恒全子集重跑(2026-10-07 前现状,保守项目用)。env SILLYSPEC_TEST_RERUN 优先。仅作用 dynamic-subset 档(显式 test_strategy: full 不受影响)。', example: 'verify: { test_rerun: full }' },
142
144
  { path: 'test_strategy', type: 'enum', values: ['full', 'module', 'skip', 'evidence-auto'], optional: true, status: 'live', readers: ['extractTestStrategy (src/verify-postcheck.js)', 'resolveTestStrategy (src/verify-postcheck.js)'], desc: '【2026-09-26-dynamic-test-inference 起】full=全量(commands.test 在场生效,否则结构推断);module=已退役折算动态子集(超集覆盖:变更文件面收窄 + FR 关联回归 + import 依赖);skip=真跳过(R-07 不变);evidence-auto=按 module-impact.md 推荐(行为→动态子集、文档/门禁→skip)。缺省=动态子集:三源并集非空实测、空则不硬跑全量(防超时/预存失败面)。', example: 'full' },
145
+ { path: 'runner_flags', type: 'object', optional: true, status: 'live', readers: ['readRunnerFlags (src/verify-postcheck.js)'], desc: '【2026-10-23-runner-flags-generic,语言中立】推断批尾随 flags 映射:批类别(CLI 自有 runner 分类 py|js|jsx|…,未知类别前向兼容)→ flags 串。flags 置于文件参数前(node/go 类参数解析在首个位置参数处截断);hits 路径自带 flags 不注入。缺省不写=现状。', example: 'py: "-q --no-cov -n auto" / jsx: "--pool=forks"' },
146
+ { path: 'py_runner_flags', type: 'string', optional: true, status: 'deprecated', readers: ['readRunnerFlags 兼容别名 (src/verify-postcheck.js)'], desc: '【已泛化】runner_flags.py 的单键旧别名(2026-10-23-py-runner-flags 遗留);新配置写 runner_flags 映射。', example: '-q --no-cov -n auto' },
147
+ { path: 'deps_regress_cap', type: 'string', optional: true, status: 'live', readers: ['readDepsRegressCap (src/verify-postcheck.js)'], desc: '【2026-10-23-runner-flags-generic,语言中立】import 依赖面档位:N(1-2000)=变更自身测试优先、依赖只取前 N;off=只跑变更自身测试(关联回归归 pre-push/CI 门);不写=全跑。own=变更触碰文件∩依赖面,纯路径集合运算不涉语言。', example: 'off' },
143
148
  { path: 'fr_regress_cap', type: 'string', optional: true, status: 'live', readers: ['readFrRegressCap (src/verify-postcheck.js)'], desc: '【2026-10-23-fr-regress-default-off 缺省翻转】FR 关联回归层本地实测开关:缺省(不写)=不跑(归 pre-push/CI 门,收口披露行留痕);N(1-2000)=前 N 个绑定文件进实测面;all=全跑旧行为显式恢复;off=同缺省。', example: '40' },
144
149
  ],
145
150
  },
@@ -299,6 +304,15 @@ export const LOCAL_YAML_SCHEMA = {
299
304
  { path: 'change-ownership.heartbeat_minutes', type: 'integer', optional: true, status: 'live', readers: ['assertChangeOwnership (src/progress/change-registry.js — 本变更 task-02 接线消费)'], desc: '所有者活跃心跳窗口(分钟):owner 非本会话且 last_active 距今在窗口内 → 拒绝接管类操作(--takeover 显式接管);窗口外 → 放行并自动接管。缺省 15。', example: '15' },
300
305
  ],
301
306
  },
307
+ {
308
+ id: 'ui_prototype',
309
+ title: 'UI 原型形态契约(每仓)',
310
+ note: '【2026-10-15-ui-proto-form-config】frontend 触达的 UI 变更定稿原型形态:real-code=真码走仓内原型管线(推荐);single-html=单 HTML 例外(须 ruling 三键留痕:by/at/reason)。缺省未配置=flow start 强提醒登记。',
311
+ keys: [
312
+ { path: 'ui_prototype.form', type: 'enum', values: ['real-code', 'single-html'], optional: true, status: 'live', readers: ['readUiPrototypeForm (src/ui-visual.js)'], desc: '本仓原型形态契约:real-code(真码管线)/ single-html(单 HTML 例外,须 ruling 裁决留痕)。', example: 'real-code' },
313
+ { path: 'ui_prototype.<key>', type: 'string', optional: true, status: 'live', readers: ['readUiPrototypeForm (src/ui-visual.js)——豁免裁决 ruling{by,at,reason} 等扩展子键'], desc: '扩展子键占位(豁免裁决 ruling 族等)——子键名开放,校验不枚举。', example: 'ruling_by' },
314
+ ],
315
+ },
302
316
  {
303
317
  id: 'ceremony',
304
318
  title: '评审仪式档位(ceremony tier)',
@@ -448,8 +462,16 @@ dispatch:
448
462
  # review_inline_wait: 180 # 评审平台单调用等待秒(1-600,需 SillyHub);不写=评审任务书面模式
449
463
  # dirty_gate_ignore: quicklog, test/.sillyspec # dirty 门已知残渣前缀(单行逗号、posix 前缀)
450
464
  # module_gate_ignore: scripts # 模块图软门固定忽略目录(单行逗号、前缀匹配)
465
+ # commands:
466
+ # scan:check: python scripts/scan-drift-check.py # scan 漂移自检命令
467
+ # module_paths: { backend: backend/, frontend: frontend/ } # 旧字段(部分工具仍用;新配置勿写)
468
+ # ui_prototype:
469
+ # form: real-code # UI 原型形态契约(real-code / single-html+ruling 豁免)
451
470
  # fr_regress_cap: 40 # FR 回归层本地开关:缺省不跑归 pre-push/CI 门;N=前 N 个;all=全跑旧行为
452
471
  # fr_unmapped_baseline: 720 # unmapped 域 FR 消音基线(跨仓历史堆积已清算时设)
472
+ # runner_flags: # 推断批尾随 flags(按批类别 py|js|jsx|…,未知类别前向兼容)
473
+ # py: "-q --no-cov -n auto"
474
+ # deps_regress_cap: off # import 依赖面:off=只跑变更自身测试;N=前 N 个依赖文件;不写=全跑
453
475
  #
454
476
  # ── 跨仓注册表(注册后参与跨仓冻结/对账;local.yaml gitignored 故真实路径不入库)──
455
477
  # repos:
@@ -6,7 +6,9 @@
6
6
  * DB 行被外部手段物理清除后(rm + 裸 SQL,任何 sillyspec 官方路径都不物理删行),后续
7
7
  * platform sync --change 一律「变更无进度数据」空转,平台行永挂 in_progress/active。
8
8
  * 台账让「存在过且已达终态」这一事实脱离 DB 行存活而可查:deleteChange/unregisterChange
9
- * 在事务提交后追加一行;sync() 的行缺失分支据此合成墓碑补推(见 sync.js 台账分支)。
9
+ * 在事务提交后追加一行;renameChange 对旧名追加 deleted 行(平台按 change_key 建行,
10
+ * rename 只改本地,旧平台名行的唯一终态凭证——2026-10-23-rename-platform-tombstone);
11
+ * sync() 的行缺失分支据此合成墓碑补推(见 sync.js 台账分支)。
10
12
  *
11
13
  * 形态契约:`.sillyspec/.runtime/deleted-changes.jsonl`,每行一个 JSON 对象
12
14
  * { name, status: 'deleted'|'archived', at: ISO };追加-only(读取侧 latest-wins),
@@ -516,6 +516,46 @@ export function detectPatchDrift({ cwd, change, freezeHead }) {
516
516
  } catch { return { drifted: false, ownCommits: [], head: null, ownFiles: [], governanceOnly: false, touchedPromiseFace: false } }
517
517
  }
518
518
 
519
+ /**
520
+ * 交付面锚定判定(2026-10-24-flow-done-review-artifact-drift-loop,坑实证三连循环):
521
+ * 复审锚(review.json.reviewedAgainst)之后的本变更 own 提交只碰治理文件(.sillyspec/**,
522
+ * 典型形态=review.json 自身的归档前提交)且未触承诺面(requirements/design)→ 评审对象
523
+ * (交付面)自锚点起未变,结论有效。
524
+ *
525
+ * 堵 detectPatchDrift 两保留路径的复合盲区:reviewAnchoredToHead 要求锚==当前 HEAD——锚与
526
+ * HEAD 之间隔着 review.json 提交即恒 false;governanceOnly 看整个冻结窗——窗口内更早的
527
+ * 复审前修复提交(复审 reviewedAgainst 已覆盖)把 ownFiles 拖进交付文件 → 恒 false →
528
+ * 隔离重评 → 重评再提交 review.json 又前移……循环。本判定把「已覆盖」的度量单位从窗口
529
+ * 换成锚点:锚后的 own 提交才是复审未见的面。
530
+ *
531
+ * fail-safe:锚空/非祖先(rebase 重写)/git 失败 → false(宁可多评)。
532
+ * @returns {{ok: boolean, ownCount: number, files: string[], touchedPromiseFace: boolean}}
533
+ */
534
+ export function deliveryFaceAnchoredAfter({ cwd, change, anchorSha }) {
535
+ try {
536
+ const a = String(anchorSha || '').trim().toLowerCase()
537
+ if (!/^[0-9a-f]{7,40}$/.test(a)) return { ok: false, ownCount: 0, files: [], touchedPromiseFace: false }
538
+ // 非零退出码(非祖先/git 失败)由 execFileSync 抛出走 catch fail-safe
539
+ execFileSync('git', ['merge-base', '--is-ancestor', a, 'HEAD'], { cwd, encoding: 'utf8', timeout: 30000, windowsHide: true })
540
+ const log = execFileSync('git', ['log', '--name-only', '--format=%x00%s', `${a}..HEAD`], { cwd, encoding: 'utf8', timeout: 30000, windowsHide: true })
541
+ const files = []
542
+ let ownCount = 0
543
+ for (const block of String(log).split('\x00')) {
544
+ const lines = block.split('\n').map((l) => l.trim()).filter(Boolean)
545
+ const subject = lines[0]
546
+ if (!subject) continue
547
+ if (parseChangeNamesFromSubject(subject).includes(change)) {
548
+ ownCount++
549
+ for (const f of lines.slice(1)) files.push(f.replace(/\\/g, '/'))
550
+ }
551
+ }
552
+ const promiseFace = [`.sillyspec/changes/${change}/requirements.md`, `.sillyspec/changes/${change}/design.md`]
553
+ const touchedPromiseFace = files.some((f) => promiseFace.includes(f))
554
+ const ok = files.every((f) => f.startsWith('.sillyspec/')) && !touchedPromiseFace
555
+ return { ok, ownCount, files, touchedPromiseFace }
556
+ } catch { return { ok: false, ownCount: 0, files: [], touchedPromiseFace: false } }
557
+ }
558
+
519
559
  /**
520
560
  * patch 冻结面收集(2026-09-25-thin-r16-patches 修复①,R16 P2:agent 提交晚于 done →
521
561
  * 冻结件只有治理件)。committed 提交面为主;exclusive(会话专属 worktree,调用方按
@@ -337,7 +337,9 @@ export function renderReviewerTaskbook({ change, changeDir, head = null, probeLi
337
337
  ] : [
338
338
  ` "reviewedAgainst": "<评审对象 HEAD 完整 sha——照抄任务书标注的 sha>",`,
339
339
  ]),
340
- ` "findings": [ { "severity": "P1|P2|P3", "title": "…", "evidence": "代码锚点+机理一句话", "location": "file:line" } ],`,
340
+ ` "findings": [ { "severity": "P1|P2|P3", "status": "open|resolved|resolved-as-accepted|accepted|false-positive", "title": "…", "evidence": "代码锚点+机理一句话", "location": "file:line" } ],`,
341
+ ` (增量复审:已解决/接受的历史条目保留在 findings 并标 status——收口阻断只计 open 的 P1;首评可省略 status,缺省=open)`,
342
+ ` (写入纪律(2026-10-24-flow-done-review-artifact-drift-loop):review.json 写 ${'<changeDir>/review.json'}——目录不存在=变更已归档/被移,停止并报告,勿重建目录;评审产物保持工作区未提交态,随归档提交一次性入库(先行提交会触发审计时点漂移判定))`,
341
343
  ` "dimensionNotes": { "乱序": "ok|finding|n/a", "并发": "ok|finding|n/a", "切换": "ok|finding|n/a", "作用域": "ok|finding|n/a" },`,
342
344
  ` "reviewedAt": "<ISO 时间>"`,
343
345
  `}`,
@@ -354,7 +356,7 @@ export function renderReviewerTaskbook({ change, changeDir, head = null, probeLi
354
356
  * 不回写文件(校验器只读铁律)。误命中面(如连写分支名含 ≥7 位 hex 段)后果有限:
355
357
  * reviewedAgainst 是溯源锚非门禁比对;风险评估见变更 design 风险1。
356
358
  */
357
- const extractAnchorSha = (v) => {
359
+ export const extractAnchorSha = (v) => {
358
360
  if (typeof v !== 'string') return null
359
361
  const m = v.match(/(?<![0-9a-fA-F])[0-9a-fA-F]{7,40}(?![0-9a-fA-F])/)
360
362
  return m ? m[0].toLowerCase() : null
@@ -376,11 +378,25 @@ function readCrossRepoKeys(changeDir) {
376
378
  }
377
379
 
378
380
  /**
379
- * review.json 校验(三态:ok / schema 错误清单)。FAIL 或含 P1 由调用方拦截。
381
+ * finding 开放态判定(2026-10-24-flow-done-review-findings-status-blind,坑实证):增量复审
382
+ * (FAIL 后修复 → resume 同一评审员)天然在 findings 中保留历史条目并标 status——阻断判定
383
+ * 只数开放项。已解决集(评审员侧语义面,大小写不敏感):resolved / resolved-as-accepted /
384
+ * accepted / false-positive;status 缺省/未知值=开放(fail-closed:不因新词表静默放行)。
385
+ */
386
+ export function isOpenFinding(f) {
387
+ const s = String((f && f.status) || '').trim().toLowerCase()
388
+ if (!s) return true
389
+ return !['resolved', 'resolved-as-accepted', 'accepted', 'false-positive'].includes(s)
390
+ }
391
+
392
+ /**
393
+ * review.json 校验(三态:ok / schema 错误清单)。FAIL 或含开放 P1 由调用方拦截。
380
394
  * reviewedAgainst(评审对象锚):可选字段——缺省容忍(旧产物向后兼容)。双形态
381
395
  * (2026-10-12-tick-evidence-multi-anchor):字符串(既有,7-40 hex)或对象
382
396
  * {workspace: sha, repos: {<key>: sha}}——跨仓交付的「对着哪个面评审」可机核;
383
397
  * 仓键开放(local.yaml repos 注册表键空间,CLI 不枚举校验键集合)。
398
+ * findings[].status:可选字段(isOpenFinding 词表)——校验不拒绝未知值(展示面保留),
399
+ * 阻断语义由调用方按 isOpenFinding 收敛(fail-closed)。
384
400
  */
385
401
  export function validateReviewJson(path) {
386
402
  let raw
package/src/flow.js CHANGED
@@ -297,6 +297,29 @@ export async function flowKnowledgeDigest({ specBase, change, changeDir, input,
297
297
  summary.domains = domains
298
298
  summary.frCount = frs.length
299
299
  lines.push(`🧠 知识注入(轻量道读取面——现行 FR/否决决策/已知坑,动手前扫一眼):`)
300
+ const scopedLines = []
301
+ // 作用域注入(2026-10-23-knowledge-scope-inject):①工具内置中立性行恒注入(规约 00——
302
+ // 不依赖仓侧配置:语言锁定类设计错误在决策时点被提醒,2026-10-23 实证驱动的内置面);
303
+ // ②always/repo 条目恒注入(帽 5 条+溢出计数);③module:<域> 条目仅触达域命中时注入;
304
+ // ④无标记条目走关键词面(零改动)。解析/读取 fail-soft 零输出。
305
+ try {
306
+ const { collectScopedKnowledge } = await import('./knowledge-scope.js')
307
+ const scoped = collectScopedKnowledge(knowledgeRoot)
308
+ const alwaysRepo = [...scoped.always, ...scoped.repo]
309
+ summary.scopedEntries = alwaysRepo.length
310
+ scopedLines.push(' 🧭 工具中立性(内置规约 00,每变更恒注入):不枚举定义开放世界、不锁死开发语言/框架——配置与机制用 CLI 自有分类(批类别/路径集合),新增语言只加探测适配。')
311
+ for (const e of alwaysRepo.slice(0, 5)) {
312
+ scopedLines.push(` 📌 [${e.scope}] ${e.id}:${e.summary}`)
313
+ }
314
+ if (alwaysRepo.length > 5) scopedLines.push(` (+${alwaysRepo.length - 5} 条恒注入条目见 knowledge/)`)
315
+ const moduleEntries = []
316
+ for (const d of domains) for (const e of (scoped.modules[d] || [])) moduleEntries.push({ ...e, domain: d })
317
+ for (const e of moduleEntries.slice(0, 3)) {
318
+ scopedLines.push(` 📌 [module:${e.domain}] ${e.id}:${e.summary}`)
319
+ }
320
+ if (moduleEntries.length > 3) scopedLines.push(` (+${moduleEntries.length - 3} 条触达域作用域条目见 knowledge/)`)
321
+ lines.push(...scopedLines)
322
+ } catch { /* 作用域注入 fail-soft:FR/关键词面不受影响 */ }
300
323
  if (domains.length === 0) {
301
324
  lines.push(` 触达域:起点无依据(--input 无路径语料、无 design 清单)——干活涉及 src 后 resume/收口会补查;`)
302
325
  lines.push(` 相关域现行 FR 可自行查 knowledge/fr/<域>.md(INDEX.md 有路由)。`)
@@ -367,6 +390,7 @@ export async function flowKnowledgeDigest({ specBase, change, changeDir, input,
367
390
  if (domains.length === 0 && rejected.length === 0 && !km.matched) {
368
391
  lines.length = 0
369
392
  lines.push(`🧠 知识注入(轻量道读取面):语料未命中知识库(无触达域依据、无否决命中、INDEX 无对齐条目)——可自行查 knowledge/INDEX.md。`)
393
+ if (scopedLines.length > 0) lines.push(...scopedLines) // 恒注入面不被空态折叠吞(2026-10-23-knowledge-scope-inject)
370
394
  }
371
395
  // 向量开关发现性提示(2026-10-15-vector-switch-discoverability):零命中=正需要语义召回
372
396
  // 而未获得的时刻——平台已连接但开关未开(缺省关)时给一行可执行提示;有命中/未连接/
@@ -1144,7 +1168,7 @@ export async function cmdFlowStart({ change, input, title: titleFlag = null, thi
1144
1168
  `📜 requirements 每条 FR = 标题锚 + 行为句 + 按需场景块(#### 场景:名 + Given/When/Then);`,
1145
1169
  ` 测试绑定节每条 FR 一行 \`FR-NN: test/路径「用例」\`(无测试面写「不适用:理由」)。`,
1146
1170
  `📦 冻结面在 flow done 时点采集(baseline..HEAD 提交面):未提交交付文件——会话专属 worktree 自动并入;`,
1147
- ` 共享主仓可带 --freeze-dirty 显式声明并入;git 中间提交归档后可 reset --soft 压扁为单提交`,
1171
+ ` 共享主仓可带 --freeze-dirty 显式声明并入;git 中间提交归档后用 sillyspec push 压扁并推送`,
1148
1172
  ` (审计真相在 change.patch 冻结件 sha256 锚定,不依赖 git 历史形态)。`,
1149
1173
  `⚖️ 独立评审定档(flow done 按危险证据判,不看文件数):高危承诺词/盲维实质作答/diff 危险`,
1150
1174
  ` 原语/决策密度任一命中即需评审(届时会收到评审任务书,起子代理产出 review.json);豁免`,
@@ -1860,13 +1884,31 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1860
1884
  // P2 文档措辞也付整轮子代理评审费(循环,被迫手工重排执行顺序自赎)。
1861
1885
  console.log(` review.json 保留不隔离——窗口内本变更提交仅触及治理文件(${drift.ownFiles.length} 个,未触 requirements/design),评审对象(交付面)未变,结论对最新面等价;change.patch 自动重冻结(治理工件不入冻结面,治理修复随归档目录本体留档)`)
1862
1886
  } else {
1863
- const ts = new Date().toISOString().replace(/[-:]/g, '').replace(/\..+$/, '')
1887
+ // 交付面锚定(2026-10-24-flow-done-review-artifact-drift-loop,坑实证三连循环):
1888
+ // 复审锚之后的 own 提交只碰治理文件(review.json 自身提交的典型形态)→ 评审对象
1889
+ // (交付面)自锚点起未变,保留。堵两保留路径的复合盲区:reviewAnchoredToHead 要求
1890
+ // 锚==当前 HEAD(隔着 review.json 提交即恒 false);governanceOnly 聚合整个冻结窗
1891
+ // (复审已覆盖的更早修复提交把 ownFiles 拖进交付文件 → 恒 false)→ 隔离重评 →
1892
+ // 重评再提交 review.json 又前移……循环。度量单位从窗口换成锚点:锚后才是复审未见。
1893
+ let _deliveryAnchored = null
1864
1894
  try {
1865
- renameSync(reviewPath, `${reviewPath}.superseded-${ts}`)
1866
- console.warn(` 旧 review.json 已隔离为 review.json.superseded-${ts}(评审结论对着旧冻结面)——重新执行评审任务书后再收口`)
1867
- } catch (e) {
1868
- quarantineOk = false
1869
- console.error(` ⚠️ 旧 review.json 隔离失败(${e.message})——本次保持现状幂等跳过,手动删除/改名 ${reviewPath} 后重跑(将自动重冻结+重评)`)
1895
+ const { extractAnchorSha } = await import('./flow-review.js')
1896
+ const { deliveryFaceAnchoredAfter } = await import('./flow-parity.js')
1897
+ const _raw = JSON.parse(readFileSync(reviewPath, 'utf8'))
1898
+ const _anchor = extractAnchorSha(_raw && _raw.reviewedAgainst)
1899
+ _deliveryAnchored = _anchor ? deliveryFaceAnchoredAfter({ cwd, change, anchorSha: _anchor }) : null
1900
+ } catch { /* 锚读取/判定失败 → 走隔离(fail-safe 宁可多评) */ }
1901
+ if (_deliveryAnchored && _deliveryAnchored.ok) {
1902
+ console.log(` review.json 保留不隔离——复审锚(reviewedAgainst)之后的本变更提交仅触及治理文件(${_deliveryAnchored.ownCount} 个提交,未触 requirements/design),评审对象(交付面)自锚点起未变,结论有效;change.patch 自动重冻结(治理工件不入冻结面)`)
1903
+ } else {
1904
+ const ts = new Date().toISOString().replace(/[-:]/g, '').replace(/\..+$/, '')
1905
+ try {
1906
+ renameSync(reviewPath, `${reviewPath}.superseded-${ts}`)
1907
+ console.warn(` 旧 review.json 已隔离为 review.json.superseded-${ts}(评审结论对着旧冻结面)——重新执行评审任务书后再收口`)
1908
+ } catch (e) {
1909
+ quarantineOk = false
1910
+ console.error(` ⚠️ 旧 review.json 隔离失败(${e.message})——本次保持现状幂等跳过,手动删除/改名 ${reviewPath} 后重跑(将自动重冻结+重评)`)
1911
+ }
1870
1912
  }
1871
1913
  }
1872
1914
  }
@@ -1982,7 +2024,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
1982
2024
  }
1983
2025
  if (freeze.dirtyWarned.length > 0) {
1984
2026
  console.warn(`⚠️ ${freeze.dirtyWarned.length} 个未提交交付文件未入冻结面(共享主仓无法归属)——三选一:`)
1985
- console.warn(` ① 先提交这些文件(信息尾缀带变更名)后重跑 ② 确认全部归属本变更:重跑 flow done --freeze-dirty 并入冻结 ③ 确认接受审计缺口:重跑 flow done --accept-dirty-gap(缺口数随 change-patch.json 留痕)。Git 中间提交可归档后压扁(见收口指引)`)
2027
+ console.warn(` ① 先提交这些文件(信息尾缀带变更名)后重跑 ② 确认全部归属本变更:重跑 flow done --freeze-dirty 并入冻结 ③ 确认接受审计缺口:重跑 flow done --accept-dirty-gap(缺口数随 change-patch.json 留痕)。Git 中间提交归档后用 sillyspec push 压扁并推送(见收口指引)`)
1986
2028
  console.warn(` ${freeze.dirtyWarned.slice(0, 3).join('、')}${freeze.dirtyWarned.length > 3 ? ' 等' : ''}`)
1987
2029
  // 收口阻断门(2026-10-08-thin-done-dirty-gate-and-paren-attribution,坑①修复:
1988
2030
  // multi-agent-platform docs/sillyspec/thin-done-src-commit-order-and-attribution-paren.md)
@@ -2063,7 +2105,10 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
2063
2105
  // 首段命中注册仓的条目额外标注 key,点名+三种正确语法指引。
2064
2106
  const _declared = []
2065
2107
  for (const e of planEntries) {
2066
- const pe = String((e && e.path) || '').split('\\').join('/')
2108
+ // NEW: 前缀剥除(坑 flow-done-new-prefix-crossrepo-ambiguity,scope-audit.js:1263 同款
2109
+ // 口径):新建声明整串 NEW:<path> 当路径查主仓必 miss → 误落「疑似跨仓」;剥后存在性
2110
+ // 核验对真实路径(NEW 文件已提交则在盘),NEW 语义留给 design 门禁的原文校验。
2111
+ const pe = String((e && e.path) || '').split('\\').join('/').replace(/^NEW:/, '')
2067
2112
  if (!pe || pe.startsWith('.sillyspec/')) continue
2068
2113
  if (e.repo) continue
2069
2114
  _declared.push({ path: pe })
@@ -2361,7 +2406,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
2361
2406
  const designPath = join(changeDir, 'design.md')
2362
2407
  if (existsSync(designPath)) {
2363
2408
  const { parseFileChangeListDetailed } = await import('./change-list.js')
2364
- const declared = parseFileChangeListDetailed(designPath).map((e) => e.path)
2409
+ const declared = parseFileChangeListDetailed(designPath).map((e) => String(e.path).replace(/^NEW:/, ''))
2365
2410
  const frozenSet = new Set(ownFiles.map((f) => String(f).replace(/\\/g, '/')))
2366
2411
  const missing = declared.filter((p) => !frozenSet.has(p) && !p.startsWith('.sillyspec/'))
2367
2412
  if (missing.length > 0) {
@@ -2388,17 +2433,18 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
2388
2433
  // ②c review:危险证据定档的独立评审(2026-09-25-thin-review-slice)。定档=证据累积制
2389
2434
  // (承诺词一票/盲维实质作答/diff 原语/决策密度/声明一票),文件数出局;缺省要评审、豁免要
2390
2435
  // 多证并举、豁免者 1/4 定额抽查采样。评审任务书由 CLI 渲染(agent 起干净上下文子代理执行,
2391
- // 协议调用数不变);FAIL 或 P1 发现=拦截(修复后删件重评再重跑 done,断点续)。
2436
+ // 协议调用数不变);FAIL 或开放 P1 发现=拦截(修复后删件重评再重跑 done,断点续——开放判定
2437
+ // 见 flow-review.js isOpenFinding,2026-10-24-flow-done-review-findings-status-blind)。
2392
2438
  if (st.substeps?.review === 'done') {
2393
2439
  skip('review')
2394
2440
  // 回填(修评审 P2②:续跑成功轮遥测不失忆——review 已发生但子步已标,从留档件回读结论)
2395
2441
  try {
2396
- const { validateReviewJson } = await import('./flow-review.js')
2442
+ const { validateReviewJson, isOpenFinding } = await import('./flow-review.js')
2397
2443
  const rp = join(changeDir, 'review.json')
2398
2444
  if (existsSync(rp)) {
2399
2445
  const v = validateReviewJson(rp)
2400
2446
  reviewOutcome = v.ok
2401
- ? { required: true, sampled: false, verdict: v.review.verdict, findingsP1: (v.review.findings || []).filter((f) => /^P1$/i.test(String(f.severity || ''))).length, backfilled: true }
2447
+ ? { required: true, sampled: false, verdict: v.review.verdict, findingsP1: (v.review.findings || []).filter((f) => /^P1$/i.test(String(f.severity || '')) && isOpenFinding(f)).length, backfilled: true }
2402
2448
  : { required: true, sampled: false, verdict: 'invalid', backfilled: true }
2403
2449
  } else {
2404
2450
  reviewOutcome = { required: false, sampled: false, verdict: 'exempt', backfilled: true }
@@ -2441,12 +2487,17 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
2441
2487
  reportMidFail('review')
2442
2488
  process.exit(1)
2443
2489
  }
2444
- const p1s = (v.review.findings || []).filter((f) => /^P1$/i.test(String(f.severity || '')))
2445
- const others = (v.review.findings || []).filter((f) => !/^P1$/i.test(String(f.severity || '')))
2490
+ // 开放 P1 才阻断(2026-10-24-flow-done-review-findings-status-blind,坑实证):增量复审
2491
+ // 保留历史条目并标 status(resolved/resolved-as-accepted/accepted/false-positive)——
2492
+ // 旧口径 findings.some(P1) 盲判 status,把 verdict=PASS 的增量复审恒拦在收口门。
2493
+ const { isOpenFinding } = await import('./flow-review.js')
2494
+ const p1s = (v.review.findings || []).filter((f) => /^P1$/i.test(String(f.severity || '')) && isOpenFinding(f))
2495
+ const others = (v.review.findings || []).filter((f) => !p1s.includes(f))
2446
2496
  if (v.review.verdict === 'FAIL' || p1s.length > 0) {
2447
- console.error(`❌ 独立评审未过(verdict=${v.review.verdict},P1 发现 ${p1s.length} 项)——承诺与实现不一致,不许归档:`)
2448
- for (const f of [...p1s, ...others]) console.error(` [${f.severity}] ${f.title} — ${f.evidence || ''}${f.location ? `(${f.location})` : ''}`)
2497
+ console.error(`❌ 独立评审未过(verdict=${v.review.verdict},开放 P1 发现 ${p1s.length} 项)——承诺与实现不一致,不许归档:`)
2498
+ for (const f of [...p1s, ...others]) console.error(` [${f.severity}${f.status ? `·${f.status}` : ''}] ${f.title} — ${f.evidence || ''}${f.location ? `(${f.location})` : ''}`)
2449
2499
  console.error(` 修复后删除 review.json 并重新执行评审任务书(起子代理重评),再重跑 flow done(断点续)`)
2500
+ console.error(` (增量复审提示:已解决/接受的历史条目保留并标 status=resolved/resolved-as-accepted/accepted/false-positive——阻断只计开放项,勿反复改 verdict)`)
2450
2501
  reviewOutcome = { required: true, sampled: tier.sampled, verdict: 'FAIL', findingsP1: p1s.length }
2451
2502
  appendTelemetry(reviewOutcome) // 修评审 P2①:sampled+FAIL 恰是误豁免率校准的最关键信号
2452
2503
  reportMidFail('review')
@@ -2508,7 +2559,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
2508
2559
  const _freezeFiles = parseFrozenPatchSummary(patchText).map((f) => f.path)
2509
2560
  if (_freezeFiles.length > 0) {
2510
2561
  const { parseFileChangeListDetailed } = await import('./change-list.js')
2511
- const _declared = parseFileChangeListDetailed(join(changeDir, 'design.md')).map((e) => e.path)
2562
+ const _declared = parseFileChangeListDetailed(join(changeDir, 'design.md')).map((e) => String(e.path).replace(/^NEW:/, ''))
2512
2563
  if (_declared.length > 0) {
2513
2564
  const { splitOwnVsForeignDiffFiles } = await import('./foreign-declared.js')
2514
2565
  const _split = splitOwnVsForeignDiffFiles(cwd, change, _freezeFiles, { specBase })
@@ -2817,7 +2868,7 @@ export async function cmdFlowDone({ change, cwd, specBase, runtimeRootOpt = null
2817
2868
  try {
2818
2869
  const finalHead = gitQuiet(cwd, ['rev-parse', 'HEAD'])
2819
2870
  if (st.baseline_commit && typeof finalHead === 'string' && finalHead.trim() && finalHead.trim() !== st.baseline_commit) {
2820
- console.log(`🧹 Git 历史可自由整理:多轮中间提交可压扁为单提交——git reset --soft ${st.baseline_commit.slice(0, 10)} && git commit -m "<最终提交信息>"`)
2871
+ console.log(`🧹 推送前可用 sillyspec push 压扁本地未推送提交(默认 by-change;--mode one 合并为一笔;禁止 reset --soft / git push --force)`)
2821
2872
  console.log(` 审计真相已冻结在归档件 ${jsonOnlyFreeze ? 'change-patch.json(跨仓 repos[] 正文+sha256 锚定)' : 'change.patch(sha256 锚定)'} + verify-result + review.json,不依赖 git 历史;`)
2822
2873
  console.log(` 注:knowledge「最近确认」hash 为溯源快照,压扁后成孤儿引用属预期(非活引用)。`)
2823
2874
  }