Navegador
init: scaffold navegador — AST + knowledge graph context engine - Package structure: ingestion/, graph/, context/, mcp/, cli/ - tree-sitter AST parsers for Python and TypeScript - FalkorDB property graph (falkordblite/SQLite local, Redis production) - ContextLoader: file, function, class context bundles (JSON + markdown) - MCP server with 7 tools for AI agent integration - CLI: ingest, context, search, stats, mcp commands - MkDocs docs site wired to navegador.dev - CI, publish, and docs GitHub Actions workflows - MIT license, pre-commit (ruff), CONTRIBUTING, SECURITY
Commit
5e4b8e49561fed9672820667bfbe1a50349406711b84e5df318f29c4ae6854f6
48 files changed
+15
+39
+17
+18
+15
+30
+26
+73
+18
+12
+50
+21
+23
+1
+3
+3
+3
+3
+3
+16
+3
+3
+3
+3
+3
+3
+3
+46
+26
+6
+41
+3
+99
+4
+50
+48
+75
+2
+74
+158
+79
+3
+97
+1
+6
+4
+
.env.example
+
.github/CONTRIBUTING.md
+
.github/PULL_REQUEST_TEMPLATE.md
+
.github/SECURITY.md
+
.github/workflows/ci.yml
+
.github/workflows/docs.yml
+
.github/workflows/publish.yml
+
.gitignore
+
.pre-commit-config.yaml
+
CHANGELOG.md
+
CLAUDE.md
+
LICENSE
+
README.md
+
docs/CNAME
+
docs/api/graph.md
+
docs/api/ingestion.md
+
docs/api/mcp.md
+
docs/architecture/graph-schema.md
+
docs/architecture/overview.md
+
docs/assets/css/custom.css
+
docs/getting-started/configuration.md
+
docs/getting-started/installation.md
+
docs/getting-started/quickstart.md
+
docs/guide/context-loading.md
+
docs/guide/graph-queries.md
+
docs/guide/ingestion.md
+
docs/guide/mcp-integration.md
+
docs/index.md
+
mkdocs.yml
+
navegador/__init__.py
+
navegador/cli/__init__.py
+
navegador/cli/commands.py
+
navegador/context/__init__.py
+
navegador/context/loader.py
+
navegador/graph/__init__.py
+
navegador/graph/queries.py
+
navegador/graph/schema.py
+
navegador/graph/store.py
+
navegador/ingestion/__init__.py
+
navegador/ingestion/parser.py
+
navegador/ingestion/python.py
+
navegador/ingestion/typescript.py
+
navegador/mcp/__init__.py
+
navegador/mcp/server.py
+
pyproject.toml
+
tests/__init__.py
+
tests/test_context.py
+
tests/test_schema.py
+15
| --- a/.env.example | ||
| +++ b/.env.example | ||
| @@ -0,0 +1,15 @@ | ||
| 1 | +# Navegador — environment configuration | |
| 2 | + | |
| 3 | +# Graph database | |
| 4 | +# SQLite (local, zero-infra default) | |
| 5 | +NAVEGADOR_DB_PATH=.navegador/graph.db | |
| 6 | + | |
| 7 | +# Redis-backed FalkorDB (production) | |
| 8 | +# NAVEGADOR_REDIS_URL=redis://localhost:6379 | |
| 9 | + | |
| 10 | +# MCP server | |
| 11 | +NAVEGADOR_MCP_HOST=127.0.0.1 | |
| 12 | +NAVEGADOR_MCP_PORT=8765 | |
| 13 | + | |
| 14 | +# Logging | |
| 15 | +NAVEGADOR_LOG_LEVEL=INFO |
| --- a/.env.example | |
| +++ b/.env.example | |
| @@ -0,0 +1,15 @@ | |
| --- a/.env.example | |
| +++ b/.env.example | |
| @@ -0,0 +1,15 @@ | |
| 1 | # Navegador — environment configuration |
| 2 | |
| 3 | # Graph database |
| 4 | # SQLite (local, zero-infra default) |
| 5 | NAVEGADOR_DB_PATH=.navegador/graph.db |
| 6 | |
| 7 | # Redis-backed FalkorDB (production) |
| 8 | # NAVEGADOR_REDIS_URL=redis://localhost:6379 |
| 9 | |
| 10 | # MCP server |
| 11 | NAVEGADOR_MCP_HOST=127.0.0.1 |
| 12 | NAVEGADOR_MCP_PORT=8765 |
| 13 | |
| 14 | # Logging |
| 15 | NAVEGADOR_LOG_LEVEL=INFO |
+39
| --- a/.github/CONTRIBUTING.md | ||
| +++ b/.github/CONTRIBUTING.md | ||
| @@ -0,0 +1,39 @@ | ||
| 1 | +# Contributing to Navegador | |
| 2 | + | |
| 3 | +Thanks for your interest in contributing! | |
| 4 | + | |
| 5 | +## Development setup | |
| 6 | + | |
| 7 | +```bash | |
| 8 | +git clone https://github.com/ConflictHQ/navegador | |
| 9 | +cd navegador | |
| 10 | +python -m venv .venv && source .venv/bin/activate | |
| 11 | +pip install -e ".[dev]" | |
| 12 | +pre-commit install | |
| 13 | +``` | |
| 14 | + | |
| 15 | +## Running tests | |
| 16 | + | |
| 17 | +```bash | |
| 18 | +pytest tests/ -v | |
| 19 | +``` | |
| 20 | + | |
| 21 | +## Code style | |
| 22 | + | |
| 23 | +We use `ruff` for linting and formatting. Pre-commit hooks run automatically on commit. | |
| 24 | + | |
| 25 | +```bash | |
| 26 | +ruff check navegador/ | |
| 27 | +ruff format navegador/ | |
| 28 | +``` | |
| 29 | + | |
| 30 | +## Pull requests | |
| 31 | + | |
| 32 | +1. Fork the repo and create a branch from `main` | |
| 33 | +2. Add tests for new behaviour | |
| 34 | +3. Ensure CI passes | |
| 35 | +4. Open a PR with a clear description of the change | |
| 36 | + | |
| 37 | +## Commit messages | |
| 38 | + | |
| 39 | +Use the imperative mood: `add X`, `fix Y`, `update Z`. Keep the first line under 72 characters. |
| --- a/.github/CONTRIBUTING.md | |
| +++ b/.github/CONTRIBUTING.md | |
| @@ -0,0 +1,39 @@ | |
| --- a/.github/CONTRIBUTING.md | |
| +++ b/.github/CONTRIBUTING.md | |
| @@ -0,0 +1,39 @@ | |
| 1 | # Contributing to Navegador |
| 2 | |
| 3 | Thanks for your interest in contributing! |
| 4 | |
| 5 | ## Development setup |
| 6 | |
| 7 | ```bash |
| 8 | git clone https://github.com/ConflictHQ/navegador |
| 9 | cd navegador |
| 10 | python -m venv .venv && source .venv/bin/activate |
| 11 | pip install -e ".[dev]" |
| 12 | pre-commit install |
| 13 | ``` |
| 14 | |
| 15 | ## Running tests |
| 16 | |
| 17 | ```bash |
| 18 | pytest tests/ -v |
| 19 | ``` |
| 20 | |
| 21 | ## Code style |
| 22 | |
| 23 | We use `ruff` for linting and formatting. Pre-commit hooks run automatically on commit. |
| 24 | |
| 25 | ```bash |
| 26 | ruff check navegador/ |
| 27 | ruff format navegador/ |
| 28 | ``` |
| 29 | |
| 30 | ## Pull requests |
| 31 | |
| 32 | 1. Fork the repo and create a branch from `main` |
| 33 | 2. Add tests for new behaviour |
| 34 | 3. Ensure CI passes |
| 35 | 4. Open a PR with a clear description of the change |
| 36 | |
| 37 | ## Commit messages |
| 38 | |
| 39 | Use the imperative mood: `add X`, `fix Y`, `update Z`. Keep the first line under 72 characters. |
| --- a/.github/PULL_REQUEST_TEMPLATE.md | ||
| +++ b/.github/PULL_REQUEST_TEMPLATE.md | ||
| @@ -0,0 +1,17 @@ | ||
| 1 | +## Summary | |
| 2 | + | |
| 3 | +<!-- What does this PR do? --> | |
| 4 | + | |
| 5 | +## Changes | |
| 6 | + | |
| 7 | +- | |
| 8 | + | |
| 9 | +## Testing | |
| 10 | + | |
| 11 | +<!-- How was this tested? --> | |
| 12 | + | |
| 13 | +## Checklist | |
| 14 | + | |
| 15 | +- [ ] Tests added / updated | |
| 16 | +- [ ] Docs updated (if needed) | |
| 17 | +- [ ] `ruff check` passes |
| --- a/.github/PULL_REQUEST_TEMPLATE.md | |
| +++ b/.github/PULL_REQUEST_TEMPLATE.md | |
| @@ -0,0 +1,17 @@ | |
| --- a/.github/PULL_REQUEST_TEMPLATE.md | |
| +++ b/.github/PULL_REQUEST_TEMPLATE.md | |
| @@ -0,0 +1,17 @@ | |
| 1 | ## Summary |
| 2 | |
| 3 | <!-- What does this PR do? --> |
| 4 | |
| 5 | ## Changes |
| 6 | |
| 7 | - |
| 8 | |
| 9 | ## Testing |
| 10 | |
| 11 | <!-- How was this tested? --> |
| 12 | |
| 13 | ## Checklist |
| 14 | |
| 15 | - [ ] Tests added / updated |
| 16 | - [ ] Docs updated (if needed) |
| 17 | - [ ] `ruff check` passes |
+18
| --- a/.github/SECURITY.md | ||
| +++ b/.github/SECURITY.md | ||
| @@ -0,0 +1,18 @@ | ||
| 1 | +# Security Policy | |
| 2 | + | |
| 3 | +## Supported Versions | |
| 4 | + | |
| 5 | +| Version | Supported | | |
| 6 | +| ------- | --------- | | |
| 7 | +| 0.x | ✅ | | |
| 8 | + | |
| 9 | +## Reporting a Vulnerability | |
| 10 | + | |
| 11 | +Please **do not** open a public GitHub issue for security vulnerabilities. | |
| 12 | + | |
| 13 | +Report vulnerabilities by emailing **[email protected]** with: | |
| 14 | +- A description of the vulnerability | |
| 15 | +- Steps to reproduce | |
| 16 | +- Potential impact | |
| 17 | + | |
| 18 | +You will receive a response within 48 hours. We will work with you on a fix and coordinate disclosure. |
| --- a/.github/SECURITY.md | |
| +++ b/.github/SECURITY.md | |
| @@ -0,0 +1,18 @@ | |
| --- a/.github/SECURITY.md | |
| +++ b/.github/SECURITY.md | |
| @@ -0,0 +1,18 @@ | |
| 1 | # Security Policy |
| 2 | |
| 3 | ## Supported Versions |
| 4 | |
| 5 | | Version | Supported | |
| 6 | | ------- | --------- | |
| 7 | | 0.x | ✅ | |
| 8 | |
| 9 | ## Reporting a Vulnerability |
| 10 | |
| 11 | Please **do not** open a public GitHub issue for security vulnerabilities. |
| 12 | |
| 13 | Report vulnerabilities by emailing **[email protected]** with: |
| 14 | - A description of the vulnerability |
| 15 | - Steps to reproduce |
| 16 | - Potential impact |
| 17 | |
| 18 | You will receive a response within 48 hours. We will work with you on a fix and coordinate disclosure. |
+15
| --- a/.github/workflows/ci.yml | ||
| +++ b/.github/workflows/ci.yml | ||
| @@ -0,0 +1,15 @@ | ||
| 1 | +name: CI | |
| 2 | + | |
| 3 | +on: | |
| 4 | + push: | |
| 5 | + branches: [main] | |
| 6 | + pull_request: | |
| 7 | + branches: [main] | |
| 8 | + | |
| 9 | +jobs: | |
| 10 | + test: | |
| 11 | + runs-on: ${{ matrix.os }} | |
| 12 | + strategy: | |
| 13 | + matrix: | |
| 14 | + os: [ubuntu-latest, macos-latest] | |
| 15 | + python-version: ["3.10", "3.11",N@CV,9T@4L,2:2"G@4W,Y@7L,2a@Eb,2uWjkR; |
| --- a/.github/workflows/ci.yml | |
| +++ b/.github/workflows/ci.yml | |
| @@ -0,0 +1,15 @@ | |
| --- a/.github/workflows/ci.yml | |
| +++ b/.github/workflows/ci.yml | |
| @@ -0,0 +1,15 @@ | |
| 1 | name: CI |
| 2 | |
| 3 | on: |
| 4 | push: |
| 5 | branches: [main] |
| 6 | pull_request: |
| 7 | branches: [main] |
| 8 | |
| 9 | jobs: |
| 10 | test: |
| 11 | runs-on: ${{ matrix.os }} |
| 12 | strategy: |
| 13 | matrix: |
| 14 | os: [ubuntu-latest, macos-latest] |
| 15 | python-version: ["3.10", "3.11",N@CV,9T@4L,2:2"G@4W,Y@7L,2a@Eb,2uWjkR; |
| --- a/.github/workflows/docs.yml | ||
| +++ b/.github/workflows/docs.yml | ||
| @@ -0,0 +1,30 @@ | ||
| 1 | +name: Deploy Docs | |
| 2 | + | |
| 3 | +on: | |
| 4 | + push: | |
| 5 | + branches: [main] | |
| 6 | + paths: | |
| 7 | + - "docs/**" | |
| 8 | + - "mkdocs.yml" | |
| 9 | + | |
| 10 | +permissions: | |
| 11 | + contents: write | |
| 12 | + pages: write | |
| 13 | + id-token: write | |
| 14 | + | |
| 15 | +jobs: | |
| 16 | + deploy: | |
| 17 | + runs-on: ubuntu-latest | |
| 18 | + steps: | |
| 19 | + - uses: actions/checkout@v4 | |
| 20 | + | |
| 21 | + - uses: actions/setup-python@v5 | |
| 22 | + with: | |
| 23 | + python-version: "3.12" | |
| 24 | + | |
| 25 | + - name: Install dependencies | |
| 26 | + run: | | |
| 27 | + pip install mkdocs-material mkdocstrings[python] pymdown-extensions | |
| 28 | + | |
| 29 | + - name: Build and deploy | |
| 30 | + run: mkdocs gh-deploy --force |
| --- a/.github/workflows/docs.yml | |
| +++ b/.github/workflows/docs.yml | |
| @@ -0,0 +1,30 @@ | |
| --- a/.github/workflows/docs.yml | |
| +++ b/.github/workflows/docs.yml | |
| @@ -0,0 +1,30 @@ | |
| 1 | name: Deploy Docs |
| 2 | |
| 3 | on: |
| 4 | push: |
| 5 | branches: [main] |
| 6 | paths: |
| 7 | - "docs/**" |
| 8 | - "mkdocs.yml" |
| 9 | |
| 10 | permissions: |
| 11 | contents: write |
| 12 | pages: write |
| 13 | id-token: write |
| 14 | |
| 15 | jobs: |
| 16 | deploy: |
| 17 | runs-on: ubuntu-latest |
| 18 | steps: |
| 19 | - uses: actions/checkout@v4 |
| 20 | |
| 21 | - uses: actions/setup-python@v5 |
| 22 | with: |
| 23 | python-version: "3.12" |
| 24 | |
| 25 | - name: Install dependencies |
| 26 | run: | |
| 27 | pip install mkdocs-material mkdocstrings[python] pymdown-extensions |
| 28 | |
| 29 | - name: Build and deploy |
| 30 | run: mkdocs gh-deploy --force |
| --- a/.github/workflows/publish.yml | ||
| +++ b/.github/workflows/publish.yml | ||
| @@ -0,0 +1,26 @@ | ||
| 1 | +name: Publish to PyPI | |
| 2 | + | |
| 3 | +on: | |
| 4 | + release: | |
| 5 | + types: [published] | |
| 6 | + | |
| 7 | +jobs: | |
| 8 | + publish: | |
| 9 | + runs-on: ubuntu-latest | |
| 10 | + environment: pypi | |
| 11 | + permissions: | |
| 12 | + id-token: write | |
| 13 | + steps: | |
| 14 | + - uses: actions/checkout@v4 | |
| 15 | + | |
| 16 | + - uses: actions/setup-python@v5 | |
| 17 | + with: | |
| 18 | + python-version: "3.12" | |
| 19 | + | |
| 20 | + - name: Build package | |
| 21 | + run: | | |
| 22 | + pip install build | |
| 23 | + python -m build | |
| 24 | + | |
| 25 | + - name: Publish to PyPI | |
| 26 | + uses: pypa/gh-action-pypi-publish@release/v1 |
| --- a/.github/workflows/publish.yml | |
| +++ b/.github/workflows/publish.yml | |
| @@ -0,0 +1,26 @@ | |
| --- a/.github/workflows/publish.yml | |
| +++ b/.github/workflows/publish.yml | |
| @@ -0,0 +1,26 @@ | |
| 1 | name: Publish to PyPI |
| 2 | |
| 3 | on: |
| 4 | release: |
| 5 | types: [published] |
| 6 | |
| 7 | jobs: |
| 8 | publish: |
| 9 | runs-on: ubuntu-latest |
| 10 | environment: pypi |
| 11 | permissions: |
| 12 | id-token: write |
| 13 | steps: |
| 14 | - uses: actions/checkout@v4 |
| 15 | |
| 16 | - uses: actions/setup-python@v5 |
| 17 | with: |
| 18 | python-version: "3.12" |
| 19 | |
| 20 | - name: Build package |
| 21 | run: | |
| 22 | pip install build |
| 23 | python -m build |
| 24 | |
| 25 | - name: Publish to PyPI |
| 26 | uses: pypa/gh-action-pypi-publish@release/v1 |
+73
| --- a/.gitignore | ||
| +++ b/.gitignore | ||
| @@ -0,0 +1,73 @@ | ||
| 1 | +# Byte-compiled / optimized / DLL files | |
| 2 | +__pycache__/ | |
| 3 | +*.py[cod] | |
| 4 | +*$py.class | |
| 5 | + | |
| 6 | +# C extensions | |
| 7 | +*.so | |
| 8 | + | |
| 9 | +# Distribution / packaging | |
| 10 | +dist/ | |
| 11 | +build/ | |
| 12 | +*.egg-info/ | |
| 13 | + | |
| 14 | +# Unit test / coverage reports | |
| 15 | +htmlcov/ | |
| 16 | +.tox/ | |
| 17 | +.coverage | |
| 18 | +.coverage.* | |
| 19 | +.cache | |
| 20 | +.pytest_cache/ | |
| 21 | +.mypy_cache/ | |
| 22 | +coverage.xml | |
| 23 | +*.cover | |
| 24 | + | |
| 25 | +# Virtual environments | |
| 26 | +.venv/ | |
| 27 | +venv/ | |
| 28 | +env/ | |
| 29 | +ENV/ | |
| 30 | + | |
| 31 | +# IDE and editor files | |
| 32 | +.idea/ | |
| 33 | +.vscode/ | |
| 34 | +.cursor/ | |
| 35 | +*.swp | |
| 36 | +*.swo | |
| 37 | + | |
| 38 | +# AI tools | |
| 39 | +.claude/ | |
| 40 | +.gemini/ | |
| 41 | +.codex/ | |
| 42 | +.aider/ | |
| 43 | +.continue/ | |
| 44 | +.copilot/ | |
| 45 | +AGENTS.md | |
| 46 | +GEMINI.md | |
| 47 | + | |
| 48 | +# API keys and secrets | |
| 49 | +.env | |
| 50 | +.env.* | |
| 51 | +!.env.example | |
| 52 | +*service_account*.json | |
| 53 | +*credentials*.json | |
| 54 | + | |
| 55 | +# Log files | |
| 56 | +*.log | |
| 57 | + | |
| 58 | +# OS specific | |
| 59 | +.DS_Store | |
| 60 | +Thumbs.db | |
| 61 | + | |
| 62 | +# MkDocs build output | |
| 63 | +site/ | |
| 64 | + | |
| 65 | +# Navegador runtime | |
| 66 | +*.db | |
| 67 | +*.db-shm | |
| 68 | +*.db-wal | |
| 69 | +.navegador/ | |
| 70 | +navegador-graph/ | |
| 71 | + | |
| 72 | +# Ruff cache | |
| 73 | +.ruff_cache/ |
| --- a/.gitignore | |
| +++ b/.gitignore | |
| @@ -0,0 +1,73 @@ | |
| --- a/.gitignore | |
| +++ b/.gitignore | |
| @@ -0,0 +1,73 @@ | |
| 1 | # Byte-compiled / optimized / DLL files |
| 2 | __pycache__/ |
| 3 | *.py[cod] |
| 4 | *$py.class |
| 5 | |
| 6 | # C extensions |
| 7 | *.so |
| 8 | |
| 9 | # Distribution / packaging |
| 10 | dist/ |
| 11 | build/ |
| 12 | *.egg-info/ |
| 13 | |
| 14 | # Unit test / coverage reports |
| 15 | htmlcov/ |
| 16 | .tox/ |
| 17 | .coverage |
| 18 | .coverage.* |
| 19 | .cache |
| 20 | .pytest_cache/ |
| 21 | .mypy_cache/ |
| 22 | coverage.xml |
| 23 | *.cover |
| 24 | |
| 25 | # Virtual environments |
| 26 | .venv/ |
| 27 | venv/ |
| 28 | env/ |
| 29 | ENV/ |
| 30 | |
| 31 | # IDE and editor files |
| 32 | .idea/ |
| 33 | .vscode/ |
| 34 | .cursor/ |
| 35 | *.swp |
| 36 | *.swo |
| 37 | |
| 38 | # AI tools |
| 39 | .claude/ |
| 40 | .gemini/ |
| 41 | .codex/ |
| 42 | .aider/ |
| 43 | .continue/ |
| 44 | .copilot/ |
| 45 | AGENTS.md |
| 46 | GEMINI.md |
| 47 | |
| 48 | # API keys and secrets |
| 49 | .env |
| 50 | .env.* |
| 51 | !.env.example |
| 52 | *service_account*.json |
| 53 | *credentials*.json |
| 54 | |
| 55 | # Log files |
| 56 | *.log |
| 57 | |
| 58 | # OS specific |
| 59 | .DS_Store |
| 60 | Thumbs.db |
| 61 | |
| 62 | # MkDocs build output |
| 63 | site/ |
| 64 | |
| 65 | # Navegador runtime |
| 66 | *.db |
| 67 | *.db-shm |
| 68 | *.db-wal |
| 69 | .navegador/ |
| 70 | navegador-graph/ |
| 71 | |
| 72 | # Ruff cache |
| 73 | .ruff_cache/ |
+18
| --- a/.pre-commit-config.yaml | ||
| +++ b/.pre-commit-config.yaml | ||
| @@ -0,0 +1,18 @@ | ||
| 1 | +repos: | |
| 2 | + - repo: https://github.com/astral-sh/ruff-pre-commit | |
| 3 | + rev: v0.9.7 | |
| 4 | + hooks: | |
| 5 | + - id: ruff | |
| 6 | + args: [--fix] | |
| 7 | + - id: ruff-format | |
| 8 | + - repo: https://github.com/pre-commit/pre-commit-hooks | |
| 9 | + rev: v5.0.0 | |
| 10 | + hooks: | |
| 11 | + - id: trailing-whitespace | |
| 12 | + - id: end-of-file-fixer | |
| 13 | + - id: check-yaml | |
| 14 | + args: [--unsafe] | |
| 15 | + - id: check-added-large-files | |
| 16 | + args: [--maxkb=500] | |
| 17 | + - id: check-merge-conflict | |
| 18 | + - id: detect-private-key |
| --- a/.pre-commit-config.yaml | |
| +++ b/.pre-commit-config.yaml | |
| @@ -0,0 +1,18 @@ | |
| --- a/.pre-commit-config.yaml | |
| +++ b/.pre-commit-config.yaml | |
| @@ -0,0 +1,18 @@ | |
| 1 | repos: |
| 2 | - repo: https://github.com/astral-sh/ruff-pre-commit |
| 3 | rev: v0.9.7 |
| 4 | hooks: |
| 5 | - id: ruff |
| 6 | args: [--fix] |
| 7 | - id: ruff-format |
| 8 | - repo: https://github.com/pre-commit/pre-commit-hooks |
| 9 | rev: v5.0.0 |
| 10 | hooks: |
| 11 | - id: trailing-whitespace |
| 12 | - id: end-of-file-fixer |
| 13 | - id: check-yaml |
| 14 | args: [--unsafe] |
| 15 | - id: check-added-large-files |
| 16 | args: [--maxkb=500] |
| 17 | - id: check-merge-conflict |
| 18 | - id: detect-private-key |
+12
| --- a/CHANGELOG.md | ||
| +++ b/CHANGELOG.md | ||
| @@ -0,0 +1,12 @@ | ||
| 1 | +# Changelog | |
| 2 | + | |
| 3 | +## 0.1.0 — 2026-03-22 | |
| 4 | + | |
| 5 | +Initial release scaffold. | |
| 6 | + | |
| 7 | +- AST ingestion pipeline (Python + TypeScript via tree-sitter) | |
| 8 | +- Property graph storage via FalkorDB-lite (SQLite) or Redis | |
| 9 | +- Context bundles: file, function, class context loading | |
| 10 | +- MCP server with 7 tools for AI agent integration | |
| 11 | +- CLI: `ingest`, `context`, `search`, `stats`, `mcp` | |
| 12 | +- MkDocs documentation site (navegador.dev) |
| --- a/CHANGELOG.md | |
| +++ b/CHANGELOG.md | |
| @@ -0,0 +1,12 @@ | |
| --- a/CHANGELOG.md | |
| +++ b/CHANGELOG.md | |
| @@ -0,0 +1,12 @@ | |
| 1 | # Changelog |
| 2 | |
| 3 | ## 0.1.0 — 2026-03-22 |
| 4 | |
| 5 | Initial release scaffold. |
| 6 | |
| 7 | - AST ingestion pipeline (Python + TypeScript via tree-sitter) |
| 8 | - Property graph storage via FalkorDB-lite (SQLite) or Redis |
| 9 | - Context bundles: file, function, class context loading |
| 10 | - MCP server with 7 tools for AI agent integration |
| 11 | - CLI: `ingest`, `context`, `search`, `stats`, `mcp` |
| 12 | - MkDocs documentation site (navegador.dev) |
+50
| --- a/CLAUDE.md | ||
| +++ b/CLAUDE.md | ||
| @@ -0,0 +1,50 @@ | ||
| 1 | +# Navegador — Claude Context | |
| 2 | + | |
| 3 | +## What it is | |
| 4 | + | |
| 5 | +AST + knowledge graph context engine for AI coding agents. Parses codebases into a FalkorDB property graph. Agents query via MCP or Python API. | |
| 6 | + | |
| 7 | +## Stack | |
| 8 | + | |
| 9 | +- **Python 3.10+**, standalone (no Django dependency) | |
| 10 | +- **tree-sitter** for multi-language AST parsing (`tree-sitter-pythone AST parsing (13 languages) | |
| 11 | +- **FalkorDB** graph DB with **falkordblite** (SQLite via redislite) for local use | |
| 12 | +- **MCP** (`mcp` Python SD | |
| 13 | +- **Click + Rich** for CLI | |
| 14 | +- **Pydantic** for data models | |
| 15 | +- **Ruff** for linting/formatting | |
| 16 | + | |
| 17 | +## Package layout | |
| 18 | + | |
| 19 | +``` | |
| 20 | +navegador/ | |
| 21 | + cli/ — Click commands (ingest, context, search, stats, mcp) | |
| 22 | + graph/ — GraphStore + schema + Cypher query templates | |
| 23 | + ingestion/ — RepoIngester + language parsers (python.py+ optimization | |
| 24 | + context/ — ContextLoader + ContextBundle (JSON/markdown) | |
| 25 | + mcp/ — MCP server with 11 tools + security hardening | |
| 26 | + enrichment/ — FrameworkEnricher base + 8 framework enrichers | |
| 27 | + analysis/ — impact, flow tracing, dead code, cycles, test mapping | |
| 28 | + intelligence/ — semantic search, community detection, NLP, doc generation | |
| 29 | + cluster/ — Redis pub/sub, task queue, locking, sessions, messaging | |
| 30 | + sdk.py — Python SDK (Navegador class) | |
| 31 | + llm.py — LLM provider abstraction (Anthropic, OpenAI, Ollama) | |
| 32 | + vcs.py — VCS abstraction (Git, Fossil) | |
| 33 | + diff.py — Git diff → graph impact mapping | |
| 34 | + churn.py — Behavioural coupling from git history | |
| 35 | + monorepo.py — Workspace detection + ingestion | |
| 36 | + security.py — Sensitive content detection + redaction | |
| 37 | + explorer/ — HTTP server + browser-based graph visualization | |
| 38 | +``` | |
| 39 | + | |
| 40 | +## FalkorDB connection | |
| 41 | + | |
| 42 | +```python | |
| 43 | +# SQLite (local, zero-infra) — uses falkordblite | |
| 44 | +from redislite import FalkorDB # falkordblite provides this | |
| 45 | +db = FalkorDB("path/to/graph.db") | |
| 46 | +graph = db.select_graph("navegador") | |
| 47 | + | |
| 48 | +# Redis (production) | |
| 49 | +import falkordb | |
| 50 | +client = falkordb.Fa |
| --- a/CLAUDE.md | |
| +++ b/CLAUDE.md | |
| @@ -0,0 +1,50 @@ | |
| --- a/CLAUDE.md | |
| +++ b/CLAUDE.md | |
| @@ -0,0 +1,50 @@ | |
| 1 | # Navegador — Claude Context |
| 2 | |
| 3 | ## What it is |
| 4 | |
| 5 | AST + knowledge graph context engine for AI coding agents. Parses codebases into a FalkorDB property graph. Agents query via MCP or Python API. |
| 6 | |
| 7 | ## Stack |
| 8 | |
| 9 | - **Python 3.10+**, standalone (no Django dependency) |
| 10 | - **tree-sitter** for multi-language AST parsing (`tree-sitter-pythone AST parsing (13 languages) |
| 11 | - **FalkorDB** graph DB with **falkordblite** (SQLite via redislite) for local use |
| 12 | - **MCP** (`mcp` Python SD |
| 13 | - **Click + Rich** for CLI |
| 14 | - **Pydantic** for data models |
| 15 | - **Ruff** for linting/formatting |
| 16 | |
| 17 | ## Package layout |
| 18 | |
| 19 | ``` |
| 20 | navegador/ |
| 21 | cli/ — Click commands (ingest, context, search, stats, mcp) |
| 22 | graph/ — GraphStore + schema + Cypher query templates |
| 23 | ingestion/ — RepoIngester + language parsers (python.py+ optimization |
| 24 | context/ — ContextLoader + ContextBundle (JSON/markdown) |
| 25 | mcp/ — MCP server with 11 tools + security hardening |
| 26 | enrichment/ — FrameworkEnricher base + 8 framework enrichers |
| 27 | analysis/ — impact, flow tracing, dead code, cycles, test mapping |
| 28 | intelligence/ — semantic search, community detection, NLP, doc generation |
| 29 | cluster/ — Redis pub/sub, task queue, locking, sessions, messaging |
| 30 | sdk.py — Python SDK (Navegador class) |
| 31 | llm.py — LLM provider abstraction (Anthropic, OpenAI, Ollama) |
| 32 | vcs.py — VCS abstraction (Git, Fossil) |
| 33 | diff.py — Git diff → graph impact mapping |
| 34 | churn.py — Behavioural coupling from git history |
| 35 | monorepo.py — Workspace detection + ingestion |
| 36 | security.py — Sensitive content detection + redaction |
| 37 | explorer/ — HTTP server + browser-based graph visualization |
| 38 | ``` |
| 39 | |
| 40 | ## FalkorDB connection |
| 41 | |
| 42 | ```python |
| 43 | # SQLite (local, zero-infra) — uses falkordblite |
| 44 | from redislite import FalkorDB # falkordblite provides this |
| 45 | db = FalkorDB("path/to/graph.db") |
| 46 | graph = db.select_graph("navegador") |
| 47 | |
| 48 | # Redis (production) |
| 49 | import falkordb |
| 50 | client = falkordb.Fa |
A
LICENSE
+21
| --- a/LICENSE | ||
| +++ b/LICENSE | ||
| @@ -0,0 +1,21 @@ | ||
| 1 | +MIT License | |
| 2 | + | |
| 3 | +Copy. All rights reserved. | |
| 4 | + | |
| 5 | +Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 6 | +of this software and associated documentation files (the "Software"), to deal | |
| 7 | +in the Software without restriction, including without limitation the rights | |
| 8 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 9 | +copies of the Software, and to permit persons to whom the Software is | |
| 10 | +furnished to do so, subject to the following conditions: | |
| 11 | + | |
| 12 | +The above copyright notice and this permission notice shall be included in all | |
| 13 | +copies or substantial portions of the Software. | |
| 14 | + | |
| 15 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 16 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 17 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 18 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 19 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 20 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| 21 | +SOFTWARE. |
| --- a/LICENSE | |
| +++ b/LICENSE | |
| @@ -0,0 +1,21 @@ | |
| --- a/LICENSE | |
| +++ b/LICENSE | |
| @@ -0,0 +1,21 @@ | |
| 1 | MIT License |
| 2 | |
| 3 | Copy. All rights reserved. |
| 4 | |
| 5 | Permission is hereby granted, free of charge, to any person obtaining a copy |
| 6 | of this software and associated documentation files (the "Software"), to deal |
| 7 | in the Software without restriction, including without limitation the rights |
| 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 9 | copies of the Software, and to permit persons to whom the Software is |
| 10 | furnished to do so, subject to the following conditions: |
| 11 | |
| 12 | The above copyright notice and this permission notice shall be included in all |
| 13 | copies or substantial portions of the Software. |
| 14 | |
| 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 21 | SOFTWARE. |
+23
| --- a/README.md | ||
| +++ b/README.md | ||
| @@ -0,0 +1,23 @@ | ||
| 1 | +# Navegador | |
| 2 | + | |
| 3 | +**AST + knowledge graph context engine for AI coding agents.** | |
| 4 | + | |
| 5 | +Navegador parses your codebase into a property graph and makes it queryable. AI coding agents can ask "what calls this function?", "what does this file depend on?", or "show me everythianswersWhy | |
| 6 | + | |
| 7 | +AI coding agents load context by reading raw files. They don't know what calls what, what depends on what, or which 5 functions out of 500 are actually relevant. Navegador builds a structured map — then exposes it via MCP so any agent can navigate your code with precisionfor a symbol | |
| 8 | +Graph schema | |
| 9 | + | |
| 10 | +**N | |
| 11 | + | |
| 12 | +--- | |
| 13 | + | |
| 14 | +## Storage | |
| 15 | + | |
| 16 | +Navegador uses **FalkorDB** (property graph, Cypher queries).— in one queryable graph.** | |
| 17 | + | |
| 18 | +Navegador parses your source code int# Navegador | |
| 19 | + | |
| 20 | +**Your cod# SQLite (default) | |
| 21 | + | |
| 22 | +# Redis LLCf 500 are actually relevant. Na) | |
| 23 | +, Rust, Java | Planned |
| --- a/README.md | |
| +++ b/README.md | |
| @@ -0,0 +1,23 @@ | |
| --- a/README.md | |
| +++ b/README.md | |
| @@ -0,0 +1,23 @@ | |
| 1 | # Navegador |
| 2 | |
| 3 | **AST + knowledge graph context engine for AI coding agents.** |
| 4 | |
| 5 | Navegador parses your codebase into a property graph and makes it queryable. AI coding agents can ask "what calls this function?", "what does this file depend on?", or "show me everythianswersWhy |
| 6 | |
| 7 | AI coding agents load context by reading raw files. They don't know what calls what, what depends on what, or which 5 functions out of 500 are actually relevant. Navegador builds a structured map — then exposes it via MCP so any agent can navigate your code with precisionfor a symbol |
| 8 | Graph schema |
| 9 | |
| 10 | **N |
| 11 | |
| 12 | --- |
| 13 | |
| 14 | ## Storage |
| 15 | |
| 16 | Navegador uses **FalkorDB** (property graph, Cypher queries).— in one queryable graph.** |
| 17 | |
| 18 | Navegador parses your source code int# Navegador |
| 19 | |
| 20 | **Your cod# SQLite (default) |
| 21 | |
| 22 | # Redis LLCf 500 are actually relevant. Na) |
| 23 | , Rust, Java | Planned |
+1
| --- a/docs/CNAME | ||
| +++ b/docs/CNAME | ||
| @@ -0,0 +1 @@ | ||
| 1 | +navegador.dev |
| --- a/docs/CNAME | |
| +++ b/docs/CNAME | |
| @@ -0,0 +1 @@ | |
| --- a/docs/CNAME | |
| +++ b/docs/CNAME | |
| @@ -0,0 +1 @@ | |
| 1 | navegador.dev |
+3
| --- a/docs/api/graph.md | ||
| +++ b/docs/api/graph.md | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +# graph | |
| 2 | + | |
| 3 | +Documentation coming soon. |
| --- a/docs/api/graph.md | |
| +++ b/docs/api/graph.md | |
| @@ -0,0 +1,3 @@ | |
| --- a/docs/api/graph.md | |
| +++ b/docs/api/graph.md | |
| @@ -0,0 +1,3 @@ | |
| 1 | # graph |
| 2 | |
| 3 | Documentation coming soon. |
| --- a/docs/api/ingestion.md | ||
| +++ b/docs/api/ingestion.md | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +# ingestion | |
| 2 | + | |
| 3 | +Documentation coming soon. |
| --- a/docs/api/ingestion.md | |
| +++ b/docs/api/ingestion.md | |
| @@ -0,0 +1,3 @@ | |
| --- a/docs/api/ingestion.md | |
| +++ b/docs/api/ingestion.md | |
| @@ -0,0 +1,3 @@ | |
| 1 | # ingestion |
| 2 | |
| 3 | Documentation coming soon. |
+3
| --- a/docs/api/mcp.md | ||
| +++ b/docs/api/mcp.md | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +# mcp | |
| 2 | + | |
| 3 | +Documentation coming soon. |
| --- a/docs/api/mcp.md | |
| +++ b/docs/api/mcp.md | |
| @@ -0,0 +1,3 @@ | |
| --- a/docs/api/mcp.md | |
| +++ b/docs/api/mcp.md | |
| @@ -0,0 +1,3 @@ | |
| 1 | # mcp |
| 2 | |
| 3 | Documentation coming soon. |
| --- a/docs/architecture/graph-schema.md | ||
| +++ b/docs/architecture/graph-schema.md | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +# graph-schema | |
| 2 | + | |
| 3 | +Documentation coming soon. |
| --- a/docs/architecture/graph-schema.md | |
| +++ b/docs/architecture/graph-schema.md | |
| @@ -0,0 +1,3 @@ | |
| --- a/docs/architecture/graph-schema.md | |
| +++ b/docs/architecture/graph-schema.md | |
| @@ -0,0 +1,3 @@ | |
| 1 | # graph-schema |
| 2 | |
| 3 | Documentation coming soon. |
| --- a/docs/architecture/overview.md | ||
| +++ b/docs/architecture/overview.md | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +# overview | |
| 2 | + | |
| 3 | +Documentation coming soon. |
| --- a/docs/architecture/overview.md | |
| +++ b/docs/architecture/overview.md | |
| @@ -0,0 +1,3 @@ | |
| --- a/docs/architecture/overview.md | |
| +++ b/docs/architecture/overview.md | |
| @@ -0,0 +1,3 @@ | |
| 1 | # overview |
| 2 | |
| 3 | Documentation coming soon. |
| --- a/docs/assets/css/custom.css | ||
| +++ b/docs/assets/css/custom.css | ||
| @@ -0,0 +1,16 @@ | ||
| 1 | +/* Navegador — MkDocs Material custom theme */ | |
| 2 | + | |
| 3 | +:root { | |
| 4 | + --md-primary-fg-color: #DC394C; | |
| 5 | + --md-primary-fg-color--light: #e8677a; | |
| 6 | + --md-primary-fg-color--dark: #8B3138; | |
| 7 | + --md-accent-fg-color: #DC394C; | |
| 8 | +} | |
| 9 | + | |
| 10 | +[data-md-color-scheme="slate"] { | |
| 11 | + --md-primary-fg-color: #DC394C; | |
| 12 | + --md-primary-fg-color--light: #e8677a; | |
| 13 | + --md-primary-fg-color--dark: #8B3138; | |
| 14 | + --md-default-bg-color: #1a1c1b; | |
| 15 | + --md-code-bg-color: #222423; | |
| 16 | +} |
| --- a/docs/assets/css/custom.css | |
| +++ b/docs/assets/css/custom.css | |
| @@ -0,0 +1,16 @@ | |
| --- a/docs/assets/css/custom.css | |
| +++ b/docs/assets/css/custom.css | |
| @@ -0,0 +1,16 @@ | |
| 1 | /* Navegador — MkDocs Material custom theme */ |
| 2 | |
| 3 | :root { |
| 4 | --md-primary-fg-color: #DC394C; |
| 5 | --md-primary-fg-color--light: #e8677a; |
| 6 | --md-primary-fg-color--dark: #8B3138; |
| 7 | --md-accent-fg-color: #DC394C; |
| 8 | } |
| 9 | |
| 10 | [data-md-color-scheme="slate"] { |
| 11 | --md-primary-fg-color: #DC394C; |
| 12 | --md-primary-fg-color--light: #e8677a; |
| 13 | --md-primary-fg-color--dark: #8B3138; |
| 14 | --md-default-bg-color: #1a1c1b; |
| 15 | --md-code-bg-color: #222423; |
| 16 | } |
| --- a/docs/getting-started/configuration.md | ||
| +++ b/docs/getting-started/configuration.md | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +# configuration | |
| 2 | + | |
| 3 | +Documentation coming soon. |
| --- a/docs/getting-started/configuration.md | |
| +++ b/docs/getting-started/configuration.md | |
| @@ -0,0 +1,3 @@ | |
| --- a/docs/getting-started/configuration.md | |
| +++ b/docs/getting-started/configuration.md | |
| @@ -0,0 +1,3 @@ | |
| 1 | # configuration |
| 2 | |
| 3 | Documentation coming soon. |
| --- a/docs/getting-started/installation.md | ||
| +++ b/docs/getting-started/installation.md | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +# installation | |
| 2 | + | |
| 3 | +Documentation coming soon. |
| --- a/docs/getting-started/installation.md | |
| +++ b/docs/getting-started/installation.md | |
| @@ -0,0 +1,3 @@ | |
| --- a/docs/getting-started/installation.md | |
| +++ b/docs/getting-started/installation.md | |
| @@ -0,0 +1,3 @@ | |
| 1 | # installation |
| 2 | |
| 3 | Documentation coming soon. |
| --- a/docs/getting-started/quickstart.md | ||
| +++ b/docs/getting-started/quickstart.md | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +# quickstart | |
| 2 | + | |
| 3 | +Documentation coming soon. |
| --- a/docs/getting-started/quickstart.md | |
| +++ b/docs/getting-started/quickstart.md | |
| @@ -0,0 +1,3 @@ | |
| --- a/docs/getting-started/quickstart.md | |
| +++ b/docs/getting-started/quickstart.md | |
| @@ -0,0 +1,3 @@ | |
| 1 | # quickstart |
| 2 | |
| 3 | Documentation coming soon. |
| --- a/docs/guide/context-loading.md | ||
| +++ b/docs/guide/context-loading.md | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +# context-loading | |
| 2 | + | |
| 3 | +Documentation coming soon. |
| --- a/docs/guide/context-loading.md | |
| +++ b/docs/guide/context-loading.md | |
| @@ -0,0 +1,3 @@ | |
| --- a/docs/guide/context-loading.md | |
| +++ b/docs/guide/context-loading.md | |
| @@ -0,0 +1,3 @@ | |
| 1 | # context-loading |
| 2 | |
| 3 | Documentation coming soon. |
| --- a/docs/guide/graph-queries.md | ||
| +++ b/docs/guide/graph-queries.md | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +# graph-queries | |
| 2 | + | |
| 3 | +Documentation coming soon. |
| --- a/docs/guide/graph-queries.md | |
| +++ b/docs/guide/graph-queries.md | |
| @@ -0,0 +1,3 @@ | |
| --- a/docs/guide/graph-queries.md | |
| +++ b/docs/guide/graph-queries.md | |
| @@ -0,0 +1,3 @@ | |
| 1 | # graph-queries |
| 2 | |
| 3 | Documentation coming soon. |
| --- a/docs/guide/ingestion.md | ||
| +++ b/docs/guide/ingestion.md | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +# ingestion | |
| 2 | + | |
| 3 | +Documentation coming soon. |
| --- a/docs/guide/ingestion.md | |
| +++ b/docs/guide/ingestion.md | |
| @@ -0,0 +1,3 @@ | |
| --- a/docs/guide/ingestion.md | |
| +++ b/docs/guide/ingestion.md | |
| @@ -0,0 +1,3 @@ | |
| 1 | # ingestion |
| 2 | |
| 3 | Documentation coming soon. |
| --- a/docs/guide/mcp-integration.md | ||
| +++ b/docs/guide/mcp-integration.md | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +# mcp-integration | |
| 2 | + | |
| 3 | +Documentation coming soon. |
| --- a/docs/guide/mcp-integration.md | |
| +++ b/docs/guide/mcp-integration.md | |
| @@ -0,0 +1,3 @@ | |
| --- a/docs/guide/mcp-integration.md | |
| +++ b/docs/guide/mcp-integration.md | |
| @@ -0,0 +1,3 @@ | |
| 1 | # mcp-integration |
| 2 | |
| 3 | Documentation coming soon. |
+46
| --- a/docs/index.md | ||
| +++ b/docs/index.md | ||
| @@ -0,0 +1,46 @@ | ||
| 1 | +# Navegador | |
| 2 | + | |
| 3 | +**AST + knowledge graph context engine for AI coding agents.** | |
| 4 | + | |
| 5 | +Navegador parses your codebase into a property graph — functions, classes, files, imports, call relationships — and makes that structure queryable by AI coding agents via MCP or a Python API. | |
| 6 | + | |
| 7 | +> *navegador* — Spanish for *navigator / sailor*. It helps agents navigate your code. | |
| 8 | + | |
| 9 | +--- | |
| 10 | + | |
| 11 | +## Why | |
| 12 | + | |
| 13 | +AI coding agents (Claude, Cursor, Copilot) load context by reading raw files. They don't know what calls what, what depends on what, or how to find the relevant 5 functions out of 500. Navegador gives agents a structured map. | |
| 14 | + | |
| 15 | +``` | |
| 16 | +agent: "find everything that depends on auth1/sessions.py" | |
| 17 | + | |
| 18 | +→ MATCH (f:File {path: "auth1/sessions.py"})<-[:IMPORTS|CALLS*1..3]-(n) | |
| 19 | +→ returns: 4 files, 12 functions, full source + docstrings | |
| 20 | +``` | |
| 21 | + | |
| 22 | +--- | |
| 23 | + | |
| 24 | +## Features | |
| 25 | + | |
| 26 | +- **Multi-language AST parsing** — Python and TypeScript/JavaScript via tree-sitter | |
| 27 | +- **Property graph storage** — FalkorDB-lite (SQLite, zero infra) or Redis FalkorDB for production | |
| 28 | +- **MCP server** — native integration with Claude, Cursor, and any MCP-compatible agent | |
| 29 | +- **Context bundles** — structured JSON or markdown export of a file/function/class and its relationships | |
| 30 | +- **CLI** — `navegador ingest ./myrepo`, `navegador context src/auth.py` | |
| 31 | + | |
| 32 | +--- | |
| 33 | + | |
| 34 | +## Quick start | |
| 35 | + | |
| 36 | +```bash | |
| 37 | +pip install navegador | |
| 38 | +navegador ingest ./myrepo | |
| 39 | +navegador context src/auth.py | |
| 40 | +``` | |
| 41 | + | |
| 42 | +--- | |
| 43 | + | |
| 44 | +## License | |
| 45 | + | |
| 46 | +MIT — [CONFLICT LLC](https://github.com/ConflictHQ) |
| --- a/docs/index.md | |
| +++ b/docs/index.md | |
| @@ -0,0 +1,46 @@ | |
| --- a/docs/index.md | |
| +++ b/docs/index.md | |
| @@ -0,0 +1,46 @@ | |
| 1 | # Navegador |
| 2 | |
| 3 | **AST + knowledge graph context engine for AI coding agents.** |
| 4 | |
| 5 | Navegador parses your codebase into a property graph — functions, classes, files, imports, call relationships — and makes that structure queryable by AI coding agents via MCP or a Python API. |
| 6 | |
| 7 | > *navegador* — Spanish for *navigator / sailor*. It helps agents navigate your code. |
| 8 | |
| 9 | --- |
| 10 | |
| 11 | ## Why |
| 12 | |
| 13 | AI coding agents (Claude, Cursor, Copilot) load context by reading raw files. They don't know what calls what, what depends on what, or how to find the relevant 5 functions out of 500. Navegador gives agents a structured map. |
| 14 | |
| 15 | ``` |
| 16 | agent: "find everything that depends on auth1/sessions.py" |
| 17 | |
| 18 | → MATCH (f:File {path: "auth1/sessions.py"})<-[:IMPORTS|CALLS*1..3]-(n) |
| 19 | → returns: 4 files, 12 functions, full source + docstrings |
| 20 | ``` |
| 21 | |
| 22 | --- |
| 23 | |
| 24 | ## Features |
| 25 | |
| 26 | - **Multi-language AST parsing** — Python and TypeScript/JavaScript via tree-sitter |
| 27 | - **Property graph storage** — FalkorDB-lite (SQLite, zero infra) or Redis FalkorDB for production |
| 28 | - **MCP server** — native integration with Claude, Cursor, and any MCP-compatible agent |
| 29 | - **Context bundles** — structured JSON or markdown export of a file/function/class and its relationships |
| 30 | - **CLI** — `navegador ingest ./myrepo`, `navegador context src/auth.py` |
| 31 | |
| 32 | --- |
| 33 | |
| 34 | ## Quick start |
| 35 | |
| 36 | ```bash |
| 37 | pip install navegador |
| 38 | navegador ingest ./myrepo |
| 39 | navegador context src/auth.py |
| 40 | ``` |
| 41 | |
| 42 | --- |
| 43 | |
| 44 | ## License |
| 45 | |
| 46 | MIT — [CONFLICT LLC](https://github.com/ConflictHQ) |
+26
| --- a/mkdocs.yml | ||
| +++ b/mkdocs.yml | ||
| @@ -0,0 +1,26 @@ | ||
| 1 | +site_name: Navegador | |
| 2 | +site_url: https://navegador.dev | |
| 3 | +site_description: for AI coding agents — code structure, business rules, and architectural decisions in one queryable graph | |
| 4 | +site_author: CONFLICT LLC | |
| 5 | +repo_url: https://github.com/ConflictHQ/navegador | |
| 6 | +repo_name: ConflictHQ/navegador | |
| 7 | + | |
| 8 | +theme: | |
| 9 | + name: material | |
| 10 | + logo: assets/images/conflict-logo.svg | |
| 11 | + favicon: assets/images/favicon.png | |
| 12 | + font: | |
| 13 | + text: Roboto | |
| 14 | + code: Roboto Mono | |
| 15 | + palette: | |
| 16 | + - media: "(prefers-color-scheme: dark)" | |
| 17 | + scheme: slate | |
| 18 | + primary: custom | |
| 19 | + accent: custom | |
| 20 | + toggle: | |
| 21 | + icon: material/brightness-4 | |
| 22 | + name: Switch to light mode | |
| 23 | + - media: "(prefers-color-scheme: light)" | |
| 24 | + scheme: default | |
| 25 | + primary: custom | |
| 26 | + |
| --- a/mkdocs.yml | |
| +++ b/mkdocs.yml | |
| @@ -0,0 +1,26 @@ | |
| --- a/mkdocs.yml | |
| +++ b/mkdocs.yml | |
| @@ -0,0 +1,26 @@ | |
| 1 | site_name: Navegador |
| 2 | site_url: https://navegador.dev |
| 3 | site_description: for AI coding agents — code structure, business rules, and architectural decisions in one queryable graph |
| 4 | site_author: CONFLICT LLC |
| 5 | repo_url: https://github.com/ConflictHQ/navegador |
| 6 | repo_name: ConflictHQ/navegador |
| 7 | |
| 8 | theme: |
| 9 | name: material |
| 10 | logo: assets/images/conflict-logo.svg |
| 11 | favicon: assets/images/favicon.png |
| 12 | font: |
| 13 | text: Roboto |
| 14 | code: Roboto Mono |
| 15 | palette: |
| 16 | - media: "(prefers-color-scheme: dark)" |
| 17 | scheme: slate |
| 18 | primary: custom |
| 19 | accent: custom |
| 20 | toggle: |
| 21 | icon: material/brightness-4 |
| 22 | name: Switch to light mode |
| 23 | - media: "(prefers-color-scheme: light)" |
| 24 | scheme: default |
| 25 | primary: custom |
| 26 |
| --- a/navegador/__init__.py | ||
| +++ b/navegador/__init__.py | ||
| @@ -0,0 +1,6 @@ | ||
| 1 | +""" | |
| 2 | +Navegador — AST + knowledge graph context engine for AI coding agent0. | |
| 3 | +""" | |
| 4 | + | |
| 5 | +__version__ = "0.1"" | |
| 6 | +Navegador — AST + knowledge |
| --- a/navegador/__init__.py | |
| +++ b/navegador/__init__.py | |
| @@ -0,0 +1,6 @@ | |
| --- a/navegador/__init__.py | |
| +++ b/navegador/__init__.py | |
| @@ -0,0 +1,6 @@ | |
| 1 | """ |
| 2 | Navegador — AST + knowledge graph context engine for AI coding agent0. |
| 3 | """ |
| 4 | |
| 5 | __version__ = "0.1"" |
| 6 | Navegador — AST + knowledge |
No diff available
| --- a/navegador/cli/commands.py | ||
| +++ b/navegador/cli/commands.py | ||
| @@ -0,0 +1,41 @@ | ||
| 1 | +""" | |
| 2 | +N) | |
| 3 | + "fmt""ingest reposools without MCP ovme,rationale=rationale, date=datDecisiocodefrom pathlib import Pathe_type", "code_label", """ | |
| 4 | +N) | |
| 5 | + store | |
| 6 | + | |
| 7 | + node_rows = ("" | |
| 8 | +N) | |
| 9 | + "fmt""" | |
| 10 | +N) | |
| 11 | + """ | |
| 12 | +N) | |
| 13 | + "--redis", "redis_u""" | |
| 14 | +N "fmt "fmt""" | |
| 15 | +N) | |
| 16 | + """"" | |
| 17 | +N) | |
| 18 | + "fmt""" | |
| 19 | +N) | |
| 20 | + DB_OPTIONas JSON."Show graph stat data = {"nodes": store.node_count(), "edges": store.edge_count()}dataelse:Graph stats", show_header=Truedataclick.option( , | |
| 21 | + N) | |
| 22 | + source graph import GraphStore | |
| 23 | + MCP | |
| 24 | + AS) | |
| 25 | +click.option( , | |
| 26 | + N) | |
| 27 | + ) | |
| 28 | +@click.option( the navegador, sh into the nave default="markdown")and print context for a filstoreile(file_path) | |
| 29 | + output = if fmt == "markdow show_header=Trueajson(outpu_header=Truquery"option( , | |
| 30 | + N) | |
| 31 | + )) by namstoreoption( , | |
| 32 | + N) | |
| 33 | + ) | |
| 34 | +def stats(db: strrepos, load context, table.add_row("Nodes", str(store.node_count())) | |
| 35 | + table.add_row("Edges", str(option( , | |
| 36 | + N) | |
| 37 | + ) | |
| 38 | +@click.option("--host", default="127.0.0.1", show_default=True) | |
| 39 | +@click.option("--port", default=8765, show_default=True) | |
| 40 | +def mcp(db: str, host: str, port: intdef store_factory(): | |
| 41 | + store_factoryf |
| --- a/navegador/cli/commands.py | |
| +++ b/navegador/cli/commands.py | |
| @@ -0,0 +1,41 @@ | |
| --- a/navegador/cli/commands.py | |
| +++ b/navegador/cli/commands.py | |
| @@ -0,0 +1,41 @@ | |
| 1 | """ |
| 2 | N) |
| 3 | "fmt""ingest reposools without MCP ovme,rationale=rationale, date=datDecisiocodefrom pathlib import Pathe_type", "code_label", """ |
| 4 | N) |
| 5 | store |
| 6 | |
| 7 | node_rows = ("" |
| 8 | N) |
| 9 | "fmt""" |
| 10 | N) |
| 11 | """ |
| 12 | N) |
| 13 | "--redis", "redis_u""" |
| 14 | N "fmt "fmt""" |
| 15 | N) |
| 16 | """"" |
| 17 | N) |
| 18 | "fmt""" |
| 19 | N) |
| 20 | DB_OPTIONas JSON."Show graph stat data = {"nodes": store.node_count(), "edges": store.edge_count()}dataelse:Graph stats", show_header=Truedataclick.option( , |
| 21 | N) |
| 22 | source graph import GraphStore |
| 23 | MCP |
| 24 | AS) |
| 25 | click.option( , |
| 26 | N) |
| 27 | ) |
| 28 | @click.option( the navegador, sh into the nave default="markdown")and print context for a filstoreile(file_path) |
| 29 | output = if fmt == "markdow show_header=Trueajson(outpu_header=Truquery"option( , |
| 30 | N) |
| 31 | )) by namstoreoption( , |
| 32 | N) |
| 33 | ) |
| 34 | def stats(db: strrepos, load context, table.add_row("Nodes", str(store.node_count())) |
| 35 | table.add_row("Edges", str(option( , |
| 36 | N) |
| 37 | ) |
| 38 | @click.option("--host", default="127.0.0.1", show_default=True) |
| 39 | @click.option("--port", default=8765, show_default=True) |
| 40 | def mcp(db: str, host: str, port: intdef store_factory(): |
| 41 | store_factoryf |
| --- a/navegador/context/__init__.py | ||
| +++ b/navegador/context/__init__.py | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +from .loader import ContextLoader | |
| 2 | + | |
| 3 | +__all__ = ["ContextLoader"] |
| --- a/navegador/context/__init__.py | |
| +++ b/navegador/context/__init__.py | |
| @@ -0,0 +1,3 @@ | |
| --- a/navegador/context/__init__.py | |
| +++ b/navegador/context/__init__.py | |
| @@ -0,0 +1,3 @@ | |
| 1 | from .loader import ContextLoader |
| 2 | |
| 3 | __all__ = ["ContextLoader"] |
| --- a/navegador/context/loader.py | ||
| +++ b/navegador/context/loader.py | ||
| @@ -0,0 +1,99 @@ | ||
| 1 | +""" | |
| 2 | +ContextLoader — builds structured context bundles from the graph. | |
| 3 | + | |
| 4 | +A context bundle contains: | |
| 5 | +- The target node (file / function / class) | |
| 6 | +- Its immediate neighbors up to a configurable depth | |
| 7 | +- Relationships between those nodes | |
| 8 | +- Source snippets (optional) | |
| 9 | + | |
| 10 | +Output can be: | |
| 11 | +- dict (structured JSON-serialisable) | |
| 12 | +- markdown string (, JSON string, or markdown fo) | |
| 13 | +""" | |
| 14 | + | |
| 15 | +import json | |
| 16 | +import logging | |
| 17 | +from dataclasses import dataclass, field | |
| 18 | +from pathlib import Path | |
| 19 | +from typing import Any | |
| 20 | + | |
| 21 | +from navegador.graph import GraphStoreath | |
| 22 | +from typing import Any | |
| 23 | + | |
| 24 | +fqueries | |
| 25 | + | |
| 26 | +logger = logging.getLogger(__name__) | |
| 27 | + | |
| 28 | + | |
| 29 | +@dataclass | |
| 30 | +class ContextNode: | |
| 31 | + type: str | |
| 32 | + name: str | |
| 33 | + file_path: strstr | |
| 34 | + file_path: str = "" | |
| 35 | + line_start: int | None = None | |
| 36 | + docstring: str | None = None | |
| 37 | + signature: str | None = None | |
| 38 | + date: str | None = None | |
| 39 | + | |
| 40 | + | |
| 41 | +@dataclass | |
| 42 | +class ContextBundle: | |
| 43 | + target: ContextNode | |
| 44 | + nodes: list[ContextNode] = field(default_factory=list) | |
| 45 | + edges: list[dict[str, str]] = field(default_factory=list) | |
| 46 | + metadata: dict[str, Any] = field(default_factory=dict) | |
| 47 | + | |
| 48 | + def to_dict(self) -> dict[str, Any]: | |
| 49 | + return { | |
| 50 | + "target": vars(self.target), | |
| 51 | + "nodes": [vars(n) for n in self.nodes], | |
| 52 | + "edges": self.edges, | |
| 53 | + "metadata": self.metadata, | |
| 54 | + } | |
| 55 | + | |
| 56 | + def to_json(self, indent: int = 2) -> str: | |
| 57 | + return json.dumps(self.to_dict(), indent=indent) | |
| 58 | + | |
| 59 | + def to_markdown(self) -> str: | |
| 60 | + lines = [ | |
| 61 | + f self.target.file_path: | |
| 62 | + nes = [ | |
| 63 | + f"# Context: `{self.target.name}`", | |
| 64 | + f"**Type:** docstring: | |
| 65 | +ader — builds s""" | |
| 66 | +ContextLoader — builds struct}"]`{self.target.file_path} or self.target.description}"] | |
| 67 | + if self.target.signature: | |
| 68 | + lines += ["", f"```python\n{self.target.signature}\n```"] | |
| 69 | + | |
| 70 | + ( if self.nodes: | |
| 71 | + ):[0], row[1] signatus: | |
| 72 | + """ | |
| 73 | +Contextr — builds structured context bundles from the graph. | |
| 74 | + | |
| 75 | +A contex""" | |
| 76 | +ContextLoader — builds structured context bundles from the graph. | |
| 77 | + | |
| 78 | +A context bundle contains: | |
| 79 | +- The target node (file / function / class) | |
| 80 | +- Its immediate neighbors up to a configurable depth | |
| 81 | +- Relationships between those nodes | |
| 82 | +- Source snippets (optional) | |
| 83 | +ts (optional) | |
| 84 | + | |
| 85 | +Output can be: | |
| 86 | +- dict (structured JSON-serialisable) | |
| 87 | +- m) | |
| 88 | +""" | |
| 89 | +ContextLoader — builds structured context bundles from the graph. | |
| 90 | + | |
| 91 | +A context bundle contains: | |
| 92 | +- The target node (file / function / class) | |
| 93 | +- Its immediate neighbors up to a configurable depth | |
| 94 | +- RelationshLoader — builds structured context bundles from the graph. | |
| 95 | + | |
| 96 | +A context) | |
| 97 | + = None | |
| 98 | + source: str | None = None | |
| 99 | + descr |
| --- a/navegador/context/loader.py | |
| +++ b/navegador/context/loader.py | |
| @@ -0,0 +1,99 @@ | |
| --- a/navegador/context/loader.py | |
| +++ b/navegador/context/loader.py | |
| @@ -0,0 +1,99 @@ | |
| 1 | """ |
| 2 | ContextLoader — builds structured context bundles from the graph. |
| 3 | |
| 4 | A context bundle contains: |
| 5 | - The target node (file / function / class) |
| 6 | - Its immediate neighbors up to a configurable depth |
| 7 | - Relationships between those nodes |
| 8 | - Source snippets (optional) |
| 9 | |
| 10 | Output can be: |
| 11 | - dict (structured JSON-serialisable) |
| 12 | - markdown string (, JSON string, or markdown fo) |
| 13 | """ |
| 14 | |
| 15 | import json |
| 16 | import logging |
| 17 | from dataclasses import dataclass, field |
| 18 | from pathlib import Path |
| 19 | from typing import Any |
| 20 | |
| 21 | from navegador.graph import GraphStoreath |
| 22 | from typing import Any |
| 23 | |
| 24 | fqueries |
| 25 | |
| 26 | logger = logging.getLogger(__name__) |
| 27 | |
| 28 | |
| 29 | @dataclass |
| 30 | class ContextNode: |
| 31 | type: str |
| 32 | name: str |
| 33 | file_path: strstr |
| 34 | file_path: str = "" |
| 35 | line_start: int | None = None |
| 36 | docstring: str | None = None |
| 37 | signature: str | None = None |
| 38 | date: str | None = None |
| 39 | |
| 40 | |
| 41 | @dataclass |
| 42 | class ContextBundle: |
| 43 | target: ContextNode |
| 44 | nodes: list[ContextNode] = field(default_factory=list) |
| 45 | edges: list[dict[str, str]] = field(default_factory=list) |
| 46 | metadata: dict[str, Any] = field(default_factory=dict) |
| 47 | |
| 48 | def to_dict(self) -> dict[str, Any]: |
| 49 | return { |
| 50 | "target": vars(self.target), |
| 51 | "nodes": [vars(n) for n in self.nodes], |
| 52 | "edges": self.edges, |
| 53 | "metadata": self.metadata, |
| 54 | } |
| 55 | |
| 56 | def to_json(self, indent: int = 2) -> str: |
| 57 | return json.dumps(self.to_dict(), indent=indent) |
| 58 | |
| 59 | def to_markdown(self) -> str: |
| 60 | lines = [ |
| 61 | f self.target.file_path: |
| 62 | nes = [ |
| 63 | f"# Context: `{self.target.name}`", |
| 64 | f"**Type:** docstring: |
| 65 | ader — builds s""" |
| 66 | ContextLoader — builds struct}"]`{self.target.file_path} or self.target.description}"] |
| 67 | if self.target.signature: |
| 68 | lines += ["", f"```python\n{self.target.signature}\n```"] |
| 69 | |
| 70 | ( if self.nodes: |
| 71 | ):[0], row[1] signatus: |
| 72 | """ |
| 73 | Contextr — builds structured context bundles from the graph. |
| 74 | |
| 75 | A contex""" |
| 76 | ContextLoader — builds structured context bundles from the graph. |
| 77 | |
| 78 | A context bundle contains: |
| 79 | - The target node (file / function / class) |
| 80 | - Its immediate neighbors up to a configurable depth |
| 81 | - Relationships between those nodes |
| 82 | - Source snippets (optional) |
| 83 | ts (optional) |
| 84 | |
| 85 | Output can be: |
| 86 | - dict (structured JSON-serialisable) |
| 87 | - m) |
| 88 | """ |
| 89 | ContextLoader — builds structured context bundles from the graph. |
| 90 | |
| 91 | A context bundle contains: |
| 92 | - The target node (file / function / class) |
| 93 | - Its immediate neighbors up to a configurable depth |
| 94 | - RelationshLoader — builds structured context bundles from the graph. |
| 95 | |
| 96 | A context) |
| 97 | = None |
| 98 | source: str | None = None |
| 99 | descr |
| --- a/navegador/graph/__init__.py | ||
| +++ b/navegador/graph/__init__.py | ||
| @@ -0,0 +1,4 @@ | ||
| 1 | +from .schema import EdgeType, NodeLabel | |
| 2 | +from .store import GraphStore | |
| 3 | + | |
| 4 | +__all__ = ["GraphStore", "NodeLabel", "EdgeType"] |
| --- a/navegador/graph/__init__.py | |
| +++ b/navegador/graph/__init__.py | |
| @@ -0,0 +1,4 @@ | |
| --- a/navegador/graph/__init__.py | |
| +++ b/navegador/graph/__init__.py | |
| @@ -0,0 +1,4 @@ | |
| 1 | from .schema import EdgeType, NodeLabel |
| 2 | from .store import GraphStore |
| 3 | |
| 4 | __all__ = ["GraphStore", "NodeLabel", "EdgeType"] |
| --- a/navegador/graph/queries.py | ||
| +++ b/navegador/graph/queries.py | ||
| @@ -0,0 +1,50 @@ | ||
| 1 | +""" | |
| 2 | +Common context loading. | |
| 3 | +""" | |
| 4 | + | |
| 5 | +# Find al | |
| 6 | +FILE_CONTENTS = """ | |
| 7 | +MATCH (f:File {path: $path})-[:CONTAINS]->(n) | |
| 8 | +RETURN labels(n)[0] AS type, n.name AS name, n.line_start AS line, | |
| 9 | + n.docstring AS docstring, n.signature AS signature | |
| 10 | +ORDER BY n.line_start | |
| 11 | +""" | |
| 12 | + | |
| 13 | +DIRECT_IMPORTS = """ | |
| 14 | +MATCH (n {file_path: $file_path, name: $name})-[:IMPORTS]->(dep) | |
| 15 | +RETURN labels(dep)[0] AS type, dep.name AS name, dep.file_path AS file_path | |
| 16 | +""" | |
| 17 | + | |
| 18 | +# ── Code: call graph ────────────────────────────────────────────────────────── | |
| 19 | + | |
| 20 | +# file_path is optional — if empty, match by name only across all files | |
| 21 | +CALLERS = """ | |
| 22 | +MATCH (caller)-[:CALLS*1..$depth]->(fn) | |
| 23 | +WHERE fn.name = $name AND ($file_path = '' OR fn.file_path = $file_path) | |
| 24 | +RETURN DISTINCT labels(caller)[0] AS type, caller.name AS name, | |
| 25 | + caller.file_path AS file_path, caller.line_start AS line | |
| 26 | +""" | |
| 27 | + | |
| 28 | +CALLEES = """ | |
| 29 | +MATCH (fn)-[:CALLS*1..$depth]->(callee) | |
| 30 | +WHERE fn.name = $name AND ($file_path = '' OR fn.file_path = $file_path) | |
| 31 | +RETURN DISTINCT labels(callee)[0] AS type, callee.name AS name, | |
| 32 | + callee.file_path AS file_path, callee.line_start AS line | |
| 33 | +""" | |
| 34 | + | |
| 35 | +# ── Code: class hierarchy ───────────────────────────────────────────────────── | |
| 36 | + | |
| 37 | +CLASS_HIERARCHY = """ | |
| 38 | +MATCH (c:Class {name: $name})-[:INHERITS*]->(parent) | |
| 39 | +RETURN parent.name AS name, parent.file_path AS file_path | |
| 40 | +""" | |
| 41 | + | |
| 42 | +SUBCLASSES = """ | |
| 43 | +MATCH (child:Class)-[:INHERITS*]->(c:Class {name: $name}) | |
| 44 | +RETURN child.name AS name, child.file_path AS file_path | |
| 45 | +""" | |
| 46 | + | |
| 47 | +# ── Code: decorators ───────────────────────────────────────────────────────── | |
| 48 | + | |
| 49 | +# All functions/methods carrying a given decorator | |
| 50 | +DECORATED_ |
| --- a/navegador/graph/queries.py | |
| +++ b/navegador/graph/queries.py | |
| @@ -0,0 +1,50 @@ | |
| --- a/navegador/graph/queries.py | |
| +++ b/navegador/graph/queries.py | |
| @@ -0,0 +1,50 @@ | |
| 1 | """ |
| 2 | Common context loading. |
| 3 | """ |
| 4 | |
| 5 | # Find al |
| 6 | FILE_CONTENTS = """ |
| 7 | MATCH (f:File {path: $path})-[:CONTAINS]->(n) |
| 8 | RETURN labels(n)[0] AS type, n.name AS name, n.line_start AS line, |
| 9 | n.docstring AS docstring, n.signature AS signature |
| 10 | ORDER BY n.line_start |
| 11 | """ |
| 12 | |
| 13 | DIRECT_IMPORTS = """ |
| 14 | MATCH (n {file_path: $file_path, name: $name})-[:IMPORTS]->(dep) |
| 15 | RETURN labels(dep)[0] AS type, dep.name AS name, dep.file_path AS file_path |
| 16 | """ |
| 17 | |
| 18 | # ── Code: call graph ────────────────────────────────────────────────────────── |
| 19 | |
| 20 | # file_path is optional — if empty, match by name only across all files |
| 21 | CALLERS = """ |
| 22 | MATCH (caller)-[:CALLS*1..$depth]->(fn) |
| 23 | WHERE fn.name = $name AND ($file_path = '' OR fn.file_path = $file_path) |
| 24 | RETURN DISTINCT labels(caller)[0] AS type, caller.name AS name, |
| 25 | caller.file_path AS file_path, caller.line_start AS line |
| 26 | """ |
| 27 | |
| 28 | CALLEES = """ |
| 29 | MATCH (fn)-[:CALLS*1..$depth]->(callee) |
| 30 | WHERE fn.name = $name AND ($file_path = '' OR fn.file_path = $file_path) |
| 31 | RETURN DISTINCT labels(callee)[0] AS type, callee.name AS name, |
| 32 | callee.file_path AS file_path, callee.line_start AS line |
| 33 | """ |
| 34 | |
| 35 | # ── Code: class hierarchy ───────────────────────────────────────────────────── |
| 36 | |
| 37 | CLASS_HIERARCHY = """ |
| 38 | MATCH (c:Class {name: $name})-[:INHERITS*]->(parent) |
| 39 | RETURN parent.name AS name, parent.file_path AS file_path |
| 40 | """ |
| 41 | |
| 42 | SUBCLASSES = """ |
| 43 | MATCH (child:Class)-[:INHERITS*]->(c:Class {name: $name}) |
| 44 | RETURN child.name AS name, child.file_path AS file_path |
| 45 | """ |
| 46 | |
| 47 | # ── Code: decorators ───────────────────────────────────────────────────────── |
| 48 | |
| 49 | # All functions/methods carrying a given decorator |
| 50 | DECORATED_ |
| --- a/navegador/graph/schema.py | ||
| +++ b/navegador/graph/schema.py | ||
| @@ -0,0 +1,48 @@ | ||
| 1 | +""" | |
| 2 | +Graph schema — node labels and edge types for the navegador property graph. | |
| 3 | + | |
| 4 | +Node properties vary by label but all share: name, file_path, line_start, line_end. | |
| 5 | +""" | |
| 6 | + | |
| 7 | +from enum import StrEnum | |
| 8 | + | |
| 9 | + | |
| 10 | +class NodeLabel(StrEnum): | |
| 11 | + �─────── | |
| 12 | + Repository = "Repository" | |
| 13 | + File = "File" | |
| 14 | + Module = "Module" | |
| 15 | + Class = "Class" | |
| 16 | + Function = "Function" | |
| 17 | + Method = "Method" | |
| 18 | + Variable = "Variable" | |
| 19 | + Import = "Import" | |
| 20 | + or, owner, or stakeholder | |
| 21 | + | |
| 22 | + | |
| 23 | +classtructuralo one grap -CONTAIN�─────�, classe ses, calls, imports) | |
| 24 | + KNOWLEDGE la connected by IMPLEMENTS, DO# dependencies connected by IMPLEMENTS, DOC | |
| 25 | + DecTATES | |
| 26 | +edges, so agents can traverseto the business ruleModule/Package level dependenge down to the exact co code that implements it. | |
| 27 | +""" | |
| 28 | + | |
| 29 | +fr""" | |
| 30 | +Graph schema — node labels and edge types for the navegador property ��───────────────────────────────────────────── | |
| 31 | + Repository = "Repository" | |
| 32 | + File = "File" | |
| 33 | + Module = "Module" | |
| 34 | + Class = "Class" | |
| 35 | + Function = "Function" | |
| 36 | + MMethod = "Method" | |
| 37 | + Variable = "Variable" | |
| 38 | + Import = "Importt" | |
| 39 | + Decorator = "Decorator" | |
| 40 | + | |
| 41 | + # ── Knowledge layer ──────� ge layer ───────────────────── ��───────────────────────� ─────────── | |
| 42 | + Domain = "Domain" # logicidea | |
| 43 | + Rule = "R─────�Method��─� ─────────── | |
| 44 | + Domain = "Domain" # logi, "signature", "class_nameels and edge types""" | |
| 45 | +Grap��─� ─────────── | |
| 46 | + Domain = "Domain" # logical grouping (auth, billing,pt = "Concept" # a named business entity or idea | |
| 47 | + Rule = "Rule" # a connstraint, invariant, or business rule | |
| 48 | + Decision = "Decision" # an architectural or product decision + rationale |
| --- a/navegador/graph/schema.py | |
| +++ b/navegador/graph/schema.py | |
| @@ -0,0 +1,48 @@ | |
| --- a/navegador/graph/schema.py | |
| +++ b/navegador/graph/schema.py | |
| @@ -0,0 +1,48 @@ | |
| 1 | """ |
| 2 | Graph schema — node labels and edge types for the navegador property graph. |
| 3 | |
| 4 | Node properties vary by label but all share: name, file_path, line_start, line_end. |
| 5 | """ |
| 6 | |
| 7 | from enum import StrEnum |
| 8 | |
| 9 | |
| 10 | class NodeLabel(StrEnum): |
| 11 | �─────── |
| 12 | Repository = "Repository" |
| 13 | File = "File" |
| 14 | Module = "Module" |
| 15 | Class = "Class" |
| 16 | Function = "Function" |
| 17 | Method = "Method" |
| 18 | Variable = "Variable" |
| 19 | Import = "Import" |
| 20 | or, owner, or stakeholder |
| 21 | |
| 22 | |
| 23 | classtructuralo one grap -CONTAIN�─────�, classe ses, calls, imports) |
| 24 | KNOWLEDGE la connected by IMPLEMENTS, DO# dependencies connected by IMPLEMENTS, DOC |
| 25 | DecTATES |
| 26 | edges, so agents can traverseto the business ruleModule/Package level dependenge down to the exact co code that implements it. |
| 27 | """ |
| 28 | |
| 29 | fr""" |
| 30 | Graph schema — node labels and edge types for the navegador property ��───────────────────────────────────────────── |
| 31 | Repository = "Repository" |
| 32 | File = "File" |
| 33 | Module = "Module" |
| 34 | Class = "Class" |
| 35 | Function = "Function" |
| 36 | MMethod = "Method" |
| 37 | Variable = "Variable" |
| 38 | Import = "Importt" |
| 39 | Decorator = "Decorator" |
| 40 | |
| 41 | # ── Knowledge layer ──────� ge layer ───────────────────── ��───────────────────────� ─────────── |
| 42 | Domain = "Domain" # logicidea |
| 43 | Rule = "R─────�Method��─� ─────────── |
| 44 | Domain = "Domain" # logi, "signature", "class_nameels and edge types""" |
| 45 | Grap��─� ─────────── |
| 46 | Domain = "Domain" # logical grouping (auth, billing,pt = "Concept" # a named business entity or idea |
| 47 | Rule = "Rule" # a connstraint, invariant, or business rule |
| 48 | Decision = "Decision" # an architectural or product decision + rationale |
+75
| --- a/navegador/graph/store.py | ||
| +++ b/navegador/graph/store.py | ||
| @@ -0,0 +1,75 @@ | ||
| 1 | +""" | |
| 2 | +GraphStore — thin wrapper over FalkorDB (SQLite or Redis backend). | |
| 3 | + | |
| 4 | +Usage: | |
| 5 | + # SQLite (local, zero-infra) | |
| 6 | + store = GraphStore.sqlite(".navegador/graph.db") | |
| 7 | + | |
| 8 | + # Redis-backed FalkorDB (production) | |
| 9 | + store = GraphStore.redis("redis://localhost:6379") | |
| 10 | +""" | |
| 11 | + | |
| 12 | +import logging | |
| 13 | +from pathlib import Path | |
| 14 | +from typing import Any | |
| 15 | + | |
| 16 | +logger = logging.getLogger(__name__) | |
| 17 | + | |
| 18 | + | |
| 19 | +class GraphStore: | |
| 20 | + """ | |
| 21 | + Wraps a FalkorDB graph, providing helpers for navegador node/edge operations. | |
| 22 | + | |
| 23 | + The underlying graph is named "navegador" within the database. | |
| 24 | + """ | |
| 25 | + | |
| 26 | + GRAPH_NAME = "navegador" | |
| 27 | + | |
| 28 | + def __init__(self, client: Any) -> None: | |
| 29 | + self._client = client | |
| 30 | + self._graph = client.select_graph(self.GRAPH_NAME) | |
| 31 | + | |
| 32 | + # ── Constructors ────────────────────────────────────────────────────────── | |
| 33 | + | |
| 34 | + @classmethod | |
| 35 | + def sqlite(cls, db_path: str, file_path)."""rt] # provided by falkordblite | |
| 36 | + except ImportError as e: | |
| 37 | + raise ImportError( | |
| 38 | + ( | |
| 39 | + "Install graph dependenciel) | |
| 40 | + returSET {prop_str}" | |
| 41 | + ) str, params: dict[str, Any] ) from e | |
| 42 | + | |
| 43 | + db_path = Path(db_path) | |
| 44 | + db_path.parent.mkdir(parents=True, exist_ok=True) | |
| 45 | + client = FalkorDB(str(db_path)) | |
| 46 | + logger.info("GraphStore opened (SQLite/falkordblite): %s", db_path) | |
| 47 | + return cls(client) | |
| 48 | + | |
| 49 | + @classmethod | |
| 50 | + def redis(cls, url: str = "redis://localhost:6379") -> "GraphStore": | |
| 51 | + AND ".join(f"a.{k} =raph (production use). | |
| 52 | + | |
| 53 | + AND ".join(f"b.{k} =is | |
| 54 | + """ | |
| 55 | + try: | |
| 56 | + import falkordb # type: ignore[import] | |
| 57 | + except ImportError as e: | |
| 58 | + raise ImportError("Install falkordb: pip install FalkorDB redis") from e | |
| 59 | + | |
| 60 | + client = falkordb.FalkorDB.from_url(url) | |
| 61 | + logger.info("GraphStore opened (Redis): %s", url) | |
| 62 | + return cls(client) | |
| 63 | + | |
| 64 | + # ── Core operations ─────────────────────────────────────────────────────── | |
| 65 | + | |
| 66 | + def query(self, cypher: str, params: dict[str, Any] | None = None) -> Any: | |
| 67 | + """Execute a raw Cypher query and return the result.""" | |
| 68 | + return self._graph.query(cypher, params or {}) | |
| 69 | + | |
| 70 | + def create_node(self, label: str, props: dict[str, Any]) -> None: | |
| 71 | + """Upsert a node by (label, name[, file_path]).""" | |
| 72 | + # Ensure merge key fields exist | |
| 73 | + props.setdefault("name", "") | |
| 74 | + props.setdefault("file_path", "") | |
| 75 | + # Filter out None values — FalkorDB rejects them as par |
| --- a/navegador/graph/store.py | |
| +++ b/navegador/graph/store.py | |
| @@ -0,0 +1,75 @@ | |
| --- a/navegador/graph/store.py | |
| +++ b/navegador/graph/store.py | |
| @@ -0,0 +1,75 @@ | |
| 1 | """ |
| 2 | GraphStore — thin wrapper over FalkorDB (SQLite or Redis backend). |
| 3 | |
| 4 | Usage: |
| 5 | # SQLite (local, zero-infra) |
| 6 | store = GraphStore.sqlite(".navegador/graph.db") |
| 7 | |
| 8 | # Redis-backed FalkorDB (production) |
| 9 | store = GraphStore.redis("redis://localhost:6379") |
| 10 | """ |
| 11 | |
| 12 | import logging |
| 13 | from pathlib import Path |
| 14 | from typing import Any |
| 15 | |
| 16 | logger = logging.getLogger(__name__) |
| 17 | |
| 18 | |
| 19 | class GraphStore: |
| 20 | """ |
| 21 | Wraps a FalkorDB graph, providing helpers for navegador node/edge operations. |
| 22 | |
| 23 | The underlying graph is named "navegador" within the database. |
| 24 | """ |
| 25 | |
| 26 | GRAPH_NAME = "navegador" |
| 27 | |
| 28 | def __init__(self, client: Any) -> None: |
| 29 | self._client = client |
| 30 | self._graph = client.select_graph(self.GRAPH_NAME) |
| 31 | |
| 32 | # ── Constructors ────────────────────────────────────────────────────────── |
| 33 | |
| 34 | @classmethod |
| 35 | def sqlite(cls, db_path: str, file_path)."""rt] # provided by falkordblite |
| 36 | except ImportError as e: |
| 37 | raise ImportError( |
| 38 | ( |
| 39 | "Install graph dependenciel) |
| 40 | returSET {prop_str}" |
| 41 | ) str, params: dict[str, Any] ) from e |
| 42 | |
| 43 | db_path = Path(db_path) |
| 44 | db_path.parent.mkdir(parents=True, exist_ok=True) |
| 45 | client = FalkorDB(str(db_path)) |
| 46 | logger.info("GraphStore opened (SQLite/falkordblite): %s", db_path) |
| 47 | return cls(client) |
| 48 | |
| 49 | @classmethod |
| 50 | def redis(cls, url: str = "redis://localhost:6379") -> "GraphStore": |
| 51 | AND ".join(f"a.{k} =raph (production use). |
| 52 | |
| 53 | AND ".join(f"b.{k} =is |
| 54 | """ |
| 55 | try: |
| 56 | import falkordb # type: ignore[import] |
| 57 | except ImportError as e: |
| 58 | raise ImportError("Install falkordb: pip install FalkorDB redis") from e |
| 59 | |
| 60 | client = falkordb.FalkorDB.from_url(url) |
| 61 | logger.info("GraphStore opened (Redis): %s", url) |
| 62 | return cls(client) |
| 63 | |
| 64 | # ── Core operations ─────────────────────────────────────────────────────── |
| 65 | |
| 66 | def query(self, cypher: str, params: dict[str, Any] | None = None) -> Any: |
| 67 | """Execute a raw Cypher query and return the result.""" |
| 68 | return self._graph.query(cypher, params or {}) |
| 69 | |
| 70 | def create_node(self, label: str, props: dict[str, Any]) -> None: |
| 71 | """Upsert a node by (label, name[, file_path]).""" |
| 72 | # Ensure merge key fields exist |
| 73 | props.setdefault("name", "") |
| 74 | props.setdefault("file_path", "") |
| 75 | # Filter out None values — FalkorDB rejects them as par |
| --- a/navegador/ingestion/__init__.py | ||
| +++ b/navegador/ingestion/__init__.py | ||
| @@ -0,0 +1,2 @@ | ||
| 1 | +e import KnowledgeIngester | |
| 2 | +from . |
| --- a/navegador/ingestion/__init__.py | |
| +++ b/navegador/ingestion/__init__.py | |
| @@ -0,0 +1,2 @@ | |
| --- a/navegador/ingestion/__init__.py | |
| +++ b/navegador/ingestion/__init__.py | |
| @@ -0,0 +1,2 @@ | |
| 1 | e import KnowledgeIngester |
| 2 | from . |
| --- a/navegador/ingestion/parser.py | ||
| +++ b/navegador/ingestion/parser.py | ||
| @@ -0,0 +1,74 @@ | ||
| 1 | +""" | |
| 2 | +RepoIngester — walks a repository, parses source files with tree-sitter, | |
| 3 | +and writes nodes + thlib import.schema import NodeLabel | |
| 4 | +from navegador.graph.store import GraphStore | |
| 5 | + | |
| 6 | +logger = logging.getLoggeEdgeType,er(__name__) | |
| 7 | + | |
| 8 | +# File extensions → language key | |
| 9 | +LANGUAGE_MAP: dict[str, str] = { | |
| 10 | + ".py": "python", | |
| 11 | + ".ts": "types"python", | |
| 12 | + ".ts":esc cript", | |
| 13 | + ".js": "ja "typescript", | |
| 14 | + ".js":avascript", | |
| 15 | + ".go": "go "javascriptes a GraphStore. | |
| 16 | + | |
| 17 | + Usage: | |
| 18 | + store = GraphStore.sqlite(".navegador/graph.db") | |
| 19 | + ingespath/to/repsitter, | |
| 20 | +and writes nodes + edges into the GraphStore. | |
| 21 | + | |
| 22 | +Supported languages (all via tree-sitter): | |
| 23 | + Python .py | |
| 24 | + b import Path | |
| 25 | + | |
| 26 | +from naom navegador.graph.store | |
| 27 | + | |
| 28 | +# File extensiotlin", | |
| 29 | + ".kts": "kotlin", | |
| 30 | + ".cs": "csharp", | |
| 31 | + ".php": "php", | |
| 32 | + ".rb": "ruby", | |
| 33 | + ".swself,port logging | |
| 34 | +import tim clear: bool = Fals""" | |
| 35 | +h tree-sitter, | |
| 36 | +and writes nodes + edges intore. | |
| 37 | + | |
| 38 | +Supported language. | |
| 39 | +""" | |
| 40 | +RepoIngester — walks a repository, parses source ry, parses source files with tree-sitter, | |
| 41 | +and writes nodes + edges into the GraphStore. | |
| 42 | + | |
| 43 | +Supported langu""" | |
| 44 | +RepoIngester — walks a repository, parses source files with tree-sitter, | |
| 45 | +and writes nodes + edges into the GraphStore. | |
| 46 | + | |
| 47 | +Supported languages (all via tree-sitter): | |
| 48 | + Python .py | |
| 49 | + TypeScript .ts .tsx | |
| 50 | + JavaScript .js .jsx | |
| 51 | + Go .go | |
| 52 | + Rust .r"files": 0, "functions": t", | |
| 53 | + ".tsx": "typescript", | |
| 54 | + ".js": "javascript", | |
| 55 | + ".jsx": "javascriptNodeLabel.Repository, { | |
| 56 | + File extensiotlin", | |
| 57 | + ".kts":}repository, parses source files with tree-sitter, | |
| 58 | +and writes nodes + edges into the GraphStore. | |
| 59 | + | |
| 60 | +Supported languages (all via tree-sitter): | |
| 61 | + Python .py | |
| 62 | + TypeScript .ts .tsx | |
| 63 | + JavaScript .js .jsx | |
| 64 | + Go .go | |
| 65 | + Rust .rs | |
| 66 | + Javlogging | |
| 67 | +from pathlib import.schema import NodeLabel | |
| 68 | +from navegador.graph.store import GraphStore | |
| 69 | + | |
| 70 | +logger = logging.getLogger(__name__) | |
| 71 | + | |
| 72 | +# File extensions → language key | |
| 73 | +LANGUAGE_MAP: dict[str, str] = { | |
| 74 | + ". |
| --- a/navegador/ingestion/parser.py | |
| +++ b/navegador/ingestion/parser.py | |
| @@ -0,0 +1,74 @@ | |
| --- a/navegador/ingestion/parser.py | |
| +++ b/navegador/ingestion/parser.py | |
| @@ -0,0 +1,74 @@ | |
| 1 | """ |
| 2 | RepoIngester — walks a repository, parses source files with tree-sitter, |
| 3 | and writes nodes + thlib import.schema import NodeLabel |
| 4 | from navegador.graph.store import GraphStore |
| 5 | |
| 6 | logger = logging.getLoggeEdgeType,er(__name__) |
| 7 | |
| 8 | # File extensions → language key |
| 9 | LANGUAGE_MAP: dict[str, str] = { |
| 10 | ".py": "python", |
| 11 | ".ts": "types"python", |
| 12 | ".ts":esc cript", |
| 13 | ".js": "ja "typescript", |
| 14 | ".js":avascript", |
| 15 | ".go": "go "javascriptes a GraphStore. |
| 16 | |
| 17 | Usage: |
| 18 | store = GraphStore.sqlite(".navegador/graph.db") |
| 19 | ingespath/to/repsitter, |
| 20 | and writes nodes + edges into the GraphStore. |
| 21 | |
| 22 | Supported languages (all via tree-sitter): |
| 23 | Python .py |
| 24 | b import Path |
| 25 | |
| 26 | from naom navegador.graph.store |
| 27 | |
| 28 | # File extensiotlin", |
| 29 | ".kts": "kotlin", |
| 30 | ".cs": "csharp", |
| 31 | ".php": "php", |
| 32 | ".rb": "ruby", |
| 33 | ".swself,port logging |
| 34 | import tim clear: bool = Fals""" |
| 35 | h tree-sitter, |
| 36 | and writes nodes + edges intore. |
| 37 | |
| 38 | Supported language. |
| 39 | """ |
| 40 | RepoIngester — walks a repository, parses source ry, parses source files with tree-sitter, |
| 41 | and writes nodes + edges into the GraphStore. |
| 42 | |
| 43 | Supported langu""" |
| 44 | RepoIngester — walks a repository, parses source files with tree-sitter, |
| 45 | and writes nodes + edges into the GraphStore. |
| 46 | |
| 47 | Supported languages (all via tree-sitter): |
| 48 | Python .py |
| 49 | TypeScript .ts .tsx |
| 50 | JavaScript .js .jsx |
| 51 | Go .go |
| 52 | Rust .r"files": 0, "functions": t", |
| 53 | ".tsx": "typescript", |
| 54 | ".js": "javascript", |
| 55 | ".jsx": "javascriptNodeLabel.Repository, { |
| 56 | File extensiotlin", |
| 57 | ".kts":}repository, parses source files with tree-sitter, |
| 58 | and writes nodes + edges into the GraphStore. |
| 59 | |
| 60 | Supported languages (all via tree-sitter): |
| 61 | Python .py |
| 62 | TypeScript .ts .tsx |
| 63 | JavaScript .js .jsx |
| 64 | Go .go |
| 65 | Rust .rs |
| 66 | Javlogging |
| 67 | from pathlib import.schema import NodeLabel |
| 68 | from navegador.graph.store import GraphStore |
| 69 | |
| 70 | logger = logging.getLogger(__name__) |
| 71 | |
| 72 | # File extensions → language key |
| 73 | LANGUAGE_MAP: dict[str, str] = { |
| 74 | ". |
| --- a/navegador/ingestion/python.py | ||
| +++ b/navegador/ingestion/python.py | ||
| @@ -0,0 +1,158 @@ | ||
| 1 | +""" | |
| 2 | +Python AST parser — extracts classes, functions, imports, calls, and | |
| 3 | +their relationships from .py files using tree-sitter. | |
| 4 | +""" | |
| 5 | + | |
| 6 | +import logging | |
| 7 | +from pathlib import Path | |
| 8 | + | |
| 9 | +from navegador.graph.schema import EdgeType, NodeLabel | |
| 10 | +from navegador.graph.store import GraphStore | |
| 11 | +from navegador.ingestion.parser import LanguageParser | |
| 12 | + | |
| 13 | +logger = logging.getLogger(__name__) | |
| 14 | + | |
| 15 | + | |
| 16 | +def _get_python_language(): | |
| 17 | + try: | |
| 18 | + import tree_sitter_python as tspython # type: ignore[import] | |
| 19 | + from tree_sitter import Language | |
| 20 | + return Language(tspython.language()) | |
| 21 | + except ImportError as e: | |
| 22 | + raise ImportError( | |
| 23 | + ython: p | |
| 24 | + raise ImportError("Insta | |
| 25 | + install tree-sitter-python") from e | |
| 26 | + | |
| 27 | + | |
| 28 | +def _get_parser(): | |
| 29 | + from tree_sitter import Pars parser = Parser(_get_python_language()) | |
| 30 | + return parser | |
| 31 | + | |
| 32 | + | |
| 33 | +def _node_text(node, source: bytes) -> str: | |
| 34 | + return source[node.start_byte:node.end_byte].decode("utf-8", errors="replace") | |
| 35 | + | |
| 36 | + | |
| 37 | +def _get_docstring(node, source: bytes) -> str | None: | |
| 38 | + """Extract the first string literal from a function/class body as docstring.""" | |
| 39 | + body = next((c for c in node.children if c.type == "block"), None) | |
| 40 | + if not body: | |
| 41 | + return None | |
| 42 | + first_stmt = next((c for c in body.children if c.type == "expression_statement"), None) | |
| 43 | + if not first_stmt: | |
| 44 | + return None | |
| 45 | + string_node = next((c for c in first_stmt.children if c.type in ("string", "string_content")), None raw = _node_text(string_node, source) | |
| 46 | + return raw.strip('"""').strip("'''").strip('"').strip("'").strip() | |
| 47 | + return None | |
| 48 | + | |
| 49 | + | |
| 50 | +class PythonParser(LanguageParser): | |
| 51 | + def __init__(self) -> None: | |
| 52 | + self._parser = _get_parser() | |
| 53 | + | |
| 54 | + def parse_file(self, path: Path, repo_root: Path, store: GraphStore) -> dict[str, int]: | |
| 55 | + source = path.read_bytes() | |
| 56 | + tree = self._parser.parse(source) | |
| 57 | + rel_path = str(path.relative_to(repo_root)) | |
| 58 | + | |
| 59 | + stats = {"functions": 0, "classes": 0, "edges": 0} | |
| 60 | + | |
| 61 | + # File nodeNodeLabel.File, { | |
| 62 | +racts classes, funct""" | |
| 63 | +Python AST parser �path.name, | |
| 64 | + "pa"language": "python", | |
| 65 | + "python", | |
| 66 | + }) | |
| 67 | + | |
| 68 | + }, | |
| 69 | + ) | |
| 70 | + | |
| 71 | + self._walk(tree.root_node, source, rel_path, store, stats,andle_import_from( | |
| 72 | + self, n | |
| 73 | + store: Graph class_name: str | None) -> None: | |
| 74 | + ocstring(node, source: b source: bytes) -> str: | |
| 75 | + return source[node.start_byte : node.end_byte].decode("utf-8", errors="replace") | |
| 76 | + | |
| 77 | + | |
| 78 | +def _get_docstring(node, source: bytes) -> str | None: | |
| 79 | + """Extract the first string literal from a function/class body as docstring.""" | |
| 80 | + body = next((c for c in node.children if c.type == "block"), None) | |
| 81 | + if not body: | |
| 82 | + return None | |
| 83 | + first_stmt = next((c for c in body.children if c.type == "expression_statement"), None) | |
| 84 | + if not first_stmt: | |
| 85 | + return None | |
| 86 | + string_node = next( | |
| 87 | + (c for c in first_stmt.children if c.type in ("string", "string_content")), None | |
| 88 | + ) | |
| 89 | + if string_node: | |
| 90 | + raw = _node_text(string_node, source) | |
| 91 | + return raw.strip('""andle_import_fr | |
| 92 | + store: GraphStore, stats: dict) -> None: | |
| 93 | + for child in node.children: | |
| 94 | + elf, path: Path, repo_root: Path, store: GraphStore) -> dict[str, int]: | |
| 95 | + source = path.reaNodeLabel.Import,parser.parse(source) | |
| 96 | + ive_to(repo_root)) | |
| 97 | + | |
| 98 | + stats "do"line_start": node.start_point[0File, | |
| 99 | + { | |
| 100 | + } = _node_text(chilstore.createNodeLabel.File, {"path": file_path}, | |
| 101 | + line_start": node.start_point[ = _node_text(chilype.IMPORTS, | |
| 102 | + p("'''").strip('"'_from(andle_import_fr | |
| 103 | + ode, source) | |
| 104 | + docst stats: dict) -> None: | |
| 105 | + NodeLabel.Import,parser.parse(source) | |
| 106 | + ive_to(repo_root)) | |
| 107 | + | |
| 108 | + stats "do"line_start": node.start_point[0"module": module, | |
| 109 | + } = _node_text(chilstore.createNodeLabel.File, {"path": file_path}, | |
| 110 | + line_start": node.start_point[ = _node_text(chilype.IMPORTS, | |
| 111 | + def _handle_class(andle_import_fr | |
| 112 | + store: GraphStore, stats: dict) -> None: | |
| 113 | + odeLabel.Class, | |
| 114 | + { | |
| 115 | + "name": name, | |
| 116 | + "file_path": file_path, | |
| 117 | + "line_start": node.start_point[0] + 1, | |
| 118 | + "line_end": node.end_point[0] + 1, | |
| 119 | + "dostore.create_node(NodeLabel.Class, { | |
| 120 | +racts class""" | |
| 121 | +Python AST parser — extracts classes, functions, imports, calls, and | |
| 122 | +their relationships from .py files using tree-sitter. | |
| 123 | +""" | |
| 124 | + | |
| 125 | +import logg}"" | |
| 126 | +Python AST — extracts cline_start": node.starpath": file_path}, | |
| 127 | + ) | |
| 128 | + stats["classes"] += 1 | |
| 129 | + stats["edges"] += 1 | |
| 130 | + | |
| 131 | + # Inheritance | |
| 132 | + for child in node.children: | |
| 133 | + if child.type == "argument_list": | |
| 134 | + for arg in child.children: | |
| 135 | + if arg.type == "identifier": | |
| 136 | + parent_name = _node_text(arg, source) | |
| 137 | + store.create_edge( | |
| 138 | + line_start": node.start_point[0] + 1, | |
| 139 | + "module": module, | |
| 140 | + }, | |
| 141 | + ) | |
| 142 | + store.create_edge( | |
| 143 | + NodeLabel.File, | |
| 144 | + {"path": file_path}, | |
| 145 | + EdgeType.IMPORTS, | |
| 146 | + NodeLabel.Import, | |
| 147 | + {"name": name, "file_path": file_path}, | |
| 148 | + ) | |
| 149 | + stats["edges"] += 1 | |
| 150 | + | |
| 151 | + def _handle_class( | |
| 152 | + self, node, source: bytes, file_path: str, store: GraphStore, stats: dict | |
| 153 | + ) -> None: | |
| 154 | + name_node = next((c for c in node.children if c.typandle_import_fr | |
| 155 | + ode, source) | |
| 156 | + docst dict, class_name: str | NoneJ@QG,Bu@1o7,1: | |
| 157 | +B@16i,H: if class_name | |
| 158 | + 8@a0,1: 1R@1~E,F: container_key,J@21G,4f@21k,9:self, fn_a@p0,M@27l,9@1PA,K@280,V@1JF,J@QG,5n@29S,1~@2Fb,4q@2Hy,2n8gf5; |
| --- a/navegador/ingestion/python.py | |
| +++ b/navegador/ingestion/python.py | |
| @@ -0,0 +1,158 @@ | |
| --- a/navegador/ingestion/python.py | |
| +++ b/navegador/ingestion/python.py | |
| @@ -0,0 +1,158 @@ | |
| 1 | """ |
| 2 | Python AST parser — extracts classes, functions, imports, calls, and |
| 3 | their relationships from .py files using tree-sitter. |
| 4 | """ |
| 5 | |
| 6 | import logging |
| 7 | from pathlib import Path |
| 8 | |
| 9 | from navegador.graph.schema import EdgeType, NodeLabel |
| 10 | from navegador.graph.store import GraphStore |
| 11 | from navegador.ingestion.parser import LanguageParser |
| 12 | |
| 13 | logger = logging.getLogger(__name__) |
| 14 | |
| 15 | |
| 16 | def _get_python_language(): |
| 17 | try: |
| 18 | import tree_sitter_python as tspython # type: ignore[import] |
| 19 | from tree_sitter import Language |
| 20 | return Language(tspython.language()) |
| 21 | except ImportError as e: |
| 22 | raise ImportError( |
| 23 | ython: p |
| 24 | raise ImportError("Insta |
| 25 | install tree-sitter-python") from e |
| 26 | |
| 27 | |
| 28 | def _get_parser(): |
| 29 | from tree_sitter import Pars parser = Parser(_get_python_language()) |
| 30 | return parser |
| 31 | |
| 32 | |
| 33 | def _node_text(node, source: bytes) -> str: |
| 34 | return source[node.start_byte:node.end_byte].decode("utf-8", errors="replace") |
| 35 | |
| 36 | |
| 37 | def _get_docstring(node, source: bytes) -> str | None: |
| 38 | """Extract the first string literal from a function/class body as docstring.""" |
| 39 | body = next((c for c in node.children if c.type == "block"), None) |
| 40 | if not body: |
| 41 | return None |
| 42 | first_stmt = next((c for c in body.children if c.type == "expression_statement"), None) |
| 43 | if not first_stmt: |
| 44 | return None |
| 45 | string_node = next((c for c in first_stmt.children if c.type in ("string", "string_content")), None raw = _node_text(string_node, source) |
| 46 | return raw.strip('"""').strip("'''").strip('"').strip("'").strip() |
| 47 | return None |
| 48 | |
| 49 | |
| 50 | class PythonParser(LanguageParser): |
| 51 | def __init__(self) -> None: |
| 52 | self._parser = _get_parser() |
| 53 | |
| 54 | def parse_file(self, path: Path, repo_root: Path, store: GraphStore) -> dict[str, int]: |
| 55 | source = path.read_bytes() |
| 56 | tree = self._parser.parse(source) |
| 57 | rel_path = str(path.relative_to(repo_root)) |
| 58 | |
| 59 | stats = {"functions": 0, "classes": 0, "edges": 0} |
| 60 | |
| 61 | # File nodeNodeLabel.File, { |
| 62 | racts classes, funct""" |
| 63 | Python AST parser �path.name, |
| 64 | "pa"language": "python", |
| 65 | "python", |
| 66 | }) |
| 67 | |
| 68 | }, |
| 69 | ) |
| 70 | |
| 71 | self._walk(tree.root_node, source, rel_path, store, stats,andle_import_from( |
| 72 | self, n |
| 73 | store: Graph class_name: str | None) -> None: |
| 74 | ocstring(node, source: b source: bytes) -> str: |
| 75 | return source[node.start_byte : node.end_byte].decode("utf-8", errors="replace") |
| 76 | |
| 77 | |
| 78 | def _get_docstring(node, source: bytes) -> str | None: |
| 79 | """Extract the first string literal from a function/class body as docstring.""" |
| 80 | body = next((c for c in node.children if c.type == "block"), None) |
| 81 | if not body: |
| 82 | return None |
| 83 | first_stmt = next((c for c in body.children if c.type == "expression_statement"), None) |
| 84 | if not first_stmt: |
| 85 | return None |
| 86 | string_node = next( |
| 87 | (c for c in first_stmt.children if c.type in ("string", "string_content")), None |
| 88 | ) |
| 89 | if string_node: |
| 90 | raw = _node_text(string_node, source) |
| 91 | return raw.strip('""andle_import_fr |
| 92 | store: GraphStore, stats: dict) -> None: |
| 93 | for child in node.children: |
| 94 | elf, path: Path, repo_root: Path, store: GraphStore) -> dict[str, int]: |
| 95 | source = path.reaNodeLabel.Import,parser.parse(source) |
| 96 | ive_to(repo_root)) |
| 97 | |
| 98 | stats "do"line_start": node.start_point[0File, |
| 99 | { |
| 100 | } = _node_text(chilstore.createNodeLabel.File, {"path": file_path}, |
| 101 | line_start": node.start_point[ = _node_text(chilype.IMPORTS, |
| 102 | p("'''").strip('"'_from(andle_import_fr |
| 103 | ode, source) |
| 104 | docst stats: dict) -> None: |
| 105 | NodeLabel.Import,parser.parse(source) |
| 106 | ive_to(repo_root)) |
| 107 | |
| 108 | stats "do"line_start": node.start_point[0"module": module, |
| 109 | } = _node_text(chilstore.createNodeLabel.File, {"path": file_path}, |
| 110 | line_start": node.start_point[ = _node_text(chilype.IMPORTS, |
| 111 | def _handle_class(andle_import_fr |
| 112 | store: GraphStore, stats: dict) -> None: |
| 113 | odeLabel.Class, |
| 114 | { |
| 115 | "name": name, |
| 116 | "file_path": file_path, |
| 117 | "line_start": node.start_point[0] + 1, |
| 118 | "line_end": node.end_point[0] + 1, |
| 119 | "dostore.create_node(NodeLabel.Class, { |
| 120 | racts class""" |
| 121 | Python AST parser — extracts classes, functions, imports, calls, and |
| 122 | their relationships from .py files using tree-sitter. |
| 123 | """ |
| 124 | |
| 125 | import logg}"" |
| 126 | Python AST — extracts cline_start": node.starpath": file_path}, |
| 127 | ) |
| 128 | stats["classes"] += 1 |
| 129 | stats["edges"] += 1 |
| 130 | |
| 131 | # Inheritance |
| 132 | for child in node.children: |
| 133 | if child.type == "argument_list": |
| 134 | for arg in child.children: |
| 135 | if arg.type == "identifier": |
| 136 | parent_name = _node_text(arg, source) |
| 137 | store.create_edge( |
| 138 | line_start": node.start_point[0] + 1, |
| 139 | "module": module, |
| 140 | }, |
| 141 | ) |
| 142 | store.create_edge( |
| 143 | NodeLabel.File, |
| 144 | {"path": file_path}, |
| 145 | EdgeType.IMPORTS, |
| 146 | NodeLabel.Import, |
| 147 | {"name": name, "file_path": file_path}, |
| 148 | ) |
| 149 | stats["edges"] += 1 |
| 150 | |
| 151 | def _handle_class( |
| 152 | self, node, source: bytes, file_path: str, store: GraphStore, stats: dict |
| 153 | ) -> None: |
| 154 | name_node = next((c for c in node.children if c.typandle_import_fr |
| 155 | ode, source) |
| 156 | docst dict, class_name: str | NoneJ@QG,Bu@1o7,1: |
| 157 | B@16i,H: if class_name |
| 158 | 8@a0,1: 1R@1~E,F: container_key,J@21G,4f@21k,9:self, fn_a@p0,M@27l,9@1PA,K@280,V@1JF,J@QG,5n@29S,1~@2Fb,4q@2Hy,2n8gf5; |
| --- a/navegador/ingestion/typescript.py | ||
| +++ b/navegador/ingestion/typescript.py | ||
| @@ -0,0 +1,79 @@ | ||
| 1 | +""" | |
| 2 | +TypeScriptplaceholder for tree-sitter-typescript. | |
| 3 | +Full implementation follows the same pattern as python.py. | |
| 4 | +""" | |
| 5 | + | |
| 6 | +import logging | |
| 7 | +from pathlib import Path | |
| 8 | + | |
| 9 | +from navegador.graph.schema import EdgeType, NodeLabel | |
| 10 | +from navegador.graph.store import GraphStore | |
| 11 | +from navegador.ingestion.parser import LanguageParser | |
| 12 | + | |
| 13 | +logger = logging.getLogger(__name__) | |
| 14 | + | |
| 15 | + | |
| 16 | +def _get_ts_language(language: str): | |
| 17 | + try: | |
| 18 | + if language == "typescript": | |
| 19 | + import tree_sitter_typescript as tsts # type: ignore[import] | |
| 20 | + from tree_sitter import Language | |
| 21 | + return Language(tsts.language_typescript()) | |
| 22 | + else: | |
| 23 | + import tree_sitter_javascript as tsjs # type: ignore[import] | |
| 24 | + from t return Language(ts return Language(tsjs.language()) | |
| 25 | + except ImportError as e: | |
| 26 | + raise ImportError( | |
| 27 | + f"Install tree-sitter-{language}: pip install tree-sitter-{language}" | |
| 28 | + ) from e | |
| 29 | + | |
| 30 | + | |
| 31 | +raise ImportError _node_text(node, source: bytes) -> str: | |
| 32 | + return source[node.start_byte : node.end_def _handle_cla | |
| 33 | + "docstrin: GraphStore, stats: dictce, file_pat: str | None) -> None: | |
| 34 | + f prev.type == "coblings = list(parent.children) | |
| 35 | + idx = next((i for i, c in enumerate(siblings) if c.id == node.id), -1) | |
| 36 | + if idx <= 0: | |
| 37 | + return "" | |
| 38 | + prev = siblings[idx - 1] | |
| 39 | + if prev.type == "comment": | |
| 40 | + raw = _node_text(prev, source).strip() | |
| 41 | + if raw.startswith("/**"): | |
| 42 | + return raw.strip("/**").strip("*/").strip() | |
| 43 | + return "" | |
| 44 | + | |
| 45 | + | |
| 46 | +class TypeScriptParser(LanguageParser): | |
| 47 | + """Parses TypeScript/JavaScript source files into the navegador graph.""" | |
| 48 | + | |
| 49 | + def __init__(self, language: str = "typescript") -> None: | |
| 50 | + from tree_sitter import Parser ] | |
| 51 | + | |
| 52 | + self._parser =h}, | |
| 53 | + | |
| 54 | + le(self, path: Path, repo_root: Path, store: GraphStore) -> dict[str, int]: | |
| 55 | + source = path.read_bytes() | |
| 56 | + tree = self._parser.parse(source) | |
| 57 | + rel_path = str(path.relative_to(repo_root)) | |
| 58 | +NodeLa | |
| 59 | + def _han return Language(tsjs., "arrow_function","" | |
| 60 | +TypeScript/JavaScScr def _han stats["edges"] += 1 | |
| 61 | + ath.read_bytes() | |
| 62 | + tre idx = next((i for i, c in enumerate(siblings) if c.id == node.id), -1) | |
| 63 | + if idx <= 0: | |
| 64 | + | |
| 65 | + store.create_edge( | |
| 66 | +, {"path": file_path}, | |
| 67 | + ile_path}, | |
| 68 | + IMPORTSode or not value_node:NodeLabel.Import, | |
| 69 | + name = _node_text(name_node,# Extract "from '...'" module path | |
| 70 | + store.create_edge( | |
| 71 | +"" | |
| 72 | +TypeScript/Javaparser — extracts classes, intersource[child.start_bchild.end_byte].decode(8m@2j2,2G@18i,~@1f0,z@1wi,v:source[name_node.start_byte:name_node.end_byte].decode() | |
| 73 | + | |
| 74 | +L@2yl,3H@1Ei,4:"", | |
| 75 | +J@2n0,V@2Dz,3M@1Iw,48@1YD,Z@1b~,1h@1rs,1X@1tx,21@1vf,v:source[name_node.start_byte:name_node.end_byte].decode() | |
| 76 | + | |
| 77 | +8@2_v,~@24w,L@2yl,37@26G,2:""3n@29W,1: | |
| 78 | +8@1Sk,B: else {"H@1UG,J@1f~,z@2Dz,Q: EdgeType.CONTAINS, label,M@2B~,T@2CR,R@1qW,i@2Gl,3trZxW;) | |
| 79 | + |
| --- a/navegador/ingestion/typescript.py | |
| +++ b/navegador/ingestion/typescript.py | |
| @@ -0,0 +1,79 @@ | |
| --- a/navegador/ingestion/typescript.py | |
| +++ b/navegador/ingestion/typescript.py | |
| @@ -0,0 +1,79 @@ | |
| 1 | """ |
| 2 | TypeScriptplaceholder for tree-sitter-typescript. |
| 3 | Full implementation follows the same pattern as python.py. |
| 4 | """ |
| 5 | |
| 6 | import logging |
| 7 | from pathlib import Path |
| 8 | |
| 9 | from navegador.graph.schema import EdgeType, NodeLabel |
| 10 | from navegador.graph.store import GraphStore |
| 11 | from navegador.ingestion.parser import LanguageParser |
| 12 | |
| 13 | logger = logging.getLogger(__name__) |
| 14 | |
| 15 | |
| 16 | def _get_ts_language(language: str): |
| 17 | try: |
| 18 | if language == "typescript": |
| 19 | import tree_sitter_typescript as tsts # type: ignore[import] |
| 20 | from tree_sitter import Language |
| 21 | return Language(tsts.language_typescript()) |
| 22 | else: |
| 23 | import tree_sitter_javascript as tsjs # type: ignore[import] |
| 24 | from t return Language(ts return Language(tsjs.language()) |
| 25 | except ImportError as e: |
| 26 | raise ImportError( |
| 27 | f"Install tree-sitter-{language}: pip install tree-sitter-{language}" |
| 28 | ) from e |
| 29 | |
| 30 | |
| 31 | raise ImportError _node_text(node, source: bytes) -> str: |
| 32 | return source[node.start_byte : node.end_def _handle_cla |
| 33 | "docstrin: GraphStore, stats: dictce, file_pat: str | None) -> None: |
| 34 | f prev.type == "coblings = list(parent.children) |
| 35 | idx = next((i for i, c in enumerate(siblings) if c.id == node.id), -1) |
| 36 | if idx <= 0: |
| 37 | return "" |
| 38 | prev = siblings[idx - 1] |
| 39 | if prev.type == "comment": |
| 40 | raw = _node_text(prev, source).strip() |
| 41 | if raw.startswith("/**"): |
| 42 | return raw.strip("/**").strip("*/").strip() |
| 43 | return "" |
| 44 | |
| 45 | |
| 46 | class TypeScriptParser(LanguageParser): |
| 47 | """Parses TypeScript/JavaScript source files into the navegador graph.""" |
| 48 | |
| 49 | def __init__(self, language: str = "typescript") -> None: |
| 50 | from tree_sitter import Parser ] |
| 51 | |
| 52 | self._parser =h}, |
| 53 | |
| 54 | le(self, path: Path, repo_root: Path, store: GraphStore) -> dict[str, int]: |
| 55 | source = path.read_bytes() |
| 56 | tree = self._parser.parse(source) |
| 57 | rel_path = str(path.relative_to(repo_root)) |
| 58 | NodeLa |
| 59 | def _han return Language(tsjs., "arrow_function","" |
| 60 | TypeScript/JavaScScr def _han stats["edges"] += 1 |
| 61 | ath.read_bytes() |
| 62 | tre idx = next((i for i, c in enumerate(siblings) if c.id == node.id), -1) |
| 63 | if idx <= 0: |
| 64 | |
| 65 | store.create_edge( |
| 66 | , {"path": file_path}, |
| 67 | ile_path}, |
| 68 | IMPORTSode or not value_node:NodeLabel.Import, |
| 69 | name = _node_text(name_node,# Extract "from '...'" module path |
| 70 | store.create_edge( |
| 71 | "" |
| 72 | TypeScript/Javaparser — extracts classes, intersource[child.start_bchild.end_byte].decode(8m@2j2,2G@18i,~@1f0,z@1wi,v:source[name_node.start_byte:name_node.end_byte].decode() |
| 73 | |
| 74 | L@2yl,3H@1Ei,4:"", |
| 75 | J@2n0,V@2Dz,3M@1Iw,48@1YD,Z@1b~,1h@1rs,1X@1tx,21@1vf,v:source[name_node.start_byte:name_node.end_byte].decode() |
| 76 | |
| 77 | 8@2_v,~@24w,L@2yl,37@26G,2:""3n@29W,1: |
| 78 | 8@1Sk,B: else {"H@1UG,J@1f~,z@2Dz,Q: EdgeType.CONTAINS, label,M@2B~,T@2CR,R@1qW,i@2Gl,3trZxW;) |
| 79 |
| --- a/navegador/mcp/__init__.py | ||
| +++ b/navegador/mcp/__init__.py | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +from .server import create_mcp_server | |
| 2 | + | |
| 3 | +__all__ = ["create_mcp_server"] |
| --- a/navegador/mcp/__init__.py | |
| +++ b/navegador/mcp/__init__.py | |
| @@ -0,0 +1,3 @@ | |
| --- a/navegador/mcp/__init__.py | |
| +++ b/navegador/mcp/__init__.py | |
| @@ -0,0 +1,3 @@ | |
| 1 | from .server import create_mcp_server |
| 2 | |
| 3 | __all__ = ["create_mcp_server"] |
+97
| --- a/navegador/mcp/server.py | ||
| +++ b/navegador/mcp/server.py | ||
| @@ -0,0 +1,97 @@ | ||
| 1 | +server.stdio import stdio_suments.get("format", "markdown") | |
| 2 | + text = bundle.to_markdown() if fmt == "markdown" else bundle.to_json() | |
| 3 | + return [TextContent(type="text", text=text)] | |
| 4 | + | |
| 5 | + elif name == "load_function_context": | |
| 6 | + bundle = load depth=arguments.get("depth", 2), | |
| 7 | + guments.get("formatormat", "markdown") | |
| 8 | + text = bundle.to_markdown() if fmt == "markdown" else bundle.to_json() | |
| 9 | + return [TextContent(type="text", text=text)] | |
| 10 | + | |
| 11 | + elif name == "load_class_context": | |
| 12 | + bundle = loader.load_class(arguments["name"], arguments["file_path"]) | |
| 13 | + fmt = arguments.get("format", "markdown") | |
| 14 | + text = bundle.to_markdown() if fmt == "markdown" else bundle.to_json() | |
| 15 | + return [TextContent(type="text", text=text)] | |
| 16 | + | |
| 17 | + elif name == "search_symbols": | |
| 18 | + results = loader.search(arguments["query"],undle.to_markdown limit=arguments.get("lim | |
| 19 | + sitoryelse bundle.to_json() | |
| 20 | + clear": {xt tools to AI cod "fault": "", | |
| 21 | + "default": False"name"]return [TextContent(type=" depth=arguments.get("depth", 2), | |
| 22 | + guments.get("formatormat", "markdown") | |
| 23 | + tile — exposes graph qes graph context tools t and their relationships bundle = loader.load_class(arguments["name"], arguments["file_path"]) | |
| 24 | + fmt = arguments.get("format", "markdownn") | |
| 25 | + text = bundle.to_markdown() if fmt == "markdown" ed": ["name"]return [TextCo"name"]return [TextCont "format"]) | |
| 26 | + fmt = afault": "", | |
| 27 | + fault": "", | |
| 28 | + function — exposes graph query"], limit | |
| 29 | +ext toollse bundle.to_json() | |
| 30 | + return [TextContent(type="text", text=text)] | |
| 31 | + | |
| 32 | + elif name == "load_class_context": | |
| 33 | + bundle = loader.load_class(arguments["name"], arguments["file_path"]) | |
| 34 | + fmt = arguments.get("format", "markdown") | |
| 35 | + text = bundle.to_markdown() if fmt == "markdown" else bundle.to_json() | |
| 36 | + return [TextContent(type="text", text=text)] | |
| 37 | + | |
| 38 | + down limit=argumentfault": "", | |
| 39 | + fault": "", | |
| 40 | + }, | |
| 41 | + }, | |
| 42 | + "required": ["name"], | |
| 43 | + }, | |
| 44 | + ), | |
| 45 | + Tool(pCP server — exposes graph query"], limit | |
| 46 | + read-only mode.", | |
| 47 | + ("limit", 20)) | |
| 48 | + if not results: | |
| 49 | + return [TextContent(tundle.to_markdownf"- **{r.type}** `{r.name}` — {r.description or ''} for r in results | |
| 50 | + " | |
| 51 | +Navegador MCP server — exposes graph context tools to AI coding poses graph context tools CP servarguments["cypher"]) | |
| 52 | + fault"down limit=argumentfault": "", | |
| 53 | + fault": "", | |
| 54 | + }, | |
| 55 | + }, | |
| 56 | + "required": ["name"], | |
| 57 | + }, | |
| 58 | + ), | |
| 59 | + Tool( | |
| 60 | + | |
| 61 | + TextContent inputSchema={ | |
| 62 | + type=" is None: | |
| 63 | + _text", | |
| 64 | + ts disabled in read-on)] | |
| 65 | + from navegador.ingestion import RepoIngester | |
| 66 | + | |
| 67 | + ingester = RepoIngester(loader.store) | |
| 68 | + stats = ingestarguments["file_path"]) | |
| 69 | + fmt = arguments.get("format", "markdown") | |
| 70 | + text = bundle.to_markdown() if fmt == "markdown" else bundle.to_json() | |
| 71 | + return [TextContent(type="text", text=text)] | |
| 72 | + | |
| 73 | + elif name == "load_function_context": | |
| 74 | + bundle = load depth=arguments.get("depth", 2), | |
| 75 | + guments.get("formatormat", "markdown") | |
| 76 | + text = bundle.to_markdown() if fmt == "markdown" else bundle.to_json() | |
| 77 | + return [TextContent(type="text", text=text)] | |
| 78 | + | |
| 79 | + elif name == "load_class_context": | |
| 80 | + bundle = loader.load_class(arguments["name"], arguments["file_path"]) | |
| 81 | + fmt = arguments.get("format", "markdown") | |
| 82 | + text = bundle.to_markdown() if fmt == "markdown" else bundle.to_json() | |
| 83 | + return [TextContent(type="text", text=text)] | |
| 84 | + | |
| 85 | + elif name == "search_symbols": | |
| 86 | + results = loader.search(arguments["query"],undle.to_markdown limit=arguments.get("lim | |
| 87 | + " | |
| 88 | +Navegador MCP server — exposes graph context tools to AI coding poses graph context tools to AI coding agents. | |
| 89 | + | |
| 90 | +Run: | |
| 91 | + navegador mcp --db .navegador/grapCP server — exposes graph query"], limit | |
| 92 | + read-only mode.", | |
| 93 | + ("limit", 20)) | |
| 94 | + if not results: | |
| 95 | + return [TextContent(tundle.to_markdownf"- **{r.type}** `{r.name}` — {r.description or ''} for r in results | |
| 96 | + " | |
| 97 | +Navegador MCP server — exposes graph context tools to AI coding poses graph context tools CP servarguments["cypher"]) |
| --- a/navegador/mcp/server.py | |
| +++ b/navegador/mcp/server.py | |
| @@ -0,0 +1,97 @@ | |
| --- a/navegador/mcp/server.py | |
| +++ b/navegador/mcp/server.py | |
| @@ -0,0 +1,97 @@ | |
| 1 | server.stdio import stdio_suments.get("format", "markdown") |
| 2 | text = bundle.to_markdown() if fmt == "markdown" else bundle.to_json() |
| 3 | return [TextContent(type="text", text=text)] |
| 4 | |
| 5 | elif name == "load_function_context": |
| 6 | bundle = load depth=arguments.get("depth", 2), |
| 7 | guments.get("formatormat", "markdown") |
| 8 | text = bundle.to_markdown() if fmt == "markdown" else bundle.to_json() |
| 9 | return [TextContent(type="text", text=text)] |
| 10 | |
| 11 | elif name == "load_class_context": |
| 12 | bundle = loader.load_class(arguments["name"], arguments["file_path"]) |
| 13 | fmt = arguments.get("format", "markdown") |
| 14 | text = bundle.to_markdown() if fmt == "markdown" else bundle.to_json() |
| 15 | return [TextContent(type="text", text=text)] |
| 16 | |
| 17 | elif name == "search_symbols": |
| 18 | results = loader.search(arguments["query"],undle.to_markdown limit=arguments.get("lim |
| 19 | sitoryelse bundle.to_json() |
| 20 | clear": {xt tools to AI cod "fault": "", |
| 21 | "default": False"name"]return [TextContent(type=" depth=arguments.get("depth", 2), |
| 22 | guments.get("formatormat", "markdown") |
| 23 | tile — exposes graph qes graph context tools t and their relationships bundle = loader.load_class(arguments["name"], arguments["file_path"]) |
| 24 | fmt = arguments.get("format", "markdownn") |
| 25 | text = bundle.to_markdown() if fmt == "markdown" ed": ["name"]return [TextCo"name"]return [TextCont "format"]) |
| 26 | fmt = afault": "", |
| 27 | fault": "", |
| 28 | function — exposes graph query"], limit |
| 29 | ext toollse bundle.to_json() |
| 30 | return [TextContent(type="text", text=text)] |
| 31 | |
| 32 | elif name == "load_class_context": |
| 33 | bundle = loader.load_class(arguments["name"], arguments["file_path"]) |
| 34 | fmt = arguments.get("format", "markdown") |
| 35 | text = bundle.to_markdown() if fmt == "markdown" else bundle.to_json() |
| 36 | return [TextContent(type="text", text=text)] |
| 37 | |
| 38 | down limit=argumentfault": "", |
| 39 | fault": "", |
| 40 | }, |
| 41 | }, |
| 42 | "required": ["name"], |
| 43 | }, |
| 44 | ), |
| 45 | Tool(pCP server — exposes graph query"], limit |
| 46 | read-only mode.", |
| 47 | ("limit", 20)) |
| 48 | if not results: |
| 49 | return [TextContent(tundle.to_markdownf"- **{r.type}** `{r.name}` — {r.description or ''} for r in results |
| 50 | " |
| 51 | Navegador MCP server — exposes graph context tools to AI coding poses graph context tools CP servarguments["cypher"]) |
| 52 | fault"down limit=argumentfault": "", |
| 53 | fault": "", |
| 54 | }, |
| 55 | }, |
| 56 | "required": ["name"], |
| 57 | }, |
| 58 | ), |
| 59 | Tool( |
| 60 | |
| 61 | TextContent inputSchema={ |
| 62 | type=" is None: |
| 63 | _text", |
| 64 | ts disabled in read-on)] |
| 65 | from navegador.ingestion import RepoIngester |
| 66 | |
| 67 | ingester = RepoIngester(loader.store) |
| 68 | stats = ingestarguments["file_path"]) |
| 69 | fmt = arguments.get("format", "markdown") |
| 70 | text = bundle.to_markdown() if fmt == "markdown" else bundle.to_json() |
| 71 | return [TextContent(type="text", text=text)] |
| 72 | |
| 73 | elif name == "load_function_context": |
| 74 | bundle = load depth=arguments.get("depth", 2), |
| 75 | guments.get("formatormat", "markdown") |
| 76 | text = bundle.to_markdown() if fmt == "markdown" else bundle.to_json() |
| 77 | return [TextContent(type="text", text=text)] |
| 78 | |
| 79 | elif name == "load_class_context": |
| 80 | bundle = loader.load_class(arguments["name"], arguments["file_path"]) |
| 81 | fmt = arguments.get("format", "markdown") |
| 82 | text = bundle.to_markdown() if fmt == "markdown" else bundle.to_json() |
| 83 | return [TextContent(type="text", text=text)] |
| 84 | |
| 85 | elif name == "search_symbols": |
| 86 | results = loader.search(arguments["query"],undle.to_markdown limit=arguments.get("lim |
| 87 | " |
| 88 | Navegador MCP server — exposes graph context tools to AI coding poses graph context tools to AI coding agents. |
| 89 | |
| 90 | Run: |
| 91 | navegador mcp --db .navegador/grapCP server — exposes graph query"], limit |
| 92 | read-only mode.", |
| 93 | ("limit", 20)) |
| 94 | if not results: |
| 95 | return [TextContent(tundle.to_markdownf"- **{r.type}** `{r.name}` — {r.description or ''} for r in results |
| 96 | " |
| 97 | Navegador MCP server — exposes graph context tools to AI coding poses graph context tools CP servarguments["cypher"]) |
+1
| --- a/pyproject.toml | ||
| +++ b/pyproject.toml | ||
| @@ -0,0 +1 @@ | ||
| 1 | +[build-sys |
| --- a/pyproject.toml | |
| +++ b/pyproject.toml | |
| @@ -0,0 +1 @@ | |
| --- a/pyproject.toml | |
| +++ b/pyproject.toml | |
| @@ -0,0 +1 @@ | |
| 1 | [build-sys |
No diff available
| --- a/tests/test_context.py | ||
| +++ b/tests/test_context.py | ||
| @@ -0,0 +1,6 @@ | ||
| 1 | +"""Tests for context bundle serialization (no graph required)."""test_bundle_to_dict(): | |
| 2 | + bundled = bundle.to_dict() | |
| 3 | + | |
| 4 | + | |
| 5 | +def test_bundle_to_json():undle.to_json()) | |
| 6 | + assert datamd = bundleassert "get_user" in md |
| --- a/tests/test_context.py | |
| +++ b/tests/test_context.py | |
| @@ -0,0 +1,6 @@ | |
| --- a/tests/test_context.py | |
| +++ b/tests/test_context.py | |
| @@ -0,0 +1,6 @@ | |
| 1 | """Tests for context bundle serialization (no graph required)."""test_bundle_to_dict(): |
| 2 | bundled = bundle.to_dict() |
| 3 | |
| 4 | |
| 5 | def test_bundle_to_json():undle.to_json()) |
| 6 | assert datamd = bundleassert "get_user" in md |
+4
| --- a/tests/test_schema.py | ||
| +++ b/tests/test_schema.py | ||
| @@ -0,0 +1,4 @@ | ||
| 1 | +from navegador.graph.schema import ""Tests for graph sche"""Tests for graph schem assert NodeLabel.File == "File" | |
| 2 | + assert NodeLabel.assert NodeLabel.Class == "Class assert Edgassert EdgeType.IMPORTS == "IMPORTS" | |
| 3 | + assert EdgeType.CONTAINS == "CONTAINS" | |
| 4 | + assert EdgeT |
| --- a/tests/test_schema.py | |
| +++ b/tests/test_schema.py | |
| @@ -0,0 +1,4 @@ | |
| --- a/tests/test_schema.py | |
| +++ b/tests/test_schema.py | |
| @@ -0,0 +1,4 @@ | |
| 1 | from navegador.graph.schema import ""Tests for graph sche"""Tests for graph schem assert NodeLabel.File == "File" |
| 2 | assert NodeLabel.assert NodeLabel.Class == "Class assert Edgassert EdgeType.IMPORTS == "IMPORTS" |
| 3 | assert EdgeType.CONTAINS == "CONTAINS" |
| 4 | assert EdgeT |