agentsope/agentsop-repo-map
>-
npx skills add https://github.com/agentsope/SkillAlchemy --skill agentsop-repo-map
> 一句话:tree-sitter 抽取符号 → 在跨文件引用图上跑 PageRank → 按 token 预算把最重要的 class/function 签名作为只读地图塞进上下文。它不是 RAG、不维护向量索引、可被人审。Aider 用同样的机制在 SWE-Bench Lite 上把"正确文件命中率"打到 70.3% [aider.chat/2024/05/22/swe-bench-lite.html]。
这是一个工具技能(tool skill),不绑定 Aider;任何 coder-agent harness 只要能给 LLM 喂上下文,都可以接入或自建 repo-map。
下列任一条件成立时,将"构建/刷新/使用 repo-map"作为该会话的标准动作:
/map 一样的 dump 必须能给人看。不应激活的反面信号:单文件改动且文件已知;任务是从零起项目;二进制资产仓库;非源代码(CSV/data lake)—— 详见 §6。
+----------------------+ +-----------------------+ +-----------------------+
| 1. tree-sitter | | 2. cross-file graph | | 3. token budget |
| symbol extraction |-->| + PageRank-style |-->| (dynamic, shrink |
| | | importance rank | | when files added) |
| - parse, no execute | | - nodes = files | | - default ~1k tokens |
| - class / fn / sig | | - edges = symbol refs | | - cap configurable |
| - works offline, | | - PageRank picks | | - 0 = disabled |
| no LLM call | | "most referenced" | | |
+----------------------+ +-----------------------+ +-----------------------+
|
v
+-----------------------------------+
| Output: skeleton text in prompt |
| (NOT a tool-call, NOT a vector) |
| |
| path/to/file.py: |
| class Auth: |
| def login(user, pwd) -> T |
| def logout() -> None |
| def hash_password(pwd) -> str |
+-----------------------------------+
不变量 A — Skeleton over snippets:地图只放 签名/类名/函数名,不放函数体。LLM 真要看实现,就让它点名要文件——这是 read-only navigation aid,不是 retriever。
> "If it needs to see more code, the LLM can use the map to figure out which files it needs to look at." [aider.chat/docs/repomap.html]
不变量 B — Dynamic budget:当对话还没加载任何文件时,地图展开到上限(给 LLM 最多导航信息);一旦真的把文件加进可读写上下文,地图自动收缩(省下的 token 让给真代码)。
> "Aider adjusts the size of the repo map dynamically based on the state of the chat." [aider.chat/docs/repomap.html]
| 维度 | Repo-map (tree-sitter + PageRank) | Embedding RAG |
|---|---|---|
| LLM 可读性 | 真签名,LLM 能直接推理 | 向量,LLM 看不懂;只能信检索器选出的片段 |
| 索引维护 | 无;每次会话按需重建 | 需要 chunker + embedder + 向量库 + 失效策略 |
| 确定性 | 同代码同输入 → 同地图 | embedding 模型/参数变化 → 检索结果漂移 |
| 可审计 | 一段纯文本,能 cat、能 diff、能给 reviewer 看 | 黑盒:哪些 chunk 被选不直观 |
| 出仓风险 | 全本地静态分析 | 通常调用外部 embedding API |
| 失败模式 | tree-sitter 不支持该语言 → 优雅降级 | 语义距离 ≠ 调用关系,错召回 |
核心判据:编辑代码的瓶颈是"找到要改的文件",不是"找到语义相近的段落"。LLM 在签名级别上做"我该改哪里"的推理远比让向量替它推理强。
> Aider's repo-map "successfully identified the correct file to edit in 70.3% of the benchmark tasks." 这一数字不依赖 embeddings、不依赖代码执行、不依赖网络 [aider.chat/2024/05/22/swe-bench-lite.html]。
| 层 | 内容 | 写权限 |
|---|---|---|
| 系统/编辑格式 | harness 固化 | harness |
| Repo-map(本技能) + read-only files + CONVENTIONS | 只读上下文 | 人/agent 配置 |
| Read-write files | LLM 唯一允许编辑的 | 人/agent 显式加入 |
铁律:repo-map 是地图,不是写集合。LLM 看见某个文件在地图里 ≠ 它能编辑它。写集合永远只由人/agent 显式声明(在 Aider 里是 /add;在自建 harness 里是"可编辑文件白名单")。这条边界是 repo-map 安全使用的前提。
> "Above about 25k tokens of context, most models start to become distracted." [aider.chat/docs/troubleshooting/edit-errors.html]
repo-map 本身就在 token 预算里。如果地图占太大,反而稀释了真正的源码上下文。所以预算需要"够找路 + 不挤压代码"。经验起点:1k–4k tokens;monorepo 上限 8k;超过就要靠子目录/ignore 文件先缩小搜索范围(§3 Phase 4)。
| 选 skeleton | 选 snippets |
|---|---|
| 全仓导航、定位修改点 | 已锁定 ≤5 个文件、想看实现细节 |
| Token 预算紧 | 真的需要函数体语义 |
| 多语言混合 | 单语言、深度分析 |
skeleton 给"哪儿",snippets 给"怎么"。永远先 skeleton,再 snippets——倒过来会把预算烧光、还没找到对的文件。
1. 确认仓库类型:源码 + git 历史。否则不要用 repo-map(见 §6)。
2. 应用 ignore 列表:
- .gitignore(必)
- 自定义 ignore(如 Aider 的 .aiderignore;自建 agent 可直接复用)
- 默认排除:vendor/, node_modules/, dist/, build/, *.min.js, generated/
3. 选定 tree-sitter 语言集:
- 主流(Python/JS/TS/Go/Rust/Java/C/C++/Ruby/PHP/...) 默认开
- 小众语言:要么接 grammars,要么留作 fallback(只列文件路径)
4. 设定 token 预算:
- 小仓库(<200 文件): 1k–2k
- 中等(200–2000 文件): 2k–4k
- Monorepo(>2000 文件): 4k–8k + 限定子目录
5. 首次运行:构建符号表 + 引用图 + PageRank。后续增量更新。
输出物:一段纯文本骨架(path + 类/函数签名),符合 token 预算。
反模式:人类拍脑袋决定加哪些文件 → 漏文件、加多文件。
正确模式:把"定位"问题外包给 LLM + repo-map。
[harness 提供给 LLM 的上下文]
- system prompt
- repo-map skeleton (read-only)
- task: "Add JWT-based auth replacing session cookies"
[LLM 输出]
- 候选目标文件: src/auth.py, src/middleware.py, tests/test_auth.py
- 候选只读引用: src/config.py, docs/auth.md
然后 harness 才把 LLM 命名的文件真正加入写集合。这一步是 repo-map 的唯一调用价值:把"navigation"从人脑卸载给 LLM。
| 触发 | 动作 |
|---|---|
| 会话开始 | 全量构建 |
| 文件被编辑(包括 LLM 自己改的)| 增量更新该文件的符号表 |
| 用户切到不同子目录 | 局部重建(限定 root) |
| LLM 报告"找不到 X 函数"但 X 应该存在 | 强制刷新(如 Aider 的 --map-refresh always)|
| 大规模重构(rename across files)| 重构完成后全量重建一次,避免地图与现实漂移 |
关键认知:地图过期的代价不是错,是幻觉——LLM 会以为某符号还存在/还在某处。每次 LLM 报告"找不到 X"时,第一反应是"地图过期了吗?",第二反应才是"真的不存在吗?"
地图 > 8k token 仍然稀释信号?这是仓库太大、问题太宽的信号,不是地图的问题。逐步收缩:
1. Subtree-only: 把工作目录定到 packages/feature-foo/,只对这棵子树建图。
2. Ignore 扩展: 把 generated/、proto/、vendored/、test fixtures 全 ignore。
3. Domain split: 一次会话只覆盖一个领域(auth / payment / search 三选一)。
4. Map-tokens 0: 已经知道改哪些文件 → 直接关闭地图,节省所有 token 给代码。
5. 拆任务: 大需求拆成多会话,每会话一个收敛子目标。
判断阈:如果你不能在一句话内描述"这次任务影响哪一类模块",那 repo-map 帮不了你——先用 /ask 类讨论 narrow 任务范围,再激活地图。
repo-map 的最大优势是可以打印给人看:
/map (Aider) 或等价的 dump_map() 钩子:用于 reviewer 复现"LLM 看到了什么"。intermediate/repo-map-<sha>.txt,作为 PR 附件——比 "trust the agent" 强。每条操作给 Trigger / Action / Output / Evidence。命令名是参考,实际接口取决于你的 harness。
map.build(root, budget, ignore) 构建/重建root 下所有匹配 tree-sitter 的源文件 → 抽符号 → 建跨文件引用图 → 跑 PageRank → 按 budget 截取顶部 → 渲染 skeleton。path:\n class X:\n def foo(a: T) -> U。map.refresh(files) 增量刷新files 重抽符号,更新引用图边集,重跑 PageRank(局部)。--map-refresh 文档;动态预算"adjusts ... based on the state of the chat" [aider.chat/docs/repomap.html]。map.scope(subtree | glob) 缩范围subtree 内或 glob 命中的文件上,根之外的文件最多保留路径不渲染签名。--subtree-only;.aiderignore。map.print() / dump 可审计intermediate/repo-map-<sha>.txt。/map。"deterministic & inspectable"(地图是确定性产物,不是模型采样)。map.locate(task_description) 让 LLM 找文件task_description + repo-map 喂给 LLM,要求输出"目标文件 + 引用文件"两组路径。不让 LLM 直接编辑,只让它命名。map.budget_set(N) / map.disable() 调预算--map-tokens 等价物。N=0 等于完全关闭。--map-tokens;25k 阈值 [aider.chat/docs/troubleshooting/edit-errors.html]。map.ignore_add(patterns) 加排除node_modules / generated/*.pb.go 类垃圾撑大。.aiderignore / 等价物);下次构建生效。map.diff(prev, curr) 地图差分触发:monorepo 10k+ 文件;首次 map.build 出来的 skeleton 接近 8k token,主对话快要破 25k。LLM 开始忽视细节、编辑跑偏。
症状:
/tokens(或等价物)显示 map 占比 > 40%。决策树(按代价递增):
| 步 | 动作 | 何时停止 |
|---|---|---|
| 1 | map.ignore_add(generated/, vendor/, *.pb.*) | 大宗噪声砍掉后预算降到 4k 以下 |
| 2 | map.scope(packages/foo) 限子树 | 你确知改动只在该子树 |
| 3 | map.budget_set(2k) 直接砍预算 | 你愿意接受"少看签名换出空间" |
| 4 | map.locate(task) 让 LLM 先用大图找一次目标文件,然后 map.disable() + 只保留这些文件 | 一旦锁定写集合 |
| 5 | 拆任务、新开会话 | 单会话扛不动 |
反模式:把地图开到 16k 期望"看全"——LLM 会被噪声呛死。地图不是越大越好;它的价值是"navigation precision per token"。
为什么这有效:repo-map 的 PageRank 已经在按重要性排序,预算砍掉的是长尾低相关符号,不是核心 API。再加 ignore 排除生成代码,信噪比改善是非线性的。
触发:LLM 给出 diff,但目标文件根本不是用户想改的;或更糟,编造了不存在的路径。
症状:
根因诊断:
| 现象 | 根因 | 修复 |
|---|---|---|
| 路径不在地图 | 地图覆盖不够 / 该路径被 ignore / 文件刚加未刷新 | map.refresh / 缩窄 ignore / 检查 tree-sitter 是否支持该语言 |
| 路径在地图但 LLM 选错 | 地图够,写集合策略错:让 LLM 自己挑写哪个 | 改流程:让 LLM 命名候选,人/agent 决定写集合(Op 5) |
| 地图过期(昨天那次会话留下) | 上次大重构后未刷新 | map.build 全量重建 |
| 多个同名 class/function | PageRank 无法区分谁更"正确" | 增加 read-only context(CONVENTIONS / 模块 README)澄清;或 map.scope 限定 |
决策规则:
评估指标(值得加进 harness):
(LLM 命名的目标文件 ∩ 实际应改文件) / 实际应改文件。Aider 在 SWE-Bench Lite 上是 70.3%——你的 harness 应该作为下限基准。LLM 命名但仓库不存在的路径 / LLM 命名总数。> 5% 说明地图过小或过期。触发:repo 是 Python + Rust + 一份 Terraform + 一份 Bash。tree-sitter 主流语言抽得出符号,HCL 和 Bash 抽不出。
决策:
触发:LLM 每分钟编辑一次,每次全量重建 PageRank 太慢。
决策:
| 场景 | 替代 |
|---|---|
| 单文件已知任务 | 直接 /add 该文件,map.disable() |
| 从零起项目 | 没有现有符号可建图 |
| 二进制 / 数据仓库 | tree-sitter 不解析;用 schema/metadata 替代 |
| 不允许静态分析的合规环境 | 罕见但存在(敏感代码);退回手动文件清单 |
| 任务跨多仓库 | repo-map 是单仓原语;多仓需要更高层"项目地图"编排 |
| 自然语言/文档仓库(pure markdown) | tree-sitter 没有意义;用文件树 + headings 索引 |
map.print() 是工程师工具,不是装饰。每次决策可疑都该 dump 一次。map.scope 限定到当前工作 package。--read 额外提示,或人工指定文件)。| 维度 | repo-map (Aider) | Embedding RAG (LlamaIndex/Chroma + 代码 chunker) | Cursor codebase index | Cline file tree + 按需读 |
|---|---|---|---|---|
| 抽取方式 | tree-sitter 符号 | 文本 chunk + embedding | 闭源(多数推测含 embedding + AST) | 不抽取;只列文件树 |
| 选择算法 | PageRank on import graph | cosine similarity / hybrid BM25 | 闭源 | LLM tool-call 现场读 |
| LLM 看到的 | 真签名 skeleton | 文本片段 | UI 内部使用 | 文件树 + LLM 主动 read_file |
| 索引存储 | 无(内存重建) | 向量库(持久) | 云端 | 无 |
| 索引更新 | 文件 mtime 增量 | 需 re-embed | 后台同步 | 不需要 |
| 出仓数据 | 否(全本地) | 通常调外部 embedding API | 是(代码上云) | 否 |
| 可审计 | 文本 dump | 难(chunk 排序不直观) | 黑盒 | tool-call 历史可读 |
| 失败模式 | 长尾符号被砍 | 语义近 ≠ 调用关系,错召回 | 不可知 | 多轮 tool-call 拖慢 + 上下文膨胀 |
| 多文件命中率(公开数据) | 70.3% (SWE-Bench Lite) | 无对应公开基准 | 无公开 | 无公开 |
| 你的约束 | 选 |
|---|---|
| 不能出仓、需要可审计、tree-sitter 覆盖你的语言 | repo-map |
| 文档/规格/non-code 大量参与 | Embedding RAG(或 repo-map + RAG 并存) |
| 你住在 VS Code、要 UI 体验、能接受闭源/上云 | Cursor codebase index |
| 你要 step-by-step tool-call 审批 | Cline 文件树 + read_file(牺牲 token 换可控) |
| 你在写完全自主 agent | repo-map 作为 base + agent 的 plan 阶段查它(OpenHands 类思路) |
repo-map 和 RAG 不互斥:
合理组合:repo-map 决定 write-set,RAG 提供 read-only references。不要倒过来——让 embedding 决定写集合是已知的失败模式(Aider 实验得到的负面证据)。
主要:
派生:
references/R1-source-evidence.md — 来源逐条 quotereferences/R2-cross-tool-comparison.md — 跨工具实现对比详表intermediate/operation_candidates.json — 操作模型抽取过程跨工具:
Take agentsope/agentsop-repo-map from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.