Observed since 20 Oct, 21:40 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Tree-sitter code navigation and search harness for LLM agents: diff slicing, symbol lookup with impact chain, symbol grep, compact outline.
Tree-sitter harness для навигации и поиска по коду, рассчитанный на LLM-агентов.
Возвращает компактные, машиночитаемые срезы кода вместо целых файлов.
Установка: uv tool install agentcodemap. Команда после установки — codenav (синоним — agentcodemap, тот же CLI).
Общий приём: команды, которые сканируют каталог-корень (symbol, impact,
trace, path, info, grep, astgrep, doctor), принимают несколько корней одним флагом
--root DIR.... Индексируются только перечисленные каталоги — соседние
директории того же уровня с кодом не попадают в поиск. Например,
--root project tests ищет ровно в project/ и tests/, игнорируя прочие
директории текущего уровня. По умолчанию корень — текущий каталог. Флаг можно
повторять: --root project --root tests — то же самое.
--root забирает все следующие слова, поэтому имена и шаблоны пишутся до него:
codenav symbol NAME --root project. Если имена всё же оказались после --root,
хвостовые слова, которых нет на диске, возвращаются в имена с пометкой в stderr.
codenav outline [PATH...] [--root DIR...] [--top-level] [--deps] [--lines] [--filter REGEX...] [--max-chars N] [--pages SPEC]PATH — файл или каталог (каталог обходится рекурсивно, outline каждого модуля);
без PATH — текущий каталог. --root — то же, что PATH, для единообразия с
остальными командами.
Компактная карта символов файла:
$ 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 (по умолчанию
текущий каталог).
При нескольких совпадениях печатается первое в порядке обхода, а перед ним —
строка note: N symbols match … с местами остальных: одноимённый символ из
другого корня не прячется молча. Для точного выбора — квалифицированное имя
или один --root. То же у impact, trace, path и info.
Если хотя бы одно имя не найдено, команда не печатает ничего и завершается
с ошибкой, перечисляя отсутствующие имена; если имя похоже на путь к файлу,
ошибка подсказывает codenav outline <путь>.
Поиск ссылок именной; строки-литералы тоже сканируются (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 добавляет
путь, строки, тип сущности и места ссылок в виде метка@строка.
Номера строк в --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... [-i] [--root DIR...] [--lang LANG] [--max-chars N] [--pages SPEC]Короткая версия поиска по регулярке. Можно передать несколько выражений: символ,
в теле которого совпало хотя бы одно из них, печатается один раз. Область поиска
задаётся --root (по умолчанию текущий каталог).
Шаблон — регэксп Python: «или» — a|b. Привычное по grep a\|b в Python ищет
буквальную черту; если такой шаблон ничего не нашёл, поиск повторяется с a|b, и
первая строка вывода об этом говорит. -i — без учёта регистра (то же, что (?i)
в начале шаблона).
Печатаются только совпавшие строки, зато заголовок блока несёт границы символа —
путь:начало-конец::имя вид (тот же формат локации, что в 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 — тип связи.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
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 1.1.0 | 9 Oct 2026 | 2 |
This describes our collected data; it is not a security assessment.
Shared repository data · GitHub
Observed since 20 Oct, 21:40 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:ef147e3fdefb8ffb1330b27fdbbbc75fc99630bfdbf8bb8130a71c2abfec7319