内容
<!-- mcp-name: io.github.jarmstrong158/context-keeper -->
# 上下文守护者
为 Claude 提供的项目记忆。记录设计决策、流程图和约束,以便 Claude 在对话中保持上下文。
## 问题
随着对话的进行,Claude 会失去早期决策背后的“原因”。新的对话从空白开始。这导致 Claude 进行更改,从而破坏已建立的模式 — 例如重写它不记得存在的管道步骤。
## 解决方案
上下文守护者为 Claude 提供了 11 种工具来记录和检索结构化的项目上下文:
| 工具 | 用途 |
|------|---------|
| `record_decision` | 保存具有结构化理由的决策(问题、为什么选择、尝试过什么、权衡) |
| `record_pipeline` | 保存具有顺序和用途的多步骤工作流 |
| `record_constraint` | 保存具有范围、执行级别和触发事件的规则 |
| `get_context` | 通过查询、标签、范围或 ID 检索相关条目 — 默认拉取相关链接 |
| `get_project_summary` | 会话开始的紧凑概述 |
| `update_entry` | 通过 ID 更新条目 |
| `deprecate_entry` | 淘汰具有原因的条目 |
| `prune_stale` | 查找最近未验证的条目 |
| `get_compaction_report` | 检查最后一次压缩是否丢失了任何上下文 |
| `verify_quality` | 扫描条目中的薄弱理由、缺失标签、孤立弧(由 PreCompact 钩子自动调用) |
| `export_markdown` | 从决策存储中重新生成 `DECISIONS.md` — 一个派生的只读投影 |
所有数据都存储为人类可编辑的 JSON 文件,位于项目目录下的 `.context/` 中。默认情况下零依赖,可选语义检索。
## v0.10: 放弃 + 超越作为排名
从研究 [Curion](https://github.com/geanatz/curion) 中适应了两个想法,保持无依赖:
- **`get_context` 现在可以说“我没有相关的信息。”** 以前它总是返回其评分最高的条目 — 但综合评分库中约 55 分来自最近状态/来源/起源,无论相关性如何,因此查询 *没有* 相关记忆的自信外观结果会被沉默。 在无答案查询中测量的编造 **100%** (`evals/abstention.py`)。现在响应带有 `top_relevance` 和当顶级条目的标签/文本相关性低于 `min_relevance` (配置,默认 0.20)时,`no_confident_match: true` 带有指导,告诉代理不要将条目作为既定事实呈现。 它 **注释,但不抑制** — 弱匹配仍然返回,因此词汇不匹配召回 `retrieval_hints` 和语义混合保留的词汇存活下来。 0.20 是最高底线,在评估集上没有错误放弃。
- **超越作为排名信号,而不仅仅是过滤器。** `record_decision` 接受 `supersedes: [ids]`:先前的决策变为 `superseded` — **在排名中降级但仍然可回忆** (“为什么我们从 X 更改为?”),这与 `deprecate_entry` 不同,后者完全从检索中删除条目。 被超越的条目被 `prune_stale`/`verify_quality` 跳过(它们是有意的历史,而不是过时的作品),并在 `DECISIONS.md` 投影中标记为 `**SUPERSEDED** by dec-NNN`。
故意 *不* 采用 Curion:其 LLM 控制器架构(每次存储和召回的 API 调用)。context-keeper 保持零依赖,默认离线。
## v0.9: 主题聚类、更多嵌入后端 + 测量捕获的错误
- **关键修复:大型存储的空会话启动注入。** 摘要截断循环在条件中评估 *原始* 文本,因此任何存储的摘要超过令牌预算(〜30+ 条目)的存储都默默地弹出每一行并在会话开始时注入一个 **空** 摘要。 在测量令牌减少时发现:一个 78 条目的存储注入了 〜0 令牌的内存。 现在正确截断到预算。
- **主题聚类。** 在 8 个决策之上,`get_project_summary` 根据决策最频繁共享的标签将决策分组,而不是一个扁平的列表 — 一个 59 决策的存储读取为十几个主题。
- **OpenAI 兼容的嵌入。** `semantic.api: "openai"` 将语义混合指向任何 `/v1/embeddings` 端点 — LM Studio、llama.cpp 服务器或 OpenAI 本身(`api_key_env` 命名持有密钥的环境变量)。 Ollama 保持默认;相同的安全词典备用。 nomic 任务前缀现在仅适用于 nomic 模型。
- **信任感知的冲突指导。** `similar_entries` 匹配现在携带每个条目的 `origin`,并且指导说明了优先级:用户声明的覆盖剂推断的覆盖导入。
- **令牌减少测量** (`evals/token_reduction.py`),针对四个真实存储运行:
| 存储 | 活动条目 | 完整存储(令牌) | 会话开始时注入 | 减少 |
|---|---|---|---|---|
| balatron | 78 | 〜75,277 | 〜2,057 | 97.3% |
| clark | 55 | 〜35,445 | 〜2,102 | 94.1% |
| context-keeper | 13 | 〜5,692 | 〜828 | 85.5% |
| conductor | 9 | 〜1,538 | 〜411 | 73.3% |
基线 = 将所有活动条目转储到上下文中;注入 = SessionStart 钩子打印的 `get_project_summary` 输出。诚实的警告:摘要是预算上限的(默认 2000 令牌),因此对于大型存储,部分减少是构造的 — 有意义的属性是注入成本随着存储增长而保持不变。
- **记录了六个额外的 MCP 客户端** (OpenCode、Copilot CLI、Antigravity、OpenClaw、Hermes、pi/oh-my-pi) — 参见下面的其他 MCP 客户端。
## v0.8: DECISIONS.md 投影(写入时渲染)
选择加入:在项目根目录下将决策存储镜像到人类可读的 `DECISIONS.md` 中。 在 `.context/config.json` 中启用:
```json
{ "markdown_export": { "enabled": true, "path": "DECISIONS.md" } }
```
- **写入时渲染。** 每次调用修改决策的工具(`record_decision`、`update_entry`、`deprecate_entry`)都会在 JSON 写入后和工具返回之前重新生成整个文件 — 因此后续的 `git commit` 会在同一个提交中捕获两者。 故意 *不* 作为 git/PostToolUse 钩子:提交后渲染将重新引入漂移。
- **JSON 保持规范;markdown 派生且只读。** 文件每次重新生成 — 绝不追加、合并或解析回。 手动编辑不保留;重新生成的投影无漂移面。
- **`export_markdown` 工具** 按需重新生成(可选自定义 `path`),因此现有仓库可以反向填充而无需启用标志。
- 纯标准库字符串格式化;禁用标志时的默认行为是字节到字节不变。
源自现场使用:Balatron 的 `DECISIONS.md` 通过手动保持与存储的同步,一次提交一个镜像编辑。 这自动化了该约定。
## v0.7: 预期查询、来源信任、时间线过滤
- **`retrieval_hints`** (所有 `record_*` 工具):未来会话可能搜索的 2-4 个替代措辞 — 同义词、症状描述、错误消息。 为词汇不匹配查询(“价值网络发散” vs. “价值头饱和”)可以命中而无需嵌入而建立索引。 零依赖于语义混合的补充。
- **`origin` + 信任加权** (所有 `record_*` 工具):条目记录谁是作者 — `user` (明确说明)、`agent` (从会话推断)或 `import` (反向填充)。 检索评分给予用户声明的条目比代理推断的条目更高的信任,后者优先于导入。 v0.7 之前的条目评分为 `agent`,保留其相对顺序。
- **`since` / `before` on `get_context`**:针对每个条目的验证/创建时间的时间过滤器 — “我们这个月决定了什么” 现在是一个查询。
## v0.6: 捕获时防护
- **作用域约束注入。** 新的 `scope_guard.py` 钩子(`Edit|Write|NotebookEdit` 上的 PostToolUse):代理编辑被约束的 `scope` 覆盖的文件时,将通过 `additionalContext` 将约束注入上下文。 会话开始注入在第一轮时向模型简要介绍了规则;这在规则即将生效时强制执行。 每个约束在每个会话中最多触发一次。
- **记录时类似条目的显示。** `record_*` 现在将新条目与存储(字集 Jaccard,阈值可配置为 `similar_threshold`)进行比较,并在现有条目重叠严重时返回 `similar_entries` — 在捕获时捕获重述和矛盾,而不是依赖 MMR 在检索时减轻重复。 仅建议:写入始终继续。
## v0.5: 数据完整性 + 检索修复
- **原子写入。** 条目文件写入临时文件,然后使用 `os.replace` 交换,确保写入过程中出现崩溃不会留下截断的 JSON 文件。
- **损坏存储保护。** 如果条目文件存在但无法解析,则 `record_*`/`update_entry`/`deprecate_entry` 现在拒绝写入(以前损坏的文件读取为空, 下一个记录默默地用一个条目替换您的整个历史)。 只读工具仍然正常运行。
- **`update_entry` 强制执行模式。** 结构化字段(`why_chosen`、`problem`、`reason`、`purpose`...)在更新时也进行最小长度验证,以确保条目在记录后不会被清空。
- **更好的预算打包。** `get_context` 跳过不适合令牌预算的条目,并继续打包较小的条目,而不是在第一个超大条目处停止。
- **新鲜压缩报告。** SessionStart 钩子现在运行快照比较(SessionStart 在压缩后立即触发,源为 `compact` — 在任何停止之前),因此注入的报告永远不会过时。 它还注入了一个一行质量扫描提示,这是模型可见的 `verify_quality` 表面(PreCompact stdout 仅显示给用户,而不是模型)。
- **语义层随包一起提供** (`semantic_index.py` 从轮子/sdist 中丢失),具有批处理嵌入请求和每个查询减少一个 HTTP 往返。
## v0.4: 结构化理由 + 弧链接
早期版本使用单个自由格式的 `rationale` 字段。实际上,代理编写的是一行摘要而不是完整的推理 — 破坏了目的。 v0.4 通过以下方式解决了这个问题:
1. **模式强制深度。** `record_decision` 需要 `problem` (最小 40 个字符)、`why_chosen` (最小 60 个字符),并接受可选的 `what_we_tried` 和 `tradeoffs`。 `record_pipeline` 需要 `purpose`。 `record_constraint` 强制执行 `reason` ≥ 40 个字符,并接受可选的 `triggering_incident`。 薄条目在服务器端被拒绝,并带有特定于字段的指导 — 懒惰路径不再产生有用的条目。
2. **通过 `related_to` 进行弧链接。** 每个条目都可以引用相关条目的 ID。 `get_context` 默认遍历这些链接(深度=1),因此当检索一个决策时,其余的弧就会随之而来。 连接组织在会话之间存活下来。
3. **质量验证。** 一个新的 `verify_quality` 工具扫描遗留条目、薄推理、缺失标签和孤立条目(标签重叠但没有 `related_to`)。 `PreCompact` 钩子自动调用它,并在压缩上下文之前显示标记的条目,以便对其进行丰富。
遗留条目(v0.4 之前)仍然有效 — 它们不会被自动拒绝,只是被 `verify_quality` 标记为可选丰富。 废弃的 `rationale` 参数仍然在 `record_decision` 上工作,以确保向后兼容(它自动映射到 `why_chosen`),但 `problem` 仍然是必需的。
## 安装
```bash
pip install context-keeper-mcp
```
### Claude 代码
```bash
claude mcp add --scope user context-keeper -- python /path/to/context-keeper/server.py
```
### Claude 桌面版
将以下内容添加到 `claude_desktop_config.json` 中:
```json
{
"mcpServers": {
"context-keeper": {
"command": "python",
"args": ["/path/to/context-keeper/server.py"],
"env": {
"CONTEXT_KEEPER_PROJECT": "/path/to/your/project"
}
}
}
}
```
### 其他 MCP 客户端(Cursor、Codex CLI、Gemini CLI、Windsurf 等)
服务器是一个标准的 stdio MCP 服务器,因此任何支持 MCP 的客户端都可以使用它 — 钩子是 Claude Code 的额外功能,不是必需的。 将客户端的 MCP 配置指向 `python /path/to/context-keeper/server.py` 并设置 `CONTEXT_KEEPER_PROJECT`:
**Cursor** (`~/.cursor/mcp.json` 或每个项目 `.cursor/mcp.json`)和 **Windsurf** (`~/.codeium/windsurf/mcp_config.json`)使用与 Claude Desktop 相同的格式:
```json
{
"mcpServers": {
"context-keeper": {
"command": "python",
"args": ["/path/to/context-keeper/server.py"],
"env": { "CONTEXT_KEEPER_PROJECT": "/path/to/your/project" }
}
}
}
```
**OpenAI Codex CLI** (`~/.codex/config.toml`):
```toml
[mcp_servers.context-keeper]
command = "python"
args = ["/path/to/context-keeper/server.py"]
env = { "CONTEXT_KEEPER_PROJECT" = "/path/to/your/project" }
```
**Gemini CLI** (`~/.gemini/settings.json`)使用与上述 Cursor 相同的 `mcpServers` JSON 格式。
**GitHub Copilot CLI** (`~/.copilot/mcp-config.json`)和 **oh-my-pi** (`mcpServers` 配置)使用带有 `"type": "stdio"` 的 `mcpServers` 形状:
```json
{
"mcpServers": {
"context-keeper": {
"type": "stdio",
"command": "python",
"args": ["/path/to/context-keeper/server.py"],
"env": { "CONTEXT_KEEPER_PROJECT": "/path/to/your/project" }
}
}
}
```
**OpenCode** (`opencode.json`):
```json
{
"mcp": {
"context-keeper": {
"type": "local",
"command": ["python", "/path/to/context-keeper/server.py"],
"environment": { "CONTEXT_KEEPER_PROJECT": "/path/to/your/project" }
}
}
}
```
**Antigravity** (`~/.gemini/config/mcp_config.json` 或工作区 `.agents/mcp_config.json`)和 **OpenClaw** (`openclaw.json`)使用带有 `command`/`args` 的 `mcpServers` 形状,与 Copilot 相同。
**Hermes** (`~/.hermes/config.yaml`):
```yaml
mcp_servers:
context-keeper:
command: "python"
args: ["/path/to/context-keeper/server.py"]
env:
CONTEXT_KEEPER_PROJECT: "/path/to/your/project"
```
如果没有 Claude Code 钩子,您将失去自动会话启动注入和编辑时约束保护 — 而是调用 `get_project_summary` 会话开始和 `record_*` 工作时(工具描述提示)。
设置 `CONTEXT_KEEPER_PROJECT` 为项目的根目录。如果省略,服务器按以下顺序解析项目目录:
1. **`CONTEXT_KEEPER_PROJECT`** 环境变量(显式选择 — 受信任)
2. **cwd** 如果它已经包含一个 `.context/` 目录
3. **从 cwd 向上遍历目录** 查找现有的 `.context/` (git 样式发现 — 找到项目,当服务器从其任何子目录启动时)
4. 否则:拒绝,并返回一个“未解析的项目”错误
步骤 2 和 3 仅解析为已经包含 `.context/` 的目录。 服务器从不隐含地创建目录,因此您永远不会通过从错误的位置启动而意外污染父目录。 通过任何工具传递 `project_dir` 以强制创建新项目。
## 工作原理
### 记录上下文
当您做出设计决策时:
```
您:我们使用 JSON 文件代替 SQLite 进行存储。
Claude:[调用 record_decision 并附上摘要、问题、为什么选择、替代方案、
以及可选的 what_we_tried + tradeoffs + related_to 链接]
```
当您建立工作流程时:
```
您:部署流程是:运行测试、构建、推送到注册表、部署。
Claude:[调用 record_pipeline 并附上有序步骤]
```
当您设置规则时:
```
您:绝不从源代码运行 Conductor。始终使用 exe。
Claude:[调用 record_constraint 并附上规则、原因和硬度=绝对]
```
### 检索上下文
在对话开始时,SessionStart 钩子直接将项目摘要(和任何压缩差异报告)注入上下文 — 无需调用工具,因此在任务聚焦的第一回合无法跳过检索。 `get_project_summary` 仍然可在需要时调用。 在进行更改之前,Claude 调用 `get_context` 并带有相关标签以检查冲突。
### 相关性评分
在没有嵌入或外部服务的情况下,Context Keeper 使用以下方法对条目进行评分:
- **标签匹配** — 查询和条目标签之间的重叠
- **文本匹配** — 查询词在摘要/理由/规则文本中找到
- **最近性** — 最近验证的条目评分更高
- **状态** — 活动条目优先于被超越的条目
结果受限于可配置的令牌预算(默认:4000 令牌)。
## Claude 代码钩子设置
Context Keeper 包含一些钩子,用于在会话开始时注入项目内存,在每次 git 提交后提醒 Claude 进行捕获,在 Claude Code 压缩之前快照您的上下文,并在之后检测是否有任何内容丢失。
将以下内容添加到您的 Claude Code 钩子配置(`~/.claude/settings.json`):
```json
{
"hooks": {
"PreCompact": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python /path/to/context-keeper/hooks/pre_compact.py"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python /path/to/context-keeper/hooks/post_compact.py"
}
]
}
],
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "python /path/to/context-keeper/hooks/session_start.py"
}
]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python /path/to/context-keeper/hooks/commit_capture_reminder.py"
}
]
},
{
"matcher": "Edit|Write|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "python /path/to/context-keeper/hooks/scope_guard.py"
}
]
}
]
}
}
```
将 `/path/to/context-keeper` 替换为实际的安装路径。如果您的项目不在当前工作目录中,请设置 `CONTEXT_KEEPER_PROJECT` 环境变量。
**Windows 用户:** 在 JSON 中使用正斜杠(`C:/Users/.../context-keeper/hooks/pre_compact.py`)或双反斜杠。单个反斜杠会被 shell 损坏。
钩子形成了一个完整的捕获和检索循环:
- **SessionStart** — 导入服务器自己的处理程序,并直接将项目摘要(以及任何压缩差异报告和一行质量扫描提示)打印到 stdout,Claude Code 将其注入到上下文中。它还会在读取报告之前运行压缩后快照比较 — SessionStart 在压缩后立即启动,任何停止钩子之前,因此保持注入的报告新鲜。这取代了打印指令以调用工具的旧方法 — 请求可靠地丢失在任务聚焦的第一回合,因为工具被推迟。只要项目中没有 `.context/`,它就保持沉默,并且发出仅 ASCII 输出,因此它不会在 Windows cp1252 stdout 上崩溃
- **PostToolUse(Bash)** — 在每次 Bash 工具调用后触发;当命令包含 `git commit` 时,它会注入提醒,在同一个工作周期中记录匹配的决策/约束/注意事项。提交是单个最佳捕获触发器 — 这是某些东西变得足够真实以至于在版本控制中持久化的确切时刻。从现场使用中诞生:在事件密集的会话中,代理批量捕获“稍后”,而用户不得不在一晚上问三次“更新上下文守护器”,同时交付十二个提交
- **PostToolUse(Edit|Write)** — `scope_guard.py`:当代理编辑受约束的 `scope`(例如,约束范围在 `hooks/` 和对 `hooks/session_start.py` 的编辑)覆盖的文件时,该约束立即通过 `additionalContext` 注入。会话开始时简介规则;这在编辑时强制执行它们。每个约束每个会话一次
- **PreCompact** — 快照所有活动 `.context/` 条目并运行质量扫描(`verify_quality`),将标记的条目(薄推理,缺少标签,孤立弧)打印到转录中。注意:PreCompact stdout 仅对用户可见 — Claude Code 不会将其注入模型的上下文,这就是为什么模型可见的质量提示生活在 SessionStart 钩子中
- **Stop** — SessionStart 执行的快照比较的安全网,在会话没有新的会话启动而结束的情况下(幂等 — 如果快照自上次比较以来没有更改,则跳过)
这关闭了捕获循环:SessionStart 在第一回合中注入检索,提交提醒将捕获锚定在更改落地时的时刻,PreCompact 是压缩前的安全网,而 Stop 处理完整性检查。检索是不可避免的;捕获现在在正确的时间**触发,而不是由代理在任务中酌情决定。
## 数据存储
```
your-project/
.context/
decisions.json # 设计决策与原理
pipelines.json # 多步骤工作流程
constraints.json # 规则和不变量
config.json #令牌预算、过时阈值
compaction_snapshot.json # 压缩前快照(自动生成)
compaction_report.json # 压缩后差异报告(自动生成)
hook.log # 钩子活动日志
```
所有文件都是人类可读的 JSON。您可以直接编辑它们。ID 是连续且可读的:`dec-001`、`pipe-001`、`con-001`。
## 配置
创建 `.context/config.json` 以自定义:
```json
{
"project_name": "my-project",
"token_budget": 4000,
"max_entry_tokens": 1000,
"stale_threshold_days": 30,
"min_relevance": 0.20,
"markdown_export": {
"enabled": false,
"path": "DECISIONS.md"
},
"semantic": {
"enabled": false,
"weight": 150,
"model": "nomic-embed-text",
"url": "http://localhost:11434",
"api": "ollama",
"api_key_env": ""
},
"mmr": {
"enabled": false,
"lambda": 0.7
}
}
```
`mmr`(可选,默认关闭)重新排序排名结果以获得**最大边缘相关性**:候选者因与其已经选择的条目的词汇相似性而受到惩罚,因此同一个主题的近似重复表述不会挤占令牌预算,而第二个相关主题会获得一个席位。`lambda` 在相关性(1.0 = 纯相关性顺序)和多样性之间进行权衡。通过 `related_to` 连接的条目是豁免的 — 这些弧线是为了让它们一起显示而设计的。对于今天的存储大小,影响很小(redundancy@5 已经很低);随着存储的增长和累积的过时/重述条目,它发挥了作用。
## 语义检索(可选)
默认情况下,`get_context` 使用纯词汇匹配(标签 + 单词重叠)对条目进行排名 — 零依赖,离线工作。弱点是**词汇不匹配**:关于“价值网络分歧”的查询不会找到关于“价值头饱和”的决策,因为它们没有共享关键词。
设置 `semantic.enabled: true` 将嵌入余弦信号混合到排名中,使用本地 [Ollama](https://ollama.com) 服务器(`ollama pull nomic-embed-text`)。在一个评估中,在三个真实项目存储库中,这提高了 **hit@5 从 80% 到 93% 和 MRR 从 0.63 到 0.88**(评估工具位于 [`evals/`](evals/))。条目嵌入缓存每个存储库在 `.context/embeddings.json` 中,按条目文本的哈希键控,因此编辑后的条目会自动重新嵌入。
它是严格附加和故障安全的:如果 Ollama 无法访问或模型丢失,检索将静默回退到词汇排名。默认设置保持 `enabled: false`,因此零依赖仍然是开箱即用的行为。
任何 OpenAI 兼容的端点也适用:设置 `"api": "openai"` 并将 `url` 指向 LM Studio / llama.cpp 服务器(`http://localhost:1234`)或 OpenAI 本身,并使用 `"api_key_env"` 命名保存密钥的环境变量。
## 跨项目上下文
通过传递 `project_dir` 查询另一个项目的上下文:
```
Claude: [调用 get_context,带有 project_dir="/path/to/other-project"]
```
或使用其他项目名称标记条目以进行交叉引用。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器