Skip to main content

Transform code repositories into Obsidian vaults—one note per class and method.

Project description

repo2obsidean

PyPI version Python versions License: MIT

Transform code repositories into Obsidian vaults — one markdown note per class and method, with wikilinks connecting callers, callees, and inheritance relationships.

On PyPI: https://pypi.org/project/repo2obsidean/pip install repo2obsidean

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 .scm query 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-*.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 #changed tag 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|D frontmatter (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 .gitignore will not be detected).

Grouping & filtering by symbol type

Every note is tagged by what it is, so you can colour or filter the graph by kind out of the box. Each symbol carries #class, #method, or #function, edited symbols get #changed, and HTTP handlers get #route, auto-detected across languages:

  • Python — decorators: Flask @app.route, FastAPI @app.get/@router.post, Odoo @http.route/@route.
  • Go — registration calls: http.HandleFunc, gin/echo/chi/fiber r.GET, r.POST, … (the referenced handler function is tagged, even cross-file).
  • JavaScript — Express: app.get('/x', handler), router.post(...).

Anonymous inline handlers are skipped (no named symbol to tag).

The vault ships a default .obsidian/graph.json with ready-made colour groups:

Group query Colour Matches
tag:#changed red changed since git HEAD
tag:#route orange HTTP route handlers
tag:#class blue classes
tag:#method purple methods
tag:#function green free functions

These appear automatically on first open. Add/edit groups in Graph view → Filters → Groups; your changes are preserved across regenerations (the default file is only written when none exists). Use the Search files… box with the same queries (e.g. tag:#route) to filter instead of colour.

Seeing changes in Obsidian's Graph view

Changed symbols light up in red via the seeded tag:#changed group. If you ever need to recreate it: Graph view → Filters → Groups → New group, query tag:#changed. After regenerating, reload Obsidian (Ctrl/Cmd+P → "Reload app without saving") so it re-reads the freshly tagged notes.

Animated obsidean graph with changed symbols pulsing red

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 the Notes/ 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 build CLI: 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.json sidecar 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

  1. Write a tree-sitter query file: repo2obsidean/parser/queries/{lang}.scm
  2. Add language to Language enum in repo2obsidean/parser/base.py
  3. Add extraction logic to TreeSitterParser._extract_{lang}_symbols() and _walk_{lang}_tree()
  4. 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. Every subsequent release is a one-liner:

scripts/release.py                           # default: auto-bump patch and publish
scripts/release.py 0.2.0                     # or pass an explicit version
scripts/release.py --bump minor              # auto-bump minor (or major)
scripts/release.py --testpypi                # rehearse on TestPyPI first
scripts/release.py --build-only              # bump + build only, skip upload

The script runs pytest, builds wheel + sdist, validates with twine check, prompts for confirmation, uploads, then commits + tags v<version>. Set your PyPI token via env (TWINE_USERNAME=__token__ TWINE_PASSWORD=pypi-...) or ~/.pypirc — never commit the token.

Or do it manually:

# 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 repo2obsidean must be free — check https://pypi.org/project/repo2obsidean/ (a 404 means it's available).
  • A version number can't be re-uploaded; bump version in pyproject.toml for each release.
  • Until then, it installs directly from GitHub: pip install "git+https://github.com/martinvelezf/repo2obsidean.git".

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

repo2obsidean-0.1.2.tar.gz (35.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

repo2obsidean-0.1.2-py3-none-any.whl (33.4 kB view details)

Uploaded Python 3

File details

Details for the file repo2obsidean-0.1.2.tar.gz.

File metadata

  • Download URL: repo2obsidean-0.1.2.tar.gz
  • Upload date:
  • Size: 35.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for repo2obsidean-0.1.2.tar.gz
Algorithm Hash digest
SHA256 bf788346384ea45c957f924db89c35ccac4bd29777ec085c3652335983f60160
MD5 391a6483ed7108b5ffdc999532fca514
BLAKE2b-256 7edc337bf27a15f5f3a3f15228502a3322942f022e169ef2fd1f53df1ba86d5e

See more details on using hashes here.

File details

Details for the file repo2obsidean-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: repo2obsidean-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 33.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for repo2obsidean-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 33537d66fa08182a377bdda09a3a3717c7a668496f46fe95746bc86b7a289adc
MD5 fbe81612f88b6775d8304750cf8cc19f
BLAKE2b-256 cda0ca1e592a160656e3259ac143ecc5a6a62a5331a6d46f443f7d8695990467

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page