repo-map scans a repository, extracts structural metadata, and asks an OpenRouter model to document eligible files. It writes a Markdown tree at the repository root and stores results in SQLite so unchanged files do not need another API call.
- Markdown and console repository trees
- Python AST extraction plus lightweight Java, JavaScript, TypeScript, and C# structure parsing
- Structured descriptions, developer considerations, maintenance flags, dependency notes, architectural roles, refactoring suggestions, and security assessments, grounded in each file's own source
- Root
.gitignoresupport plus built-in exclusions for caches, dependencies, build output, credential-bearing config formats, and repo-map artifacts - Symlink-safe traversal that does not follow linked files or directories
- SHA-256-based SQLite cache for unchanged files, keyed by repository-relative path so it survives a rename, clone, or CI checkout at another prefix
- Cache reuse gated on the file's hash, the selected model, and the analysis contract that produced the entry, so switching
--modelreanalyzes rather than returning another model's results
See examples/example.md for a full sample report.
Install the published CLI with uv:
uv tool install repo-mapFor development, clone the repository and install its Poetry environment:
git clone https://github.com/cyanheads/repo-map.git
cd repo-map
poetry installrepo-map requires Python 3.12 or newer.
Set an OpenRouter API key in the environment or a project-root .env file:
export OPENROUTER_API_KEY=your_api_key_hereOptional environment variables:
| Variable | Default | Purpose |
|---|---|---|
OPENROUTER_MODEL_NAME |
anthropic/claude-sonnet-4.6 |
OpenRouter model |
API_SEMAPHORE_LIMIT |
3 |
Maximum concurrent API calls |
repo-map sends repository paths, languages, imports, symbols, existing descriptions, and the full text of each eligible file to OpenRouter. Review the target repository and its ignore rules before approving a run. Do not analyze secrets or source you are not authorized to disclose.
A file's source is sent only when it is a regular (non-symlinked) file in a supported text format, at most 64 KiB, free of NUL bytes, and valid UTF-8. Recognized binary and media formats, oversized files, and unreadable files are never sent and never cached.
With the published tool installed:
repo-map /path/to/repositoryFrom a source checkout:
poetry run repo-map /path/to/repositoryOptions:
| Option | Purpose |
|---|---|
-y, --yes |
Skip the disclosure confirmation |
--model MODEL |
Override OPENROUTER_MODEL_NAME |
--concurrency INT |
Override API_SEMAPHORE_LIMIT |
Examples:
poetry run repo-map /path/to/repository --model anthropic/claude-sonnet-4.6
poetry run repo-map /path/to/repository --concurrency 3 -yEach run creates these files inside the target repository:
<repository>_repo_map.md: generated Markdown report.repo-map-cache.db: source hashes and cached LLM metadata.repo_map_structure.json: pre-enhancement structural data
Add them to the target repository's ignore rules if needed. The cache is self-maintaining. Entries are keyed by repository-relative path, so a move does not invalidate them; an edited file, a different --model, or a release that changed the analysis contract each trigger reanalysis on their own; and every run drops the entries whose files it no longer finds. Deleting .repo-map-cache.db still forces a complete reprocessing pass, but none of those changes require it.
- Load built-in exclusions and the target repository's root
.gitignore. - Walk the directory tree and extract supported structural metadata.
- Compare each file's hash, the selected model, and the analysis contract revision with the SQLite cache.
- Request structured JSON metadata from OpenRouter for eligible changed files, sending each file's source alongside the tree.
- Cache validated results only, drop cache entries with no matching file, then write the console, JSON, and Markdown outputs.
A directory matching an exclusion is pruned rather than traversed, so nothing inside it is read, hashed, or sent.
| Group | Patterns |
|---|---|
| Version control | .git/, .hg/, .svn/, CVS/ |
| Caches and bytecode | __pycache__/, *.pyc, *.pyo, *.pyd, .pytest_cache/, .mypy_cache/ |
| Environments and dependencies | .venv/, venv/, env/, node_modules/ |
| Build output | build/, dist/, *.egg-info/ |
| Credential-bearing config | .env, .envrc, *.tfvars, *.tfstate, *.ini, *.conf, *.cfg |
| Databases and logs | *.db, *.sqlite3, *.log |
| Local noise and repo-map artifacts | .DS_Store, .repo-map-cache.db, .repo_map_structure.json, *_repo_map.md |
These load before the target repository's root .gitignore, and matching follows gitignore last-match-wins semantics. A repository that wants an excluded file documented re-includes it with a negation in its own .gitignore, such as !app.conf.
Run the local gate and build both distribution artifacts before submitting a change:
poetry run python scripts.py check
poetry buildFocused commands:
poetry run python scripts.py format
poetry run python scripts.py lint
poetry run python scripts.py test
poetry run python scripts.py list-skillsProject workflows live under skills/. Bugs and feature requests use the forms on the issues page.
Apache-2.0. See LICENSE.