agentcodemap
Tree-sitter harness для навигации и поиска по коду, рассчитанный на LLM-агентов.
Возвращает компактные, машиночитаемые срезы кода вместо целых файлов.
Установка: uv tool install agentcodemap. Команда после установки — codenav.
Проверенное фактическое поведение и ограничения: аудит CLI.
Команды
Общий приём: команды, которые сканируют каталог-корень (symbol, impact,
trace, path, info, grep, astgrep, doctor), принимают несколько корней одним флагом
--root DIR.... Индексируются только перечисленные каталоги — соседние
директории того же уровня с кодом не попадают в поиск. Например,
--root project tests ищет ровно в project/ и tests/, игнорируя прочие
директории текущего уровня. По умолчанию корень — текущий каталог.
codenav outline PATH... [--top-level] [--deps] [--lines] [--filter REGEX...] [--max-chars N] [--pages SPEC]
PATH — файл или каталог (каталог обходится рекурсивно, outline каждого модуля). Компактная карта символов файла:
$ codenav outline project/mymodule.py
project.mymodule:
A MY_MODULE_ATTR
F my_func
C MyClass
A my_attr
M my_method
Модули без символов не выводятся. Дандеры не печатаются: магические методы
(__init__, __repr__, __call__ и т.п.) и атрибуты-метаданные (__all__,
__version__) — служебная механика, а не карта кода (магические методы при
этом остаются в связях impact/trace).
Буквы: C класс, M метод, F функция, A атрибут/константа/тип.
--lines добавляет L<start>-<end> к каждой записи.
--top-level сокращает вывод до верхнеуровневых символов модуля: вложенные
члены (атрибуты, методы классов, внутренние классы/функции) не печатаются.
Например, тот же файл с этим флагом:
$ codenav outline project/mymodule.py --top-level
project.mymodule:
A MY_MODULE_ATTR
F my_func
C MyClass
Флаг сочетается с --lines и действует на каждый модуль при обходе каталога.
Флаг --deps печатает под каждым символом его зависимости — символы, на которые
ссылается тело этого символа, в виде -> имя [виды] (виды — короткие метки
типов связи: call, inh, par, ref, ret, str, см. «Типы связей»). Ссылки
разрешаются по индексу репозитория, поэтому связи видны и между файлами;
зависимость приписывается ближайшему символу, который её содержит: класс не
повторяет зависимости своих методов. Для PATH-файла индексируется содержащий
его каталог. По умолчанию под каждым символом печатается одна строка; флаг
--deps добавляет под ним зависимости:
$ codenav outline project/mymodule.py --deps
project.mymodule:
A MY_MODULE_ATTR
F my_func
-> MY_MODULE_ATTR [ref]
C MyClass
A my_attr
M my_method
-> MyClass.my_attr [ref]
Зависимости сочетаются с --top-level (тогда показаны зависимости только
верхнеуровневых символов) и --lines. Символы без зависимостей печатаются
как обычно, лишних строк нет.
Порядок и пагинация рассчитаны на большие проекты: модули сортируются от корня
вглубь (сначала файлы в корне PATH, затем на один уровень глубже и т.д.) и
нарезаются на страницы по --max-chars символов (по умолчанию 10000; строки
никогда не разрезаются, разрыв страницы проходит между модулями). По умолчанию
команда печатает первую страницу и сообщает, сколько страниц всего и как
запросить остаток:
(page 1 of 4; 3 more: --pages 2-4)
--pages SPEC печатает выбранные страницы за один вызов: отдельный номер,
диапазон или список (--pages 3, --pages 2-4, --pages 1,3; флаг можно
повторять — страницы объединяются и печатаются по возрастанию). Так после
первого запроса агент сразу берёт весь вывод:
$ codenav outline src --pages 2-4
Каждая страница заканчивается строкой (page N of M; …): последняя страница —
(page M of M), остальные — с подсказкой, сколько частей и каким SPEC осталось
запросить. Если один модуль больше размера страницы, он печатается на своей
странице до границы строки, а его путь честно помечается в конце (not fully shown: … (module(s) larger than page size …)); чтобы прочитать такой модуль
целиком, поднимите --max-chars.
--filter REGEX оставляет только модули, путь которых совпадает с регуляркой
(проверка по пути файла, как в grep). Флаг можно повторять и перечислять
несколько значений за раз: --filter schemas models — модуль остаётся, если
совпал хотя бы один паттерн (OR). Например: --filter 'schemas|services'.
Пагинация применяется уже к отфильтрованному набору.
Если после фильтрации ничего не нашлось или каталог пуст, команда завершается
успешно с сообщением (no modules found) / (no modules match: …), а не
пустым выводом и не ошибкой.
codenav diff [PATH] [--lines SPEC] [--lang LANG] [--repo DIR]
Нарезка кода по диффу: изменённые строки разворачиваются до содержащих их символов, соседние символы склеиваются (зазор ≤ 5 строк).
Источник изменений выбирается явно; флаги взаимоисключающие — два источника в одном вызове отклоняются:
$ codenav diff --working-tree --repo ../code-master # tracked-изменения относительно HEAD (staged + unstaged), исходники — с диска
$ codenav diff --staged --repo ../code-master # изменения индекса относительно HEAD, исходники — из индекса
$ codenav diff --base main --repo ../code-master # main...HEAD (от merge base), исходники — из ревизии HEAD
$ git diff HEAD | codenav diff --stdin --repo . # unified diff из stdin, даже на терминале
$ codenav diff src # без флагов (прежнее поведение): терминал → git diff HEAD, pipe → stdin
$ codenav diff src/codenav/cli.py # PATH ограничивает diff одним файлом/каталогом
$ codenav diff src --lines '10,15-20' # изменённые строки задаются явно (нужен PATH)
При явно выбранном режиме результат одинаков с терминала и без терминала.
--repo DIR — каталог, в котором выполняются git-команды, поэтому результат
не зависит от текущего каталога; пути из git-диффа разрешаются от корня
репозитория, а для --stdin/--lines — от --repo (по умолчанию от текущего
каталога). untracked-файлы git не диффит — в нарезку они не попадут. Без PATH
нарезаются все изменённые файлы (блоки разделяются ---, не-код файлы
пропускаются), с PATH — только указанный файл. Полностью удалённые и новые
модули не нарезаются — короткая пометка MODULE DELETED / NEW MODULE; при
отсутствии изменений — (no changes).
codenav symbol NAME... [--root DIR...]
Исходник каждого символа по имени (простому или квалифицированному, например
MyClass.my_method). Можно перечислить несколько имён в одном вызове — индекс
строится один раз. Поиск идёт только по перечисленным --root (по умолчанию
текущий каталог).
При нескольких совпадениях текущая реализация печатает первое в порядке обхода; для точного выбора используйте квалифицированное имя, когда оно однозначно. Если хотя бы одно имя не найдено, команда не печатает ничего и завершается с ошибкой, перечисляя отсутствующие имена.
Поиск ссылок именной; строки-литералы тоже сканируются (DI-регистрации вида
"pkg.mod:Symbol" — тип str; кавычечные forward-аннотации — типы par/ret
по позиции), docstring исключены.
Типы связей
Отношения в impact, trace, path, info и outline помечаются
типом ссылки, которая их создала — по тому, как имя употреблено в исходнике.
Печатается короткая метка (полные имена съедали бы контекст), полное имя
принимает --kind:
| Метка | Тип | Место ссылки |
|---|---|---|
call |
call |
имя вызывается: helper(), obj.method() |
inh |
inheritance |
имя в списке базовых классов: class A(B) |
par |
param |
имя в аннотации типа вне позиции возврата: параметр f(x: Service), поле dep: Service, локальная переменная x: Service, в том числе кавычечная forward-ссылка body: "Service" |
ref |
reference |
прочие упоминания имени (чтение, значение) |
ret |
return |
имя в позиции возврата: аннотация -> Service или выражение return x / return build() — сторона производителя данных |
str |
string |
слово из DI-строки ("pkg.mod:Symbol" в LazyService(...)); текстовый кандидат, синтаксисом не подтверждён |
ret и par разделяют стороны потока данных по объекту: у
trace Data --kind ret остаются только функции, которые его возвращают, у
--kind par — только принимающие его на вход.
Если имя встречается в разных местах, связь получает сразу несколько типов
([inh,par]), они выводятся в алфавитном порядке.
Флаг --kind (impact, trace, path, info) оставляет только связи
выбранных типов; несколько типов указываются сразу (--kind call param)
или повтором флага (--kind call --kind str). Принимается и короткая метка
(--kind par). В impact фильтр отсекает связи и оставляет в метках только
выбранные типы; в trace/path/info он действует и на обход, поэтому
цепочка не проходит через ребро отфильтрованного типа.
$ codenav trace Base --root project --kind call
Base:
make -[call]-> Base
codenav impact NAME... [--root DIR...] [--detailed] [--kind KIND...]
Цепочка влияния каждого символа (несколько имён за один обход индекса):
В обычном выводе пути не печатаются: выводятся отсортированные и уникальные ID
объектов, к каждому — типы связей в квадратных скобках. --detailed добавляет
путь, строки, тип сущности и места ссылок в виде метка@строка.
- depends-on — пользовательские символы, на которые ссылается цель (включая всё её поддерево: методы и атрибуты класса);
- dependents — символы, чьи тела ссылаются на цель.
Номера строк в --detailed принадлежат ссылающейся стороне: для depends-on
это файл цели (он указан в заголовке), для dependents — сам перечисленный
символ.
Связи учитывают кавычечные forward-аннотации (job_store: "JobStore" — тип
par) и DI-строки в атрибутах класса (LazyService("pkg.mod:Symbol") — str).
Docstring и произвольные строки в телах функций связей не дают.
$ codenav impact CodeReviewService --root project
impact chain for CodeReviewService:
- depends-on:
Constants [ref]
...
- dependents:
Services [str]
$ codenav impact my_func --root src --detailed
impact chain for my_func (src/mod.py:20-22):
- depends-on:
src/mod.py:10-12::mid function [call@21]
- dependents:
src/cmd.py:30-33::cmd_outline function [call@31]
codenav trace NAME... [--root DIR...] [--direction DIRECTION] [--depth N] [--max-paths K] [--kind KIND...]
Цепочки влияния через каждый символ (несколько имён за один обход индекса);
ребро A -[call]-> B означает «A ссылается на B», а метка ребра — тип ссылки
(см. «Типы связей»). Рёбра разрешаются тем же qualified/type-aware анализом,
что и в impact.
--direction выбирает, какие стороны обходить (по умолчанию both):
both— цепочки через символ в обе стороны: кто на него ссылается и что он тянет за собой; бюджет--depthобщий на обе стороны;down— только вглубь того, что символ тянет за собой; цель первой в цепочке, поэтому весь бюджет--depthуходит в одну сторону и глубина не съедается «шумом» от потребителей слева;up— только потребители: цепочки печатаются в порядке стрелки (referrer -[call]-> NAME).
--depth ограничивает глубину каждой отдельной цепочки —
сколько символов цепочки будет выведено; глубина считается в символах:
--depth 3 печатает цепочки не длиннее трёх символов, например
side -[call]-> my_func (по умолчанию 3). --depth не ограничивает количество
найденных цепочек — для этого есть отдельный страховочный потолок
--max-paths. Цепочка, целиком содержащаяся в более длинной, убирается как
дубликат. Одноимённые функции в разных файлах остаются разными узлами графа.
Для списка прямых зависимостей — impact.
$ codenav trace my_func --root src
my_func:
side -[call]-> my_func -[call]-> mid -[call]-> base
top -[call]-> my_func -[call]-> mid -[call]-> base
$ codenav trace _collect_code_files --root src/codenav --direction down
_collect_code_files:
_collect_code_files -[ref]-> SKIP_DIRS
_collect_code_files -[call]-> detect_language
Если цепочек нет, команда печатает no influence data (0 paths) (при
--direction down — no dependency chains (0 paths); усечение печатается как
not shown: N paths (max_paths=…)).
codenav path SOURCE TARGET [--root DIR...] [--depth N] [--max-paths K] [--kind KIND...]
Ответ на вопрос «как SOURCE связан с TARGET»: все самые короткие цепочки от
SOURCE до TARGET вдоль зависимостей (ребро A -[call]-> B означает «A
ссылается на B», тип ребра — тип ссылки). Показываются только цепочки
минимальной длины — без посторонних ветвей, которыми перегружен trace.
Направление одно — от SOURCE к TARGET; обратный вопрос задаётся тем же
способом: codenav path TARGET SOURCE. --depth считает символы цепочки,
оба конца включены, и не длиннее --depth (по умолчанию 10): маршрут вне
глубины — честно пустой ответ. --kind фильтрует ребра, как в trace.
$ codenav path _print_graph impact_entity --root src/codenav
_print_graph -> impact_entity:
_print_graph -[par]-> RepoIndex -[call,ret]-> impact_entity
Если пути нет (или он длиннее --depth), команда печатает
SOURCE -> TARGET: no chains (0 paths) и завершается с кодом 0; усечение по
--max-paths — как в trace: not shown: N paths (max_paths=…). Имя,
которое нигде не найдено, — ошибка, как у symbol/trace.
codenav info NAME... [--root DIR...] [--depth N] [--max-paths K] [--kind KIND...]
Один обход индекса вместо трёх: аккумулированный ответ из symbol (полный
исходник), trace (цепочки влияния) и impact (depends-on/dependents) для
каждого имени. Секции идут в порядке symbol → trace → impact и повторяют
формат соответствующих команд. Для части цепочек действуют собственные
дефолты: --depth 20 и --max-paths 50 (флаги можно переопределить;
--depth — та же семантика, что у trace). --kind фильтрует обе
части: и цепочки, и список прямых зависимостей.
Каждая секция честно сообщает о невыведенной информации: отсутствующие
зависимости помечаются (none found), отсутствие путей — no influence data (0 paths), усечение цепочек — not shown: N paths (max_paths=…). Если имя нигде
не найдено, команда, как и symbol/impact/trace, завершается с ошибкой,
перечисляющей отсутствующие имена.
$ codenav info my_func --root src
### my_func (src/mod.py:20-22, function)
20 def my_func():
21 return mid()
my_func:
side -[call]-> my_func -[call]-> mid -[call]-> base
top -[call]-> my_func -[call]-> mid -[call]-> base
impact chain for my_func:
- depends-on:
mid [call]
- dependents:
cmd_outline [call]
codenav grep PATTERN... [--root DIR...] [--lang LANG] [--max-chars N] [--pages SPEC]
Короткая версия поиска по регулярке. Можно передать несколько выражений: символ,
в теле которого совпало хотя бы одно из них, печатается один раз. Область поиска
задаётся --root (по умолчанию текущий каталог).
Печатаются только совпавшие строки, зато заголовок блока несёт границы символа —
путь:начало-конец::имя вид (тот же формат локации, что в impact --detailed),
так что видно, в каком символе найдено и где он начинается и кончается. Обычный
grep границ символа не знает.
$ codenav grep 'entity.kind' --root src
src/codenav/cli.py:438-474::_print_grep function
463 f"{entity.qualified_name} {entity.kind}"
---
src/codenav/cli.py:351-408::_print_impact function
381 f"{entity.qualified_name} {entity.kind}"
codenav astgrep PATTERN... [--root DIR...] [--lang LANG] [--max-chars N] [--pages SPEC]
Расширенная версия того же поиска: вместо отдельных строк печатается полный исходник каждого найденного символа по его границам. Заголовок здесь — просто путь: границы видны по номерам строк самого исходника.
$ codenav astgrep 'def _grep_blocks' --root src
src/codenav/cli.py
411 def _grep_blocks(files: list[str], patterns: Sequence[str], lang: str | None):
412 """Regex hits grouped by smallest enclosing symbol, in file order.
...
433 for key in order:
434 entity, matched_lines = blocks[key]
435 yield file, entity, matched_lines, parsed.content_lines
Обе команды группируют хиты по наименьшей объемлющей сущности (метод, а не весь
класс) и разделяют блоки ---; строки вне символов (импорты, константы модуля)
в обеих версиях печатаются как есть — у них нет символа, чьи границы можно
назвать.
Пагинация та же, что у outline: вывод режется на страницы по --max-chars
(по умолчанию 10000 символов; границы блоков не режутся), печатается первая
страница и подсказка (page 1 of N; …: --pages 2-N). --pages SPEC выбирает
страницы (2, 2-4, 1,3; флаг повторяется). Блок крупнее страницы получает
свою страницу целиком — совпавшие строки не обрезаются.
Если ничего не нашлось, команда завершается успешно с сообщением
(no matches for: …) (или (no code files found), когда в корнях нет
исходников), а не пустым выводом: агент отличает отработавший поиск без
совпадений от сбоя.
codenav doctor [--root DIR...] [--verbose]
Диагностика индексации: что обход корней прочитал и почему остальное осталось за бортом. Отвечает на то, что «ничего не найдено» не различает: символа нет в исходниках — или файл вообще не индексировался (незнакомое расширение, лимит размера, файл не читается как UTF-8, разбор не удался, каталог вырезан обходом, корень не существует).
$ codenav doctor --root project tests
roots:
project -> /work/project: 37 files indexed
tests -> /work/tests: 12 files indexed
vendor: does not exist
files: 49 indexed of 53 code files; 15 non-code files skipped
languages: python 40 files/780 symbols, javascript 9 files/14 symbols
skipped: too large 2, unreadable 1, parse failed 1, ignored dirs 4
extraction: 794 symbols; 3 <unknown> names in 1 files; syntax errors in 2 files; 5 files parsed without symbols
(paths behind these counts: --verbose)
Обход здесь ровно тот же, что у остальных команд (RepoIndex.scan_tree),
поэтому отчёт описывает именно тот набор файлов, который они ищут, а не
похожий: те же вырезанные каталоги (.git, node_modules, .venv, … и любые
скрытые), тот же лимит размера файла (512 KiB), та же проверка языка по
расширению, тот же разбор.
roots— каждый--root, как он задан: куда ведёт (realpath) и сколько файлов дал. Корень, которого нет (does not exist), файл вместо каталога (not a directory) и корень, уже покрытый другим (redundant (covered by another root)), названы явно; первые два к тому же завершают команду кодом 1 — опечатка в корне не должна выглядеть как успешно проиндексированный пустой проект.files— сколько файлов попало в индекс из всех файлов с поддержанным расширением; файлы без поддержанного расширения считаются отдельно.languages— файлы и извлечённые символы по языкам.skipped— почему файл с поддержанным расширением не попал в индекс:too large,unreadable,parse failed, а также вырезанные каталоги (ignored dirs— один такой каталог скрывает целое поддерево).extraction— измеренные признаки неполноты извлечения: имена<unknown>(объявление найдено, имя — нет), файлы, где tree-sitter оставил ERROR/MISSING-узлы, и модули, разобранные без единого символа. Успешный разбор здесь не подаётся как доказательство полноты — команда печатает только то, что измерила.
Сбой одного файла (не читается, не разбирается) не скрывает остальные: файл попадает в счётчик с причиной, а обход идёт дальше.
Обычный режим — сводка; --verbose допечатывает пути за каждым счётчиком
(блок details:).
Команда описывает текущие возможности экстракторов, а не улучшает их: ноль символов у языка — сообщение о том, что искать в нём пока нечего.
Языки
Python, JavaScript/TypeScript/TSX, Go, Rust, Java, Scala, Ruby, PHP, C#, C/C++.
Язык определяется по расширению; переопределяется флагом --lang.
Грамматики всех перечисленных языков загружаются, но полнота извлечения символов
пока различается. Например, smoke-тест выявил <unknown> для части объявлений
Go/C/C++ и пропущенный метод PHP; подробности есть в аудите CLI. Найти такие дыры
на своём проекте помогает codenav doctor (см. выше): он считает <unknown>,
модули с ERROR-узлами tree-sitter и модули, разобранные без единого символа.
Скилл и сабагент
.agents/skills/codenav-research/SKILL.md— скилл для агента: какую команду брать под какой вопрос, какие флаги режут вывод, какой бюджет держать на один результат..omp/agents/codenav-researcher.md— read-only сабагент (omp): ищет черезcodenavи возвращает отчёт сsymbol—path:line— тип связи.- Замер A/B (codenav против
read/grep/globна одних и тех же задачах):benchmarks/codebase-research/ab_tokens.py.
Установка в пользовательский конфиг — симлинками, чтобы копии не расходились:
ln -sfn "$PWD/.agents/skills/codenav-research" ~/.agents/skills/codenav-research
ln -sfn "$PWD/.omp/agents/codenav-researcher.md" ~/.omp/agent/agents/codenav-researcher.md
Бюджет в скилле выведен из замеров на репозитории ~220 модулей:
grep ≈ 200 символов, impact --detailed --kind call ≈ 2.4k,
symbol Class.method ≈ 4.7k, symbol Class ≈ 24k, info ≈ 34k. astgrep
отдаёт полный исходник каждого совпавшего символа, поэтому его счёт — как у
symbol, умноженный на число попавших символов; для шага обнаружения он дорог.
Разработка
uv sync --group dev
uv run pytest
Metadata
Release files for agentcodemap 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| agentcodemap-1.0.0.tar.gz | 56.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agentcodemap-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 108.6 kB
Release files / agentcodemap-1.0.0.tar.gz
| Download URL | agentcodemap-1.0.0.tar.gz |
|---|---|
| Size | 56.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1786f31f3e9b72d5e7e69d416e10819629e20d5fa4a47607a6381ae78acf7df5
|
|
BLAKE2b-256 checksum How to use checksums |
9aa9ffb5ee68c075213b8a38fa3ca571941a2e7aaf8420f393c38ee09304b91c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.11 {"installer":{"name":"uv","version":"0.12.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / agentcodemap-1.0.0-py3-none-any.whl
| Download URL | agentcodemap-1.0.0-py3-none-any.whl |
|---|---|
| Size | 52.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5307c3cb4f850d362e6304c6e2d5e18aa053cef9776b6bb989297db65dfdd0f0
|
|
BLAKE2b-256 checksum How to use checksums |
0419a15077c5152def8c63eb3e5043ff99369eaa603a31c62181e37e64b2c299
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.11 {"installer":{"name":"uv","version":"0.12.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|