Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Symbol-level code context for LLM coder-agents: tree-sitter extracts symbols, PageRank ranks them over the cross-file reference graph, and the top class/function signatures are fed to the LLM as a token-budgeted read-only map (not RAG, no vector index, human-auditable). Use when an agent must locate the right files in a large/multi-file repo, when builds/refreshes/scopes a repo-map, or when "model edits the wrong file" needs fixing.
.claude/skills/agentsope-agentsop-repo-map/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 206% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 294% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 265% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 128% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 655% | 0% |
> 一句话: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 — 操作模型抽取过程跨工具:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | pass→pass | 4,856 | 5,150 | +6% | 1 | 1 | 0% | 736 | 7,567 | +928% | 0 | 0 | — |
case-01 | fail→fail | 8,321 | 8,246 | -1% | 1 | 1 | 0% | 1,328 | 7,944 | +498% | 0 | 0 | — |
case-03 | fail→pass | 18,951 | 15,806 | -17% | 1 | 1 | 0% | 2,977 | 9,096 | +206% | 0 | 0 | — |
case-04 | pass→pass | 9,694 | 7,222 | -26% | 1 | 1 | 0% | 1,532 | 7,935 | +418% | 0 | 0 | — |
case-05 | fail→pass | 14,034 | 11,980 | -15% | 1 | 1 | 0% | 2,187 | 8,624 | +294% | 0 | 0 | — |
case-06 | fail→pass | 18,558 | 22,626 | +22% | 1 | 1 | 0% | 2,823 | 10,302 | +265% | 0 | 0 | — |
case-07 | fail→pass | 20,357 | 2,693 | -87% | 1 | 1 | 0% | 3,159 | 7,214 | +128% | 0 | 0 | — |
case-08 | pass→pass | 13,257 | 14,204 | +7% | 1 | 1 | 0% | 1,971 | 8,817 | +347% | 0 | 0 | — |
case-09 | pass→pass | 16,056 | 16,333 | +2% | 1 | 1 | 0% | 2,383 | 9,296 | +290% | 0 | 0 | — |
case-10 | pass→pass | 8,532 | 9,355 | +10% | 1 | 1 | 0% | 1,299 | 8,257 | +536% | 0 | 0 | — |
case-11 | fail→pass | 7,794 | 9,203 | +18% | 1 | 1 | 0% | 1,086 | 8,196 | +655% | 0 | 0 | — |
case-12 | fail→pass | 16,152 | 13,228 | -18% | 1 | 1 | 0% | 2,644 | 8,889 | +236% | 0 | 0 | — |
case-13 | pass→pass | 8,606 | 7,343 | -15% | 1 | 1 | 0% | 1,326 | 7,960 | +500% | 0 | 0 | — |
case-14 | fail→pass | 15,472 | 16,398 | +6% | 1 | 1 | 0% | 2,511 | 9,441 | +276% | 0 | 0 | — |
case-15 | pass→pass | 17,387 | 18,681 | +7% | 1 | 1 | 0% | 2,573 | 9,664 | +276% | 0 | 0 | — |
case-16 | pass→pass | 6,550 | 6,679 | +2% | 1 | 1 | 0% | 1,027 | 7,875 | +667% | 0 | 0 | — |
case-17 | pass→pass | 11,834 | 9,389 | -21% | 1 | 1 | 0% | 1,725 | 8,182 | +374% | 0 | 0 | — |
case-18 | pass→pass | 13,095 | 11,532 | -12% | 1 | 1 | 0% | 2,062 | 8,532 | +314% | 0 | 0 | — |
case-19 | pass→pass | 3,263 | 3,314 | +2% | 1 | 1 | 0% | 454 | 7,278 | +1503% | 0 | 0 | — |
case-20 | fail→pass | 34,743 | 16,381 | -53% | 1 | 1 | 0% | 1,392 | 9,291 | +567% | 0 | 0 | — |
case-21 | fail→pass | 18,604 | 22,909 | +23% | 1 | 1 | 0% | 2,833 | 10,228 | +261% | 0 | 0 | — |
case-22 | pass→pass | 12,224 | 5,794 | -53% | 1 | 1 | 0% | 1,933 | 7,629 | +295% | 0 | 0 | — |
case-23 | pass→pass | 14,144 | 7,675 | -46% | 1 | 1 | 0% | 2,091 | 7,815 | +274% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 22 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +39 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.