Transform code repositories into Obsidian vaults—one note per class and method.
Project description
repo2obsidean
Transform code repositories into Obsidian vaults — one markdown note per class and method, with wikilinks connecting callers, callees, and inheritance relationships.
Everyone is welcome to contribute to this repository and build a crazy open source package.
Why?
- Token efficiency: Instead of reading a whole repository file (20k tokens), agents fetch just the relevant symbol's note (200–500 tokens). Per-symbol notes are individually addressable, unlike a repomix bundle.
- Human navigation: Obsidian's backlinks and graph view turn code into a browseable knowledge graph.
- Language-agnostic: Tree-sitter + static analysis scales to any language (Python, Go, JS, Java, …) by adding a
.scmquery file.
Installation
Installs the repo2obsidean command (with obsidean as a short alias). Pick
whichever fits:
# From PyPI (once published) — no repo needed:
pip install repo2obsidean
# or as an isolated global CLI tool, like running `repomix`:
pipx install repo2obsidean
# Directly from GitHub (works today, no PyPI needed):
pip install "git+https://github.com/martinvelezf/repo2obsidean.git"
# From a built wheel (offline):
pipx install dist/repo2obsidean-0.1.1-py3-none-any.whl
# From source, editable, for development:
python3 -m venv venv && source venv/bin/activate
pip install -e ".[dev]"
To build the distributables yourself: python -m build writes a wheel and
sdist to dist/.
Quick Start
Run it in a repository directory with no arguments — just like repomix:
cd /path/to/your/repo
obsidean # auto-detects .py/.go, writes ./obsidean-vault
Or point it explicitly:
obsidean /path/to/repo --out ./my-vault # explicit path + output
obsidean /path/to/repo -l python -l javascript # limit languages
Multiple source roots (layers)
Point Obsidean at several trees at once. Each root is labelled by its folder name (its "layer") so identically-named classes don't collide, and cross-tree relationships are linked:
obsidean ./odoo ./user ./enterprise --out ./vault
Supported languages (auto-detected): Python (.py), Go (.go),
JavaScript (.js, .mjs, .cjs, .jsx). Add a language by writing a
tree-sitter walker in tree_sitter_parser.py
and a glob in cli.py.
Choosing directories and files
Give explicit layer names, and filter with include/exclude globs (repeatable):
# Name the layers explicitly instead of using the folder name
obsidean --layer odoo=./odoo --layer custom=./addons --out ./vault
# Only scan model files; skip tests and migrations
obsidean . --include 'models/**' --exclude 'tests/**' --exclude '**/migrations/**'
Globs match against both the path relative to the root and the bare filename.
Common build/vendor dirs (node_modules, .git, venv, dist, …) are always
skipped.
Git change tracking (review what changed)
By default Obsidean compares each file to git HEAD and flags uncommitted working-tree changes (staged + unstaged + new untracked files). Each affected symbol note gets:
- a
#changedtag both in frontmatter (tags: [..., changed]) and as an inline tag in the note body — so Obsidian recognises it everywhere, changed: true/change_status: M|A|Dfrontmatter (for Dataview/CSS),- an inline
> [!warning]callout with a diff of just that symbol's lines.
It also writes Notes/recent-changes.md — an index of every changed symbol,
grouped by layer and file, with links. A symbol is flagged only when a diff hunk
actually overlaps its line range (zero-context diffing), so unchanged neighbours
aren't marked. Disable with --no-git.
A symbol is flagged only for changes to files git already tracks — brand new files only count once they are
git add-ed (an untracked file hidden by.gitignorewill not be detected).
Seeing changes in Obsidian's Graph view
Changed symbols light up in the graph once you add a colour group keyed on the
#changed tag:
- Open Graph view → Filters → Groups → New group.
- Query:
tag:#changed— pick a bright colour (e.g. red). - Reload Obsidian after regenerating (
Ctrl/Cmd+P → "Reload app without saving") so it re-reads the freshly tagged notes.
The red nodes are exactly the symbols whose source changed since HEAD — a
live, navigable change-impact view of the codebase. Tip: add a second group like
path:rebound (blue) to tint a specific layer, and drag the node-size slider up
so changed nodes stand out.
That animation is generated straight from the symbol graph (no screen recording) and can be regenerated any time:
python scripts/animate_graph.py repo2obsidean --out docs/graph-changed.gif
It rebuilds the graph, flags git-changed symbols, and writes a looping GIF where the changed nodes pulse red — handy for PRs or change reviews outside Obsidian.
Regenerating preserves your
.obsidian/config (graph groups, workspace), so the colour group you set up survives every rebuild. Only theNotes/content is rewritten.
Framework awareness: Odoo model inheritance
Odoo models relate through _name / _inherit / _inherits class attributes,
not Python base classes. Obsidean extracts these and links a model's base
definition to every extension across all scanned layers. The vault includes
Notes/odoo-models.md — a report that, per model, lists where it's defined
and extended, and flags any _inherit target that has no base
definition in the scanned roots (a likely missing addon tree). This is the
"are the inherited models wired up correctly?" check. It's inert for non-Odoo
codebases (no _name/_inherit ⇒ empty report).
Then open the result in Obsidian: File → Open folder as vault → select the output directory.
Output Structure
my-vault/
├── Notes/
│ ├── python/
│ │ ├── mymodule/
│ │ │ ├── MyClass.md
│ │ │ ├── MyClass__init.md
│ │ │ ├── MyClass__method.md
│ │ │ └── standalone_function.md
│ │ └── MOC-python.md
│ ├── index.md
└── .obsidian/ # (generated by Obsidian)
Each note includes:
- Signature — function/method signature
- Docstring — extracted documentation
- Source — collapsible snippet
- Calls —
[[wikilinks]]to callees - Called by — backlinks from callers
- Related — nearby symbols in the graph
Roadmap
MVP (Week 1) ✓
obsidean buildCLI: parse Python, emit per-class/method notes with wikilinks- Local filesystem output
- Same-module call resolution
Phase 2 (Week 2)
- REST API:
POST /jobs,GET /jobs/{id}, async job execution - Redis-backed job queue
- Zip download support
Phase 3 (Week 3)
- Go support via tree-sitter
- S3/MinIO storage backend
- Webhook callbacks with HMAC signatures
- API key authentication
Phase 4 (Week 4)
- Cross-module/cross-language call resolution
- MOC (Map of Content) generation
search.jsonsidecar for embedded search
Development
Run tests
uv run pytest tests/unit -v
Run integration test
# Parse a real repository
repo2obsidean /path/to/requests --out /tmp/requests-vault
# Open the vault in Obsidian Desktop and verify:
# - Session.md exists
# - Session__send.md exists and contains [[Session]]
# - Backlinks panel shows cross-references
Add a new language
- Write a tree-sitter query file:
repo2obsidean/parser/queries/{lang}.scm - Add language to
Languageenum inrepo2obsidean/parser/base.py - Add extraction logic to
TreeSitterParser._extract_{lang}_symbols()and_walk_{lang}_tree() - Create test fixture:
tests/fixtures/sample_{lang}.{ext}
Architecture
- Parser:
repo2obsidean/parser/tree_sitter_parser.py— uses tree-sitter to extract symbols - Graph:
repo2obsidean/graph/builder.py— networkx graph with typed edges (CALLS, INHERITS, IMPORTS) - Generator:
repo2obsidean/generator/vault.py— emits markdown files from graph + Jinja2 templates - CLI:
repo2obsidean/cli.py— orchestrates parsing → graph building → generation - API (Phase 2):
repo2obsidean/api/— FastAPI service wrapping the core engine
Publishing to PyPI
pip install repo2obsidean works by name only once the package is published to
PyPI. One-time setup, then a 3-step release:
# 0. one-time: create a PyPI account + an API token (pypi.org → Account → API tokens)
pip install build twine
# 1. build wheel + sdist
python -m build
# 2. (recommended) dry-run on TestPyPI first
twine upload --repository testpypi dist/*
pip install -i https://test.pypi.org/simple/ repo2obsidean
# 3. publish to the real PyPI
twine upload dist/* # username: __token__ password: pypi-<your-token>
After step 3, anyone can pip install repo2obsidean. Notes:
- The name
repo2obsideanmust be free — check https://pypi.org/project/repo2obsidean/ (a 404 means it's available). - A version number can't be re-uploaded; bump
versioninpyproject.tomlfor each release. - Until then, it installs directly from GitHub:
pip install "git+https://github.com/martinvelezf/repo2obsidean.git".
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file repo2obsidean-0.1.1.tar.gz.
File metadata
- Download URL: repo2obsidean-0.1.1.tar.gz
- Upload date:
- Size: 28.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c4d52282075a0d36fa65af46fd447654681a0eed2089f53c336f5b0c2de5e90d
|
|
| MD5 |
726a54a7db81d2f34b8058da5e1428ec
|
|
| BLAKE2b-256 |
f9b7ebe944842aa2747ea3db6e06a6e6e54b09a2808f15fa4ff8d2097bbf4afb
|
File details
Details for the file repo2obsidean-0.1.1-py3-none-any.whl.
File metadata
- Download URL: repo2obsidean-0.1.1-py3-none-any.whl
- Upload date:
- Size: 29.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0aa64444cd5e722791acbaef5da8d61dc6565b0338ae37e63a137db1d6143d65
|
|
| MD5 |
734e18aaac6d9947f4da630fa12e417a
|
|
| BLAKE2b-256 |
0f140a065dda34199554bbcbf352515b0abfe02d4eb91ba165e55190d0788090
|