---
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
metadata:
  patches:
    - "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 就是给跨工具场景设计的。
