repo-map:本地仓库地图
机制概览(三层识别链路)
| 层 | 载体 | 覆盖场景 | 可靠性 |
|---|---|---|---|
| L1 | UserPromptSubmit hook 字符串匹配 | 用户提到明确仓库名 | 确定性 100% |
| L2 | 项目 CLAUDE.md「## 关联仓库」节 | 确认过的结构性老关系(选择性沉淀) | 常驻上下文 |
| L3 | 全局 CLAUDE.md 一行规则 + resolve 命令 |
模糊指代("那个缺陷分级服务") | 尽力而为兜底 |
核心不变量:缓存是纯产物,人永远不维护。每次 resolve/list/sync 先做低成本一致性检查(find .git 清单 vs 缓存路径集),差集增量分析、死路径自动剪枝。
自愈由谁触发,决定了"新仓库多久进地图":
| 触发方式 | 时机 | 覆盖 |
|---|---|---|
resolve/list 内置同步 |
用户手动跑命令那一刻 | 被动,不跑就不更新 |
launchd 定时 sync(可选,冷启动询问) |
每 N 秒自动 | 主动,新仓库无需任何动作 |
sync 比 list 的增量多做一件事:重析未成型仓库(无 remote 或无提交)。刚 git init 的仓库会被记成"本地·可写"且无摘要,等它配好远端、有了首次提交,必须复查才能修正角色——纯增量永远不会回头看已存在路径,这是定时任务存在的真正理由。
组件:scripts/repo_map.py(scan/resolve/list/sync/schedule)+ scripts/prompt_hook.py(hook)。脚本就地运行于 skill 目录,不复制副本(单一事实源)。
安装步骤(幂等,可重复执行)
1. 前置检查
python3 --version、git --version、~/.claude/settings.json 存在。任一缺失即停下说明,不带病安装。
2. 探测扫描根,与用户确认
- 从历史 session 反推:
ls ~/.claude/projects/的目录名即历史 cwd(-分隔),统计高频路径前缀。 - 辅助验证:对候选前缀
find <root> -maxdepth 6 -name .git | head确认确实有仓库群。 - 把探测结果(根目录 + 预计仓库数)报给用户确认后再写配置。扫描根是配置不是数据——一年变不了一次,手工列出最诚实。
3. 身份引导,写配置 ~/.claude/repo-map.config.json
配置含个人邮箱与组织域名,是机器级私密文件:绝不提交进任何仓库(本仓库 .gitignore 已设防线),只存在于 ~/.claude/。
首次安装(配置不存在)时不要让用户手打邮箱,先探测候选、再让用户选择(选择优于输入):
- 候选邮箱来源:
git config --global user.email,再抽 3-5 个扫描根内仓库统计高频作者(git log --format=%ae | sort | uniq -c | sort -rn | head)。 - 候选 host 来源:对扫描根内仓库的 remote host 做频次统计。
- 用选择题(AskUserQuestion,多选 + 其他手动输入出口)确认:哪些邮箱是"你本人"(含开源身份如 GitHub noreply 邮箱——漏了会导致自己的开源仓库被误判为第三方只读);哪个 host 是组织内部的。
{"scan_roots": ["~/code"],
"self_emails": ["a@x.com", "you@users.noreply.github.com"],
"trusted_hosts": ["gitlab.your-org.com"]}
- 角色判定证据链:有本人 commit → 自研·可写;无 remote → 本地·可写(不存在上游,谈不上第三方);host 可信 → 协作·可写;其余 → 第三方·只读。
4. 首次全量扫描
python3 <skill目录>/scripts/repo_map.py scan,报告仓库总数、自研/第三方比例、耗时。
5. 注册 hook(修改 settings.json 前先备份)
cp ~/.claude/settings.json ~/.claude/settings.json.bak-<日期>- 向
hooks.UserPromptSubmit合并追加(绝不覆盖已有条目):
{"matcher": "*", "hooks": [{"type": "command", "command": "python3 <skill绝对路径>/scripts/prompt_hook.py"}]}
- 用 python json 读改写,不用文本替换;改完
python3 -m json.tool校验合法性。
6. 定时增量更新(冷启动必问一次,之后幂等跳过)
先探测状态,已开启就直接跳过本步、不要重复打扰:
python3 <skill目录>/scripts/repo_map.py schedule status # exit 3 = 未开启
未开启时用 AskUserQuestion 问用户(不要替用户决定,这会往系统里装一个常驻后台任务):
是否开启定时增量更新?开启后新建的仓库会自动进地图,无需手动跑命令。
- 开启·每小时(推荐):
schedule on。增量同步实测亚秒级,开销可忽略。- 开启·每天一次:
schedule on 86400。新仓库最长隔一天才进地图。- 不开启:靠
resolve/list被动同步;新建仓库后需手动跑一次list。
选择开启则执行 schedule on [间隔秒],它会写 ~/Library/LaunchAgents/com.claude-code.repo-map-sync.plist 并 launchctl bootstrap 加载(RunAtLoad 注册即跑一次,可立刻验证)。plist 里的解释器固定为 /usr/bin/python3——launchd 不继承 shell PATH,更不跑 pyenv 初始化,写 python3 必然找不到。
仅 macOS。非 darwin 平台 schedule 会明确报错并提示改用 cron 调 sync 子命令,不做静默降级。
7. 全局 CLAUDE.md 追加规则节(已存在则跳过)
定位真实文件(注意软链:readlink 确认目标),追加:
## 跨仓库协作(repo-map)
- 任务涉及其他本地仓库时,用 `python3 <skill绝对路径>/scripts/repo_map.py resolve <名称>` 解析路径与读写角色;"第三方·只读"仓库禁止写入。
- 为 A 仓库任务修改 B 仓库时,改动在 B 仓库独立成 commit、独立授权,不与 A 仓库改动混杂。
- 关联沉淀遵循"宁可漏掉,不能沉淀错":仅当本次真实读取/修改了该仓库、且关系会复发(依赖/对接/上下游)时,才向用户一句话确认后幂等追加到当前项目 CLAUDE.md 的「## 关联仓库」节(名称、路径、角色、一句话关系);仅提及、误提及、一次性查询、cwd 为全局配置目录一律不沉淀——漏掉的成本≈0(hook 每次都会重新命中),沉淀错会长期误导决策。
8. 验证(铁律:不验证不得称完成)
全部真跑并收集输出:
- resolve 命中:挑一个真实仓库名
resolve <名>,返回路径与角色。 - resolve 未命中:
resolve 不存在的名字,exit code 2 + 提示语。 - 增量自愈:
git init一个临时新仓库到扫描根内 →list应出现;删除它 →list应剪枝消失。 - hook 命中:构造 stdin JSON 模拟真实输入(
echo '{"prompt": "参考一下 <真实仓库名>", "cwd": "/tmp"}' | python3 .../prompt_hook.py),应输出 additionalContext。 - hook 中英相邻:
"跟<仓库名>对接"(无空格)应命中。 - hook 误报:泛词(如 "kubernetes" 不应命中 kube 类短名仓库)、以及不含仓库名的普通输入,应零输出。
- hook 自身排除:cwd 在某仓库内且 prompt 提到该仓库,不注入。
- 性能:
time ... list(增量路径)应在秒级;hook 单次应在 1s 内(含 python 解释器启动)。 - 判定抽查:抽 ≥10 个仓库人工核对自研/第三方判定,报告准确率与误判原因。
开启定时任务时,追加三项由 launchd 真实触发的端到端验证(用 launchctl kickstart gui/$(id -u)/com.claude-code.repo-map-sync 立即触发,不要手跑脚本冒充):
- 自动纳入:扫描根内新建裸仓库 → kickstart → 缓存出现该仓库。
- 定型刷新:给它配 remote + 首次提交 → kickstart → 角色/摘要/活跃日期被修正(这一项证明"未成型重析"生效,纯增量会在此失败)。
- 自动剪枝:删除该仓库 → kickstart → 缓存中消失;全过程日志见
~/.claude/logs/repo-map-sync.log。
9. 汇报
仓库数与角色比例、各验证项结果、日志位置(hook ~/.claude/logs/repo-map-hook.jsonl、定时 ~/.claude/logs/repo-map-sync.log)、定时任务开关状态、误判仓库清单(如有)。
日常使用
- 自动:直接在输入里提仓库名,hook 注入解析结果,无需任何命令。
- 手动:
resolve <关键词>/list/scan(仅在怀疑缓存脏时才需要 scan)。 - 检出状态标注(防跨仓库读到任务分支残留):目标仓库非 master/main 检出时,hook 在命中行打【⚠️ 检出在 <分支>】标(纯文件读
.git/HEAD,零 subprocess,不破毫秒级契约),resolve 额外给 ahead/behind 与脏净全量状态;两处都附读取建议——用git -C <路径> show origin/master:<相对路径>读主分支内容,或先在该仓库归位(repo-tidy)。 - 定时任务:
schedule status查状态与最近一次同步;schedule off关闭;schedule on [间隔秒]改间隔(幂等重装)。 - 换机器/新根目录:编辑 config 的
scan_roots后跑一次scan。
卸载
先 schedule off(注销 launchd 任务并删 plist),再删除 settings.json 中对应 hook 条目、全局 CLAUDE.md 的「跨仓库协作」节、~/.claude/repo-map.config.json、~/.claude/repo-map-cache.json、~/.claude/logs/repo-map-{hook.jsonl,sync.log},最后删 skill 目录。
注意顺序:先注销定时任务再删 skill 目录,否则 launchd 会按分钟级重试一个已不存在的脚本,在日志里刷错误。
已知边界
- 仅支持 macOS/Linux(依赖 git 与 POSIX 路径约定);
schedule子命令仅 macOS(launchd),Linux 需自行用 cron 调sync。 - 定时
sync只重析"未成型仓库"(无 remote 或无提交)。已定型仓库后来换了 remote、改了 README 首行或转手他人,不会自动刷新——这类变更靠手动scan(全量约 25s/191 个仓库)。这是刻意的成本权衡:定时全量意味着每次上千次 git 调用,而定型后的元信息几乎不变。 - launchd
StartInterval在机器睡眠期间不触发;唤醒后补跑一次,不累积。合盖一周再打开,地图会在唤醒后数秒内追平。 - hook 匹配的是封闭名字集合;模糊指代走 L3 兜底,漏了退回手贴路径一次。
- 只读硬闸(PreToolUse 拦截写只读仓库)为可选增强,本 skill 不默认安装——误写第三方仓库可用 git 恢复,成本中等。
- hook 注入的仓库职责摘要取自各仓库 README 首行(截断 80 字符)。第三方仓库的 README 首行会进入对话上下文,理论上存在提示注入面;克隆来源不可信的仓库时留意其 README 首行内容。