|
89816aa…
|
lmata
|
1 |
# Framework Enrichment |
|
89816aa…
|
lmata
|
2 |
|
|
89816aa…
|
lmata
|
3 |
## What enrichment does |
|
89816aa…
|
lmata
|
4 |
|
|
89816aa…
|
lmata
|
5 |
After ingestion, navegador's graph contains generic structural nodes: `Function`, `Class`, `File`, `Import`. Enrichment promotes those generic nodes to **semantic types** that reflect how the code is actually used. |
|
89816aa…
|
lmata
|
6 |
|
|
89816aa…
|
lmata
|
7 |
For example, a Django view function becomes a `View` node. A pytest function becomes a `Test` node. A Flask route decorator triggers creation of a `Route` node with the URL pattern extracted. |
|
89816aa…
|
lmata
|
8 |
|
|
89816aa…
|
lmata
|
9 |
This lets you ask questions that wouldn't be possible from structure alone: |
|
89816aa…
|
lmata
|
10 |
|
|
89816aa…
|
lmata
|
11 |
```bash |
|
89816aa…
|
lmata
|
12 |
# without enrichment: grep for "def test_" |
|
89816aa…
|
lmata
|
13 |
# with enrichment: query the graph by semantic type |
|
89816aa…
|
lmata
|
14 |
navegador query "MATCH (t:Test) RETURN t.name, t.file ORDER BY t.file" |
|
89816aa…
|
lmata
|
15 |
|
|
89816aa…
|
lmata
|
16 |
# find all API routes |
|
89816aa…
|
lmata
|
17 |
navegador query "MATCH (r:Route) RETURN r.method, r.path, r.handler ORDER BY r.path" |
|
89816aa…
|
lmata
|
18 |
``` |
|
89816aa…
|
lmata
|
19 |
|
|
89816aa…
|
lmata
|
20 |
--- |
|
89816aa…
|
lmata
|
21 |
|
|
89816aa…
|
lmata
|
22 |
## How it works |
|
89816aa…
|
lmata
|
23 |
|
|
89816aa…
|
lmata
|
24 |
Enrichment runs as a post-ingest pass. It reads existing nodes and edges, applies framework-specific pattern matching (decorator names, base class names, naming conventions), and: |
|
89816aa…
|
lmata
|
25 |
|
|
89816aa…
|
lmata
|
26 |
1. Adds semantic labels to matched nodes (e.g., adds `View` label to Django view functions) |
|
89816aa…
|
lmata
|
27 |
2. Creates typed edges where the framework implies relationships (e.g., `HANDLES` from a `Route` to its handler function) |
|
89816aa…
|
lmata
|
28 |
3. Extracts framework-specific properties (e.g., HTTP method and URL pattern from route decorators) |
|
89816aa…
|
lmata
|
29 |
|
|
89816aa…
|
lmata
|
30 |
Enrichment is **non-destructive** — it never removes or modifies existing nodes, only adds labels and edges. |
|
89816aa…
|
lmata
|
31 |
|
|
89816aa…
|
lmata
|
32 |
--- |
|
89816aa…
|
lmata
|
33 |
|
|
89816aa…
|
lmata
|
34 |
## Supported frameworks |
|
89816aa…
|
lmata
|
35 |
|
|
89816aa…
|
lmata
|
36 |
| Framework | Language | Detected patterns | Semantic types added | |
|
89816aa…
|
lmata
|
37 |
|---|---|---|---| |
|
89816aa…
|
lmata
|
38 |
| Django | Python | `View` subclasses, `urlpatterns`, `@login_required` | `View`, `Route`, `Model`, `Form`, `Middleware` | |
|
89816aa…
|
lmata
|
39 |
| Flask | Python | `@app.route`, `@blueprint.route`, `MethodView` | `Route`, `View`, `Blueprint` | |
|
89816aa…
|
lmata
|
40 |
| FastAPI | Python | `@router.get/post/put/delete/patch`, `APIRouter` | `Route`, `Schema`, `Dependency` | |
|
89816aa…
|
lmata
|
41 |
| pytest | Python | `def test_*`, `@pytest.mark.*`, `conftest.py` | `Test`, `Fixture`, `TestSuite` | |
|
89816aa…
|
lmata
|
42 |
| SQLAlchemy | Python | `Base` subclasses, `Column`, `relationship()` | `Model`, `Column`, `Relation` | |
|
89816aa…
|
lmata
|
43 |
| Next.js | TypeScript | `pages/`, `app/`, `getServerSideProps` | `Page`, `Route`, `ServerComponent` | |
|
89816aa…
|
lmata
|
44 |
| Express | JavaScript | `app.get/post/put/delete`, `Router` | `Route`, `Middleware` | |
|
89816aa…
|
lmata
|
45 |
| NestJS | TypeScript | `@Controller`, `@Injectable`, `@Module` | `Controller`, `Service`, `Module` | |
|
dcf17e9…
|
lmata
|
46 |
| **Terraform** | HCL | `main.tf`, `variables.tf`, `outputs.tf` | Cross-file module resolution, provider grouping | |
|
dcf17e9…
|
lmata
|
47 |
| **Chef** | Ruby | `metadata.rb`, `Berksfile` | `chef_recipe`, `chef_resource`, `chef_cookbook`, `chef_include` | |
|
89816aa…
|
lmata
|
48 |
|
|
89816aa…
|
lmata
|
49 |
!!! note |
|
89816aa…
|
lmata
|
50 |
Framework detection is automatic when `--framework auto` is used (the default). Navegador inspects imports and decorator patterns to identify which frameworks are present. |
|
89816aa…
|
lmata
|
51 |
|
|
89816aa…
|
lmata
|
52 |
--- |
|
89816aa…
|
lmata
|
53 |
|
|
89816aa…
|
lmata
|
54 |
## Usage |
|
89816aa…
|
lmata
|
55 |
|
|
89816aa…
|
lmata
|
56 |
### Auto-detect and enrich all frameworks |
|
89816aa…
|
lmata
|
57 |
|
|
89816aa…
|
lmata
|
58 |
```bash |
|
89816aa…
|
lmata
|
59 |
navegador enrich ./src |
|
89816aa…
|
lmata
|
60 |
``` |
|
89816aa…
|
lmata
|
61 |
|
|
89816aa…
|
lmata
|
62 |
This runs after ingestion and enriches everything it can detect automatically. |
|
89816aa…
|
lmata
|
63 |
|
|
89816aa…
|
lmata
|
64 |
### Enrich immediately after ingestion |
|
89816aa…
|
lmata
|
65 |
|
|
89816aa…
|
lmata
|
66 |
```bash |
|
89816aa…
|
lmata
|
67 |
navegador ingest ./src && navegador enrich ./src |
|
89816aa…
|
lmata
|
68 |
``` |
|
89816aa…
|
lmata
|
69 |
|
|
89816aa…
|
lmata
|
70 |
Or use the `--enrich` flag on ingest: |
|
89816aa…
|
lmata
|
71 |
|
|
89816aa…
|
lmata
|
72 |
```bash |
|
89816aa…
|
lmata
|
73 |
navegador ingest ./src --enrich |
|
89816aa…
|
lmata
|
74 |
``` |
|
89816aa…
|
lmata
|
75 |
|
|
89816aa…
|
lmata
|
76 |
### Target a specific framework |
|
89816aa…
|
lmata
|
77 |
|
|
89816aa…
|
lmata
|
78 |
```bash |
|
89816aa…
|
lmata
|
79 |
navegador enrich ./src --framework django |
|
89816aa…
|
lmata
|
80 |
navegador enrich ./src --framework fastapi |
|
89816aa…
|
lmata
|
81 |
navegador enrich ./src --framework pytest |
|
89816aa…
|
lmata
|
82 |
``` |
|
89816aa…
|
lmata
|
83 |
|
|
dcf17e9…
|
lmata
|
84 |
Valid values: `django`, `flask`, `fastapi`, `pytest`, `sqlalchemy`, `nextjs`, `express`, `nestjs`, `terraform`, `chef`, `auto` (default). |
|
89816aa…
|
lmata
|
85 |
|
|
89816aa…
|
lmata
|
86 |
### JSON output |
|
89816aa…
|
lmata
|
87 |
|
|
89816aa…
|
lmata
|
88 |
```bash |
|
89816aa…
|
lmata
|
89 |
navegador enrich ./src --json |
|
89816aa…
|
lmata
|
90 |
``` |
|
89816aa…
|
lmata
|
91 |
|
|
89816aa…
|
lmata
|
92 |
Returns a summary of labels and edges added per framework. |
|
89816aa…
|
lmata
|
93 |
|
|
89816aa…
|
lmata
|
94 |
--- |
|
89816aa…
|
lmata
|
95 |
|
|
89816aa…
|
lmata
|
96 |
## Querying enriched nodes |
|
89816aa…
|
lmata
|
97 |
|
|
89816aa…
|
lmata
|
98 |
Once enriched, the semantic types are queryable via Cypher: |
|
89816aa…
|
lmata
|
99 |
|
|
89816aa…
|
lmata
|
100 |
```bash |
|
89816aa…
|
lmata
|
101 |
# all FastAPI routes with their HTTP methods |
|
89816aa…
|
lmata
|
102 |
navegador query "MATCH (r:Route) RETURN r.method, r.path, r.handler ORDER BY r.path" |
|
89816aa…
|
lmata
|
103 |
|
|
89816aa…
|
lmata
|
104 |
# all SQLAlchemy models and their columns |
|
89816aa…
|
lmata
|
105 |
navegador query "MATCH (m:Model)-[:HAS_COLUMN]->(c:Column) RETURN m.name, c.name, c.type ORDER BY m.name" |
|
89816aa…
|
lmata
|
106 |
|
|
89816aa…
|
lmata
|
107 |
# all pytest tests that reference a specific function |
|
89816aa…
|
lmata
|
108 |
navegador query "MATCH (t:Test)-[:CALLS]->(f:Function {name: 'process_payment'}) RETURN t.name, t.file" |
|
89816aa…
|
lmata
|
109 |
|
|
89816aa…
|
lmata
|
110 |
# all Django views governed by a rule |
|
89816aa…
|
lmata
|
111 |
navegador query "MATCH (r:Rule)-[:GOVERNS]->(v:View) RETURN r.name, v.name, v.file" |
|
89816aa…
|
lmata
|
112 |
``` |
|
89816aa…
|
lmata
|
113 |
|
|
89816aa…
|
lmata
|
114 |
--- |
|
89816aa…
|
lmata
|
115 |
|
|
89816aa…
|
lmata
|
116 |
## Adding custom enrichers |
|
89816aa…
|
lmata
|
117 |
|
|
89816aa…
|
lmata
|
118 |
Enrichers are subclasses of `FrameworkEnricher`. Create one to add support for an internal framework or library. |
|
89816aa…
|
lmata
|
119 |
|
|
89816aa…
|
lmata
|
120 |
### 1. Create the enricher |
|
89816aa…
|
lmata
|
121 |
|
|
89816aa…
|
lmata
|
122 |
```python |
|
89816aa…
|
lmata
|
123 |
# myproject/enrichers/celery.py |
|
89816aa…
|
lmata
|
124 |
from navegador.enrichment.base import FrameworkEnricher, EnrichmentResult |
|
89816aa…
|
lmata
|
125 |
from navegador.graph import GraphStore |
|
89816aa…
|
lmata
|
126 |
|
|
89816aa…
|
lmata
|
127 |
class CeleryEnricher(FrameworkEnricher): |
|
89816aa…
|
lmata
|
128 |
name = "celery" |
|
89816aa…
|
lmata
|
129 |
|
|
89816aa…
|
lmata
|
130 |
def detect(self, store: GraphStore) -> bool: |
|
89816aa…
|
lmata
|
131 |
"""Return True if this framework is present in the graph.""" |
|
89816aa…
|
lmata
|
132 |
results = store.query( |
|
89816aa…
|
lmata
|
133 |
"MATCH (i:Import {name: 'celery'}) RETURN count(i) AS n" |
|
89816aa…
|
lmata
|
134 |
) |
|
89816aa…
|
lmata
|
135 |
return results[0]["n"] > 0 |
|
89816aa…
|
lmata
|
136 |
|
|
89816aa…
|
lmata
|
137 |
def enrich(self, store: GraphStore) -> EnrichmentResult: |
|
89816aa…
|
lmata
|
138 |
"""Add semantic labels and edges for Celery tasks.""" |
|
89816aa…
|
lmata
|
139 |
# Find functions decorated with @shared_task or @app.task |
|
89816aa…
|
lmata
|
140 |
tasks = store.query( |
|
89816aa…
|
lmata
|
141 |
"MATCH (d:Decorator)-[:DECORATES]->(f:Function) " |
|
89816aa…
|
lmata
|
142 |
"WHERE d.name IN ['shared_task', 'task'] " |
|
89816aa…
|
lmata
|
143 |
"RETURN f.id, f.name" |
|
89816aa…
|
lmata
|
144 |
) |
|
89816aa…
|
lmata
|
145 |
labels_added = 0 |
|
89816aa…
|
lmata
|
146 |
for row in tasks: |
|
89816aa…
|
lmata
|
147 |
store.query( |
|
89816aa…
|
lmata
|
148 |
"MATCH (f:Function) WHERE id(f) = $id " |
|
89816aa…
|
lmata
|
149 |
"SET f:Task", |
|
89816aa…
|
lmata
|
150 |
params={"id": row["f.id"]} |
|
89816aa…
|
lmata
|
151 |
) |
|
89816aa…
|
lmata
|
152 |
labels_added += 1 |
|
89816aa…
|
lmata
|
153 |
|
|
89816aa…
|
lmata
|
154 |
return EnrichmentResult(labels_added=labels_added, edges_added=0) |
|
89816aa…
|
lmata
|
155 |
``` |
|
89816aa…
|
lmata
|
156 |
|
|
89816aa…
|
lmata
|
157 |
### 2. Register the enricher |
|
89816aa…
|
lmata
|
158 |
|
|
89816aa…
|
lmata
|
159 |
```python |
|
89816aa…
|
lmata
|
160 |
# myproject/enrichers/__init__.py |
|
89816aa…
|
lmata
|
161 |
from navegador.enrichment.registry import register_enricher |
|
89816aa…
|
lmata
|
162 |
from .celery import CeleryEnricher |
|
89816aa…
|
lmata
|
163 |
|
|
89816aa…
|
lmata
|
164 |
register_enricher(CeleryEnricher()) |
|
89816aa…
|
lmata
|
165 |
``` |
|
89816aa…
|
lmata
|
166 |
|
|
89816aa…
|
lmata
|
167 |
### 3. Load at startup |
|
89816aa…
|
lmata
|
168 |
|
|
89816aa…
|
lmata
|
169 |
Import the registration module before running enrichment. In a CLI wrapper or agent hook: |
|
89816aa…
|
lmata
|
170 |
|
|
89816aa…
|
lmata
|
171 |
```python |
|
89816aa…
|
lmata
|
172 |
import myproject.enrichers # registers the enricher |
|
89816aa…
|
lmata
|
173 |
from navegador.enrichment import run_enrichment |
|
89816aa…
|
lmata
|
174 |
from navegador.graph import GraphStore |
|
89816aa…
|
lmata
|
175 |
|
|
89816aa…
|
lmata
|
176 |
store = GraphStore.sqlite(".navegador/navegador.db") |
|
89816aa…
|
lmata
|
177 |
result = run_enrichment(store, framework="celery") |
|
89816aa…
|
lmata
|
178 |
print(f"Added {result.labels_added} labels") |
|
89816aa…
|
lmata
|
179 |
``` |
|
89816aa…
|
lmata
|
180 |
|
|
89816aa…
|
lmata
|
181 |
Or pass the module path to the CLI via `NAVEGADOR_ENRICHERS`: |
|
89816aa…
|
lmata
|
182 |
|
|
89816aa…
|
lmata
|
183 |
```bash |
|
89816aa…
|
lmata
|
184 |
NAVEGADOR_ENRICHERS=myproject.enrichers navegador enrich ./src --framework celery |
|
89816aa…
|
lmata
|
185 |
``` |