PATCHPILOT
A developer platform that takes a GitHub issue through diagnosis, bounded repository context, a proposed patch, commit-pinned validation, review, and a pull request — built around FastAPI, Redis, GitHub Apps, and a cloud-hosted client. Designed, built and operated solo.
Fixing a GitHub issue is rarely just writing code. Someone still has to find the right files, give a model enough of the repository to be useful without dumping the whole tree, and prove the change against the code as it actually was. Validating against a moving HEAD cannot tell a pre-existing analyser failure from a regression the patch introduced. PatchPilot treats that as a staged system problem — Diagnosis → context → Patch → Validation → Review → Pull Request — not a single prompt.
An issue is diagnosed, packed into a T0–T6 ContextPackage, patched, checked on a commit-pinned snapshot (with baseline diagnostic compare where the profile requires it), then reviewed and opened as a pull request. Redis keeps analysis and sessions across backend restarts. The hops are staged on purpose: each gate can stop a bad change before the next stage runs.
A gated issue lifecycle
Designed and operated end to end: a GitHub issue moves through Diagnosis, Patch, Validation, Review and Pull Request as one backend path. Each stage is a gate. The client shows the same sequence the API executes, including GitHub OAuth, GitHub App repository authorisation, and anonymous sessions that can be adopted after sign-in so work is not thrown away.
Deterministic code intelligence first
Before a model sees anything, language adapters analyse the repository for the files, declarations, types, callers, contracts and tests that matter to the issue. That work runs across Dart, TypeScript, JavaScript and Python so context is assembled from structure, not a keyword grep. The same issue against the same commit is supposed to produce the same ContextPackage — the model does not choose the files.
Bounded T0–T6 ContextPackage
A ContextBuilder packs a fixed tier ladder under line and token budgets: T0 defect site, T1 supporting declarations, T2 referenced types, T3 callers / dependents, T4 contracts, T5 tests, T6 secondary ranked files. Fill and eviction follow that priority, so truncation drops the least important context first. The model gets a dense, bounded package — not an unbounded dump that grows with the repository.
Context → model → proposal — then a validation gate
The ContextPackage goes to the model and comes back as a PatchProposal — not a merge. The change is applied against a commit-pinned repository snapshot. Validation profiles are chosen from repository evidence (manifests, lockfiles, config), never from issue text. For Flutter, analyze compares baseline vs patched diagnostics: pre-existing findings stay pre-existing; new diagnostics fail the gate. Tests still run as their own check. Only then does the workflow move to review and opening a pull request.
WHAT I CHOSE, AND WHAT IT COST.
Six decisions where the shorter path was available and I took the other one. Each is a constraint the system still lives with — context, validation, staging, state, and deploy split.
Deterministic analysis, not dumping the repository
Sending the whole tree to a model is simpler and wastes the context window on files that have nothing to do with the issue. Ranking declarations, callers, contracts and tests first means the prompt is assembled from structure — at the cost of writing that analysis for every language the product claims to support.
Tiered T0–T6 packing, not unbounded context
An unbounded prompt would have been easier to ship and would degrade as repositories grow. A fixed ladder — defect site, supporting declarations, referenced types, callers, contracts, tests, secondary files — packed under line and token budgets forces a choice about what the model is allowed to see. Truncation drops the least important tier first.
Validate on a commit pin, with baseline diagnostics
Validating against whatever HEAD is now cannot tell a pre-existing analyser finding from one the patch introduced. The change is applied to a commit-pinned snapshot. For Flutter, analyze compares baseline vs patched output: pre-existing diagnostics do not fail the run; new ones do. Profiles are selected from repository evidence, not from the issue text.
Staged workflow over a single generation step
A one-shot “issue in, patch out” path would have been shorter to ship. Separating diagnosis, context, patch, validation, review and PR means a bad proposal can stop before it becomes a pull request — at the cost of more state, more failure modes, and a UI that has to expose the stages honestly.
Redis persistence, not in-memory sessions
Keeping analysis only in process is cheaper until the backend restarts and the visitor's work disappears. Redis holds the run so OAuth restoration and anonymous-to-authenticated adoption can resume rather than start over.
S3 and CloudFront for the client, Railway for the API
Serving Flutter Web from the same box as FastAPI would have been one deploy. Splitting them means the static client sits on S3 behind CloudFront and the Python API runs on Railway, so each can scale and fail on its own — at the cost of two pipelines and a CORS seam.
Flutter Web on AWS S3 behind CloudFront; Python/FastAPI on Railway; Redis for analysis persistence and session restoration; GitHub OAuth and a GitHub App for sign-in and repository access. The public entry is patchpilot.sanjaybuilds.online. Client and API are split so each can deploy and fail independently.