Catalog PyPI
PACKAGE EXPLORERView in registry

agentcodemap

PyPI

Tree-sitter code navigation and search harness for LLM agents: diff slicing, symbol lookup with impact chain, symbol grep, compact outline.

Versions collected1
Direct dependencies2From release 1.1.0
Last observed20 Oct, 21:40 UTC

README

Version 1.1.0 documentation

agentcodemap

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:

МеткаТипМесто ссылки
callcallимя вызывается: helper(), obj.method()
inhinheritanceимя в списке базовых классов: class A(B)
parparamимя в аннотации типа вне позиции возврата: параметр f(x: Service), поле dep: Service, локальная переменная x: Service, в том числе кавычечная forward-ссылка body: "Service"
refreferenceпрочие упоминания имени (чтение, значение)
retreturnимя в позиции возврата: аннотация -> Service или выражение return x / return build() — сторона производителя данных
strstringслово из 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... [-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 — тип связи.
  • Замер 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

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
1.1.09 Oct 20262

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
pavelmaksimov/agentcodemap

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 21:40 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:ef147e3fdefb8ffb1330b27fdbbbc75fc99630bfdbf8bb8130a71c2abfec7319