Skip to content

Commit 3c005b3

Browse files
authored
Merge pull request #1 from Modsofthenation/cursor/loadpath-reviewer-ccb4
Loadpath: architecture-typed impact graphs for Django + React PRs
2 parents 1c6d4c0 + 7b2548e commit 3c005b3

111 files changed

Lines changed: 12429 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: actions/setup-python@v5
14+
with:
15+
python-version: "3.12"
16+
- uses: actions/setup-node@v4
17+
with:
18+
node-version: "22"
19+
cache: npm
20+
cache-dependency-path: ui/package-lock.json
21+
- name: Python tests
22+
run: |
23+
python -m pip install -e ".[dev]"
24+
python -m playwright install chromium
25+
pytest
26+
- name: UI tests
27+
working-directory: ui
28+
run: |
29+
npm install
30+
npm test
31+
npm run build

‎.gitignore‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
.venv/
2+
venv/
3+
__pycache__/
4+
*.py[cod]
5+
*.egg-info/
6+
.pytest_cache/
7+
.mypy_cache/
8+
.ruff_cache/
9+
.coverage
10+
htmlcov/
11+
dist/
12+
build/
13+
node_modules/
14+
ui/dist/
15+
*.db
16+
*.sqlite3
17+
.loadpath/
18+
.env
19+
.DS_Store
20+
.idea/
21+
.vscode/
22+
*.log
23+
.playwright-mcp/
24+
ui/playwright-report/
25+
ui/test-results/

‎README.md‎

Lines changed: 189 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,189 @@
1-
# PR-Reviewer
1+
# Loadpath
2+
3+
Review as **load-path inspection** on a Django + React architecture graph. Not another hunk-comment bot.
4+
5+
A change is a force. Loadpath traces where that force travels until it hits a sink — HTTP response, UI, Celery/Dramatiq job, migration, permission — then scores whether you have enough evidence to merge.
6+
7+
```
8+
Loadpath: MEDIUM — Invoice.total field change
9+
Sinks: GET/POST /api/invoices/{id}; Celery send_invoice_email, apply_credit; Dramatiq rebuild_ledger; React InvoicePage + InvoiceForm
10+
Tests: pytest hits serializer and view; no RTL test on InvoiceForm
11+
Architecture: stays inside billing
12+
Residual: total also formatted in a Signal update_ledger — no test
13+
Suggested reviewers: billing-team
14+
```
15+
16+
On the demo monorepo that path is:
17+
18+
`Invoice.total → InvoiceSerializer → InvoiceViewSet → /api/invoices/{id} → OpenAPI → fetch → useInvoice → InvoicePage → InvoiceForm / Zod`
19+
20+
plus the jobs the view enqueues (`send_invoice_email.delay`, `rebuild_ledger.send`).
21+
22+
## App
23+
24+
`loadpath serve --port 7345` opens a local desktop-style UI. Tokens stay on the machine in `~/.loadpath/settings.json`. AI is used **only** for residual uncertainty the graph cannot close.
25+
26+
### Review
27+
28+
Confidence brief, read-order, clusters, architecture findings on the impact path, residual list, and the subgraph for the git range. Review walks the **indexed** graph (incremental refresh by default).
29+
30+
![Review](docs/screenshots/review.png)
31+
32+
### Architecture
33+
34+
Index a repo first. The architecture tab is the full typed graph plus `loadpath.yml` contexts and rules — not a PR diff. Findings here are repo-wide; review then scopes them to the change.
35+
36+
![Architecture](docs/screenshots/architecture.png)
37+
38+
### Impact graph
39+
40+
Toggle **This review** (impact subgraph) vs **Indexed architecture** (the repo map). Dashed edges are inferred (URL/Zod overlap); solid edges are extracted or generated-client stitches.
41+
42+
![Impact graph](docs/screenshots/graph.png)
43+
44+
### Pull requests
45+
46+
GitHub and Bitbucket via API tokens from Settings. Pick a PR and jump to a branch-range review.
47+
48+
![Pull requests](docs/screenshots/pull-requests.png)
49+
50+
### Settings
51+
52+
GitHub / Bitbucket tokens; AI providers (Anthropic, OpenAI, Grok/xAI, DeepSeek, Cursor-compatible, Ollama). Residual analysis only — Loadpath does not comment every hunk.
53+
54+
![Settings](docs/screenshots/settings.png)
55+
56+
## Install
57+
58+
Python 3.12+ and Node 22+ (UI).
59+
60+
```bash
61+
pip install -e ".[dev]"
62+
python -m playwright install chromium # optional, for UI screenshot tests
63+
cd ui && npm install && npm run build && cd ..
64+
loadpath --help
65+
```
66+
67+
## CLI
68+
69+
```bash
70+
# Index a monorepo (SQLite graph at .loadpath/graph.sqlite3, incremental on file hashes)
71+
loadpath index /path/to/repo
72+
73+
# Inspect bounded contexts, rules, and type counts from that index
74+
loadpath architecture /path/to/repo
75+
76+
# Review a git range against the index (incremental refresh; --no-reindex to reuse as-is)
77+
loadpath review /path/to/repo --base origin/main --head HEAD
78+
loadpath review /path/to/repo --base origin/main --no-reindex
79+
80+
# Cross-platform app (API + visual graph + PR list)
81+
loadpath serve --port 7345
82+
```
83+
84+
**Flow:** `index` builds the architecture graph → `architecture` shows contexts and rule hits on the whole repo → `review` walks that same graph for a git range. The app mirrors this: Index registers a workspace, Architecture inspects it, Review traces a change through it.
85+
86+
Put `loadpath.yml` at the repo root (see [`loadpath.yml.example`](loadpath.yml.example) and [`fixtures/demo_monorepo/loadpath.yml`](fixtures/demo_monorepo/loadpath.yml)). The tool is opinionated about *your* architecture, not a generic module graph.
87+
88+
## Django support
89+
90+
AST is the default extractor. It is a **framework overlay**, not an import graph.
91+
92+
| Surface | What Loadpath extracts |
93+
| --- | --- |
94+
| Models | Fields, FK / M2M / O2O, `on_delete`, string refs (`ForeignKey("accounts.User")`) as residuals |
95+
| Serializers | `Meta.fields` / `exclude`, declared fields, `serializes` edges, queryset-in-serializer flag |
96+
| Views | DRF ViewSets / APIViews, `serializer_class`, `get_serializer_class` (residual), `permission_classes`, `get_queryset`, `filterset_class`, `authentication_classes`, `pagination_class` |
97+
| Function views | `@api_view`, `@login_required`, `@csrf_exempt`, … |
98+
| Django Ninja | `@router.get/post/…` routes and views |
99+
| URLs | `path` / `re_path`, DRF `router.register`, `include()` mount composition (`/api` + `invoices/<id>/` → `/api/invoices/{id}`) |
100+
| Signals | `@receiver`, `signal.connect()` residual, `AppConfig.ready()` residual |
101+
| Management commands | `BaseCommand` + `handle()`, including `.delay(` / `.send(` enqueue edges |
102+
| Migrations | `CreateModel` / `AddField` / `RemoveField` / `DeleteModel` / `RunPython` as `destructive_migration` |
103+
| Tests | `test_*` in `tests.py` / `tests/` as `tested_by` |
104+
105+
### Celery
106+
107+
- `@shared_task`, `@app.task`, `@periodic_task`
108+
- `celery.Task` subclasses (`run(self, invoice_id)`)
109+
- Enqueue: `.delay(`, `.apply_async(`
110+
- Signatures / canvas: `.s(`, `.si(`, `chain` / `group` / `chord` (canvas is a residual; inner signatures are `enqueues` edges)
111+
- `current_app.send_task("billing.tasks.send_invoice_email")` — residual + inferred enqueue
112+
- `transaction.on_commit(lambda: task.apply_async(...))` — residual, nested enqueue walked
113+
- `CELERY_BEAT_SCHEDULE` / `beat_schedule` in settings
114+
115+
### Dramatiq
116+
117+
- `@dramatiq.actor`
118+
- `dramatiq.GenericActor` subclasses (`perform(self, invoice_id)`)
119+
- Enqueue: `.send(`, `.send_with_options(` (heuristic: dramatiq import or `actors` / `tasks` module)
120+
121+
Call-site placeholders (`rebuild_ledger.send` in a view) do **not** overwrite the actor definition’s file. The graph keeps `actors.py` / `tasks.py` as the node home.
122+
123+
### Idempotency rule
124+
125+
`celery_tasks_must_be_idempotent_on_model_pk` (alias `async_tasks_must_be_idempotent_on_model_pk`) warns when a Celery **or** Dramatiq task takes a full object payload instead of `pk` / `*_id`. Message names the broker.
126+
127+
### Optional `django.setup()` overlay
128+
129+
AST is enough for review. If you need live `_meta` (db_table, resolved relations), set `boot_django: true` in `loadpath.yml`. Loadpath then imports Django, calls `django.setup()`, and merges model/field nodes. Failures become residuals; the AST graph still stands. Leave it `false` in CI unless the fixture is a bootable project.
130+
131+
## React + stitch
132+
133+
**React:** react-router tables, composition, TanStack Query `queryKey` + fetch/axios URL templates, Zod schemas, feature-folder imports, RTL `render(<Page/>)` as `tested_by`.
134+
135+
**Stitch (the moat):** OpenAPI from Spectacular/schema files first; generated clients (`generated/`, orval, openapi-typescript) as high-confidence `consumed_by_client`; fallback URL-template matching and serializer/Zod field overlap marked **inferred**.
136+
137+
## Architecture rules (`loadpath.yml`)
138+
139+
| Rule | Meaning |
140+
| --- | --- |
141+
| `views_cannot_import_other_context_models` | A billing view must not import `accounts.UserProfile` |
142+
| `react_feature_may_only_call_own_or_shared_api` | Billing UI may not `fetch('/api/me')` |
143+
| `serializers_are_the_only_published_contract` | Zod/form fields must not drift from the serializer |
144+
| `no_queryset_in_serializer` | Serializers must not run querysets |
145+
| `celery_tasks_must_be_idempotent_on_model_pk` | Celery and Dramatiq tasks take a model pk |
146+
147+
Waivers live under `waivers:` in the same file. Reviewers are the `owners` of the bounded contexts in the impact subgraph.
148+
149+
Impact walk skips permission/app/context hubs, does not climb `renders` into the App shell, and does not follow cross-context `relates_to` (an Invoice FK to UserProfile does not pull identity into a billing review).
150+
151+
## How confidence is scored
152+
153+
Line coverage on changed files is the wrong metric. Loadpath scores the **impact subgraph**:
154+
155+
| Signal | High | Low |
156+
| --- | --- | --- |
157+
| Tests | Sinks in the radius are hit by tests that still reach the changed symbol | Serializer changed, tests only on the view happy path |
158+
| Contract | OpenAPI/client types track the serializer | React path/Zod field still old |
159+
| Architecture | No new cross-context edges | `crosses_context` with no waiver |
160+
| Graph | Resolved edges | Many inferred/dynamic edges |
161+
162+
`high` / `medium` / `low` plus three reasons. Isolated leaf UI with green tests and no rule hits is labeled `loadpath:low-risk`.
163+
164+
## Tests
165+
166+
```bash
167+
pytest
168+
cd ui && npm test
169+
```
170+
171+
| Suite | What it covers |
172+
| --- | --- |
173+
| `tests/unit/` | Django/React extractors, architecture rules, stitch, SCM/AI providers |
174+
| `tests/integration/test_review_vertical_slice.py` | Serializer field change reaches InvoicePage/Zod, not MePage; reviewers `billing-team` |
175+
| `tests/e2e/test_cli_review.py` | `loadpath index` / `architecture` / `review` markdown, JSON, HTML |
176+
| `tests/e2e/test_api_flow.py` | health, index, architecture, review-from-index, graph, settings, GitHub + Bitbucket PR list |
177+
| `tests/e2e/test_index_architecture_flow.py` | index snapshot, review without index, review walking an existing graph |
178+
| `tests/e2e/test_brokers_and_django.py` | Celery + Dramatiq sinks, actor-only PR, non-idempotent Dramatiq warning, destructive migration, cross-context blocker, boot overlay, management commands, beat/canvas |
179+
| `tests/e2e/test_ui_screenshots.py` | Playwright: Architecture, Review, Impact graph, Pull requests, Settings → `docs/screenshots/` |
180+
181+
CI installs Chromium and runs the full suite.
182+
183+
## Demo fixture
184+
185+
[`fixtures/demo_monorepo`](fixtures/demo_monorepo) is a billing/identity split: DRF ViewSet, FBV, Ninja ledger route, Celery tasks + beat + canvas, Dramatiq actor + GenericActor, management command, signal, FK string ref, React InvoicePage/Zod.
186+
187+
## What this is not
188+
189+
Not CodeRabbit (comments without a closed impact set). Not CodeScene (historical coupling). Not django-orm-lens (models only). Not a generic SCIP call graph. The product is review as load-path inspection.

‎docs/screenshots/architecture.png‎

245 KB
Loading

‎docs/screenshots/graph.png‎

165 KB
Loading

‎docs/screenshots/pull-requests.png‎

41.2 KB
Loading

‎docs/screenshots/review.png‎

279 KB
Loading

‎docs/screenshots/settings.png‎

55.6 KB
Loading

‎fixtures/demo_monorepo/backend/accounts/__init__.py‎

Whitespace-only changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from django.apps import AppConfig
2+
3+
4+
class AccountsConfig(AppConfig):
5+
name = "accounts"

0 commit comments

Comments
 (0)