Session Wrap(项目感知的收尾工具)
两部分:§1 是通用的;§2 按项目路由(状态表是按项目划分的——永远不要为一个项目写入另一个项目的表)。§0 = 会话中心跳(见下文)。<MEM> = 通过 bash ~/.claude/hooks/session-resume.sh --dir 打印的记忆目录。
在任何保存之前进行编辑(心跳、交接记忆、规则/当前文件、数据库行、恢复提示):去除 API 密钥、令牌、密码、私钥、账号和余额、个人数据、任何可识别真实账户的信息。写为 <REDACTED — see vault> 或改写(例如“已验证实时余额,未记录在此处”)。指明真实值所在的位置是可以的;但值本身不行。与 §2 中的护栏改写规则模式相同——两者都要遵守。
0. 心跳检查点——会话中(仅限交接记忆)
在完成的每个子任务之后,以及在任何长时间/高风险步骤(大型重构、部署、数据库迁移)之前,仅刷新项目当前的 §1 交接文件(如果不存在则创建它):一个 ## 实时状态(心跳) 块——刚刚完成了什么,正在进行中什么,下一步是什么。替换前一个块,不要追加;目标 <15 行。保持标题精确:钩子会匹配它。
- 不要重新标记
MEMORY.md,不要触碰任何数据库,也不要发出恢复提示——这些留到会话结束时执行(§1 标记、§2、§3)。在收尾时,将该块折叠到已验证状态中并删除它。 - 现在任何时刻的崩溃最多只会丢失一步的记忆。
session-resume.sh会在下次启动时,将仍持有该块的每个交接文件列为UNWRAPPED HEARTBEAT,因此一个未标记的前心跳文件仍然可以被找到。
1. 交接记忆——始终执行(任何项目)
写入/更新 <MEM>/project_<项目>_handoff_<YYYYMMDD>.md:已验证的状态(关键 SHA、已落地内容、 未决定的决策、有序的下一步工作、强制要求)。在 <MEM>/MEMORY.md 中,将其索引行标记为 ★首先阅读 <项目>(当前)★;将前一个标记为“(已取代)”。旧的交接文件保留在磁盘上:被取代的链条就是项目的历史。
- SessionStart 钩子
~/.claude/hooks/session-resume.sh是项目无关的:它注入每一行匹配首先阅读 <任何项目>且未被标记为已取代的行 → 标记该行即为任何项目的恢复保证(无需每个项目的钩子)。即使 MEMORY.md 被自动加载,也要保留该钩子:它会添加“在开始工作之前阅读交接”的指令,可以从任何目录工作,并且在 MEMORY.md 超过 200 行的加载限制时仍能工作。它还会在项目有两个实时指针或指针指向缺失文件时发出警告——在收尾时修复这些。
规则需要向前传递。 任何新的所有者规则、常驻指导、有效命令或学到教训,在给出的那一刻就要写入该项目始终加载的规则文件中,而不仅仅是写入交接文件。规则文件:<MEM>/<项目>_rules.md,由 session-resume.sh 为 <MEM>/session-wrap.conf 中列出的每个项目强制完整加载。保持简短;替换而非追加。在收尾时检查:任何所有者在本会话中不得不重复的事情,现在必须写入该文件。同时重写 <MEM>/<项目>_now.md(当前状态和后续步骤,保持简短;钩子上下文大约在 10,000 个字符处被截断)。
2. 项目状态存储同步——首先路由
从当前工作目录/本会话的工作中检测项目。然后遵循该项目的路由。为你拥有的每个项目添加一个 ### 路由;下面的三个是模式。
<具有 Postgres 存储的项目> → 其自己的数据库
使用环境中的凭据(psql "$PROJECT_DB_URL")连接,绝不要在命令中使用字面量。examples/state-store.sql 中的示例表:
INSERT INTO session_handoff (session_id, handoff_prompt, created_at) VALUES ('<sid>','<resume>',now());
INSERT INTO decisions (title,rationale,applies_to,status,session,"timestamp",decided_at)
VALUES ('<d>','<why>','<project>','implemented','<sid>',now()::text,now()); -- 每行一个 INSERT
INSERT INTO code_map (module,path,lines,purpose,bounded_context) VALUES (...); -- 如果没有唯一约束,则先删除该路径
陷阱(在这里写下你自己的存储的陷阱;这些是那种每个会话都要付出代价的陷阱):给关键字命名的列加引号(decisions."timestamp";对于像 "order" 这样的保留字是必需的,否则无害),并注意它们的类型(这里是 text + NOT NULL,因此使用 now()::text);一个具有 CHECK 约束的列只接受其列出的值(pending|implemented|superseded|deferred|rejected);不要假设表存在——先用 \d 检查;多行 VALUES 是原子的,因此一行错误就会丢弃所有行 → 使用 -v ON_ERROR_STOP=off 逐行操作。护栏误报:一个阻止危险命令的 PreToolUse 钩子会 grep 整个 Bash 命令——如果你的 SQL 文本包含一个被阻止的模式的字面量(reset --hard、git push、git clean),那么整个命令会被阻止。在交接/决策文本中改写这样的字符串(例如,写“破坏性 git”而非字面量)。
<运行中的项目> → 其自己的存储(而非其他项目的)
在写入前验证模式;关于它的笔记容易过时。实时基础设施——外部记录系统(支付提供商、生产数据库、正在运行的服务)是真相,而非交接文件;永远不要作为收尾的一部分切换任何实时标志或设置。
<所有其他情况> → 仅限内存
没有数据库状态存储——§1 交接记忆就是记录。注意这一点;不要写入任何其他项目的表。
3. 发出恢复提示(最后的产物——用户粘贴到下一个 Claude 中的内容)
在保存后,输出一个围栏起来的可复制粘贴的提示,用于下一个会话。SessionStart 钩子已经注入了交接指针,因此此提示仅添加:重新加载检查 + 用户必须做出的(一个或多个)未决决策。模板:
恢复 <项目>。首先阅读记忆 <交接文件>,然后技能 /<项目映射技能>。
已验证状态:<1 行:关键 SHA + 当前运行的内容 + 受门控/阻塞的内容>。
首先:1) /上下文 — <任何重新加载检查,例如测量新加载插件的成本>;2) <确认已加载检查>。
然后我对 <未决分叉> 的决策:[选择一种] (a) <建议> (b) … (c) …
<你的常驻每次会话指令,例如在声称完成前进行验证>
保持简短——指针 + 决策,而不是状态转储(状态保存在交接记忆 + 数据库中;不要重复 → 造成上下文污染)。同时将此提示粘贴到交接记忆中的 ## 恢复提示 标题下,以便可查询,而不是在聊天中丢失。
完成(验证,引用结果)
- 协调门——首先运行,必须执行。 扫描整个对话(不仅仅是记忆),列出本会话中的每个决策、未决事项、所有者纠正和强制要求。对于每一项,引用它及其落地位置(交接记忆行、规则文件行或数据库行),或标记为
已丢弃:<一行理由>。“我认为已经涵盖了”是不允许的——指向具体行或标记为已丢弃。任何未说明的内容 → 立即写入(如果某个未决决策发生变化则刷新 §3),然后重新列出。 - §1:交接记忆的 MEMORY.md 行带有
(current);之前的标记为已取代;grep -c '实时状态(心跳)' <交接>= 0。 - §2:对于数据库项目,
SELECT max(created_at) FROM session_handoff;≈ 现在 + 你写入的行数。对于仅内存项目,说明这一点。 - §3:恢复提示已发出并保存到交接记忆中。