Metadata-Version: 2.3
Name: graphlens
Version: 0.4.0
Summary: Extensible polyglot code analysis framework with a graph IR
Keywords: code-analysis,graphlens,ast,tree-sitter,static-analysis,graph,polyglot,dependency-analysis,python,code-intelligence
Author: Neko1313
Author-email: Neko1313 <nikita.ribalchencko@yandex.ru>
License: MIT License
         
         Copyright (c) 2026 Neko1313
         
         Permission is hereby granted, free of charge, to any person obtaining a copy
         of this software and associated documentation files (the "Software"), to deal
         in the Software without restriction, including without limitation the rights
         to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         copies of the Software, and to permit persons to whom the Software is
         furnished to do so, subject to the following conditions:
         
         The above copyright notice and this permission notice shall be included in all
         copies or substantial portions of the Software.
         
         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         SOFTWARE.
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Classifier: Typing :: Typed
Classifier: Operating System :: OS Independent
Requires-Dist: graphlens-typescript ; extra == 'all'
Requires-Dist: graphlens-python ; extra == 'all'
Requires-Dist: graphlens-python ; extra == 'python'
Requires-Dist: graphlens-typescript ; extra == 'typescript'
Requires-Python: >=3.13
Project-URL: Repository, https://github.com/Neko1313/graphlens
Project-URL: Issues, https://github.com/Neko1313/graphlens/issues
Project-URL: Changelog, https://github.com/Neko1313/graphlens/blob/main/CHANGELOG.md
Provides-Extra: all
Provides-Extra: python
Provides-Extra: typescript
Description-Content-Type: text/markdown

<div align="center">

  <h1>graphlens</h1>

  <p>Extensible polyglot code analysis framework that parses source projects, normalizes their structure into a shared graph IR, and exposes it for dependency analysis, navigation, and code intelligence tooling.</p>

  [![PyPI](https://img.shields.io/pypi/v/graphlens?color=blue)](https://pypi.org/project/graphlens/)
  [![Python](https://img.shields.io/pypi/pyversions/graphlens)](https://pypi.org/project/graphlens/)
  [![License](https://img.shields.io/github/license/Neko1313/graphlens)](LICENSE)
  [![CI](https://img.shields.io/github/actions/workflow/status/Neko1313/graphlens/ci.yml?label=CI)](https://github.com/Neko1313/graphlens/actions)
  [![codecov](https://codecov.io/gh/Neko1313/graphlens/graph/badge.svg?token=n3oRe180jg)](https://codecov.io/gh/Neko1313/graphlens)

  [Repository](https://github.com/Neko1313/graphlens) · [Issues](https://github.com/Neko1313/graphlens/issues)

</div>

---

## Architecture

```
Repository → Language Adapter → GraphLens (IR) → Graph Backend
```

| Layer | Responsibility |
|---|---|
| **Language Adapter** | Parses source files, produces `GraphLens` |
| **GraphLens** | Typed nodes + directed relations (the IR) |
| **Graph Backend** | Persists or queries the graph (Neo4j, in-memory, …) |

Adapters are **pure data producers** — they never write to any backend. The graph is the only output.

## Why graph IR?

- **Language-agnostic** — one shared model for Python, TypeScript, Rust, …
- **Plugin-based adapters** — each language is a separate package, registered via Python entry points
- **Tree-sitter powered** — all adapters use tree-sitter for CST parsing and exact span positions, combined with type-aware resolution (ty for Python, TypeScript Compiler API for TypeScript)
- **Monorepo aware** — `can_handle()` and `find_*_roots()` handle multi-language repos correctly
- **Deterministic node IDs** — SHA-256 hash of `project::kind::qualified_name` → stable across re-scans

## Installation

```bash
# Core library only (models, contracts, registry)
pip install graphlens

# Core + Python adapter
pip install "graphlens[python]"

# Core + TypeScript adapter
pip install "graphlens[typescript]"

# CLI (graphlens analyze / visualize / neo4j)
pip install "graphlens-cli[python]"          # with Python adapter
pip install "graphlens-cli[all]"             # Python + TypeScript + Neo4j
```

With uv:

```bash
uv add graphlens
uv add "graphlens[python]"
uv add "graphlens[typescript]"
uv add "graphlens-cli[all]"
```

## Quick start

```python
from pathlib import Path
from graphlens import adapter_registry

# Load and instantiate the Python adapter
adapter = adapter_registry.load("python")()

# Analyze a project — returns a GraphLens
graph = adapter.analyze(Path("./my-project"))

print(f"Nodes:     {len(graph.nodes)}")
print(f"Relations: {len(graph.relations)}")

# Inspect nodes by kind
from graphlens import NodeKind

modules = [n for n in graph.nodes.values() if n.kind == NodeKind.MODULE]
classes = [n for n in graph.nodes.values() if n.kind == NodeKind.CLASS]
```

## CLI (`graphlens-cli`)

Install `graphlens-cli` to get the `graphlens` entry point with three commands:

```bash
# Print node/relation statistics
graphlens analyze <project_root>
graphlens analyze ~/myrepo --lang python,typescript

# Interactive HTML graph viewer (opens in browser)
graphlens visualize <project_root>
graphlens visualize ~/myrepo --lang python --show-external --max-nodes 500
graphlens visualize . --output graph.html --no-open

# Export to Neo4j
graphlens neo4j <project_root> --uri bolt://localhost:7687 --user neo4j --password secret
graphlens neo4j . --wipe --batch-size 200
```

### `visualize` — interactive HTML graph viewer

Produces a self-contained HTML file powered by vis.js and opens it in the browser.

| Flag | Description |
|---|---|
| `--lang auto\|python\|typescript\|python,typescript` | Adapters to use (default: auto-detect all) |
| `--show-external` | Include stdlib / third-party external symbol nodes |
| `--show-structure` | Add `CONTAINS` / `DECLARES` structural edges |
| `--max-nodes N` | Prune low-degree nodes above N (default: 1500) |
| `--output PATH` | Write HTML to PATH instead of `graph-<name>.html` |
| `--no-open` | Do not open the browser automatically |

**Click behaviour** — click any node to see its info panel. For `FUNCTION`
and `METHOD` nodes the panel has a **"Show callers"** button that switches the
graph into focus mode: only the selected node and every node that calls or
references it are shown, with the caller list in the sidebar. Click empty
space or **← Back** to return to the full graph.

### `neo4j` — export to Neo4j

Uses `UNWIND … MERGE` Cypher (no APOC required). Every node gets a `:Code`
label plus a kind-specific label (`:Function`, `:ExternalSymbol`, …).
Relations are created grouped by type. Install the optional `neo4j` extra:

```bash
pip install "graphlens-cli[neo4j]"
```

## Graph model

### Node kinds

| Kind | Description |
|---|---|
| `PROJECT` | Root project node |
| `MODULE` | Python/TS/… module (directory or file) |
| `FILE` | Source file |
| `CLASS` | Class declaration |
| `FUNCTION` | Top-level function |
| `METHOD` | Method inside a class |
| `PARAMETER` | Function/method parameter |
| `VARIABLE` | Module-level or local variable |
| `ATTRIBUTE` | Class attribute |
| `TYPE_ALIAS` | Type alias declaration |
| `IMPORT` | Import statement |
| `DEPENDENCY` | Declared package dependency |
| `EXTERNAL_SYMBOL` | External symbol (stdlib, third-party, or unknown); carries `metadata["origin"]` |

### Relation kinds

| Kind | Description |
|---|---|
| `CONTAINS` | Structural containment (project → module → file → class) |
| `DECLARES` | Declaration (file declares function, class declares method) |
| `IMPORTS` | Import edge (file → import node) |
| `RESOLVES_TO` | Import resolved to a module or external symbol |
| `CALLS` | Function/method call (resolved to declaration node) |
| `REFERENCES` | Value reference (variable/attribute used as a value) |
| `INHERITS_FROM` | Class inheritance (resolved to declaration node) |
| `HAS_TYPE` | Type annotation/inference edge (function/param/variable → class or external) |
| `DEPENDS_ON` | Package dependency |

## Adapter plugin system

Language adapters register themselves via Python entry points — no changes to the core needed:

```toml
# packages/graphlens-python/pyproject.toml
[project.entry-points."graphlens.adapters"]
python = "graphlens_python:PythonAdapter"
```

The registry discovers installed adapters automatically at runtime:

```python
from graphlens import adapter_registry

adapter_registry.available()          # ["python", ...]
adapter_cls = adapter_registry.load("python")
adapter = adapter_cls()
```

Adapters can also be registered manually (useful for testing):

```python
adapter_registry.register("python", MyPythonAdapter)
```

## Implementing an adapter

Subclass `LanguageAdapter` and implement four methods:

```python
from pathlib import Path
from graphlens import GraphLens, LanguageAdapter

class MyLangAdapter(LanguageAdapter):
    def language(self) -> str:
        return "mylang"

    def file_extensions(self) -> set[str]:
        return {".ml", ".mli"}

    def can_handle(self, project_root: Path) -> bool:
        return (project_root / "dune-project").exists()

    def analyze(
        self, project_root: Path, files: list[Path] | None = None
    ) -> GraphLens:
        graph = GraphLens()
        files = files or self.collect_files(project_root)
        # ... parse and populate graph ...
        return graph
```

Register in `pyproject.toml` and the core registry finds it automatically.

## Project structure

```
graphlens/                      ← uv workspace root (core library)
  src/graphlens/                ← models, contracts, registry, exceptions, utils
  packages/
    graphlens-python/           ← Python adapter (tree-sitter + ty)
    graphlens-typescript/       ← TypeScript adapter (tree-sitter + Compiler API)
    graphlens-cli/              ← CLI package (typer): analyze, visualize, neo4j
  tests/                         ← core tests (100% coverage)
  examples/                      ← standalone usage examples
```

## Development

Requires Python 3.13+, [uv](https://docs.astral.sh/uv/), [task](https://taskfile.dev/).

```bash
task install        # uv sync --all-groups
task lint           # ruff + ty + bandit for all packages
task tests          # all tests with coverage
```

Individual package tasks:

```bash
task core:lint           task core:test
task python:lint         task python:test
task typescript:lint     task typescript:test
task cli:lint            task cli:test
```

## License

MIT
