内容
# 上下文守护进程(Context Daemon 缩写)
[](https://github.com/giuseppecardenas/contextd/actions/workflows/ci.yml)
Contextd 是一个为项目文件提供的本地知识层。它索引 markdown、代码和结构化数据到一个混合图 + 向量存储(Neo4j 社区版或 Memgraph,由您选择),生成 AI 推断的关系和每个文件的摘要,并通过 MCP 服务器将结果暴露给 Claude Desktop、Cursor 和任何支持 MCP 的客户端。您可以使用 MCP 服务器冷启动任何 AI 会话,并获得整个语料库的紧凑、语义组织的概述。
- **存储:** Neo4j 社区版 5.x(默认)或 Memgraph 3.x - 两者都在 Docker 中运行,两者都绑定端口 7687。
- **推理:** Google Gemma(`gemma-4-31b-it` 默认)通过 Gemini API 进行总结、关系推理和 NL→Cypher 翻译; Voyage AI `voyage-4-large`(1024-dim,32k-token 上下文)用于向量嵌入。
- **接口:** stdio MCP 服务器(`contextd-mcp`)和 CLI(`contextd`)。
- **平台:** 原生运行于 Linux、macOS 和 Windows 11。守护进程、IPC、文件监视和 Docker 调用是平台无关的 - Windows 不仅仅是 WSL 的一个后备选择。WSL2 仍然是 Windows 上一个不错的主机,但原生 Python 安装在 Windows 上同样受支持。
- **隐私:** 所有状态都存放在 `~/.contextd/`(Windows 上为 `%USERPROFILE%\.contextd`);除了每个文件的 API 调用之外,没有数据存储在您的机器之外。
> **状态:alpha.** v0.1.0 处于 PyPI 之前。使用下面的开发安装。
---
## 快速入门
```bash
# 1. 安装(开发路径 - 参见安装部分了解详情)
git clone git@github.com:giuseppecardenas/contextd.git
cd contextd
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
# 2. 设置 API 密钥
export GEMINI_API_KEY=<您的密钥>
export VOYAGE_API_KEY=<您的密钥>
# 3. 首次运行向导 - 创建 ~/.contextd/ 布局
contextd init
# 4. 启动存储后端 + 索引守护进程
contextd up
# 5. 注册语料库并索引它
contextd add-corpus examples/minimal-notes --name notes
contextd index notes --bootstrap
# 6. 查询图
contextd ask "哪些文件引用 note-1?"
```
在 `contextd up` 和成功的引导后,打开 Claude Desktop 并调用 `describe_project` MCP 工具 - 您将获得一个 JSON primer,包含最常被引用的文件及其摘要,准备好让 AI 助手锚定其会话。
---
## 概念
### 文件节点 vs 章节节点
默认情况下,语料库中的每个文件都索引为一个单独的 `File` 节点,并具有 AI 生成的摘要和 `voyage-4-large` 嵌入(1024-dim)。这是 **文件粒度** 模式。
对于结构化的 markdown 语料库(长 PRDs、规格、设计文档),**章节粒度** 模式将每个标题提升为一个一等 `Section` 节点。结构边 - `CONTAINS`、`PARENT_OF`、`NEXT_SIBLING` - 模拟文档树;AI 推断语义边(`REFERENCES`、`DOCUMENTS`、`SUPERSEDES` 等)跨章节边界。启用它时,在 `add-corpus` 中使用 `--granularity section`。
### 边起源
每条边都有一个 `origin` 值:
| 起源 | 含义 |
|---|---|
| `structural` | 文档结构(CONTAINS / PARENT_OF / NEXT_SIBLING)。在重新索引时永远不会被覆盖。 |
| `inferred` | AI 推断的语义关系。在重新索引时进行覆盖。 |
| `manual` | 人工编写的。在重新索引时永远不会被覆盖。 |
**推断边连接到真实节点,从不占位符。** 当推理引用 `File` 或 `Section`(通过索引磁盘内容创建的节点类型)时,边连接到现有节点 - 按规范路径/ID 匹配,或对于文件按唯一 basename 匹配 - 或如果没有匹配则丢弃。它永远不会创建一个存根。只有抽象实体类型(`Pattern`、`Risk`、`Ticket`、`Technology` 等)按需创建为推理目标。节点身份是一个规范的前向斜线路径,因此重新索引文件会原地更新其节点,而不是创建分离变体的副本。
### GraphStore 抽象
所有更高层(索引器、MCP 服务器、CLI)都通过 `GraphStore` ABC 与后端通信。具体的后端 - `Neo4jBackend` 或 `MemgraphBackend` - 由 `~/.contextd/config.toml` 中的一行选择:
```toml
[storage]
backend = "neo4j" # 或 "memgraph"
```
切换后端是 `contextd down && contextd up` 周期;不需要更改代码或重新安装。
### 本体论
基本本体论(`contextd/ontology/base.json`)定义了 AI 允许推断的节点类型和边类型。未识别的类型在索引时会被静默丢弃。每个语料库的别名允许您将领域特定词汇映射到规范类型 - 请参见下面的本体论自定义。
节点类型:`File`、`Section`、`Artifact`、`Ticket`、`Pattern`、`Technology`、`Client`、`Repo`、`Service`、`Integration`、`Risk`、`WorkSession`、`Corpus`、`Meta`。
边类型:`CONTAINS`、`PARENT_OF`、`NEXT_SIBLING`、`BELONGS_TO`、`CREATED_BY`、`DOCUMENTS`、`DOCUMENTED_IN`、`APPLIES_TO`、`PART_OF`、`SIMILAR_TO`、`RELATED_TO`、`REFERENCES`、`SUPERSEDES`、`CONTRADICTS`、`USES`、`MODIFIES`、`DEPENDS_ON`、`IDENTIFIES_RISK`、`RECOMMENDS`、`EDITED_DURING`、`VERIFIED_ON`、`NEEDS_UPDATE`。
---
## 安装
### Pre-PyPI(当前)
该软件包尚未在 PyPI 上。克隆存储库并在可编辑模式下安装。
**Linux / macOS:**
```bash
git clone git@github.com:giuseppecardenas/contextd.git
cd contextd
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
export GEMINI_API_KEY=<您的密钥>
export VOYAGE_API_KEY=<您的密钥>
```
**Windows 11(原生 PowerShell):**
```powershell
git clone git@github.com:giuseppecardenas/contextd.git
Set-Location contextd
uv venv
.venv\Scripts\Activate.ps1
uv pip install -e ".[dev]"
$env:GEMINI_API_KEY = "<您的密钥>"
$env:VOYAGE_API_KEY = "<您的密钥>"
```
需求(两个平台):Python 3.11+,Docker(Windows/macOS 上的 Docker Desktop 或 Linux 上的任何 Docker 引擎与 Compose v2)。Windows 上的 Docker Desktop 受支持 - `contextd up` 调用 `docker compose` 以对抗与 `docker` 相同的守护进程,从 PowerShell 调用。在 Windows 上,Docker Desktop 必须设置为 Linux 容器(默认)才能使用 Neo4j/Memgraph 镜像。
其余的 README 部分都使用 `bash` 语法编写。等效的 PowerShell 表单仅在 shell 特有的表面上有所不同(`export` ↔ `$env:`,`~/.contextd/` ↔ `$env:USERPROFILE\.contextd\`);`contextd` CLI 调用本身在两个平台上都是相同的。
### 无需激活虚拟环境的全局命令(可选)
在可编辑安装后,`contextd`、`contextd-mcp` 和 `contextd-indexer` 入口点位于 `.venv/bin/`(Linux/macOS)或 `.venv\Scripts\`(Windows),并且仅在激活虚拟环境时才在 `PATH` 上。要从任何 shell 调用它们 - 对 Claude Desktop 的 MCP 配置、计划任务或一次性调用很有用 - 将文件链接放入一个已经在您的用户 `PATH` 上的目录中。虚拟环境存根是薄启动器,导入可编辑安装的包,因此链接始终获取磁盘上的最新源;没有编译步骤来保持同步。
**Linux / macOS** (符号链接,无需权限):
```bash
# 从存储库根目录运行,创建虚拟环境后
mkdir -p ~/.local/bin
ln -s "$PWD/.venv/bin/contextd" ~/.local/bin/contextd
ln -s "$PWD/.venv/bin/contextd-mcp" ~/.local/bin/contextd-mcp
ln -s "$PWD/.venv/bin/contextd-indexer" ~/.local/bin/contextd-indexer
```
**Windows 11** (硬链接,无需管理员 - 与符号链接一样,但有一个约束,即源和目标必须位于同一卷):
```powershell
# 从存储库根目录运行,创建虚拟环境后
$src = "$PWD\.venv\Scripts"
$dst = "$env:USERPROFILE\.local\bin" # 您 PATH 上的任意目录
foreach ($name in "contextd.exe","contextd-mcp.exe","contextd-indexer.exe") {
fsutil hardlink create "$dst\$name" "$src\$name" | Out-Null
}
```
**注意事项 - pip 存根再生。** 上面的链接指向 pip 生成的启动器存根。编辑 `.py` 文件不会影响它们 - 可编辑安装直接在导入时加载源代码,无论哪个副本的存根运行。唯一需要注意的情况是,如果 `pyproject.toml` 中的 `[project.scripts]` 更改(入口点名称或模块路径):pip 会写入一个新的存根到虚拟环境中,Windows 上的 `~/.local/bin` 硬链接最终会指向现在孤立的旧 inode。如果您怀疑漂移,请重新运行链接创建命令。要在 Windows 上验证:
```powershell
fsutil hardlink list "$env:USERPROFILE\.local\bin\contextd.exe"
```
如果虚拟环境路径仍出现在输出中,则硬链接是健康的;如果只有 `.local\bin` 条目被列出,请重新创建链接。
### v0.1.0 之后的 PyPI(未来)
```bash
pipx install contextd
```
`pipx` 路径是预期的生产安装,一旦软件包发布。现在,请使用上面的开发安装。
---
## 使用指南
本指南使用 `examples/minimal-notes` - 一个个人笔记夹层(10 个笔记文件 + 一个 README)包含在存储库中。相同的步骤适用于任何语料库。
### 步骤 1 - 首次设置
```bash
contextd init
```
创建 `~/.contextd/`,并复制 `config.toml`,检查所需的环境变量(`GEMINI_API_KEY`、`VOYAGE_API_KEY`)和 Docker。 在继续之前,请在 shell 中设置两个密钥:
```bash
export GEMINI_API_KEY=<从 https://aistudio.google.com/app/apikey 获取>
export VOYAGE_API_KEY=<从 https://www.voyageai.com/ 获取>
```
### 步骤 2 - 启动后端
```bash
contextd up
```
运行 `docker compose --profile neo4j up -d`(使用 `~/.contextd/docker-compose.yml`),等待 Neo4j 准备就绪,然后应用模式迁移(约束,向量索引,全文索引)。输出:
```
✓ neo4j 容器在 127.0.0.1:7687 处启动
✓ 应用迁移
ready
```
要使用 Memgraph,请在运行 `contextd up` 之前在 `~/.contextd/config.toml` 中设置 `backend = "memgraph"`。
### 步骤 3 - 注册语料库
```bash
contextd add-corpus examples/minimal-notes --name notes
```
写入 `~/.contextd/corpora/notes.toml`,根路径,通配符模式(`**/*.md`)和 `granularity = "file"`。要索引到章节粒度:
```bash
contextd add-corpus examples/minimal-notes --name notes --granularity section
```
### 步骤 4 - 引导索引
```bash
contextd index notes --bootstrap
```
运行五阶段的引导管道:
1. **枚举** - 发现文件,在 CREATE 时计算嵌入。
2. **嵌入** - 会计存根(嵌入已经在阶段 1 中写入)。
3. **总结** - 调用 Gemma 每个文件以生成 `summary` 和 `key_points`。
4. **关联** - 调用 Gemma 每个文件以推断节点之间的类型边。
5. **关闭** - 持久化语料库统计和检查点状态。
进度按阶段打印:
```
found 11 files in corpus 'notes'
✓ enumerate: processed=11 skipped=0
✓ embed: processed=11 skipped=0
✓ summarise: processed=11 skipped=0
✓ relate: processed=11 skipped=0
✓ close: processed=1 skipped=0
```
预览令牌成本而无需索引:
```bash
contextd index notes --estimate-only
```
**恢复部分运行。** 重新运行 `contextd index <corpus> --bootstrap` 是幂等的:已经有摘要或 `inferred_at` 标记(由先前的成功 `relate` 传递写入)的节点会自动跳过。被杀死的中间引导只需要一个普通的 `--bootstrap` 重新启动 - 无需额外标志。
**擦除图层以进行全新推理。** 使用 `--refresh <scope>`:
| 范围 | 擦除 | 保留 | 重新计算成本 |
|---|---|---|---|
| `inferred` | `origin='inferred'` 边 + `inferred_at` 标记 | 摘要、结构嵌入 | 仅 Gemini 关联 |
| `summaries` | `summary`/`key_points`/`summary_confidence` | 推断边、结构、嵌入 | 仅 Gemini 总结 |
| `llm` | 上面两者 | 结构化、嵌入 | 所有 Gemini 工作 |
| `all` | DETACH DELETE 这个语料库的每个 `Section`/`File`/`Corpus` 节点 | 无(结构 + 推断边级联删除) | 航程 + Gemini 从零开始 |
```bash
contextd index notes --bootstrap --refresh llm # 例如,在提示更改后
```
**增量重新索引(一次性)。** 重新索引自上次运行后更改的文件:
```bash
contextd index notes --incremental
```
扫描语料库中的每个文件。对于文件粒度语料库,MD5 哈希跳过未更改的文件。对于章节粒度语料库,每个章节的存储哈希与当前内容进行比较 - 只有更改、添加或删除的章节触发推理;未更改的章节受 IS-NULL 保护并自动跳过。输出:
```
✓ 增量扫描完成:indexed=2 deleted=0 skipped=9
```
对于连续监视,请使用守护进程 - 参见 [增量索引守护进程](#incremental-indexer-daemon) 下面。
### 步骤 5 - 查询
```bash
contextd ask "哪些笔记提到了 cooking?"
```
将问题翻译为 Cypher,通过 Gemini 运行查询,打印结果为 JSON。 Cypher 首先打印出来,以便您检查生成的内容。
### 步骤 6 - 连接 Claude Desktop
将 `contextd-mcp` 添加到您的 Claude Desktop 配置(参见 [MCP 集成](#mcp-integration)。一旦连接,从 Claude Desktop 界面调用 `describe_project`。对于 minimal-notes 语料库,您会得到类似:
```json
[
{"path": "examples/minimal-notes/note-1.md", "name": "note-1", "summary": "...", "key_points": [...], "inbound": 3},
...
]
```
该工具返回最多 40 个节点,按传入引用计数排序,每个节点都有 AI 总结。 AI 助手可以使用此作为会话启动器 - 一次工具调用替换阅读每个文件。
### 步骤 7 - 监控成本和日志
```bash
# 尾随守护进程的结构化 JSON 日志
contextd logs --follow
# 聚合的提供者令牌支出
contextd costs
contextd costs --since 2026-04-01
# 列出已注册的语料库
contextd list-corpora
# 守护进程 + 后端 + 语料库状态一瞥
contextd status
```
### 通用工具(始终注册)
| 工具 | 功能 |
|---|---|
| `describe_project` | 按引用计数排序的前 N 个文件节点,并附有摘要。接受 `corpus` 和 `n`(默认为 40)。 |
| `search` | 对摘要进行全文搜索。接受 `query`,可选 `kind`(默认为 `File`;在按章节划分的语料库中,`Section` 也可用),可选 `limit`(默认为 20)。 |
| `related` | 在 N 个 hops 内进行外链和内链遍历(1-5)。接受 `node_id` 和 `depth`(默认为 2)。 |
| `inbound` | 哪些节点引用了当前节点?接受 `node_id`。 |
| `outbound` | 当前节点引用了哪些节点?接受 `node_id`。 |
| `get_file_summary` | 单个文件的摘要和关键点。接受 `path`。 |
| `section_tree` | 文件的大纲(仅适用于按章节划分的语料库)。接受 `file_path`。 |
| `query_graph` | 只读 Cypher 查询接口。接受 `cypher`。写入关键词会被 guard 拒绝。 |
### 按语料库划分的工具
语料库适配器可以在其 TOML 配置文件中注册额外的 Cypher 支持工具,位于 `[mcp.tools]` 部分。这些工具在工具列表中以 `<corpus>.<tool>` 的形式命名,以避免与通用工具冲突。调用注册工具的 AI 助手会运行预先编写的 Cypher 查询,并带有 `$placeholder` 参数绑定,所有操作都通过同一个只读 guard 进行。
大多数适配器不需要此功能——`query_graph` 和通用工具可以组合成相同的查询,并且可以在会话中调整,无需重启服务器。在需要频繁调用某个查询时,可以考虑使用 `[mcp.tools]` 来优化性能。
详见 [docs/mcp.md](docs/mcp.md) 以获取完整的工具参考。
---
## 增量索引守护进程
`contextd up` 启动图后端和后台文件监视守护进程(`contextd-indexer`)。守护进程监视每个注册的语料库根目录的文件更改,并自动重新索引修改后的文件——无需手动调用 `contextd index`。
### 生命周期
```bash
contextd up # 启动图后端和索引守护进程
contextd status # 显示守护进程状态(pid、uptime、语料库)
contextd down # 停止守护进程和图后端
```
`contextd status` 首先尝试 IPC 端点获取实时运行时信息,如果守护进程不可达,则回退到 PID 文件:
```
backend: neo4j running at 127.0.0.1:7687
daemon: running (pid=12345, uptime=42s, corpora=['notes'])
```
### 工作原理
1. `CorpusWatcher`(Linux 上使用 inotify,macOS 上使用 FSEvents,Windows 上使用 ReadDirectoryChangesW)会在语料库根目录下的每个文件更改时触发。
2. 事件被收集到一个 `DebouncedQueue` 中,该队列具有可配置的时间窗口(默认为 30 秒),以便将快速编辑批处理为单个操作。
3. 窗口关闭后,守护进程运行哈希检查。对于按文件粒度的语料库,不会修改的文件通过 MD5 被跳过。对于按章节粒度的语料库,比较章节级别的哈希值——仅更改、添加或删除的章节会被清除并重新推断;未更改的章节保持不变。
4. 更改的文件通过调用与 `--incremental` 相同的阶段管道重新索引(默认为 4 个 worker)。
5. **崩溃恢复**:在处理每个批次之前,会写入一个检查点。如果守护进程在批次中被杀死,则下次启动时会重放任何正在进行的事务。索引失败的路径会缓冲到 `~/.contextd/state/pending-upserts.jsonl` 并在下次启动时重试。
### 周期性扫描(WSL2 / Windows 侧编辑)
在 Linux 和 macOS 上,`CorpusWatcher` 依赖于 inotify / FSEvents。在 WSL2 上,从 Windows 端进行的编辑(例如,VS Code Windows 应用程序写入 WSL 文件系统)会静默绕过 inotify——守护进程永远不会接收到事件,索引就会过时而没有错误。
**周期性扫描** 是解决方法。在可配置的时间间隔(默认为 900 秒)内,守护进程会查询图中的所有 Section 节点(按章节粒度的语料库)或扫描磁盘中的所有语料库文件(按文件粒度的语料库),并比较存储的哈希值与当前内容。如果至少有一个章节发生更改、添加或删除,文件会被重新排队进行索引——就像 inotify 触发了一样。
速率限制使用预算累积模型:`sweep_rate_sections_per_second`(默认为 0.017 ≈ 1 section/minute)按时间片累积。守护进程仅在累计预算 ≥ 1.0 时处理一个工作单元,扣除 `max(1, len(sections))` 单位。这使守护进程在扫描之间保持空闲。设置 `sweep_interval_seconds = 0` 以完全禁用扫描。
```toml
[indexer]
sweep_interval_seconds = 900 # 0 禁用扫描
sweep_rate_sections_per_second = 0.017 # 预算率;默认为 ≈ 1 section/minute
```
扫描活动时的日志输出:
```
corpus notes: sweep started (12 files, 47 sections)
corpus notes: sweep complete, next in 900s
corpus notes: indexed /path/to/changed-file.md
```
### 分支门控
如果您检出一个远程分支进行比较,守护进程将重新索引每个不同的文件——可能导致数百次 Gemini 调用针对一个临时分支。`allowed_branches` 门控可以防止这种情况发生:
```toml
# ~/.contextd/config.toml
[indexer]
allowed_branches = ["main", "develop"]
```
当语料库存储库的活动分支不在列表中时,守护进程和 `contextd index --incremental` 都会跳过所有工作并记录警告。空列表(默认值)允许所有分支。
detached HEAD 在配置了白名单时始终被阻止(比较检出会使存储库进入 detached HEAD 状态)。
### 调整
```toml
[indexer]
debounce_seconds = 30 # 秒为单位,在最后一个事件发生后等待 dispatching 批次
incremental_workers = 4 # 每个批次的并发文件工作线程(与 inference_concurrency 不同)
inference_concurrency = 1 # 每个文件的 summarise+relate 阶段的 LLM 调用并行度
sweep_interval_seconds = 900 # 多久运行一次周期性扫描;0 禁用
sweep_rate_sections_per_second = 0.017 # 预算累积率;≈ 1 section/minute
[logging]
max_log_bytes = 10485760 # 10 MB 每日志文件;0 禁用旋转
log_backup_count = 5 # 保留的旋转文件数量
```
守护进程仅写入配置的日志文件(默认为 `~/.contextd/logs/contextd.log`)——无终端输出——因此这些旋转设置对长期运行的安装很重要。
### 在 systemd 下运行(可选)
`contextd up` 将守护进程作为 shell 的 detached 子进程启动,因此 WSL VM 关闭或注销会杀死它,而不会进行干净的 `SIGTERM` 操作——您将看到日志中的不明晰的空白,接着是新的 `daemon started` 行。要跨 WSL/machine 重启自动重启,请安装提供的用户单元:
```bash
# 1. 提供密钥 - systemd 用户单元 不会加载 ~/.bashrc,因此请将守护进程需要的密钥放入专用 env 文件中,并具有限制性权限。
mkdir -p ~/.config/contextd
umask 077
cat > ~/.config/contextd/env <<EOF
GEMINI_API_KEY=$GEMINI_API_KEY
VOYAGE_API_KEY=$VOYAGE_API_KEY
EOF
chmod 600 ~/.config/contextd/env
# 2. 安装并启用单元(从 contextd 仓库根目录运行)。
mkdir -p ~/.config/systemd/user
cp scripts/systemd/contextd-indexer.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now contextd-indexer.service
# 3. 允许单元在没有活动登录会话的情况下运行(WSL 用户:需要)。
loginctl enable-linger $USER
```
验证:
```bash
systemctl --user status contextd-indexer # → Active: active (running)
tail -f ~/.contextd/logs/contextd.log # → "daemon started ... watching corpus ..."
```
如果 `status` 显示 `code=exited, status=1/FAILURE`,请运行 `journalctl --user -u contextd-indexer -n 30` - 最常见的错误原因是缺少或不可读的 `~/.config/contextd/env`(守护进程拒绝在没有 `GEMINI_API_KEY` / `VOYAGE_API_KEY` 的情况下启动)。
然后使用 `systemctl --user {status,stop,restart} contextd-indexer` 代替 `contextd up/down` 进行守护进程部分。`contextd up` 仍然处理图后端容器;只需跳过其守护进程启动步骤(或者让 PID 文件守护进程为您跳过 - `up` 检测到正在运行的守护进程并让它保持不变)。
---
## Windows 支持
Contextd 原生运行于 Windows(直接安装 Python 3.11+,CLI 在 cmd.exe 或 PowerShell 中,守护进程作为原生 Windows 进程)。无需 WSL2。
唯一的平台差异是守护进程的 IPC 传输:在 Linux/macOS 上,守护进程使用 Unix 域套接字(`~/.contextd/ipc.sock`);在 Windows 上,它绑定到一个临时 TCP 端口的 `127.0.0.1` 并将端口号写入 `~/.contextd/ipc.port`。JSON-lines 线协议相同 - 这对用户和 MCP 客户端来说是透明的。
进程管理也适应了这一点:守护进程通过 `CREATE_NEW_PROCESS_GROUP` 与 `start_new_session` 不同,而 `contextd down` 使用 `TerminateProcess` 而不是 `SIGTERM`。所有平台特定代码都隔离在 `contextd/_compat.py` 中;没有其他模块根据 `sys.platform` 进行分支。
### Windows 安装
```powershell
git clone https://github.com/giuseppecardenas/contextd.git
cd contextd
python -m venv .venv
.venv\Scripts\activate
pip install -e ".[dev]"
```
需求:Python 3.11+,Docker Desktop(启用 Linux 容器 - Neo4j/Memgraph 镜像需要)。
### WSL2 替代方案
如果您更喜欢在 WSL2 中运行,`scripts/windows/` 下的四个 PowerShell 包装器会将命令转发到 WSL 发行版中:
| 脚本 | 等效命令 |
|---|---|
| `contextd-init.ps1` | `wsl -d $Distro -- contextd init` |
| `contextd-up.ps1` | `wsl -d $Distro -- contextd up` |
| `contextd-down.ps1` | `wsl -d $Distro -- contextd down` |
| `contextd-status.ps1` | `wsl -d $Distro -- contextd status` |
将 `$Distro` 设置为您的 WSL 分发名称(例如 `Ubuntu`)。
**WSL2 特定说明:** 从 Windows 端进行的编辑(例如,VS Code Windows 应用程序写入 WSL 文件系统)会静默绕过 Linux inotify - 守护进程永远不会看到事件。 [周期扫描](#periodic-sweep-wsl2--windows-side-edits) 通过在可配置的间隔(默认为 900 秒)内轮询来弥补这一点。如果您主要从 Windows 端编辑,请考虑降低 `sweep_interval_seconds` 以实现更快的跟进。
---
## 本体自定义
基础本体故意设计得非常通用。特定领域的语料库通过两种机制将词汇映射到 Contextd 的规范节点和边类型:
1. **内联别名**(`[ontology.aliases]` 在语料库 TOML 中)- 将特定领域的名称映射为规范节点类型。例如,`Registry = "Pattern"` 告诉 AI 推断的 `Registry` 节点应存储为 `Pattern`。
2. **覆盖文件**(`[ontology] overrides = "ontology.json"`)- 一个 JSON 文件,添加特定领域的边类型别名。所有别名都会在索引时针对基础本体进行验证;未识别的类型将被静默丢弃,不会存储。
适配器配置位于它们所描述的语料库旁边(按照惯例,`.contextd/` 目录位于语料库根目录下,包含 `corpus.toml`、`ontology.json` 和 `prompts/`)。`contextd add-corpus <path> --from <path>/.contextd/corpus.toml` 将模板的相对路径重写为从模板目录锚定的绝对路径,使适配器在不同机器上保持可移植。参见 [docs/ontology.md](docs/ontology.md) 以获取完整的自定义参考。
---
## 编写领域适配器
领域适配器教会 Contextd 特定语料库的词汇和结构。按照惯例,适配器位于与语料库根目录相邻的 `.contextd/` 目录中:
```
my-project/
├── .contextd/
│ ├── corpus.toml # 语料库配置,具有本体别名、glob 包含/排除模式、提示
│ ├── ontology.json # 领域特定边类型别名
│ └── prompts/
│ └── summary.md # 自定义摘要提示模板
├── docs/
│ └── spec.md
└── src/
└── ...
```
注册适配器:
```bash
contextd add-corpus /path/to/my-project --name my-project --from /path/to/my-project/.contextd/corpus.toml
```
`--from` 标志将模板的设置(本体覆盖、提示路径、包含/排除模式)复制到注册的语料库 TOML 中 `~/.contextd/corpora/my-project.toml`,将相对路径重写为从模板目录锚定的绝对路径。适配器在不同机器上保持可移植。
**何时使用适配器与纯 `add-corpus`:**
- **纯 `add-corpus`** - 默认本体和提示适用于通用 markdown、代码和结构化数据。
- **适配器** - 您的语料库有特定领域的词汇(例如,`Registry` 应该映射到 `Pattern`),您想要自定义摘要提示,或者您需要特定的包含/排除模式或章节级标题边界。
---
## 配置参考
Contextd 使用三个级别的配置,均位于 `~/.contextd/` 下:
| 路径 | 用途 |
|---|---|
| `~/.contextd/config.toml` | 全局配置 - 存储后端、提供者模型、推理设置、日志记录。由 `contextd init` 创建。 |
| `~/.contextd/corpora/<name>.toml` | 按语料库配置 - 根路径、glob 包含/排除粒度、本体别名和覆盖、MCP 工具、摘要覆盖。由 `contextd add-corpus` 创建。 |
| `~/.contextd/state/` | 运行时状态 - 检查点 JSON 每语料库、成本日志、会话日志。不可手动编辑。 |
关键全局配置字段(完整参考 [docs/architecture.md](docs/architecture.md)):
```toml
[storage]
backend = "neo4j" # "neo4j"(默认)或 "memgraph"
[providers]
# 推理提供者每个调用站点。每个独立选择 "gemini"
# (云)或 "openai_compat"(任何本地 OpenAI 兼容的 HTTP 服务器:
# Ollama、LM Studio、vLLM、LocalAI)。嵌入式保留为 Voyage。
summary = "gemini"
inference = "gemini"
translation = "gemini"
embedding = "voyage"
[inference]
summary_max_words = 100
[indexer]
debounce_seconds = 30 # 秒收集 FS 事件后 dispatching 批次
parallel_embedding_batches = 4
inference_concurrency = 1 # LLM 调用并行度(summarise+relate);5 是 Gemma 免费层的好
# 默认 15 RPM 配额
incremental_workers = 4 # 并发文件工作线程每增量批次
allowed_branches = [] # 白名单;空列表 = 允许所有分支
sweep_interval_seconds = 900 # 0 禁用;多久检查一次丢失更改
sweep_rate_sections_per_second = 0.017 # 预算率;默认 ≈ 1 section/minute
[logging]
level = "info"
format = "json"
path = "~/.contextd/logs/contextd.log"
max_log_bytes = 10485760 # 10 MB;0 = 无旋转
log_backup_count = 5
```
完整的语料库配置模式和 CLI 参考资料位于 [docs/cli.md](docs/cli.md)。
### 每语料库配置
每个语料库通过 `~/.contextd/corpora/<name>.toml` 文件进行配置,由 `contextd add-corpus` 创建。关键字段:
```toml
[corpus]
name = "notes"
root = "/path/to/notes"
include = ["**/*.md"] # glob 模式;默认为 ["**/*"]
exclude = ["drafts/**"] # 跳过要跳过的 glob 模式;默认为 []
granularity = "file" # "file"(默认)或 "section"
heading_min_level = 2 # section 模式:最浅的标题提升(默认为 2)
heading_max_level = 4 # section 模式:最深的标题提升(默认为 4)
[embedding]
model = "voyage-4-large" # 覆盖全局嵌入模型
chunk_tokens = 32000 # 每块最大令牌数(默认为 32000)
chunk_overlap = 200 # 块间重叠(默认为 200)
[ontology]
base = "default" # 基础本体名称
overrides = "ontology.json" # 领域特定边类型别名路径(相对于 TOML)
aliases = { Registry = "Pattern", Procedure = "Artifact" } # 节点标签别名
[mcp.tools]
# 每语料库 Cypher 工具;每个键成为 <corpus>.<key> 在 MCP 工具列表中
# my_query = "queries/my_query.cypher"
[summarization]
prompt_override = "prompts/summary.md" # 自定义摘要提示
max_words = 150 # 覆盖全局 summary_max_words
```
`include` 和 `exclude` 模式使用 glob 语法,`**` 用于递归匹配。`.git`、`.__venv`、`__pycache__` 和 `node_modules` 下的文件始终被排除,无论模式如何。
### 使用本地模型
`summary`、`inference` 和 `translation` 可以独立针对本地 OpenAI 兼容的 HTTP 服务器(Ollama、LM Studio、vLLM、LocalAI),而不是 Gemini。嵌入式保持为航行。一个常见的分割是将高容量摘要 + 关联流量推送到本地模型,并为 `contextd ask` 保留更高质量的 Gemini 翻译(Cypher 生成):
```toml
[providers]
summary = "openai_compat"
inference = "openai_compat"
translation = "gemini"
embedding = "voyage"
[providers.openai_compat]
base_url = "http://localhost:11434/v1" # Ollama 默认值
# api_key_env = "OPENAI_API_KEY" # 仅适用于需要令牌的服务器
model_summary = "qwen2.5:7b-instruct"
model_inference = "qwen2.5:14b-instruct"
model_translation = "qwen2.5:14b-instruct"
max_retries = 5
request_timeout_seconds = 120.0
json_mode = true # 为 summary+inference 调用站点发送 response_format JSON
```
质量底线推荐:关系推理调用站点发出严格类型边 JSON。低于 ~14B 参数的模型往往会经常失败 JSON 形状,从而减慢索引速度。Qwen-2.5-14B-Instruct 或 Llama-3.1-70B-Instruct 是可靠的选择;较小的模型仅适用于 `summary`。
**迁移现有配置。** 预-v0.2 的单行 `inference = "gemini"` 在 `[providers]` 下被替换为三行(`summary`、`inference`、`translation`)。Pydantic 将拒绝旧形状的 `extra fields not permitted` 错误 - 重命名,文件其余部分保持不变。
---
## 成本分析
每个文件(或分段模式下的每个章节)会触发两次 Gemini API 调用(总结 + 关联)并为每次启动贡献一个批量 Voyage AI 嵌入调用。在 `gemma-4-31b-it` 和 `voyage-4-large` 定价下:
- **每个文件:** 在典型情况下(短的 Markdown 文件)费用为几分钱;内容密集的文件可能会花费几美分。
- **典型的 100 个文件语料库:** 完整启动的费用为 $0.10–$1.00,具体取决于文件大小和摘要长度(`[inference] summary_max_words` 是主要控制因素)。
- **增量重新索引:** 对于文件粒度语料库,只有更改的文件会重新处理(MD5 哈希门控)。对于分段粒度语料库,只有更改的章节触发 LLM 调用 — 未更改的章节即使在修改的文件中也会跳过。持续成本与编辑率成比例,而不是语料库大小。
这些是一致性估计。确切的支出记录在 `~/.contextd/state/session-log/` 下的每个会话中。可以使用以下命令检查:
```bash
contextd costs
contextd costs --since 2026-04-01
```
---
## 安全
Contextd 是一个单用户本地工具。其安全态势反映了这一点:
- **API 密钥** (`GEMINI_API_KEY`, `VOYAGE_API_KEY`) 在进程启动时从环境变量中读取。Contextd 绝不会将它们写入磁盘。
- **图存储** 仅绑定到 `127.0.0.1:7687`。在默认的 compose 配置中,Neo4j 和 Memgraph 都不会暴露在环回接口之外。
- **MCP 只读保护。** `query_graph` 工具和所有每个语料库的 Cypher 工具在执行前都会通过 `assert_read_only`。该保护拒绝包含 `CREATE`、`MERGE`、`DELETE`、`SET`、`REMOVE`、`DROP`、`DETACH`、`FOREACH` 和具有副作用的 `CALL` 的 Cypher。这保护了免受通过 MCP 接口尝试写入图的提示注入攻击。
- **请勿将 Contextd 的 MCP 服务器暴露在网络上。** 它设计用于与本地运行的 MCP 客户端进行标准输入/输出传输。作为共享网络服务运行它超出了范围且未经测试。
---
## 故障排除
| 症状 | 原因 | 解决方法 |
|---|---|---|
| `contextd up` 失败并显示“Docker not found” | Docker 引擎未运行或未安装 | 启动 Docker Desktop 或安装 Docker 引擎;使用 `docker ps` 验证 |
| `contextd index` 挂起或非常缓慢 | Gemini API 速率限制(免费层 15 RPM) | 减少 `inference_concurrency`;或将 `summary`/`inference` 指向[本地模型](#使用本地模型)以避免配额限制 |
| `contextd ask` 返回空结果 | 语料库未启动,或查询不匹配索引内容 | 首先运行 `contextd index <corpus> --bootstrap`;尝试更简单的查询 |
| `contextd status` 显示“daemon not running”重启后 | 守护进程是一个 shell 子进程,而不是服务 | 安装 [systemd 单元](#在 systemd 下运行可选)以跨重启持续运行 |
| 守护进程在 git 提交时崩溃 | Git 临时文件(例如 `index.lock`)进入观察者管道 | 更新到最新版本;已在 `2b6d33d` 中修复 — 守护进程现在排除 `.git/` 事件并处理消失的临时文件 |
| 分段粒度索引跳过标题 | `heading_min_level` / `heading_max_level` 太窄 | 在语料库 TOML 中调整范围(默认:级别 2–4);级别 1 (`#`) 默认被排除 |
| `contextd index --incremental` 重新处理所有内容 | 图早于 `inferred_at` 标记迁移 | 运行 `contextd index <corpus> --bootstrap` 一次 — 迁移 `_0004` 填充现有节点上的标记 |
| 虚构或重复的 `Section` / `File` 节点混淆 `query_graph` / `related` / `inbound` 结果 | 旧图积累了无路径的 `Section` / 无哈希的 `File` 存根(推理曾经为每个边缘目标创建一个节点),并且在不同操作系统下索引时混合使用 `\` 和 `/` 路径标识 | 更新到最新版本并运行 `contextd up` — 迁移 `_0005` 清除了虚构的存根(及其悬垂边缘)并将路径标识规范化为正斜杠。从现在开始,索引器将推断出的 `File` / `Section` 目标解析为现有节点,而不是创建存根 |
| Neo4j 容器无法在端口 7687 上启动 | 另一个后端已经绑定到该端口 | 首先运行 `contextd down`,然后在配置中切换 `backend` 并运行 `contextd up` |
| Windows 的编辑不触发重新索引 | WSL2 inotify 看不到 Windows 端的写入 | 在配置中降低 `sweep_interval_seconds`(参见 [periodic sweep](#periodic-sweep-wsl2--windows-side-edits));或从 WSL 触摸文件 |
| 大型 MCP 搜索 payloads | 结果中包含嵌入向量(`96c409a` 之前) | 更新到最新版本;`search` 工具现在从结果中剥离嵌入向量 |
---
## 路线图 / 已知空白
设计并部分构建但尚未连接或发布的功能:
| 空白 | 详情 |
|---|---|
| **混合搜索** | `search()` 仅限全文。向量相似性回退(混合排名)被推迟;需要向量空间匹配的调用者可以暂时直接调用 `GraphStore.vector_search`。 |
| **`CONTEXTD_INFERENCE_DAILY_BUDGET`** | 设计指定了每日 Gemini 调用上限的环境变量。未实现;通过 `contextd costs` 的手动成本监控是当前的保护措施。 |
| **每个语料库的 MCP 工具 `$` 误报** | `extract_placeholders` 使用简单的正则表达式,并将在 Cypher 字符串字面量中匹配 `$` 作为虚假参数。正确的 Cypher 标记化被推迟。 |
| **分段刷新时的陈旧 `CheckpointStore` 条目** | 每个文件的检查点记录阶段级完成(总结,关联)但不是每个分段的粒度。在差异重新索引仅更新某些分段后,检查点仍然反映之前的整个文件状态。通过 `contextd status` 报告将显示旧的完成时间,直到下一次完整的启动。 |
---
## 贡献
请参阅 [CONTRIBUTING.md](CONTRIBUTING.md),了解开发设置、CI 强制执行的架构不变性以及提交消息约定。
---
## 许可
Contextd 在 MIT 许可下发布。请参阅 [LICENSE](LICENSE)。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器