handoff 是一个会话结束交接记录工具(Session-End Handoff Writer),用于在会话结束时更新 HANDOFF.yaml,记录已完成的工作、新发现的缺口以及当前项目状态。
用途
HANDOFF.yaml 分为两部分:items 是仍待处理工作的短期上下文层,log 是已完成工作的持久单行历史。GitHub issues 通过 valerie 和 doob 协调,是活动工作的真实来源。非 Valerie 技能应通过 handoff-db 使用本地 SQLite 数据库加 HANDOFF YAML,绝不直接使用 doob。从 HANDOFF 到 backlog 的脚本化桥接是 handoff-reconcile,应使用它而不是手工重建 doob 命令。项目状态(构建、测试、分支)单独存放在 .ctx/HANDOFF.<name>.<base>.state.yaml 中——生成文件,从不提交。渲染的参考文档也会写入 .ctx/HANDOFF.md。
文件发现
使用 handoff-detect 解析 HANDOFF.yaml 路径(如果可用):
handoff-detect # 存在则返回路径,不存在则返回预期路径并以退出码 2 结束 handoff-detect --name # 仅返回预期文件名(例如 HANDOFF.devkit.devkit.yaml) handoff-detect --root # 仓库根目录 handoff-detect --project # 项目名称
如果 handoff-detect 不在 PATH 中,则回退到在仓库根目录用 glob 匹配 HANDOFF.*.yaml。
旧版回退(只读):如果不存在 HANDOFF.yaml 且仓库根目录存在 HANDOFF.md,则将其作为自由格式读取。除非被要求,否则不要转换。
步骤
- 获取当前状态
git branch --show-current git log --oneline -5 cargo check 2>&1 | tail -3 # 或对应语言 cargo test 2>&1 | tail -5
-
读取现有 HANDOFF.yaml 和 .ctx/HANDOFF...state.yaml(如果存在)
-
更新 HANDOFF.yaml
items——应用不可变性规则(见 references/schema.md):
新缺口 → 以新 id 追加
已完成或上游关闭 → 在 log 中记录结果后从 items 中移除该项
阻塞 → 设置 status: blocked,追加一条 type: blocker 的额外条目
不要编辑现有条目的 title、description、priority 或 files
不要在已提交的 items 中保留 done 或 parked 条目
human-edit 确认——对于本次会话中由 handoff 呈现的任何没有 reviewed 字段的 type: human-edit 额外条目,为该条目添加 reviewed: <today>。
log——前置一条新条目(最新在前)。此部分持久,非临时。必填字段和格式:
date:会话结束时的 ISO 8601 日期时间——date +%Y%m%d.%H%M%S——绝不用裸日期
session:从上一 log 条目的 session 值递增(如果没有先前 log 则从 1 开始)
claude_session_id:如果设置了 $CLAUDE_SESSION_ID 环境变量则从中捕获;未设置则省略该字段
summary:一行,过去时
commits:每个提交使用 {sha, branch} 对象形式——绝不用裸哈希字符串:
commits:
- sha: abc1234 branch: main
updated——设置为当前 UTC 日期时间:date +%Y%m%d.%H%M%S。
- 写入 HANDOFF.yaml
输出干净的 YAML。无锚点,无别名。
- 写入 .ctx/HANDOFF...state.yaml
如果 .ctx/ 不存在则创建。用步骤 1 的当前状态完全覆盖。
从会话开始以来的提交中更改的文件填充 touched_files。如果会话边界不清晰,使用自 HANDOFF.yaml 中最后一条 log 条目日期以来更改的文件:
git diff --name-only $(git log --format="%H" --since="<last-log-date>" | tail -1)..HEAD
如果为空则省略该字段。
- 同步到 SQLite
运行 handoff-db(通过插件的 bin/ 在 PATH 中可用):
handoff-db upsert --project <project> --handoff <path-to-HANDOFF.yaml>
如果脚本未找到或退出非零,跳过并在输出中注明。
不要从此技能调用 doob。valerie 拥有 doob 和 GitHub issue 同步。
6b. 将打开的 HANDOFF 条目协调到 backlog
运行脚本化的 Valerie 桥接:
handoff-reconcile sync --project <project> --handoff <path-to-HANDOFF.yaml>
这是必需的。在通过此命令将每个打开或阻塞的 HANDOFF 条目协调到配置的 doob 后端之前,handoff 更新不算完成。除非你在调试协调器本身,否则不要用临时的 doob todo add / doob todo list 命令重建此流程。
如果脚本未找到或退出非零,停止并报告失败,而不是静默继续。
- 生成 .ctx/HANDOFF.md 和 .ctx/HANDOVER.md
运行辅助脚本一步写入两个渲染文档:
generate-ctx-docs
--handoff <path-to-HANDOFF.yaml>
--state <path-to-HANDOFF.state.yaml>
--ctx <repo-root>/.ctx
generate-ctx-docs 通过插件的 bin/ 在 PATH 中可用。它优先使用 nu(如果存在),回退到 POSIX sh。两个文件都被完全覆盖。
HANDOFF.md——组合参考文档:
头部:项目、更新时间戳、来自状态文件的分支/构建/测试
Items 表按 P0 → P2 排序,打开在阻塞之前
Log:仅最后 5 条条目
HANDOVER.md——静态 ASCII 依赖/流程图:
条目流用 [ ] 打开 / [!] 阻塞 / [x] 完成符号
depends_on 存在时内联显示
最近会话(最后 5 条 log 条目)
如果辅助脚本未找到或退出非零,回退到使用 Write 工具内联写入 .ctx/HANDOFF.md(模板来自上一步格式)。在这种情况下跳过 HANDOVER.md 并注明失败。
- 确保 .gitignore 覆盖 .ctx/
验证 .gitignore 包含:
.ctx/* !.ctx/HANDOFF..yaml .ctx/HANDOFF..state.yaml
如果不存在则添加或更新。此模式忽略所有 .ctx/ 内容,除了 HANDOFF 文件。
- 迁移预检
写入前,检查 HANDOFF 文件是否仍在仓库根目录。如果是,先迁移:
migrate-handoff <repo-root> <old-root-path>
然后暂存重命名并继续使用新的 .ctx/ 路径。
- 提交
git add <path-to-HANDOFF.yaml> .gitignore git commit -m "docs: update handoff"
仅暂存 .ctx/ 下的持久 HANDOFF 文件以及托管块所需的任何 .gitignore 更新。绝不暂存 .ctx/HANDOFF.*.state.yaml、.ctx/HANDOFF.md 或 .ctx/HANDOVER.md。
从零创建
从 git 上下文引导:
git log --oneline -10 git status
从最近提交填充 log。将 items 留空,或如果有明显的下一步则保留一个 P1。不要将已关闭的工作回填到 items,但确实保留持久的 log 历史。从实际构建/测试输出写入 .ctx/HANDOFF.<name>.<base>.state.yaml。
将新的 HANDOFF 文件放在 .ctx/HANDOFF.<name>.<base>.yaml,其中 <name> 是最近清单中的包/crate 名称,<base> 是仓库根目录名。使用 handoff-detect --name 获取正确的文件名。
旧版 HANDOFF.md
如果仓库根目录存在 HANDOFF.md 且不存在 HANDOFF.yaml:将其作为自由格式上下文读取,不要自动转换。注意可以创建 HANDOFF.yaml。