Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4afdcaa
docs: add framework extract wiring plan
timomeara Apr 24, 2026
e496695
feat(resolution): replace extractNodes with extract() returning nodes…
timomeara Apr 24, 2026
741f812
feat(resolution): add getApplicableFrameworks helper for per-language…
timomeara Apr 24, 2026
8b0de2a
feat(django): emit route nodes and route->view references in extract()
timomeara Apr 24, 2026
f88391f
feat(flask,fastapi): emit route nodes and route->handler references
timomeara Apr 24, 2026
ce02005
feat(express): emit route nodes and route->handler references
timomeara Apr 24, 2026
92230af
feat(laravel): emit route nodes and route->handler references
timomeara Apr 24, 2026
77baeb4
feat(rails): emit route nodes and route->handler references
timomeara Apr 24, 2026
aba50d7
feat(spring): emit route nodes and route->handler references
timomeara Apr 24, 2026
f561e32
feat(go): emit route nodes and route->handler references
timomeara Apr 24, 2026
1d820aa
feat(rust): emit route nodes and route->handler references
timomeara Apr 24, 2026
94c88bd
feat(aspnet): emit route nodes and route->handler references
timomeara Apr 24, 2026
1ccf942
feat(swift,vapor): emit route nodes and route->handler references
timomeara Apr 24, 2026
5aa602c
chore(react,svelte): migrate resolvers to extract() interface
timomeara Apr 24, 2026
f701e7e
feat(extraction): run framework extractors after tree-sitter parse
timomeara Apr 24, 2026
024d1cf
docs: document framework route extraction
timomeara Apr 24, 2026
ce9538d
feat(strip-comments): add per-language comment stripper for framework…
timomeara Apr 28, 2026
469ac63
feat(frameworks): strip comments before regex extraction (prevents ph…
timomeara Apr 28, 2026
4fea115
Merge main into feat/framework-extract-wiring
colbymchenry May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,32 @@ All tests used Claude Opus 4.6 (1M context) with Claude Code v2.1.91. Each test
| **Impact Analysis** | Trace callers, callees, and the full impact radius of any symbol before making changes |
| **Always Fresh** | File watcher uses native OS events (FSEvents/inotify/ReadDirectoryChangesW) with debounced auto-sync — the graph stays current as you code, zero config |
| **19+ Languages** | TypeScript, JavaScript, Python, Go, Rust, Java, C#, PHP, Ruby, C, C++, Swift, Kotlin, Dart, Svelte, Liquid, Pascal/Delphi |
| **Framework-aware Routes** | Recognizes web-framework routing files and links URL patterns to their handlers across 13 frameworks |
| **100% Local** | No data leaves your machine. No API keys. No external services. SQLite database only |

---

## Framework-aware Routes

CodeGraph detects web-framework routing files and emits `route` nodes linked by `references` edges to their handler classes or functions. Querying callers of a view/controller now surfaces the URL pattern that binds it.

| Framework | Shapes recognized |
|---|---|
| **Django** | `path()`, `re_path()`, `url()`, `include()` in `urls.py` (CBV `.as_view()`, dotted paths) |
| **Flask** | `@app.route('/path', methods=[...])`, blueprint routes |
| **FastAPI** | `@app.get(...)`, `@router.post(...)`, all standard methods |
| **Express** | `app.get(...)`, `router.post(...)` with middleware chains |
| **Laravel** | `Route::get()`, `Route::resource()`, `Controller@action`, tuple syntax |
| **Rails** | `get '/x', to: 'users#index'`, hash-rocket `=>` syntax |
| **Spring** | `@GetMapping`, `@PostMapping`, `@RequestMapping` on methods |
| **Gin / chi / gorilla / mux** | `r.GET(...)`, `router.HandleFunc(...)` |
| **Axum / actix / Rocket** | `.route("/x", get(handler))` |
| **ASP.NET** | `[HttpGet("/x")]` attributes on action methods |
| **Vapor** | `app.get("x", use: handler)` |
| **React Router** / **SvelteKit** | Route component nodes |

---

## Quick Start

### 1. Run the Installer
Expand Down
59 changes: 59 additions & 0 deletions __tests__/frameworks-integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { describe, it, expect, beforeAll, afterEach } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { CodeGraph } from '../src';
import { initGrammars, loadAllGrammars } from '../src/extraction/grammars';

beforeAll(async () => {
await initGrammars();
await loadAllGrammars();
});

describe('Django end-to-end framework extraction', () => {
let tmpDir: string | undefined;
afterEach(() => {
if (tmpDir) fs.rmSync(tmpDir, { recursive: true, force: true });
tmpDir = undefined;
});

it('creates a route->view edge from urls.py to view class', async () => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-django-'));
fs.writeFileSync(path.join(tmpDir, 'manage.py'), '# marker\n');
fs.writeFileSync(path.join(tmpDir, 'requirements.txt'), 'django==4.2\n');
fs.mkdirSync(path.join(tmpDir, 'users'));
fs.writeFileSync(path.join(tmpDir, 'users/__init__.py'), '');
fs.writeFileSync(
path.join(tmpDir, 'users/views.py'),
'class UserListView:\n def get(self, request): pass\n'
);
fs.writeFileSync(
path.join(tmpDir, 'users/urls.py'),
'from django.urls import path\n' +
'from users.views import UserListView\n' +
'urlpatterns = [path("users/", UserListView.as_view(), name="user-list")]\n'
);

const cg = CodeGraph.initSync(tmpDir);
await cg.indexAll();

// Route node exists
const routes = cg.getNodesByKind('route');
expect(routes.length).toBeGreaterThan(0);
const route = routes.find((n) => n.name === 'users/');
expect(route).toBeDefined();

// View class exists
const classNodes = cg.getNodesByKind('class');
const view = classNodes.find((n) => n.name === 'UserListView');
expect(view).toBeDefined();

// Edge route -> view exists
const edges = cg.getOutgoingEdges(route!.id);
const toView = edges.find((e) => e.target === view!.id);
expect(toView).toBeDefined();
expect(toView!.kind).toBe('references');

cg.close();
});
});
Loading