v4.9.3 to v4.9.4

2 added, 1 removed. Audit A to A.

---
name: project-handoff
description: >-
Maintain a project's handoff store (.handoff/, plain text): a multi-dimensional
project handoff — status, summary, actions, pitfalls, decisions, commands, scope, exit,
plus a machine-checked read-back — with a next-step pointer, driven by a script-enforced
`handoff` CLI (single write path, coverage statement). Use when handing off between
sessions or tools, or resuming or continuing prior work on a project. Initializing a
new project's store or migrating an old HANDOFF.md model is done only when the user
explicitly asks. NOT for: ephemeral scratch notes, or work outside a project.
- version: 4.9.3
+ version: 4.9.4
metadata:
patches:
+ - "v4.9.4:**frontmatter 切分在本件里并成一处**(停手条件② 的口径改为「每套自包含代码面各 1 处单源」——本脚本随技能分发、装进别仓时不能 import 宿主仓路径,故认自己目录内的单源)。统一前三套走法并存:`_frontmatter` 用非贪婪正则、`_fm_fields` 用字符串定位闭合、回读那段用整串切分取正文尾。**实测分歧两面**:① CRLF 行尾的决策件在 `_frontmatter`(`view --id` 与写后回读走它)里读成**空**、而在 `_fm_fields`(`import` 预校验与 `supersedes` 扫描走它)里读得到字段——同一个文件在两条命令里被解析成不同结果;② 开括号多一横的畸形件被后者**假解析出 id**、前者返回空。现三套并成 `_fm_split`+`_fm_parse`,边界语义与仓内单源一致(按行找闭合、开括号须整行、不认文档结束符、正文水平线不参与切分)。**行为面零漂移**(本仓无 CRLF 决策件,已扫):`check`/`view`/`export` 三份输出改前后**逐字节相同**、`selftest` PASS。新增三条夹具钉住该性质,**鉴别力已变异验证**:把 `_fm_split` 还原成旧正则那套 ⇒ selftest rc 0→1、红在 CRLF 那条。交接块无需更新(未改纪律,`agents-block --check` rc=0)。"
- "v4.9.3:**关账事件进流水 + 关账时机从「提交」改成「判据满足」**。① 此前 `log.jsonl` **只有 check 事件**——本仓实测 474 次 check:ok / 38 次 check:fail、**0 条 close**,条目生命周期根本没进流水,事后查不出「哪一轮闭的、当时 exit 更新没」,`exit` 过期连痕迹都没有(`handoff-freshness-check` 的 docstring 早点了这件事,它是症状不是根因)。现 `cmd_close` 落一条 `close` 事件,带被关 id/outcome/型别 + 当时的槽位快照。落点选在 `cmd_close` 而非 `apply_close`——**第一版插错了函数**(那里拿不到 `outcome`,NameError 且事件从未落地,症状是「close 成功但 log 没变」)。② 关账时机:原纪律写「提交即关账」是**错的**(用户反例证伪:干完不提交⇒条目一直挂 open;一批活拆多次提交⇒触发器与条目对不上;结论要复验才能定终态⇒与 git 无关)。正解是**完成判据被满足时立刻 close**,且**先落账再进版本**(close 写交接事实、commit 写版本事实)。该条已随技能下沉进 `references/agents-handoff-block.md`,本仓块已同批重装(AGENTS 规则 15)。实测:沙箱 close 后 log 出现 close 事件(id/outcome/typ 齐);把 `_log` 那行变异掉后事件数归 0 ⇒ 判据有鉴别力。"
- "v4.9.2:**把交接新鲜度判据从本仓下沉进技能**(`check` 新增 fresh-queue / fresh-stale 两维)。此前它们住在本仓 `scripts/handoff-freshness-check.py`,而**消费方是交接技能的使用方、不是本仓维护者**——每件装了本技能的项目都同样需要「open 条目在 exit 里沉默」「把已闭说成候立」这两条判据,写成库专属脚本等于让兄弟项目自己抄。两条判据本身通用、可随技能走;D3 跨日陈旧**不下沉**(它硬编码了年份,跨年就错,且自述抓不到同日闭的条目);`scripts/handoff-content-check.py` 的现态计数维**留在本仓**(判的是技能库结构,别的项目没有那套结构,抄过去就是一条判不了的判据——比不给更坏,agent 会以为自己被检查过了)。本仓副本已删,命令区同步。**同源化**:新判据扫文本 id 用 `FIND_ID_RE`,与既有死指针判据共用同一模式——原先那个脚本自己抄了一份,两处模式各自演进就会出现「一次判了、另一次没判」的双源缺口。`references/agents-handoff-block.md` 增强档 A 同步改写:原写「**仅本仓**装了判据时带上」,判据随技能走后这句已成假陈述,兄弟仓现在照抄即可。本仓 AGENTS.md 交接块已同批 `agents-block --install` 重装(AGENTS 规则 15)。"
- "v4.9.1:**补 4.9.0 三态分流没覆盖的第二支**——「认不出标记区」时,`--install` 走的是**整节收敛**,会顶掉那个 `## 交接` 节里的非空内容;而旧提示只在 install **执行之后**才打印「N 行非空内容已被收敛」,**为时已晚**。实测触发面比想象的宽:标记行**少个空格/多空格/带属性**三种都认不出(`BEGIN_MARK` 全等匹配),用户手滑一下就会走进这条路径。实测损失:用户写的「- 我自己写的条款」被标准块顶掉(节外不动、不产生第二份节——这点 install 做对了)。现提前说清:`--check` 在这支先实算非空节行数,判「整节收敛会顶掉 N 行」并要求先备份;确认无非空交接节时才说「install 会新建一节,不丢内容」。**刻意没做「放宽容差识别」**(那正是被否决的内容戳方案要连带动的四处硬编码 + C 维度)——低频场景的正确形态是**诚实文案**,不是加复杂度。"
- "v4.9.0:**修 `agents-block --check` 的危险修法**(独立审计+分块收敛审计,实测三态)——旧形对「块内不一致」只有一句「--install 覆盖块内内容(块外不动),别手改条目」,而 `--install` 真的覆盖块内一切:**块里有人手写的本仓条款时,照这句做就静默吃掉它**(与 4.8.6 修掉的 prev/「回退建议」同型:把静默丢弃说成正常操作)。现按**行集合**(不是行位置)分三态:① 仓里有源里没有的行 ⇒ 判「人写内容」并**明说 install 会覆盖**、点名那几行、要求先抄到块外;② 源里有仓里没有的行 ⇒ 判「版本落后/被删」并**明说补装不丢内容**;③ 两侧行集合相同、只是顺序或重复不同 ⇒ 判「手工整理过」且收敛无损。顺带**修差异定位零鉴别力**:旧形用 `zip` 按短序列截断,只在块末追加一行时 **rc≠0 但一行差异都不打**(审计发现),现按 `max(len)` 补哨兵、并从「只打首个差异」改成最多 3 处 + 余量计数。配套三条夹具(人写/落后/install 后回到一致),**每个分支都经变异验证有关联力**——第一版夹具对零差异缺陷**无鉴别力**(分流文案里也含那行人写内容,把断言喂饱了),加「差异 行」标记才转红。**候补 NO_STAMP 作废**:它建立在「内容戳」之上,而戳方案已否决(打戳即标记区残缺、install 拒绝修、C 维度静默失效),前提消失故不落。"
- "v4.8.9:**AGENTS 交接标准块与 4.7/4.8 新纪律对齐**——**① 修一处已反的断言**:块里写「手改绕过门禁」,而 4.7.8 起 `check` 逐文件对内容哈希、手改**会被抓**(沙箱实测:手写一行 ⇒ `check` rc=1),继续挂着是**假陈述**。**② 补 `set --dry-run` 纪律**:文本槽是整槽覆写、无追加语义,而块里只说「刷新 exit」——本仓自己就因此差点丢掉整槽(`--dry-run` 报「另有 467 行将丢失」),使用方会踩同一个坑。**③ 补 `rebase --force` 出路**:判据缺陷留下的历史误报修判据也消不掉,未核对就 rebase 会把真实改动一起结案、不可追回,故写明「核对在前」。**④ 未收进去的**:`--from-file`、`sync-dims`(后者维护技能自身文件、不属使用方项目)——前者是命令形式、属正文职责。"
- "v4.8.8:独立审计(4.8.7 版)1 高 3 中 2 低已修——**[高] 漂移检查把写事件组 4 条夹具吞进 `except`**:`try:` 误缩进到 4 空格、提前关掉 `with tempfile`,其后 4 条期望(未经 CLI 记录的写入/写前旁证/补手续同字节覆写/纯 CLI 写后无新问题)**在正常路径上一次都不打印**,而漂移检查自身仍绿 ⇒ 没有任何门禁能发现(审计独立复现确认:实跑 `ok` 比应少四条)。已归位。**[中] ① 正文 CLI 枚举补 `rebase`/`agents-block`**(4.8.6 patch ⑤ 自报过、没落地,而失败出口节正把 `rebase --force` 当正式修法交给读者);**② 补 `sync-dims` 子命令**——单源化三件套的「生成」此前没有可执行命令,正文与漂移失败文案都指向不存在的脚本路径,且 `CHECK_DIMENSIONS` 上方注释称「check 的汇总行也从它生成」与实现相反,两处一并改准;**③ patch 里的 `\\x7f` 是 YAML 合法转义**,解析后变成真 DEL 字节(审计实测 `has DEL: True`)⇒ 改 `\\x7f`。**④ 两处硬写断言条数改指路式**(4.8.5 立的规则 13,此处漏了)。**⑤ [低] 失败出口节只覆盖部分判据**、M4(源与块同步改成假陈述仍绿,机械判不了)登记为候补,本轮不加判据。"
- "v4.8.7:判据清单单源化试点(治「只改了一半」)——「`check` 有哪些判据」此前被复述在两处(`cmd_check` 的代码段与本文正文的清单),加一条判据要改两处,本会话已因此反复分叉。现新增 `CHECK_DIMENSIONS`(判据 id 加一句描述)为**唯一产生源**,`SKILL.md` 的清单由 `render_check_dims()` **生成**(带 begin/end 标记),并加**漂移检查**:改了判据不重跑生成,`selftest` 立刻红并指出差异行。**三件套(代码维护 + 生成 + 漂移检查)缺第三件就等于只换了个地方漂移**,本条特意点明。判据的描述文案在 `check` 的运行时输出里**不复述**(避免第三处)。"
- "v4.8.6:独立审计 1 高 2 中 2 低已修——**[高] `prev/` 的「回退建议」是死命令且危险**:被骤降/空内容守卫拒绝时**一个字节都没写**、槽仍是原内容,**根本不需要回退**;而 `prev/` 存的是**上一次成功写入**的版本,读者照那条建议复制反而会把当前内容**覆盖成上一个版本且全程无警告**(独立审计自建样例:当前 W、prev 是陈旧 V,照做 ⇒ W 被 V 覆盖)。**同批四处**:两条运行时 die 文案 + 正文「快照先于全部守卫」(与 4.8.4 实现**完全相反**)+ 失败出口节。**② [中] 分发点 hook 补夹具**(此前零夹具,而独立审计变异实测它有真实鉴别力:手改 `scope` → `scope add` → `check` 会报;只删分发点那一处则永静默)。**③ [中] patch 的「上移」改准**:实现是**复制**到分发点,各写命令内的同名调用**仍在**(变异证明已成死代码,保留为冗余兜底,但别称分发点为「唯一入口」)。**④ [低] `echo_back` decisions 分支的 `title` 是死键**(writer 只把 title 写成正文 H1,frontmatter 永不含它)——4.8.4 刚把 `title` 当死项从通用键表删掉,分支里那份漏了,半成品。**⑤ [低] 正文 CLI 枚举补 `rebase`**。selftest 断言条数以实跑 `handoff.py selftest` 的 `ok` 行数为准。"
- "v4.8.5:本条自身按 AGENTS.md 规则 13「会变的数字只允许有一个产生源」改写——原文写「本仓 66 条」这类**会变的自报计数**,现改为**指路式**(条数以 `handoff check` 实算输出为准,勿在注释里写死计数)。规则来自 2026-10-03 的负结果实测:分类器调优三轮、覆盖 93 件 patches,现状型候选里**真漂移 0、误伤 4-5 处**(规范阈值、引用的报错文本、举例数字、方法描述)⇒ 噪声比信号多,**不做闸**、从源头消掉漂移面。"
- "v4.8.4:独立审计第二批 3 条已修——**① 写事件覆盖面小于措辞**:`unconfirmed.jsonl` 原不在对账面(手改它 `check` 仍 rc=0),已补;`scope` 的三个写分支原**不记写事件** ⇒ `scope add` 之后**任何**写命令都把它当成「CLI 之外的改动」(**纯 CLI 即可复现**的误报),已让它们走 `write_index`;并在 `main()` **分发点**统一调一次 `note_pre_tamper()`(幂等)——逐个加是「靠人记得逐个加」的老毛病(与 p000016「新增 flag 忘了同步」同源)。**准确说法是「复制到分发点」而非「上移」**:各写命令内的同名调用**仍在**(独立审计变异证明它们已成死代码:只删分发点那一处,selftest 仍全绿)。保留它们是有意的冗余兜底,但别把分发点说成「唯一入口」。**② `echo_back` 在 decisions 路径把成功报成「异常」**(decisions 是 `decisions/<id>.md`、不在 live/closed 里,原实现查不到就报异常——**把成功报成失败**,与 P3 的本意相反),已补 decisions 专用回读(含 `body`);并按**实测**把通用键表收成 live/closed 行真实有的字段(`summary`/`topic`/`detail`/`src`/`outcome`),上一版塞进去的 `title`/`body`/`ref` 全是**死项**。**③ 副作用边界**:`--from-file` 同文件**重复键**改为报错(静默后者覆盖=静默丢弃);`prev/` 快照从「所有守卫之前」移到「真正写入之前」(`--dry-run` 与被拒的写入都**不该**留痕迹——原实现 `--dry-run` 明明输出「未写入任何内容」却改了 `prev/`);`rebase` 补 `index` 存在性守卫(与 `_log`/`log_write` 一致),非库目录不再凭空造出 `writes.jsonl`。"
- "v4.8.3:M6「`next` 只写一次」补 **mock 覆盖**(monkeypatch `Store.set_next` 数调用次数,断言 `close` 期间**恰好被调用 1 次**且那一次写的**就是最终值**;**零生产代码改动**)。此前它「结构上不可测」——两步写与一步写的**最终值完全相同**,看落盘结果区分不了;那是**测错了层面**(该测调用序列而非终态),不是不可测。变异复测:恢复两步写 ⇒ 2 项红,**M6 由「无鉴别力」转为有鉴别力**。"
- "v4.8.2:独立审计 7 条必须改的第一批——**① [高] 4.7.5 × 4.7.8 互伤致永久红**:`edit --domain` 换分区会「搬空即删」旧分区文件(4.7.5 的刻意修复),而写事件**只记「写」不记「删」** ⇒ `check` 把「latest 里有、落盘没有」一律判成「疑似被 CLI 之外删掉」⇒ 每次换分区后永久红,**且 `rebase` 也解不开**(它同样保留 `latest`)。根治:写事件增 `removed` 字段(记录被 CLI 删掉的文件),对账时区分「经 CLI 正常删掉」与「凭空消失」,`rebase` 的新基线把已删文件剔除。**② 正文自相矛盾**:失败出口节里「`add`/`edit` 报「含 shell 危险字符」」那条是 4.7.7 旧措辞,与实现(4.7.9 起是**提示**不拒收、输出串为「特殊字符」)矛盾,已删。**③ `note_pre_tamper()` 插在 docstring 之前** ⇒ `cmd_add`/`cmd_set`/`cmd_rm` 的 `__doc__` 变 None(独立审计实测抓到),已全部移到 docstring 之后。**④ 三项能力零夹具**(审计变异测试证明改了 selftest 仍全绿)已补夹具:`--dry-run` 必须在骤降守卫**之前**、**确实不落盘**且逐条列出将丢失的行;`rebase` 必须**真丢弃**带旁证的事件;`next` 换分区后落在最终值。selftest 断言条数以实跑输出为准,变异测试 M1/M2/M6/M7 由「无鉴别力」转为变红;M6「`next` 只写一次」用 **mock 数调用次数**覆盖(monkeypatch `Store.set_next`,**不给生产代码加测试钩子**)——光看最终态测不出来,两步写的最终值与一步写相同。"
- "v4.8.1:**修 p000069**——`close` 拆两阶段:`plan_close()` 纯算、零落盘(`require_text`/找 live/引用守卫/**含补位候选**全部前置),`apply_close()` 纯写、无 `die`、无依赖外部状态的分支。旧形把「算补位」放在落盘**之后**,补位策略一旦抛错就是半写:条目已归档、`next` 仍指着已关条目,而 `check` 只能事后发现。两个关键实现点:**① `refill_pick`/`refill_pool` 加 `exclude`**——阶段 1 算候选时源行**还没删**、被关条目**仍是 open**,不排除它就会「把自己补成 next」(漏了这点,方案就从根治变成换一个 bug);**② `next` 只写一次**——旧形先 `set_next(空串)` 再 `set_next(nid)`,中间那个窗口正是「指针既不是原值也不是新值」的事故现场。配套 **B 兜底**:`check` 报 next 失效时**给出可执行修法**(`handoff next --auto`)而非只报错;并顺带**修了 `next --auto` 本身**:池空时旧形直接 `die`、什么也不写,于是「next 指向已关条目且池里确实无可补」时,照着 check 的修法跑完**指针依然坏着**——指引给了一条不保证有效的命令。现池空即**清空指针**(`--auto` 的语义本就是「把 next 设成按策略算出的值」,池空时那个值就是空)。"
- "v4.8.0:修 `p000016` 的 Namespace 半写崩溃——`cmd_close` 改收**带默认值的显式形参**(`id, outcome=None, no_refill=False, **_ignored`)并同步 dispatcher 与 `unconfirmed resolve` 的内部调用点。旧形收 `argparse.Namespace`、调用点手挑字段:将来给 `close` 加 flag 忘了同步,就在访问新字段处 AttributeError,而那已在**半写状态**(条目已进 `closed`、指针仍指已关条目,要靠 `index.next` 门禁才发现)。显式形参**漏传也不崩**(Python 语义保证),不依赖人记得同步;`**_ignored` 吸收 dispatcher 传来的其余 CLI 字段。全仓**手工构造 Namespace 从 1 处降到 0**。顺带**修正 4.7.9 的 pre_tamper 修法**(第一版把快照从「每次 write_index 算」改成「每 Store 实例算一次」,可调用时机仍在**内容写完之后** ⇒ 等于没修,真源 `set exit` 仍自误报)。现由**五个写命令在入口第一行**调 `note_pre_tamper()`。配套补了一条**能区分修没修好**的夹具:在**干净 store** 上连写 3 次 `set status` 后 check 不得报 pre_tamper,并配一条**对照**(真手改后跑一次写必须报出来)——原先那条「补手续」断言期望的就是报出来,修没修好都绿,夹具形同虚设。"
- "v4.7.9:**shell 引号族(p000019/p000041)根治的第二步**——① argv 危险字符由**硬拒降为提示**:本机实测单引号内反引号/`$` **不展开**,而 CLI 收到的值**无法区分**「双引号已被展开」与「单引号本就安全」(前者已无反引号、后者还带字面量)⇒ 硬拒必然**误拒单引号这种安全写法**,而该抓的那批又抓不到。**更正一处先前转述**:CWE-88 原文说的是「不要**只**依赖查找畸形输入」,并明确承认 denylist「对判定某些输入畸形到应当直接拒绝是有用的」——它没否定 denylist;Parameterization 才是唯一 High 效力项(即 `--from-file`)。② **新增写后回读**(`echo_back`):`add`/`edit`/`close` 写完把落盘行读回来显示(长文本前 160 字)——**这是唯一能覆盖「已被 shell 吃掉」那一面的判据**,因为它看的是**结果**而不是猜输入形态:双引号被吃掉时当场可见,单引号安全写法零干扰(已实测四个场景)。③ **修 4.7.8 的判据缺陷**:pre_tamper 原在 `write_index` 里取快照,而 `set`/`add`/`edit` 的写序是「先写内容、后 write_index」,于是**本次自己的写**也被当成外部改动 ⇒ 每次经这些命令写 `status`/`exit` 都误报(真源实测:连我自己用 `set exit` 回填一次都被报)。改为**写命令开始时**取快照。④ 新增 **`rebase --force`**:把写事件基线重建为当前落盘事实。`audit_writes` 扫全部历史事件,判据缺陷留下的误报**修判据也消不掉**(会被永久卡红),而唯一的手动出路是手改 `writes.jsonl`——那正是本工具要防的事。rebase 需显式确认、逐条打印「已结案」清单、留 `__rebase__` 标记,不是静默自愈。"
- "v4.7.8:把 **p000025(补手续)从纪律变成判据**——新增 append-only 写事件 `writes.jsonl`:每次 CLI 写记录各受管文件的**内容哈希 + 纳秒级 mtime**;`check` 两维对账:**哈希对不上** ⇒ 存在**未经 CLI 记录的写入**(绕过 CLI 手改文件);**写之前**发现某文件 mtime 已被外部动过 ⇒ 疑似「先手改、再用 CLI 覆写同样字节」的补手续(内容哈希可能仍与记录一致,只有时间戳留下了)。时间戳用 `st_mtime_ns` 而非 `int(st_mtime)`——秒级取整会把同一秒内的多次写压成同一个值,判据当场全绿(第一版就这么废的)。顺带补上一个**漏记**:`set` 原先不调 `write_index`,写事件在最常用的写命令上**根本没产生**。"
- "v4.7.7:**根治 shell 引号族**(p000016/p000019/p000035/p000039/p000041,本会话已踩四次)——它们到达 CLI 时**通常已被 shell 展开或截断**,所以 CLI 看见的往往不是用户想写的内容。① 写入型参数新增 **`--from-file <路径|->`**:格式为每行 `字段=值`、值可用行尾 `\\` 续行,**由程序解析、完全不经 shell**——反引号与 `$` 原样落盘(实测含反引号的代码片段与 $HOME 引用均原样入库)。② 内联值若含 **反引号或 `$`** 即**拒收并指向 `--from-file`**:denylist 单独不够(CWE-88:denylist 不可作唯一防线,parameterization 才是 High 效力缓解),但它能把「shell 吃掉了内容」当场暴露,而不是让人拿着一段被截断的文本以为写对了。③ **反斜杠刻意不拦**(只做转义、不替换内容,且 Windows 路径/Markdown/代码引用里极常见,拦它是误拒)。④ 危险字符检查**只作用于走 argv 的值**——`--from-file` 来的内容从未经过 shell,拦它同样误拒;空串守卫对两者都生效(数据丢失与来源无关)。⑤ `--from-file` 与 flag 给同一字段即报错,不静默取一个。"
- "v4.7.6:修 `p000029`——`set` 骤降守卫原带 `len(old) > 200` 的**盲区**(2026-10-03 实测:旧内容 123B/315B 时只喂一行 → 截断到 17B 却**静默放行 rc=0**,1957B 起才拦;而小仓/新项目恰最容易被随手写一行,那条下限把它们全漏了)。现改为**旧内容非空即判**(不设字节下限)——「整槽覆写 vs 局部编辑」是语义问题、不该由文件大小决定。配套两件:**`set --dry-run`**(写入前逐条列出**将丢失的行**、不落盘) + **拒写信息直接给出可复制的恢复命令**(`prev/<slot>` → 槽位)。**已知边界(如实写明)**:极小内容上比例阈值不可靠(25 字符删到 10 字符=40% 降幅,分不清「有意删一行」与「只喂表头」),这类残余误用由 `--dry-run` 兜而非继续调阈值——阈值再调就开始误伤正常的小幅删减。"
- "v4.7.5:**Windows 原生实测推翻了两条推演**(用户提示可连 Windows 后实测,走 Win32 `CreateFile`=CPython 同一条路)。① **撤销设备名拒绝**:`CON.jsonl`/`NUL.jsonl`/`con.jsonl`/`COM1.jsonl` 实测**全部创建成功、`File.Exists=True`、目录枚举可见** —— 设备名解析发生在 cmd/PowerShell **路径层**,不在 `CreateFile` 层(用 `New-Item` 验会因两层解析不一致得到自相矛盾的结果,第一轮差点据此下错结论)。同理撤销尾随空格/点(`com1 .txt` 实测正常创建)。② **保留 `:` 拦截**:ADS 实测写失败且不可见。③ **新增判据「同槽分区名大小写折叠后不得重名」**:NTFS 大小写不敏感(实测 `Case.jsonl` 与 `case.jsonl` 枚举只得 **1** 个)⇒ 仅大小写不同的两个 domain 会**合并成一个分区文件**、index 计数与现实脱节;**该判据在 Linux 上也判**,因为「折叠后重名」是平台无关的前提、在 Linux 上无害而在 Windows 上丢数据。④ 该判据**暴露出一处既有残留**:`edit --domain` 换分区后旧分区被搬空却**留着空文件**、仍占着分区名——已改为搬空即删。"
- "v4.7.4:正文不再写死 `selftest` 的断言数(实测 13,且会随夹具增长)——改为「要数就跑 `selftest` 数 `ok` 行」;补齐 v4.7.3 的 changelog 条目。"
- "v4.7.3:把 `guard_partition()` 的鉴别力**落成仓内夹具**(`selftest` 新增该组:危险名必拒 + 合法名必放行 + 被拒后不留子目录 + 跑完 `check` 仍绿)——此前只在 patches 里**自报**过例数,而自报数会随库漂移、且对没有本仓的消费者恒假。同时:`fixed` 语义 docstring **首段**更正(先前只改了尾部更正段,首段仍反着说,而人先读到的是首段);设备名 stem 补 `rstrip(' .')`(`com1 .txt`/`nul .jsonl` 原能绕过,Windows 仍按设备名处理 ⇒ 静默丢弃);控制字符判据收准为 **ASCII C0**(`\\x7f`/ZWSP/RTL 刻意不拦——不改路径指向、幽灵模式不复现,拦了属超纲)"
- "v4.7.2:`guard_partition()` 补 **Windows 语义**——独立审计实测三种值能在 Windows 上**精确复现 p000046 的同一失效模式**(已登记、check 仍 OK、index 计数偏低、无任何报错):`C:evil`(盘符相对→写到 store 之外)、`ok:hidden`(NTFS 备用数据流→`glob` 永扫不到)、`CON`/`NUL`(保留设备名→**落盘静默丢弃**,目录里根本没有)。前一轮 docstring 自称「只禁改变路径指向的东西」,而 `:` 与设备名同样改变指向 ⇒ 那是该原则的**未完成实现**,不是超纲要求(本技能正文承诺支持 Windows)。现一并禁 `:`、保留设备名(CON/PRN/AUX/NUL/COM1-9/LPT1-9,大小写不敏感、看扩展名前的 stem 并去尾随空格点)、ASCII C0 控制字符。**双向鉴别力已落成仓内夹具**(`selftest` 的 `guard_partition` 组:14 类危险名必拒 + 9 个合法名必放行 + 被拒后不留子目录),**不靠 patches 自报数字**——自报数会随库漂移、且对没有本仓的消费者恒假"
- "v4.7.1:`pitfall` 的 `fixed` **语义改正**——它**不是销账**。「缺陷已修、留案以免重犯」:条目仍在 live 区、仍计入各槽计数、仍被 `check`/`view` 看到,只是不进 `closed/`。**别把它读成销账**——它是**基本自述**(唯一读它的判据是 next 指针的「须为 `open`」,那是在守 next 有效性、不在销坑;**别为它加闸**)。**别把「多数坑都 open」读成「多数坑都没修」**——本库实测过一批:多数坑其实有守卫已修、有意设计的不算缺陷、真正的遗留缺陷是少数。写死这个语义是因为发现「坑槽 57 条全 open、0 条进 closed」被误读成「销账机制失效」;实测 `edit --status fixed` 完全可用,真根因是**『已修』本就不可从落盘证据机械判定**,钉不进判据、退化为自述"
- "v4.7.0:修 `p000046` 路径注入——分区键(`--domain`/`--purpose`)会被拼成文件名(`<槽>/<值>.jsonl`),值里带 `/` 会写进**子目录**,而加载面用 `glob('*.jsonl')` **不递归** ⇒ 那条已登记的条目对 `check`/`view`/`index` **完全隐形**(实测:check 报 rc=0 OK、index 计数 0、递归扫到 1 条,无任何东西报错)。新增 `guard_partition()`,在 `add`、`edit` 换分区、`import` 三条路径统一校验。**不用字符白名单**(会把本库实际在用的 CJK domain「交接」「发布」「门禁」和 `_global` 一起拒掉),只禁**改变路径指向**的东西:分隔符/NUL、`.`/`..`、前后空白、超长名"
- "v4.6.2:作废口径**单源化**——新增 `_voided_ids()`(=`void` ∪ `trash/*.jsonl` ∪ `trash/*.md`),由 `check` 的 D1 与 `view --id` 的已作废分支共用。原先两处各算一遍(`check` 算 void∪trash、`view` 只算 void),只差一步就会分叉成「check 说已作废、view 说查不到」——判据间的口径必须同源,否则同一事实两处结论相反。实测 trash-only(文件只在 `trash/`、不在 `void`)场景下两者同声。"
- "v4.6.1:`view --id` 补 `trash/`·`void` 分支——对已 `rm` 过的 id 报「未找到」rc=2,与本技能「已作废 ≠ 不存在」的口径**相反**(会把人引去查一个根本不该再查的东西);改为一律先查作废,命中即打印「已作废」rc=0,真查不到才退 2。与 `view` 补 decisions 同批落地(4.6.0)。"
- "v4.6.0:`view --id` 修**假报**——decisions 槽是 `decisions/<id>.md`(frontmatter + 正文),不在 `load_live()`/`load_closed()` 覆盖的 jsonl 槽里,故原实现对**每一条**决策记录(本仓全部;条数以 `handoff check` 实算输出为准,勿在注释里写死计数)都报「未找到(live 与 closed 均无)」并退 2。查不到的假报比查不到更坏:接收方据此以为决策丢了。现补查 decisions 目录并打印文件路径;「未找到」文案扩为「live / closed / decisions 均无」"
- "v4.5.0:`check` 增两条**通用**对账(随技能走,所有使用方自动获得)——① 文本槽 `status`/`summary`/`exit` 点名的条目 id 必须既不在册、也不在 `trash/`·`void` 之外(死指针;`check rc=0` 而散文漂移是最常见的『看起来交接没问题』)。两个刻意的收窄:**已作废 ≠ 不存在**(`rm` 后文本写「t000105 作废」是正当历史陈述,判它死指针等于逼人把正确记录改掉);**不判 `c` 前缀**(`c` 型是 commands 槽;排除它的收益是零成本——模式长度恒为 7 字符,6 字符短 hash 够不着,`c697750` 也不匹配(因 `c` 不在字符类,与词边界无关))。② `next` 补「status 必须是 open」(原只校验存在)。**收窄 `d`(10-03 用户裁定,走停手线后落地)**:字符类里只有 `d` 是 hex 字符,故 7 字符全 hex 的 commit 短 hash 会被当 `d` 型条目——可复现的假阳性。已把 `d` 从判据字符类里去掉,并先修好 `view --id` 对 decisions 槽的假报(原先**每一条**决策记录都报「未找到」,而它们在册)——收窄的前提是「另有语义正确的检查面」,那个面当时是坏的。 另:AGENTS.md 标准块那句「check 只管结构」同步改为「结构 + 通用对账」"
- "v4.4.0:`agents-block --install` 改为**整理覆盖**——已有 `## 交接` 整节但无标记区时收敛该节,而不是拒绝(旧行为要求人先手工并入标记区);交接标题的识别只认标题行本身(不吃提到交接的普通段落,含三级标题 `### 交接`);标记区只有 begin 或只有 end(残缺)时**拒绝并报错**,不再静默整节替换;仓里有多份标记区/多个交接节时**合并到一份**(`--check` 独立报「多份并存」,不靠 install 才有痕迹)"
- "v4.3.2:标准交接块随 `init` 自动装(`agents-block --install`,只动块内、块外内容不变)——写入是使用方自己项目里的动作,库维护者不跨仓代劳;块内去掉写死的 `python3` 与仓内相对路径(技能装在 `~/.agents/skills/`,各仓路径不同、解释器随平台而异),命令准确形式留在正文并补 Windows 用 `python`"
- "v4.3.1:退出码纪律 + 门禁退出码只取脚本本身(不判管道末端)"
slug: project-handoff
displayName: project-handoff
---
# 项目交接(Project Handoff · v3 存储模型)
## 角色
维护项目**交接存储** `.handoff/`(纯文本):**9 槽** = 一份「让接收方(agent / LLM)接得上」的**多维交接件**(**不止未决项**)。条目**只经**本技能自带的 CLI 写(准确命令形式见下 `## CLI`;**单一写入口** + **机检门禁**)。
- **维度闭合**:9 槽固定;槽外信息 → `unconfirmed`(未能确认),**不即兴开槽**。
- **只记不可推导项**:可读的用指针,读不到的才记——交接的意义=**免重读仓库**。
- **P1 不删 / P2 可重建 / P3 不静默**(见下)。
## 何时读哪个相位
| 意图 | 读 |
|---|---|
| 接手 / 继续之前的工作 / 新 session 进入已有项目 | `references/resume.md` |
| 交接 / 收尾 / 跨会话·跨工具移交 | `references/handoff.md` |
| 新项目 / 尚无工作存储 | `references/init.md`(**仅显式调用**,非自主命中)|
| **发现旧模型**(`HANDOFF.md` / `HANDOFF-ARCHIVE/` / `.handoff/fp.*`)| **先** `references/migrate.md`(一次性,**仅显式调用**)|
## 存储(`.handoff/`,纯文本,不假设 git)
```
index status summary scope exit next # 导航 + 单文件槽 + 指针
actions/<域>.jsonl pitfalls/<域>.jsonl commands/<用途>.jsonl
decisions/<id>.md unconfirmed.jsonl closed/
prev/<slot> # 单文件槽覆写前快照(自动,回读用)
```
- **9 槽**:S1 `status` · S2 `summary` · S3 `actions` · S4 `pitfalls` · S5 `confirm`(运行时,不入盘)· S6 `decisions` · S7 `commands` · S8 `scope` · S9 `exit`;基础设施:`index`(脚本生成)/ `next`(「下一步唯一一条」指针)/ `unconfirmed`(未能确认账本)/ `closed`(归档)/ `log`(使用快照)。
- **条目(JSONL,键序固定)**:`{"id","created","summary",[status,blockedBy],topic?,src?,detail?}`;归档行加 `closed`/`outcome`。
- `id` = `<type><6 位>`;`type↔槽`:`t`→actions / `p`→pitfalls / `c`→commands / `d`→decisions / `u`→unconfirmed / `q`→closed(历史)。**全槽唯一、永不复用、脚本分配**。
- **决策**:`decisions/<id>.md` = frontmatter(`id/created/status/domain/topic`) + ADR 正文。
- **`index` 由脚本重建**;**永不手写**。
## CLI(技能自带脚本,路径见下方 `$H`;解释器 `python3`,Windows 用 `python`)
> 节标题**不写死仓内相对路径**——各仓装本技能的位置不同,写 `scripts/handoff.py`
> 会让别的项目的 agent 去执行一个它那儿不存在的文件。
`init` · `index` · `check` · `selftest` · `log` · `add` · `set` · `edit` · `rm` · `close` · `next` · `unconfirmed` · `scope` · `confirm` · `filter` · `view` · `export` · `import` · `rebase` · `agents-block` · `sync-dims`。
- **`check` = 相位门禁**:不过即非零退出,相位不得前进。它逐条检下列维度(**这份清单由 `CHECK_DIMENSIONS` 生成**:加判据只改代码那一处,再重跑生成;忘了会被漂移检查点名):
<!-- check-dims:begin (由 CHECK_DIMENSIONS 生成,勿手改)-->
1. **slots** — 9 槽与 `index` 齐备(status/summary/scope/exit + 四目录 + unconfirmed)
2. **id** — id 形如 `<t|p|d|c|u><6 位>`、全局唯一、**前缀与所在槽相符**
3. **status** — live 行的 `status` 在该型闭集内(**`closed` 不是可写值**,关闭只经 `close`)
4. **fields** — 未知字段 + 必填字段缺失(按型)
5. **dates** — `created`/`closed` 是合法日期、非未来日、且 `created ≤ closed ≤ today`
6. **single-line** — `summary` 单行(不得含换行/制表)
7. **next** — `next` 是合法 id、存在、且 **`status=open`**(附可执行修法)
8. **text-dangling** — 文本槽(`status`/`summary`/`exit`)点名的 `t/p/q/u` 型 id 必须既不在册、也不在 `trash/`·`void` 之外(**已作废 ≠ 不存在**;`c`/`d` 两型不参与,各有专门检查面)
9. **decision** — `decisions/*.md` 的 frontmatter id 必须等于文件名、日期合法、status 在闭集
10. **scope** — `scope` 登记指向**活路径**或 glob 有命中(机械判据:登记必须可解析)
11. **void-reuse** — id 未复用已作废 id(`void` 拦截)
12. **writes** — 写事件对账:内容哈希对不上=**未经 CLI 记录的写入**;写前发现被外部动过=**疑似补手续**(含判据缺陷留下的历史误报,用 `rebase --force` 结案)
13. **partition-case** — 同槽分区文件名**大小写折叠后不得重名**(NTFS 大小写不敏感 ⇒ 仅大小写不同的两个 domain 会合并成一个分区文件、计数与现实脱节)
14. **index** — `index` 的各槽计数与 `next` 与落盘一致(漂移即红)
15. **fresh-queue** — **交接新鲜度**:仍 open 的 `t` 型条目必须在 `exit`/`summary` 里出现(抓「新条目进队列、exit 沉默」;只取 `t` 型——p/d 进 exit 属背景叙述,逐条要求提及会淹没真信号)
16. **fresh-stale** — **陈旧陈述**:`exit` 里提及**已闭**条目的那一行必须带显式关闭标记(抓「把已闭说成候立」;**只查 exit 不查 summary**——summary 是流水式摘要、逐条列已闭 id 是它的本职,强求标记产出纯噪声)
<!-- check-dims:end -->
- **`add` / `close` = 唯一写入口**;**日期脚本盖、id 脚本分配**(LLM 无从编造)。**`add` 参数面按型分列**:`handoff add action|pitfall|command|decision --…`,每型只收本型字段——旧 `add --slot <槽>` 的**并集面**会静默丢弃本型不消费的键,现**硬报错并印新形**;`edit` 亦按条目型校验可改面(不符即报错)。
- **`next` 自动补位**:`close`(含 `unconfirmed resolve` 的内部关闭)关掉的**正是当前 `next`** → 按策略补下一条(`--no-refill` 关;`handoff next --auto` 人工触发同一策略;`next <id>` 随时覆盖补位结果)。策略**确定性可复算**:候选池=live `actions`(**仅 `status=open`/缺省入池**;blocked=等待中、其余值非法均排除);排序键 `(有效档, created↑, id↑)`;基础档 `[高]`0 / `[中]`·无前缀1 / `[低]`2(前缀亦认全角 `【高】` 与 `high`/`med`/`mid`/`medium`/`low`,大小写不敏感);**时间维**=超期每满 30 天升一档(下限 0,故陈年 `[低]` 会越过新鲜 `[高]`)。补位/留空均**打印依据**(P3 不静默)。空池或全 blocked:`close` 留空并说明,`next --auto` **拒绝且不写指针**。
- **S7 `commands` 先薄**:只收 `AGENTS` / `README` 里**没有**的非显然命令 + 环境例外(可推导的**不重复记**)→ **常空正常**。每次门禁落一条 `log` 快照(`handoff log --stats`),供日后据数据决定是否保留该槽。
- **`set <status|summary|exit>`**:单文件槽写入口(`add` 只覆盖条目槽 + `decisions`);**单文件槽唯一写路径**。**整槽覆写、无追加语义**——想改一行也必须先读回全量再写。两道守卫:空内容拒写(`--allow-empty` 才放行);旧内容非空且新内容 <60% 亦拒写(`--force` 才放行,防「把整槽覆写当局部编辑」;**已去掉原「>200 字符」下限**——那是条盲区:123B/315B 的小槽只喂一行会**静默截断**(p000029),而小仓/新项目恰最容易被随手写一行。**写入前不确定就先 `--dry-run`**:逐条列出**将丢失的行**、不落盘。拒写时错误信息直接给出**可复制的恢复命令**(`prev/<slot>` → 槽位),**易 undo 优于反复确认**——确认会被自动化掉清空槽位)。**快照在真正写入之前**(4.8.4 改):守卫拒绝与 `--dry-run` **都不在盘上留痕迹**——被拒时一个字节都没写、槽还是原内容,**不需要回退**。因此**被拒时的报错里不给「回退到 prev/」的建议**:`prev/` 存的是**上一次成功写入**的版本,照做会把当前内容覆盖成上一个版本且全程无警告(独立审计实测)。要回退请用 `git` 或自己的备份。
- **纠错入口**:`edit <id>`(改安全字段;`id`/`created`/`closed` 不可改)与 `rm <id>`(→ `.handoff/trash/` + 记 `void` 防 id 复用 + 引用守卫)——错误**不经手搓**,免 `export→改→import --force` 全量重写。
- **`status` 值域按型闭集**(`add`/`edit` 同一守卫,t000138):`action`/`command`=`open`|`blocked`;`pitfall`=`open`|`fixed`|`blocked`——**`fixed` 不是销账**,它表示「缺陷已修、**留案以免重犯**」:条目**仍在 live 区**、仍计入各槽计数、仍被 `check`/`view` 看到,只是不进 `closed/`。要销账走 `close --outcome`(另一回事)。它**基本是自述**:别把「多数坑都 `open`」读成「多数坑都没修」;缺省(不传或清空)=`open`。**`closed` 不是可写值**——关闭唯一入口=`close --outcome`(edit/add 传 `closed` 或表外值即硬报错);`check` 对 live 行同闭集硬判;补位池**仅 open 入池**(blocked=等待中、其余=非法值,均不补位)。`selftest` = 闭集守卫 + `guard_partition` 分区名的双向夹具自证(临时 store 内真 CLI;**断言数会随夹具增长,别在正文写死**——要数就跑 `selftest` 数 `ok` 行)。
- **`confirm` = 机检 read-back**:脚本出题、脚本判卷;**不 `PASS` = 交接未完成**。题面**每次调用重新随机抽**——「先出题、后作答」必须两次带**同一 `--seed`**,否则题已换而答案错位。抽查题按**视图行文本**判分(`[id] topic summary`,非 open 条目含末尾 ` (status)`),照 `handoff view` 行原样抄(去掉行首 `- `)即可,勿只抄 summary。
- **`view`**:全局视图(认识整体),默认 stdout;`--save` 存盘(默认 `<项目根>/handoff-view.md`),属**用户所有、非权威、不 `check`**。
- 无 `python3` → **询问用户安装**,或降级最小 `sh`。
## 不变量(任一相位必须守)
- **P1 不删**:只追加;`closed` 永不重写;移除仅在关闭序内原子完成。
- **P2 可重建**:`index` 由脚本重建;**不生成、不落盘第二副本**。
- **P3 不静默**:每次运行输出「**读到 / 写入 / 未能确认**」;`confirm` 为**双向确认**。
## 硬约束
- 不假设 git / 语言 / 工具 / 布局;需用则**先探测**。
- **未决语义唯一落点 `.handoff/`**(白名单硬契约);其余处的待办文本是**候选**,非未决项。
- **维度闭合**:9 槽固定,槽外→`unconfirmed`。
- **源登记表闭合**:**只有 `scope` 登记的源被读**;未登记 → **非未决源**(定义使然)。`check` 校每条登记**可解析**(路径存在 / glob 有命中);`scope scan` **仅提议**(机械候选),漏扫不影响契约成立。
- **迁移先于交接**:有旧模型时,**告知用户并请求迁移**(`init`/`migrate` **仅显式调用**);同意后先走 `migrate`(它建存储),勿先建空存储、勿在旧模型上直接交接。
- **项目约定(标准块,自动装)**:`init` 会把**标准交接块**写进**本项目**的 `AGENTS.md`(`agents-block --install .`,节外内容不动)。三种情形都收敛到**单源**:已有标记区 → 覆写块内;已有 `## 交接`(或 `### 交接`)整节但无标记区 → **整理覆盖该节**(手写副本被标准块接管,节外不动;被替换的非空行数会打印);都没有 → 新建该节。**标记区残缺**(只有 begin 或只有 end,说明被人手改坏过)→ **拒绝并报错**,不自动修:静默整节替换会把那段人写内容一起吃掉。识别交接标题只认标题行本身,不会误吃「## 交接约定」这类普通章节。唯一源=`references/agents-handoff-block.md`。**不手工编辑块内条目**——`--check` 字节比对,漂移 rc=1。
- **写入是使用方自己的动作**:库维护者不跨仓代劳——每个项目在**自己**启用交接时安装一次。
- **防漂移**:`agents-block --check .` 字节比对,块内被手改即 rc=1。要升级条目,改标准源再让各仓重装,**不要在仓里改条目**(复制即漂移,与 catalog 条款同病)。
- **档位**:`--profile basic`(通用纪律)/`--profile freshness`(该仓装了交接新鲜度判据时才用,抄了却没装判据=给 agent 一条无法执行的指令)。`--check` 会按块内标记**自动识别**档位。
- 崩溃最坏产生重复(按 id 去重,closed 胜)——**绝不丢**。
## 反模式
- 手改 `.handoff/` 条目(绕过 CLI → 门禁兜不住)。
- 手填日期 / 自选 id。
- 把未决写在 `.handoff/` 之外(→ 走 `unconfirmed`,**不假装覆盖**)。
- 手写第二份「视图 / 摘要」当交接件(→ 双源必漂)。
## 最短真实样例(初始化 → 交接一轮)
前置:项目根目录、解释器可用、已获用户显式授权建存储。
**解释器**:下文示例统一写 `python3`;**Windows 上改用 `python`**(那里通常没有 `python3`)。
`$H` 指本技能目录(通常在 `~/.agents/skills/project-handoff`,各仓相对路径不同,故用绝对/家目录路径而非仓内相对路径)。
```bash
python3 "$H/scripts/handoff.py" init # 建 .handoff/ + 自动装标准交接块;自动生成 index
python3 "$H/scripts/handoff.py" add action --summary "[高] 修复登录重定向循环" --domain auth --src src/auth/login.ts:42
python3 "$H/scripts/handoff.py" add pitfall --summary "测试库必须先 seed,否则 check 全红" --domain test
printf '已完成认证重构;测试待补\n' | python3 "$H/scripts/handoff.py" set status --file -
python3 "$H/scripts/handoff.py" check # 门禁:非零退出 = 不得收尾
python3 "$H/scripts/handoff.py" confirm --seed 1 # 出题(两次须同 --seed)
python3 "$H/scripts/handoff.py" confirm --seed 1 --answers "<按 view 行原样逐行>" # 判卷,PASS 才算交接完成
python3 "$H/scripts/handoff.py" agents-block --check . # 交接块 vs 唯一源,漂移 rc=1
```
产出(摘录):`handoff check` 打印 `handoff check: OK` 并落一条 `log` 快照;`confirm` 判卷通过打 `PASS`——**不 PASS = 交接未完成**。接收方恢复上下文只读 `references/resume.md` 相位(`view` + `next` 即可接上)。
## 失败出口
- **`check` 非零退出**:按输出行修——列出的即不满足的判据(槽缺失 / index 计数不一致 / id 冲突 / 日期违规 / `status` 值域 / `next` 无效或非 `open` / **文本槽死指针**)。修法只经 CLI:缺条目 `add`,多出条目 `rm`/`close`,**不手改文件**。**文本槽死指针**(`status`/`summary`/`exit` 提到一个既不在册也不在 `trash/`·`void` 的 `t`/`p`/`q`/`u` 型 id)修法:在文本里改掉那个引用,或把条目补回库里——**别去判据里加豁免**。若是**已作废**的 id 却被报,先确认它是否真在 `trash/`/`void`(在那儿就不会被报);`c` 型不参与本判据是刻意的收窄(不是漏了)。**`c`/`d` 两型不在本判据范围内**(`d` 与 7 字符全 hex 的 commit 短 hash 形态不可区分),故这两型**不会**被这里报出——由 `view --id` 负责:查得到给记录,**已作废的明说「已作废」**(`rm` 落 `trash/` + 记 `void`,与本闸「已作废 ≠ 不存在」同口径),真正查不到才 rc=2。「改号」另由本闸的 `decisions/<file>` frontmatter-id-等于-文件名判据兜。修完重跑,`OK` 为唯一通过判据。
- **`add`/`edit`/`close` 打印了「⚠ 含 shell 特殊字符」**:不是错误,是**提醒你核对**——若你用了双引号,这段内容可能已被 shell 展开(反引号=命令替换、`$`=变量)。**单引号安全**,该警告对单引号写法是噪声,可忽略;要彻底避开请用 `--from-file`。**真正要看的是紧随其后的「↩ 回读」块**——那是被吃掉的版本唯一会露出来的地方。
- **`check` 报「未经 CLI 记录的写入」/「疑似补手续」**:`writes.jsonl` 记的是**每次 CLI 写**后各文件的内容哈希与纳秒 mtime。手改文件不会留下记录 ⇒ 哈希对不上即报。修法:**别去改判据**,把那处手改经 CLI 重做一遍(`add`/`edit`/`set`),或直接从 `prev/` 快照恢复。若报的是「写之前发现被改动过」而内容哈希仍一致,那是**同字节覆写**的补手续痕迹——CLI 看不见那次手改的内容,但文件被动过的事实留下了。
- **`check` 报出一堆「写入**之前**发现被改动过」而你确信没手改**:多半是**判据版本自身的缺陷**留下的历史误报(修判据不会消掉已写入的事件)。先**手工核对**那几个文件确实没被手改,再 `handoff rebase --force` 把基线对齐现实——它会逐条打印「已结案」清单,**被结案的结论不可追回**,所以核对在前。
- **`set` 被拒(空内容 / 骤降守卫)**:输出已说明是哪道守卫。确属要写空 → `--allow-empty`;确属大幅精简 → `--force`。**被拒时一个字节都没写、槽仍是原内容,不需要回退**——所以拒写信息里**不会**给「回退到 `prev/`」的建议(`prev/` 是**上一次成功写入**的版本,照做反而会把当前内容覆盖掉)。要回退用 `git` 或你自己的备份。**不确定就先 `set <slot> --file - --dry-run`**:逐条列出将丢失的行、不落盘(骤降判据在**极小内容**上不可靠——25 字符的槽删到 10 字符分不清「有意删一行」与「只喂了表头」,这类残余误用由 `--dry-run` 兜,而不是继续调阈值、再调就开始误伤正常删减)。
- **`add` 报「参数面不符」**:你用的是旧 `add --slot <槽>` 并集形——按报错打印的新形重输(`add action|pitfall|command|decision --…`,每型只收本型字段)。
- **`add`/`edit` 报「status 非法」**:值域按型闭集(`action`/`command`=`open|blocked`,`pitfall`=`open|fixed|blocked`);要关闭条目走 `close --outcome`,不要把 `closed` 当状态写。注意 `pitfall` 的 `fixed` **只是自述、不是销账**——它**基本是自述**:别把「多数坑都 `open`」读成「多数坑都没修」。只有 next 指针判据读它(要求 next 指向 `open` 行)——那是守 next 有效性、不是销坑,**别为它加闸**;值域校验只验「在不在闭集里」,各槽计数与补位池都不看它。
- **`confirm` 不 PASS**:答案按 `handoff view` 行文本原样抄(去行首 `- `,含末尾 `(status)`);若两次调用 `--seed` 不同,题已换,重取题再答。
- **无 `python3`**:询问用户安装;不可安装时降级最小 `sh` 约定(写入口纪律照守,门禁缺失须在交接里明示)。
## FAQ
**常见问题从哪查?** 本文「硬约束 / 反模式 / 失败出口」三节覆盖高频误用;相位细节读 `references/` 对应文件(见「何时读哪个相位」表)。
**为什么不能直接编辑 `.handoff/` 里的文件?** 单一写入口是门禁的前提:日期、id、index 全由脚本管理,手改会绕过校验且 `check` 兜不住(见反模式)。
**commands 槽老是空的,坏了吗?** 不是——S7 只收 AGENTS/README 里没有的非显然命令,常空正常(见「S7 `commands` 先薄」)。
**MFA/多工具协作也一样吗?** 是——不假设 git/语言/工具;跨工具交接同一存储,`confirm` 的双向 read-back 就是给跨工具场景设计的。